先记住这个答案
TypeScript 主要采用结构类型系统。把源值交给目标类型时,编译器检查源的成员能否满足目标要求;两个接口即使名称不同,只要相关结构兼容,也可以赋值。这个判断有方向:拥有坐标和颜色的对象可以作为坐标使用,只有坐标的对象却不能满足必需颜色。新鲜对象字面量还有额外属性检查,类的私有或受保护成员也有来源限制,因此不能把结构兼容简化成任何同形对象都无条件互换。
- 兼容性以目标要求的成员为出发点
- 源拥有更多成员不代表两个方向都可赋值
- 对象字面量和类私有成员存在额外规则
为什么不必显式实现接口
前端经常把对象字面量、接口返回值和第三方库对象交给同一个函数。如果函数只读取两个坐标,要求所有提供者继承同一个基类会增加大量适配代码。结构检查允许不同来源直接满足这份契约,但仍会检查字段是否存在、字段类型是否可用。
下面彩色坐标变量可以传入读取坐标的函数,因为它具备需要的两个数字字段。函数参数的静态视图仍然只有坐标,不能据此随意读取颜色。类型兼容只是允许这种使用方式,并没有复制对象、移除字段或改变它的原型。
interface Point { x: number; y: number }
interface ColoredPoint { x: number; y: number; color: string }
function distance(point: Point) {
return Math.hypot(point.x, point.y);
}
const colored: ColoredPoint = { x: 3, y: 4, color: 'blue' };
const result = distance(colored);
const point: Point = { x: 1, y: 2 };
// @ts-expect-error 缺少 ColoredPoint 必需的 color
const missingColor: ColoredPoint = point;正向调用保留原对象,距离为五。反向赋值缺少必需字段,因此应出现编译错误;这里用预期错误注释验证该约束没有意外放宽。
结构兼容不等于精确字段集合
将新鲜对象字面量直接传给带明确对象类型的参数时,多写一个未知字段可能触发额外属性检查。这主要帮助发现配置拼写错误,并不表示目标类型从此只能对应恰好拥有这些键的对象。先保存到变量后,通常回到结构兼容规则。
可选字段也不是忽略类型的许可。源对象一旦提供该字段,它的值仍要满足目标声明及相关编译选项;只含可选字段的弱类型还可能要求源与目标存在共同属性。排查赋值错误时,应查看完整诊断,而不是套用多余字段永远允许的口诀。
业务身份需要额外表达
两个都包含字符串标识的接口可能结构完全相同,但订单编号与用户编号在业务上不能交换。可以用不同的字段结构,或通过受控构造函数生成带品牌标记的类型,让错误传参更早暴露。品牌标记如何创建和验证也要有统一边界。
类的 private、protected 成员涉及声明来源,来自互不相关类的同名私有字段不能仅靠形状匹配绕过。另一方面,类型标记最终不能代替服务器权限检查和外部数据校验;编译器接受的对象仍可能来自错误断言或未经验证的网络输入。
容易答错的地方
- 接口名字不同就不能赋值
- 普通接口通常按成员结构比较,名称差异并不足以阻止赋值;若业务需要区分身份,应设计额外的类型约束和构造入口。
- 赋值成功会删除多余字段
- 类型检查不会执行对象裁剪,原有字段和对象身份仍然存在;若需要输出白名单字段,应在运行时显式构造新的结果。
面试官还会怎么问?
结构相同的两个类一定兼容吗?
不能一概而论,公共实例成员可以按结构比较,但私有、受保护成员有来源规则,构造函数静态侧也需要与实例侧分开看。
为什么只读函数参数也要检查字段类型?
只读意味着函数不主动写入,不意味着它能处理任意值;例如数学计算仍要求数字字段,结构契约必须描述实际读取能力。
品牌类型能验证从接口返回的编号吗?
不能自动验证。应先检查外部值的格式和业务归属,再通过受控入口赋予品牌;直接断言品牌只是让编译器相信调用者。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。