先记住这个答案
用 Object.prototype.toString.call(value) 调用时,方法内部读取该值对应的内置标签,返回形如 [object Array]、[object Date]、[object RegExp] 的字符串,对 null 和 undefined 则返回 [object Null]、[object Undefined]。typeof 无法区分各种对象,而这套标签由引擎在创建内置对象时确定,因此能给出更细的类型结论。注意必须用 call 或 apply 指定 this,直接写 arr.toString() 会走数组重写过的版本,返回元素拼接结果。封装一个按标记切片比较的 getType 函数即可作为通用判断工具。
- call 调用返回 [object Type] 精确标记
- 封装切片比较函数即可通用判断类型
- Symbol.toStringTag 可伪造标签需谨慎
- 能直接区分 null 和 undefined
内部标签机制与正确调用方式
typeof 的设计目标是区分基本类型,因此数组、日期、正则这些对象细分种类全部被归入 object,无法进一步识别。Object.prototype.toString 的行为不同:它以 [object Type] 的格式返回内部标签,Type 由引擎为内置对象设定,数组是 Array,日期是 Date,正则是 RegExp,函数是 Function,错误对象是 Error。
关键点在于必须通过 Object.prototype.toString.call(value) 使用,因为许多内置类型都重写了自己的 toString:数组实例调用它得到的是元素拼接字符串,日期得到的是时间字符串。只有借助 call 把待检测值作为 this 传入基类方法,才能稳定拿到类型标签。对 null 和 undefined 该方法也安全,分别返回 [object Null] 和 [object Undefined]。
const toStr = Object.prototype.toString;
function getType(value) {
return toStr.call(value).slice(8, -1);
}
console.log(getType([1, 2]));
console.log(getType(/ab/g));
console.log(getType(123));
console.log(getType(null));
console.log(getType('x'));
function isType(value, tag) {
return toStr.call(value).slice(8, -1) === tag;
}
console.log(isType([], 'Array'));
console.log(isType({}, 'Array'));查看输出与解释
Array
RegExp
Number
Null
String
true
falseslice(8, -1) 剥掉 [object 前缀和 ] 后缀得到纯标签,按标签比较即可。代码在任意 JavaScript 环境可直接运行,不依赖日期和随机值。
接口响应字段的统一类型归一
假设一个数据可视化模块接收后端返回的配置对象,字段 series 契约上应为数组,但历史版本接口在只有一条数据时直接返回对象,异常分支还会返回日期字符串或正则过滤配置。展示层需要决定走循环渲染还是单点渲染,用 typeof 得到的都是 object 或 string,无法驱动分支,判断逻辑一度靠猜测字段名,错误率很高。
处理方式是引入上述 getType 工具:先判 Array 走批量渲染,Object 包装成单元素数组,String 尝试解析为日期,RegExp 交给过滤管线,其余标签直接拒绝并记录告警。预期上线后类型误路由会清零,因为标签是引擎级别的客观信息,不依赖字段名约定,且对 null 有独立分支,可避免之前 typeof null === 'object' 造成的空指针误报。
标签可被伪造与跨环境的失效点
最大的失效条件是 Symbol.toStringTag:任何对象只要定义了字符串值的该属性,Object.prototype.toString 就会用它作为标签返回。第三方库可能给原型打上自定义标签,恶意代码也可以故意伪造 [object Array],此时标签判断会得出错误结论。本题按约定不讨论给自定义类打标签的用法,但必须知道标签并非只读事实。
对应处理是:在可信内部代码里仍可用标签判断,但进入安全敏感逻辑时改用 Array.isArray 这类结构化检测,它不读取 Symbol.toStringTag,且能识别跨 iframe 或跨 realm 创建的数组;标签法虽然也能识别跨 realm 数组,但通用封装无法排除符号伪造,若需不可伪造的准确性,则要为日期、正则等类型各写专用检测,通用性随之降低。
容易答错的地方
- 直接调用实例的 toString 来判型
[1,2].toString()返回'1,2',这是数组重写过的方法而非基类标签方法。正确写法是Object.prototype.toString.call(value),通过显式指定this绕过派生类的重写。- 认为标签结果绝对不可伪造
- 对象定义字符串值的
Symbol.toStringTag后,Object.prototype.toString会优先采用它,伪造[object Array]轻而易举。不可信数据应改用Array.isArray等结构化检测。
面试官还会怎么问?
它和 Array.isArray 判断数组哪个更可靠?
Array.isArray 更可靠:它按引擎内部结构判断,不受 Symbol.toStringTag 伪造影响,且天然支持跨 realm 数组。标签法胜在通用,能一个函数覆盖日期、正则等多种类型。
为什么它能区分 null 而 typeof 不行?
typeof null 返回 object 是早期实现遗留的历史问题,规范保留至今。Object.prototype.toString.call(null) 由规范明确定义返回 [object Null],同样 undefined 返回 [object Undefined],可安全区分。
对自定义 class 实例它返回什么?
默认返回 [object Object],与字面量对象无区别,无法识别具体类名。只有实例或其原型链上定义了 Symbol.toStringTag 时才会显示该标签,所以判自定义类型应使用 instanceof。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。