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

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

相关推荐
青柠之夏cc1 小时前
前端拖拽功能原生实现,不引入拖拽库完成业务
开发语言·前端·python
liangshanbo12152 小时前
前端大文件切片上传怎么做?
前端
Sand(ContextGate)2 小时前
Python Agent 测试实战:测试与评估,让 Agent 像传统软件一样可交付
前端·javascript·python·microsoft·ai
广州华水科技3 小时前
单北斗GNSS变形监测系统在城市安全与地质灾害中的应用前景
前端
颜进强3 小时前
20 · NestJs循环依赖与 forwardRef:容器为什么在环面前会死,"占位再补齐"怎么救
前端·后端·ai编程
颜进强3 小时前
19 · NestJs @Global 落地账:装饰器与 `isGlobal` 参数,各在什么场景上岗
前端·后端·ai编程
码艺-Alimjan3 小时前
Tauri 网站To桌面应用实战总结
前端·javascript·vue
颜进强3 小时前
18 · NestJS动态模块与 forRoot:`imports: [ConfigModule.forRoot({...})]` 到底在 import 什么
前端·后端·ai编程
YZ1225523 小时前
【Docker专题】使用Docker部署Vue-Flask项目前后端分离版【前端Docker部署】
前端·vue.js·docker
liangshanbo12153 小时前
浏览器渲染过程:高级前端面试题
前端