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
vbscript
[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
vbscript
Object.prototype.toString([])
// 等同于 [].toString(),返回 "1,2,3"
因为 Object.prototype.toString 里的 this 指向了数组,数组又重写了 toString,所以结果还是乱的。
3. .call() 强制绑定 this
JavaScript
css
Object.prototype.toString.call([])
这里用 .call([]) 把 this 强制绑定到数组实例上,但调用的是 Object.prototype 上的原版 toString,数组自己的重写版本被绕过了。
于是原版 toString 读取数组的内部 [[Class]],返回 [object Array]。
Object.prototype.toString.call(x) 通过绕过对象重写、强制绑定 this、读取内部 \[Class] 的方式,返回最准确的类型标签。它是 JS 中判断内置类型最 robust 的方法,没有之一。