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() 的调用是合法的。

同理,虽然 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'} // 非空对象字面量

参考文献

  1. TS GitHub :github.com/microsoft/T...
  2. Object :www.typescriptlang.org/docs/handbo...
  3. object :www.typescriptlang.org/docs/handbo...

粉丝互动

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

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

相关推荐
Data analyse4561 小时前
rnweb 回放数据量太大怎么办?分片存储与断点续传的实现思路
前端·网络·算法·数据分析
xiaogai_gai2 小时前
金蝶收料通知单查询策略实战教程:从源系统拉取到中间层落地的完整路径
java·前端·数据库
环境栈笔记2 小时前
指纹浏览器 WebRTC 怎么设置?从 ICE 候选、代理出口到功能验证
前端·人工智能·后端·自动化
赵大仁3 小时前
官网性能优化实战:LCP 3.2s → 2.1s、CLS 0.107 → 0,以及被我推翻的 6 个判断
前端·性能优化·cls·lcp·core web vitals
xiancai_xianyu3 小时前
Text2SQL落地生产库前,SQL要过哪三道闸
前端·数据库·sql·sql注入·text2sql·ai agent开发·企业级ai框架
小羊没烦恼!3 小时前
T-SQL查询进阶--理解SQL SERVER中的分区表
服务器·开发语言·前端·javascript·html
史瓦辛格3 小时前
给 DeepSeek Harness 写费用插件:把「这次会话花了多少钱」算准
前端·deepseek
星光不问赶路人3 小时前
Vue 3 + TypeScript 中 enum 的使用实践
前端·vue.js
鸥梨菌Honevid3 小时前
UVM 中 REQ 和 RSP 类型详解:从通信模型到 item_done 与 put_response
java·前端·servlet
IT_陈寒3 小时前
Python多进程共享变量这个坑,差点让我加班到凌晨
前端·人工智能·后端