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 的基本对象类型?你可以在本文下方自由言论,或者转发分享。

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

相关推荐
霍先生的虚拟宇宙网络8 分钟前
webp 网页如何录屏?
开发语言·前端·javascript
温吞-ing10 分钟前
第十章JavaScript的应用
开发语言·javascript·ecmascript
彪82511 分钟前
第十章 JavaScript的应用 习题
javascript·css·ecmascript·html5
jessezappy28 分钟前
jQuery-Word-Export 使用记录及完整修正文件下载 jquery.wordexport.js
前端·word·jquery·filesaver·word-export
旧林8431 小时前
第八章 利用CSS制作导航菜单
前端·css
yngsqq1 小时前
c#使用高版本8.0步骤
java·前端·c#
Myli_ing2 小时前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风2 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave2 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟2 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js