JavaScript 的动态类型特性给编程带来了极大的灵活性,但也带来了一个实际问题:如何准确判断一个值的类型?判断数组、判断 null、判断某个对象的具体构造来源——这些在日常开发中高频出现的需求,若方法选择不当,很容易踩坑。本节系统梳理四种核心类型判断方案,它们各有优劣,组合使用方能覆盖绝大多数场景。
8.3.1 typeof 运算符
typeof 是最基础的类型判断手段,用于检测一个变量的基本类型。它返回一个表示类型的字符串。
用法
typeof 42; // "number"
typeof 'hello'; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol(); // "symbol"
typeof 123n; // "bigint"
typeof {}; // "object"
typeof []; // "object"
typeof null; // "object" ← 著名历史遗留问题
typeof function(){}; // "function"
原理typeof 检测的是值在引擎内部的类型标签,这个标签在变量创建时就已经确定。大多数基本类型可以准确检测,但有两个特殊情况:
typeof null返回"object",这是因为 JavaScript 最初实现时,值的类型标签用二进制表示,对象的前三位是000,而null的二进制表示恰好全部为 0,导致被错误地识别为对象。这个 bug 因为历史包袱无法修复。- 函数本质上也是对象,但
typeof对函数单独返回"function",这可以视为一种便利。
适用场景与局限typeof 适合快速区分基本类型(尤其适合检测 undefined)以及判断一个值是否为函数。但对于引用类型的细分(数组、普通对象、日期、正则等)无能为力,全部返回 "object"。因此,typeof 经常作为第一层粗筛,后续再用其他方法精细判断。
8.3.2 instanceof 运算符
instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,即判断一个对象是否是某个构造函数的实例。
用法
[] instanceof Array; // true
{} instanceof Object; // true
new Date() instanceof Date; // true
function Foo() {}
const f = new Foo();
f instanceof Foo; // true
f instanceof Object; // true
原理instanceof 沿着左侧对象的原型链(proto)逐级向上查找,看是否能找到右侧构造函数的 prototype。示例中的 f instanceof Object 返回 true,正是因为所有对象最终都继承自 Object.prototype。
局限与陷阱
- 无法检测基本类型:
'hello' instanceof String返回false,因为基本类型没有原型链(除非通过包装对象)。 - 跨执行环境失效:如果数组是从一个 iframe 中产生的,它的原型链与当前页面的
Array.prototype不是同一个引用,iframeArray instanceof Array会返回false。 - 原型链可以被动态修改,导致判断失准:
Object.setPrototypeOf(arr, null); arr instanceof Array; // false。
适用场景
适合在同一个全局执行上下文中做自定义类或内置对象的实例检测。对于第三方库引入的对象或跨窗口通信数据,需谨慎使用。
8.3.3 constructor 属性
每个对象(除 Object.create(null) 创建的对象外)都有一个 constructor 属性,指向创建该对象的构造函数。可以利用它来判断对象的类型。
用法
(3).constructor === Number; // true
'hello'.constructor === String; // true
[].constructor === Array; // true
new Date().constructor === Date; // true
原理constructor 属性来自构造函数的 prototype。当对象通过构造函数创建时,实例的 proto 指向构造函数的 prototype,而 prototype.constructor 默认指向该构造函数自身。
局限与陷阱
null和undefined没有constructor,会报错。- 原型被重写时,
constructor可能丢失或指向错误:如果手动修改了构造函数的prototype,而没有重新指定constructor,就会断掉这条链。 - 跨执行环境存在与
instanceof类似的问题。
适用场景constructor 检测比 instanceof 更加直接且无需跨越原型链查找,但必须保证 constructor 没有被篡改。在内部代码中,如果你确保 constructor 未被破坏,这是一种快速且语义清晰的判断方式。
8.3.4 Object.prototype.toString
这是 JavaScript 中最可靠、最全面的类型判断方法。通过调用 Object 原型上的 toString 方法,可以获取引擎内部用来描述对象的 [[Class]] 属性,返回格式为 "[object Type]" 的字符串。
用法
const toString = Object.prototype.toString;
toString.call(42); // "[object Number]"
toString.call('hello'); // "[object String]"
toString.call(true); // "[object Boolean]"
toString.call(undefined); // "[object Undefined]"
toString.call(null); // "[object Null]"
toString.call([]); // "[object Array]"
toString.call({}); // "[object Object]"
toString.call(new Date()); // "[object Date]"
toString.call(/regex/); // "[object RegExp]"
toString.call(function(){}); // "[object Function]"
原理
ECMAScript 规范要求,Object.prototype.toString 被调用时,会根据传入值的内部属性返回标准类型字符串。很多内置对象都覆盖了自身的 toString 方法(如 Array.prototype.toString 返回元素拼接的字符串),所以必须通过 call 使用原始方法才能得到准确的类型标签。
优势
- 完全不受跨执行环境影响,它检测的是值的内部属性,而不是原型链上的引用。
- 可以准确区分所有内置类型(包括
null和undefined)。 - 对于自定义对象,默认返回
"[object Object]",可以通过Symbol.toStringTag自定义。
自定义类型标签
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
Object.prototype.toString.call(new MyClass()); // "[object MyClass]"
适用场景
当你需要精确判断所有内置类型,或者需要处理跨框架、跨窗口的数据时,Object.prototype.toString 是最值得信赖的工具。通常在工具库中封装一个通用的类型判断函数,内部就用这个方法。
8.3.5 方法选型与最佳实践
这四种方法并非互相替代,而是互补:
- 检测基本类型(含
undefined):首选typeof,它最简洁且不会抛出错误(除了typeof null需额外注意)。 - 区分数组、普通对象等引用类型:
Array.isArray()是 ES6 专门为数组提供的 API,内部实现逻辑与Object.prototype.toString一致,是最佳选择。对于其他内置对象,使用Object.prototype.toString。 - 判断自定义类的实例:在同一执行环境中,
instanceof是自然的选择,因为它表达了“是否是某类的实例”这一语义。 - 性能敏感场景:
typeof和constructor速度较快,Object.prototype.toString需要调用函数并解析字符串,虽然开销不大,但高频调用时需留意。
日常开发中,一个健壮的类型工具函数通常如下组合:
function getType(value) {
if (value === null) return 'null';
const type = typeof value;
if (type !== 'object') return type;
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
这样既保留了 typeof 的高效,又利用 Object.prototype.toString 弥补了引用类型的细分。理解每种方案的机理,你就能在适当时机做出最正确的选择。