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

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

相关推荐
觉醒法师1 分钟前
HarmonyOS开发 - 电商App实例二( 网络请求http)
前端·http·华为·typescript·harmonyos·ark-ts
沈剑心2 分钟前
Kotlin的协程,真能提升编程效率么?
android·前端·kotlin
堕落年代12 分钟前
Vue主流的状态保存框架对比
前端·javascript·vue.js
没资格抱怨14 分钟前
el-pagination的使用说明
javascript·vue.js·elementui
OpenTiny社区23 分钟前
TinyVue的DatePicker 组件支持日期面板单独使用啦!
前端·vue.js
冴羽23 分钟前
Svelte 最新中文文档教程(22)—— Svelte 5 迁移指南
前端·javascript·svelte
树上有只程序猿27 分钟前
Vue3组件通信:多个实战场景,轻松玩转复杂数据流!
前端·vue.js
青红光硫化黑32 分钟前
React基础之useEffect
javascript·react.js·ecmascript
剪刀石头布啊35 分钟前
css属性值计算过程
前端·css
bin915340 分钟前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14基础固定表头示例
前端·javascript·vue.js·ecmascript·deepseek