在JavaScript中,扩展运算符(...)用于方便地复制和扩展对象,早已广泛应用于数组,从ES2018开始也用于对象。在TypeScript中,扩展运算符不仅是语法的改进,也是处理对象时进行精确类型化的工具。
历史上,JavaScript中对象的克隆或扩展是通过Object.assign实现的,但这种方法很容易导致类型安全性丧失,并且在对象结构复杂时可能出现危险的键冲突。
问题在于,当使用扩展运算符合并/扩展对象时,TypeScript将根据输入结构推导出新类型,处理可能的键冲突(“最后的胜利者”),但这并不总是开发者所期望的。特别需要注意可选字段、只读字段和类中私有属性的交集。
解决方案:使用扩展运算符,让TypeScript编译器自动推导结果。对于复杂的情况,重要的是要明确限制输入结构的类型,并仔细关注类型结构的变化。
代码示例:
interface User { name: string; age: number; } interface Extra { isAdmin?: boolean; readonly city: string; } const base: User = { name: 'Ivan', age: 28 }; const extended: User & Extra = { ...base, city: 'Moscow', isAdmin: true };
关键特点:
能否通过扩展继承实现TypeScript中的“深拷贝”对象?
回答:不能。扩展运算符只进行浅拷贝,嵌套对象仍按引用存在。
const original = { user: { name: 'Anna' } }; const cloned = { ...original }; cloned.user.name = 'Maria'; // original.user.name也会改变
在有重叠键的对象中,类型会收缩还是扩展,且这如何影响变量注释?
回答:在扩展中,变量的类型会被扩展,在重叠键的情况下右侧的属性会胜出;如果有明确的类型注释,如果类型不兼容,可能会出现错误。
const a = { id: 4, value: "abc" }; const b = { value: 123 }; const c: { id: number; value: number } = { ...a, ...b }; // ok
扩展运算符可以应用于类吗?私有属性会发生什么?
回答:扩展运算符仅适用于类的公共属性。私有(private/#)和受保护的protected字段不会出现在结果对象中。
class Person { private id = 77; name = "Bob"; } const p = new Person(); const spreaded = { ...p }; // spreaded: { name: string }
开发者使用扩展运算符从settings对象扩展用户选项:
const common = { theme: 'light', notifications: true }; const user = { notifications: false, signature: 'Sasha' }; const merged = { ...common, ...user };
预计merged将类型一致,但意外地引入了signature属性类型的错误,忘记了扩展运算符只是复制键而不验证其值。
优点:
缺点:
在合并配置时,确保先验证并使用返回的类型注释,仅在此后使用扩展运算符:
interface Settings { theme: "light" | "dark"; notifications: boolean; signature?: string; } function getSettings(common: Settings, specific: Partial<Settings>): Settings { return { ...common, ...specific }; }
优点:
缺点: