类型判断什么时候用 typeof?

12 阅读2分钟

typeof 是 JavaScript 中最常用的类型判断运算符,但它有几个致命的盲区和历史包袱

1. null 被误判为 'object'(最著名的历史 bug)

2. 无法区分数组和普通对象

3. NaN 被判定为 'number'

4. 无法区分普通函数和类

class 本质上是函数的语法糖,typeof 无法区分"普通函数"和"类构造函数"。

5. 无法区分包装对象和原始值(严格来说不算 bug,但容易混淆)

什么时候用 typeof

typeof 不是不能用,而是只适合判断原始类型和函数
一旦涉及对象的具体类型判断,立即换 Object.prototype.toString.call

Object.prototype.toString.call()

JavaScript 中最可靠的内置类型判断方法,江湖人称「类型判断的终极武器」。
返回格式永远是 [object Xxx] ,其中 Xxx 就是该值的内部 [[Class]]Symbol.toStringTag

为什么它这么准?

1. 每个对象都继承了 toString

JavaScript 中,几乎所有对象都自带 toString 方法,但很多都重写了

JavaScript

[1,2,3].toString()        // "1,2,3"  ← Array 重写了
({a:1}).toString()        // "[object Object]"  ← Object 默认
(function(){}).toString() // "function(){}"Function 重写了

直接调用对象的 toString,得到的往往是该对象自定义的字符串,无法判断类型

2. Object.prototype.toString 是「原版」

Object.prototype.toString 没有被重写,它的行为是固定的:

读取被调用对象的内部 [[Class]] 属性,返回 [object ClassName]

但问题是,如果你直接写:

JavaScript

Object.prototype.toString([])    
// 等同于 [].toString(),返回 "1,2,3"

因为 Object.prototype.toString 里的 this 指向了数组,数组又重写了 toString,所以结果还是乱的。

3. .call() 强制绑定 this

JavaScript

Object.prototype.toString.call([])

这里用 .call([])this 强制绑定到数组实例上,但调用的是 Object.prototype 上的原版 toString,数组自己的重写版本被绕过了。

于是原版 toString 读取数组的内部 [[Class]],返回 [object Array]

Object.prototype.toString.call(x) 通过绕过对象重写、强制绑定 this、读取内部 [[Class]] 的方式,返回最准确的类型标签。它是 JS 中判断内置类型最 robust 的方法,没有之一。