TypeScript 冷知识:三大对象类型的魔鬼细节

00. 写在前面

大家好,我是大家的林语冰。

本人时曾被面试官拷问过,JS 支持哪几种创建对象的方式

学习"类型体操"之后,我发现 TS 中也存在类似的刁钻问题。如果说 JS 是关于值的计算,那么 TS 则是关于类型的计算。

TS 支持三大基本对象类型,它们之间的类型兼容性允许互相赋值,但使用场景却天差地别。

在本文中,我们将深度学习 TS 中三大对象类型的历史演变和技术细节:

  1. 基本类型为什么可以赋值给 Object 对象类型
  2. 为什么需要 object 对象类型(注意是小写)
  3. 如何使用 {} 对象字面量类型

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 时,容易进入两个舒适圈:

  1. 把 TypeScript 用作"anyScript",万物皆可 any
  2. 把对象类型注解为 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() 的调用是合法的。

同理,虽然 nullundefined 也是基本类型,但它们没有对应的基本包装类型,所以无法赋值给 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'} // 非空对象字面量

参考文献

  1. TS GitHubgithub.com/microsoft/T...
  2. Objectwww.typescriptlang.org/docs/handbo...
  3. objectwww.typescriptlang.org/docs/handbo...

粉丝互动

本期话题是:如何看待 TS 的基本对象类型?你可以在本文下方自由言论,或者转发分享。

欢迎持续关注我,深度学习更多前端进阶的技术细节。谢谢大家的点赞和分享,我们下期再见~

相关推荐
JayceM1 小时前
Vue中v-show与v-if的区别
前端·javascript·vue.js
HWL56791 小时前
“preinstall“: “npx only-allow pnpm“
运维·服务器·前端·javascript·vue.js
咪咪渝粮1 小时前
JavaScript 中constructor 属性的指向异常问题
开发语言·javascript
德育处主任1 小时前
p5.js 掌握圆锥体 cone
前端·数据可视化·canvas
mazhenxiao1 小时前
qiankunjs 微前端框架笔记
前端
无羡仙2 小时前
事件流与事件委托:用冒泡机制优化前端性能
前端·javascript
秃头小傻蛋2 小时前
Vue 项目中条件加载组件导致 CSS 样式丢失问题解决方案
前端·vue.js
CodeTransfer2 小时前
今天给大家搬运的是利用发布-订阅模式对代码进行解耦
前端·javascript
阿邱吖2 小时前
form.item接管受控组件
前端
韩劳模2 小时前
基于vue-pdf实现PDF多页预览
前端