先记住这个答案
typeof 只能返回 8 种字符串:'undefined'、'boolean'、'number'、'bigint'、'string'、'symbol'、'function'、'object'。对象这一侧它只看能不能被调用,能调用的返回 'function',其余一律返回 'object',所以普通对象、数组、Date、正则、Map、Set、new String('a') 这种包装对象全是 'object'。另外 typeof null 也返回 'object',这是早期实现留下的问题,因为兼容性一直没改。所以判断「是不是可用的对象」要写成 v !== null && typeof v === 'object',判数组用 Array.isArray。
- 普通对象的
typeof是 object typeof null历史上是 object- 数组用
Array.isArray判断 - 细分类型可用内置
toString
一张表看清哪些值是 'object'
把常见的值都跑一遍,旁边再用 Object.prototype.toString 打出它们真正的内部类型,对比就很清楚了。
typeof 和内部类型对照JavaScriptconst values = [{}, [], new Date(0), /a/, new Map(), new String('a'), null, class {}, () => {}]
for (const v of values) {
// 左边是 typeof 的结果,右边是真实的内部类型标签
console.log(typeof v, Object.prototype.toString.call(v))
}查看输出与解释
object [object Object]
object [object Array]
object [object Date]
object [object RegExp]
object [object Map]
object [object String]
object [object Null]
function [object Function]
function [object Function]前七个 typeof 都是 'object',右边的标签却各不相同。class 也返回 'function',因为类本质上就是构造函数。new String('a') 是包装对象,和原始字符串 'a' 不一样,后者的 typeof 是 'string'。
typeof null 为什么是 'object'
第一版 JavaScript 引擎里,每个值在内存里由「类型标签 + 数据」组成,类型标签占低几位。对象的标签是 0,而 null 被表示成空指针,全部位都是 0,引擎一看标签是 0,就把它当成了对象。
后来 ES6 讨论阶段有人提议把它改成返回 'null',但当时大量线上代码写的是 typeof x === 'object' 并默认包含了 null 的情况,一改就会让这些代码的分支走错,所以提案被否掉了。结论就是:这个行为永远不会改,只能自己处理。
- 一次真实的线上报错
- 缓存模块约定没命中时返回
null。业务代码写了if (typeof config === 'object') applyTheme(config.theme),本地一直有缓存没问题,新用户第一次访问时config是null,判断照样通过,接着读config.theme就抛出TypeError: Cannot read properties of null。修法是加上config !== null,或者直接用可选链写成config?.theme。
判断类型时该换什么工具
typeof 最适合判断原始类型和函数,比如 typeof cb === 'function'、typeof id === 'string',简单又快。一旦要区分对象的具体种类,就要换工具。
判断 null 直接用 v === null。判断数组用 Array.isArray(v),它能跨 iframe 正确识别,instanceof Array 做不到。要细分 Date、RegExp、Map 这些内置类型,用 Object.prototype.toString.call(v),它返回 '[object Date]' 这样的标签。不过这个标签可以被对象的 Symbol.toStringTag 改写,所以它适合做类型分发,不适合做安全校验。
容易答错的地方
- 以为
typeof null以后会被修好 - 修正提案已经被否决,规范里写死了
null返回'object'。任何拿到'object'就去读属性的代码,都要先排除null。 - 以为
typeof []是'array' typeof的返回值里根本没有'array'。数组是不可调用的对象,所以只能是'object'。- 以为
typeof永远不会报错 - 对未声明的变量用
typeof是安全的,返回'undefined'。但在let、const、class声明之前访问,变量处于暂时性死区,typeof也会抛ReferenceError。
面试官还会怎么问?
有没有返回 'undefined' 但其实是对象的值?
有,浏览器里的 document.all。它是个真实存在的对象,但为了兼容上古时代的浏览器检测代码,规范特意让它的 typeof 返回 'undefined',转布尔值也是 false。
怎么写一个判断「纯对象」的函数?
先排除 null 和非对象:v !== null && typeof v === 'object'。再看原型,Object.getPrototypeOf(v) 是 Object.prototype 或 null 才算纯对象。这样数组、Date、类实例都会被排除,Lodash 的 isPlainObject 就是这个思路。
typeof 和 instanceof 怎么选?
判断原始类型和函数用 typeof。判断对象是不是某个类的实例用 instanceof,它沿原型链查找。instanceof 不能用在原始值上,'a' instanceof String 是 false,而且不同 iframe 里的 Array 构造函数不是同一个,跨窗口判断会失效。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。