先记住这个答案
typeof 返回 'object' 的情况包括:普通对象字面量、数组、Date、正则、Map、Set、原始类型的包装对象(如 new String('a')),以及 null。其中 typeof null === 'object' 是最早实现中类型标签与空指针表示撞车的历史遗留,修复提案被拒绝后一直保留至今。因此判断一个值是否为可用对象时,必须同时排除 null;要区分数组应使用 Array.isArray,要区分更细的内置类型可用 Object.prototype.toString。
- 对象、数组、日期、正则均返回 'object'
- typeof null 为 'object' 是历史遗留
- 判对象须先排除 null,数组用 Array.isArray
为何这么多值都落在 'object'
typeof 的设计只区分七种原始类型加一个对象大类。规范约定:任何非原始值,若不实现内部方法 [[Call]],结果一律是 'object'。数组、Date、正则、Map、Set、包装对象都不可调用,所以全部归入这一类。这也是 typeof [] 不等于 'array' 的原因:语言层面根本没有为数组单独设返回值。
typeof null === 'object' 来自首个 JavaScript 引擎的表示方式:值由类型标签加数据组成,对象的标签是 0,而 null 被表示为空指针,其低位恰好也是 0,于是 null 被误判为对象。后来有人提议修正为返回 'null',但因兼容性风险被拒绝,这一行为固化至今,成为面试中最常被追问的怪异点。
const values = [
{ name: 'plain object', value: { a: 1 } },
{ name: 'array', value: [1, 2] },
{ name: 'date', value: new Date(0) },
{ name: 'regexp', value: /ab/ },
{ name: 'null', value: null },
{ name: 'function', value: function () {} }
];
for (const item of values) {
console.log(item.name + ': ' + JSON.stringify(typeof item.value));
}
console.log('array check: ' + Array.isArray(values[1].value));
console.log('null check: ' + (values[4].value === null));查看输出与解释
plain object: "object"
array: "object"
date: "object"
regexp: "object"
null: "object"
function: "function"
array check: true
null check: true数组、日期、正则、null 的 typeof 结果完全相同,均为 'object';区分数组需 Array.isArray,识别 null 需直接全等比较。
接口返回值判空的一次真实决策
某前端模块从缓存读取用户配置,约定返回值要么是配置对象,要么是 null 表示未缓存。最初代码写成 if (typeof config === 'object') 就调用 config.theme,结果缓存在首次访问时返回 null,判断通过,随后访问属性抛出 TypeError。这正是把 typeof 当作对象可用性判断的典型失败。
修复方式是把条件改为 config !== null && typeof config === 'object',或更简洁地用可选链 config?.theme。两种写法都显式排除了 null。选择后者的代价是返回值可能为 undefined,调用方需接受这一点;选择前者则语义更直白,适合后续还要确认数组或日期等具体形态的场合,此时可再叠加 Array.isArray 或 Object.prototype.toString 细分。
typeof 失效的几种条件
第一,typeof 无法区分 null 与对象,任何 'object' 结果都必须先排除 null 才能安全访问属性。第二,它区分不了数组、日期、正则等对象子类型,typeof []、typeof new Date()、typeof /x/ 三者完全一样。第三,new String('a') 这类包装对象也返回 'object',与原始字符串 'string' 不同,混用容易踩坑。
对应处理是:判 null 用全等 value === null;判数组用 Array.isArray;判具体内置类型用 Object.prototype.toString.call(value),它返回形如 '[object Date]' 的标签。代价是代码变长、可读性略降,且 Symbol.toStringTag 可被自定义改写,不能完全防伪。另外未声明的变量用 typeof 探测是安全的,返回 'undefined',但对暂时性死区中的 let 变量使用会抛 ReferenceError。
容易答错的地方
- 认为 typeof null 是 bug 会在新版修复
- 该行为自首个引擎就存在,修正提案因破坏兼容性被正式拒绝,规范明确保留
typeof null === 'object',写代码时必须自己排除null,不能指望语言改变。 - 用 typeof 判断数组
typeof []返回'object',与普通对象无法区分。判断数组应使用Array.isArray,需要更细的内置类型时用Object.prototype.toString,typeof本身做不到。
面试官还会怎么问?
为什么修复 typeof null 的提案被拒绝?
因为大量现存代码依赖 typeof x === 'object' 同时覆盖对象与 null,改变返回值会破坏这些站点。委员会权衡后认为兼容成本大于收益,提案被否决,行为保留至今。
typeof 对函数和类返回什么?
实现了内部方法 [[Call]] 的对象返回 'function',包括普通函数、class 声明的类以及 Math.sin 这类内置方法。类本质是函数,所以 typeof class C {} 也是 'function'。
对未声明变量用 typeof 安全吗?
安全,typeof someUndeclaredVar 返回 'undefined' 而不抛错,这是它独有的能力。但同一块级作用域内对声明前的 let、const、class 使用会因暂时性死区抛 ReferenceError。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。