编程前端开发者

解释 TypeScript 中 typeof 运算符的 guard 类型是如何工作的,它的用途是什么,以及它的限制是什么?

用 Hintsage AI 助手通过面试

回答。

问题的背景

在 JavaScript 中,typeof 运算符用于在运行时检查原始值的类型。TypeScript 扩展了这一机制,并通过类型保护(type guards)将其纳入类型缩减(type narrowing)系统中。TypeScript 使用 typeof 运算符的结果来澄清代码块内变量的类型,这使得在处理联合类型时可以更精确地描述函数的逻辑。

问题

在普通的 JavaScript 中,通过 typeof 检查值的类型后,并没有类型保证——程序员必须记住在代码的哪个部分发生了什么。但是,TypeScript 的任务变得复杂,因为存在不同的类型和联合类型,如果没有正确的类型缩减,很容易出错,例如,调用不存在的方法。此外,该运算符还有具体的限制:它只能“看到”基本的原始类型,例如,对数组和对象会返回 'object'。

解决方案

TypeScript 允许将 typeof 运算符与自定义的类型分析结合使用,以便在代码块内缩减变量的类型。这自动提高了安全性——编译器知道代码正在处理的类型,并提示可能的属性和方法。

示例代码:

function printId(id: number | string) { if (typeof id === 'string') { // 在这个分支中 id: string console.log(id.toUpperCase()); } else { // 在这个分支中 id: number console.log(id.toFixed(2)); } }

关键特点:

  • 仅适用于原始类型:'string', 'number', 'boolean', 'symbol', 'undefined', 'object', 'function', 'bigint'。
  • 帮助 TypeScript 将联合类型缩减为代码块内部的具体原始类型。
  • 不区分数组和对象,对于它们总是返回 'object'。

乍看之下的问题。

typeof 运算符能否识别数组?

不能,typeof 对于数组和对象返回相同的值——'object'。要区分数组,最好使用 Array.isArray() 方法。

示例代码:

const arr = [1, 2, 3]; console.log(typeof arr); // 'object' console.log(Array.isArray(arr)); // true

可以用 typeof 区分 null 和对象吗?

不能,typeof null 返回 'object',这是 JavaScript 的历史特性。

示例代码:

console.log(typeof null); // 'object'

可以用 typeof 检查用户定义的类吗?

不能,对于类的实例,typeof 也会返回 'object'。为此使用 instanceof 运算符或自定义类型保护函数。

示例代码:

class User {} const u = new User(); console.log(typeof u); // 'object' console.log(u instanceof User); // true

类型错误和反模式

  • 使用 typeof 检查复杂数据结构(例如,数组、null、对象)。
  • 忽视使用 instanceofArray.isArray() 运算符来更精确地缩减类型。
  • 仅在运行时检查类型,忽略静态检查。

生活中的例子

负面案例

程序员编写了一个函数,想通过 typeof 检查 value 是否为数组,并根据结果调用不同的方法。

优点:

  • 代码简单。
  • 不需要额外的函数。

缺点:

  • 当传递数组时,函数崩溃:数组被视为对象,运行时出现错误。

积极案例

使用 Array.isArray 并与类型保护结合起来。

优点:

  • 安全的静态类型。
  • 没有与对象和数组类型相关的错误。

缺点:

  • 需要记住不同的类型保护工具。