为何TypeScript允许将子类型赋值给对象属性?示例报错解析
为何TypeScript允许将子类型赋值给对象属性?
先看你提供的示例代码:
type Base = { a: number, b: number } type SubTest = Base & { c: number } function run(arg: { prop: Base }) { arg.prop = { a: 1, b: 2 }; } const prop: SubTest = { a: 1, b: 2, c: 3 }; const originalObject = { prop }; run(originalObject); console.log(originalObject.prop.c.toFixed(2));
编译时TypeScript没有报错,但运行时会抛出错误:
originalObject.prop.c is undefined
原因解析
TypeScript采用结构类型系统,判断类型兼容性时只看结构是否匹配。这里SubTest是Base的子类型(包含Base的所有属性,还多了c),所以{ prop: SubTest }可以赋值给{ prop: Base }——这是因为TypeScript默认允许对象属性的协变:子类型属性可以赋值给父类型属性。
但问题出在run函数内部:它直接将arg.prop替换成了一个仅包含a和b的纯Base对象,覆盖了原本的SubTest类型值。此时originalObject.prop的实际值已经丢失了c属性,运行时访问自然会报错。TypeScript没有检测到这个问题,是因为它认为{ prop: Base }类型的参数允许将prop赋值为任何Base类型的实例,包括不包含额外属性的版本。
解决方法
可以通过以下几种方式避免这类运行时错误:
将属性设为只读
修改run函数的参数类型,把prop标记为只读,禁止内部修改:function run(arg: { readonly prop: Base }) { // 这里会报错:无法分配到 "prop" ,因为它是只读属性。 arg.prop = { a: 1, b: 2 }; }使用泛型约束
让函数接受泛型参数,确保赋值的类型与原始子类型一致:function run<T extends Base>(arg: { prop: T }) { // 这里只能赋值为 T 类型的对象,不能用纯 Base arg.prop = { a: 1, b: 2, c: 3 } as T; }约束原始对象的属性为只读
定义originalObject时把prop设为只读,避免外部函数修改:const originalObject: { readonly prop: SubTest } = { prop }; // 传递给 run 时会报错,因为 run 接受的是可写的 prop 属性
内容的提问来源于stack exchange,提问作者Jacob Lockard
相关产品推荐
相关产品推荐

