- 作者:老汪软件技巧
- 发表时间:2024-12-07 11:05
- 浏览量:
00. Hello World
大家好,我是大家的林语冰。
在本文中,我们会结合 TS 源码,深度学习 Object 相关的技术细节:
Object = 接口 + 构造函数为什么 Object 不是 Object 类型Object vs ObjectConstructor01. TS 类型 vs JS 值
在 TS 中,一个 Object 代表了两种概念:
由于 Object 接口和构造函数同名,这有时容易混淆:
let o: Object = new Object()
这里的 Object 既用作值,也用作类型 —— 左侧类型注解中的 Object 代表 TS 接口,右侧 new 实例化中 Object() 则代表 JS 构造函数。
我们一般会根据经验法则来区分 —— Object 位于类型注解中则推断它是 TS 接口,Object() 后面紧跟 () 函数调用运算符则推断它是 JS 函数。
多数情况下,这种心智模型行得通,但有时可能会踩雷:
// prettier-ignore
let o: InstanceType<typeof Object> = new Object
上述代码中,TS 重载了 typeof 运算符,允许它在类型上下文使用;JS 允许无参构造时省略 ()(虽然合法,但不是最佳实践),这两个 Object 其实 都是 JS 值,不是 TS 接口。
换而言之,类型注解中的 Object 不一定都是 TS 类型。
当你对代码中的 Object 感到困惑时,一种想象练习是根据当前上下文尝试对 Object 逐步拆解:
let a1: Object = new Object()
let b1: InstanceType<typeof Object>
// 可以拆解为:
let ObjValue = Object
type ObjType = Object
let a2: ObjType = new ObjectValue() // ok
let b2: InstanceType<typeof ObjValue> // ok
let b3: InstanceType<typeof ObjType> // error
这样,我们可以直观地厘清 Object 到底是用作接口(TS 类型),还是用作函数(JS 值)。
当你熟练这种思维实验后,拆解过程可以内化为底层的心智模型,无需在 TS IDE 真实推理。
请记住,值和类型不是一回事,不能一概而论。Object 的不同位置和用法共同决定了它是作为值,还是作为类型。
02. Object 不是 Object 类型
有趣的是,Object 其实 不是 Object 类型,即使两者的拼写一模一样。
由于 JS 可以使用 typeof 来查询运行时类型,所以 TS 也可以,即使是在类型上下文中:
type Constructor = typeof Object
// => ObjectConstructor
type Prototype = typeof Object.prototype
// => Object
可以看到,Object.prototype 是 Object 的原型属性,Object 接口描述的是它的类型;Object 是 Object.prototype 的构造函数,ObjectConstructor 描述的才是它的类型。
我们可以结合 TS 源码来理解:
// es5.d.ts
interface Object {
constructor: Function
toString(): string
// ...
}
interface ObjectConstructor {
readonly prototype: Object
create(o: object | null): any
// ...
}
declare var Object: ObjectConstructor
这里,Object 不能用来描述 Object() 构造函数的接口,因为在 TS 中,接口只能约束实例对象的类型,无法直接约束类的静态类型或构造函数类型。
03. 为何需要两个接口?
那么,JS 中有且仅有一个 Object 函数,为什么 TS 却需要声明两个接口来描述它呢?
这是因为,在运行时类类型是不存在的,TS 的静态类型只能存在于编译时。Object 不是由 TS 编译器提供,而是由 JS 运行时提供的。
当我们在 TS 中声明一个类时,实际上类声明会引入一个新的命名类型,即与类同名的类类型。类类型表示类的实例类型,它由类的实例成员类型构成。
同时,类类型还会引入构造函数类型,如果类上存在静态成语,该类类型还可以约束类对象本身。
本质上,在定义一个类时,实际上我们定义了一个构造函数。
随后,我们可以使用 new 运算符和该构造函数来创建类的实例。我们可以将该类型称作类的构造函数类型,在该类型中也包含了类的静态成员类型。