00. 写在前面
大家好,我是大家的林语冰。
本人时曾被面试官拷问过,JS 支持哪几种创建对象的方式?
学习"类型体操"之后,我发现 TS 中也存在类似的刁钻问题。如果说 JS 是关于值的计算,那么 TS 则是关于类型的计算。
TS 支持三大基本对象类型,它们之间的类型兼容性允许互相赋值,但使用场景却天差地别。
在本文中,我们将深度学习 TS 中三大对象类型的历史演变和技术细节:
- 基本类型为什么可以赋值给
Object
对象类型 - 为什么需要
object
对象类型(注意是小写) - 如何使用
{}
对象字面量类型
01. Object
对象类型
大写的 Object
类型和 JS 内置的 Object()
构造函数同名,所以比较迷惑。
ts
const o: Object = new Object()
// 前者是对象类型(TS 类型),后者是构造函数(JS 值)
现实开发中,我们只需要明确,我们是想要 Object
作为 TS 类型使用,还是作为函数调用(JS 值),就不会混淆了。
粉丝请注意,虽然两者同名,但 Object()
构造函数的类型不是 Object
类型。
我们可以结合 TS 源码来解释:
ts
interface ObjectConstructor {
// 1. prototype 是 Object 类型
readonly prototype: Object
// 其他源码先省略...
}
// 2. Object() 是 ObjectConstructor 类型
declare var Object: ObjectConstructor
// 3. o 是 Object 类型
const o: Object = new Object()
可以看到,Object()
构造函数是 ObjectConstructor
类型,而实例对象 o
和原型对象 prototype
都是 Object
类型。
刚开始使用 TS 时,容易进入两个舒适圈:
- 把 TypeScript 用作"anyScript",万物皆可
any
- 把对象类型注解为
Object
,万物皆对象
ts
const obj: Object = { cat: 'daisy' }
const arr: Object = []
// ...
obj.cat // 无代码补全,且编译时报错
上述代码是一种最渣实践的 反模式 ,因为把对象类型注解为 Object
类型毫无卵用,VSCode 等 IDE 既没有代码补全的拼写提示,TS 编译时也会报错,这就丧失了静态类型语言的优势。
另一点比较反直觉的是,基本数据类型可以赋值给 Object
对象类型。
ts
// 1. 基本类型允许赋值给 Object 类型
const str: Object = 'cat'
// 2. 字符串无法使用字符串的方法
str
.slice()(
// 报错
str as String
)
.slice()
// 3. 字符串可以使用 Object 原型方法
str.toString()
可以看到,字符串等基本类型可以赋值给 Object
类型的变量,因为 ES6 中存在基本包装类型,即类似 Java 的装箱/拆箱机制。
当字符串 str
作为对象调用 slice()
方法时,JS 引擎或运行时会在底层将基本类型装箱,比如字符串专属的 String(str)
或更通用的 Object(str)
,slice()
方法调用完毕后又会将包装对象拆箱,强制转换为基本类型。
所以,从类型兼容性角度考虑,基本类型允许赋值给 Object
类型。但是,在 TS 中,当我们把 str
声明为 Object
类型后,在静态类型系统中,它就不再是基本类型,所以 str.slice()
会报错,需要我们手动修正。
而 toString()
原本就存在于 Object
原型链上,所以 str.toSting()
的调用是合法的。
同理,虽然 null
和 undefined
也是基本类型,但它们没有对应的基本包装类型,所以无法赋值给 Object
类型。
ts
let nullish: Object = null // 报错
nullish = undefined // 报错
虽然 Object
类型看似通用,但实则相当鸡肋,所以 TS 团队官方也不推荐直接使用 Object
类型,而应该优先使用接口或类型别名,来为对象进行类型注解。
02. object
对象类型
TS 2.2 添加了小写的 object
对象类型,专门表示非原始类型。
object
类型比 Object
类型更严格,它强调变量必须为对象类型,所以支持装箱的基本包装类型也无法赋值给它:
ts
let o1: Object = 'cat' // 合法
let o2: object = 'cat' // 报错
let o3: object = { cat: 'daisy ' } // 合法
o3.cat // 报错
即便如此,object
也很鸡肋,虽然允许赋值,但还是无法发挥代码补全等静态类型系统的优势,因为它无法提供对象类型更具体的键值等信息。
但 object
类型并非没有用武之地,它主要在 TS 内部或某些极端情况下使用。
举个栗子,Object.create()
方法要求参数要么是对象类型,要么是 null
,TS 源码就是通过 object
来定义的:
ts
interface ObjectConstructor {
create(o: object | null, ...): any;
// 其他源码先省略...
}
03. {}
对象字面量类型
{}
是普通的对象字面量类型,有时候作为 Object
的替代品使用,一般不建议使用。
ts
let o1: {} = {} // 空对象字面量
let o2: {cat: string} = {"cat": 'daisy'} // 非空对象字面量
参考文献
- TS GitHub :github.com/microsoft/T...
- Object :www.typescriptlang.org/docs/handbo...
- object :www.typescriptlang.org/docs/handbo...
粉丝互动
本期话题是:如何看待 TS 的基本对象类型?你可以在本文下方自由言论,或者转发分享。
欢迎持续关注我,深度学习更多前端进阶的技术细节。谢谢大家的点赞和分享,我们下期再见~