最基础的类型检测工具——typeof, instanceof
typeof 和 instanceof 是 JavaScript 中两个最基础但也最容易让人困惑的类型检测工具。要深入理解它们的原理,我们需要从 JavaScript 的底层数据存储、类型系统和原型链机制入手。
typeof
typeof 是一个一元运算符,它的核心任务是返回一个代表操作数类型的字符串。其原理深入到 JavaScript 引擎是如何在底层存储和标识变量的。
1. 核心原理:底层类型标签(Type Tagging)
在 JavaScript 的早期实现中,值在引擎内部是由一个**类型标签(Type Tag)**和实际的数据值来表示的。这个类型标签存储在变量的机器码低位中,用于标识该值的类型。
- 底层存储机制:JavaScript 引擎在存储变量时,会在内存中为变量分配空间,并用低位的1-3个比特位来存储其类型信息。
-
typeof的工作方式:当你对变量使用typeof操作符时,JavaScript 引擎并不会去回溯变量的创建过程,而是直接读取这个变量在内存中机器码低位的类型标签,然后将其映射为对应的类型字符串返回。
2. 机器码的类型映射
不同的类型对应着不同的低位标识。一个常见的类型标签映射如下:
-
000:对象(object)。 -
010:浮点数(number)。 -
100:字符串(string)。 -
110:布尔值(boolean)。 -
1:整数(number),整数类型标签是1,但会被归为number类型。 -
-2^30:undefined。
3. 著名的历史遗留问题:typeof null === 'object'
这是 JavaScript 中最著名的Bug之一,至今未被修复以保持兼容性。
-
原因:如前所述,对象的类型标签是
000。而**null在底层表示的是空指针,在大多数实现中,空指针的机器码全是0**。因此,当typeof读取null的类型标签时,发现是000,就错误地将其判断为object。
4. 特例:函数的识别
虽然函数在底层也是对象(类型标签是 000),但 typeof function(){} 返回的是 'function'。这是因为 JavaScript 引擎内部对可调用对象做了特殊处理。当 typeof 操作符检测到一个对象内部实现了 [[Call]] 方法时,它会特殊处理并返回 "function"。
instanceof
instanceof 是一个二元运算符,用于检测一个对象的原型链上是否存在另一个构造函数的 prototype 对象。它的核心是原型链查找。
1. 核心原理:原型链检查
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。其内部机制可以简单地用以下代码模拟:
function myInstanceof(instanceObj, constructorFunc) {
// 1. 获取实例对象的隐式原型(内部 [[Prototype]] 属性)
let proto = Object.getPrototypeOf(instanceObj);
// 2. 获取构造函数的显式原型
let prototype = constructorFunc.prototype;
// 3. 沿着原型链向上查找
while (true) {
// 4. 如果找到头(null)还没找到,返回 false
if (proto === null) {
return false;
}
// 5. 如果找到了匹配的原型,返回 true
if (proto === prototype) {
return true;
}
// 6. 继续向上一级查找
proto = Object.getPrototypeOf(proto);
}
}
2. 工作步骤详解
假设我们执行 obj instanceof Constructor,引擎会执行以下步骤:
-
获取隐式原型:获取左侧对象
obj的内部[[Prototype]]属性(在浏览器中可以通过非标准__proto__或标准Object.getPrototypeOf()访问)。 -
获取显式原型:获取右侧构造函数
Constructor的prototype属性。 -
循环比较:将
obj的隐式原型与Constructor的显式原型进行比较。- 如果相等,返回
true。 - 如果不相等,则将
obj的隐式原型的隐式原型(即原型链的上一级)取出来,再次与Constructor.prototype比较。 - 这个过程持续进行,直到原型链的末端(即
null)。如果一直没找到相等的对象,则返回false。
- 如果相等,返回
3. 重要特征
-
跨窗口问题:
instanceof依赖于原型链,因此它不能跨不同的全局执行环境(例如,来自iframe的数组在父页面中使用array instanceof parentWindow.Array会返回false),因为它们的原型链指向的是不同的Array.prototype对象。 -
只能用于对象:由于
instanceof的机制是查找原型链,对于原始类型(string,number,boolean等),它们不是对象,没有原型链,因此直接用instanceof检测原始类型会始终返回false。
总结对比
| 特征 | typeof |
instanceof |
|---|---|---|
| 本质原理 | 读取变量机器码低位的类型标签。 | 遍历左侧对象的原型链,查找右侧构造函数的 prototype。 |
| 返回值 | 字符串(如 "string", "object", "function") |
布尔值(true / false) |
| 适用场景 | 检测原始类型(除 null 外)和函数。 |
检测对象类型及其继承关系。 |
| 局限性 |
null 返回 "object";数组、日期等对象均返回 "object",无法细分。 |
不能跨窗口(iframe)使用;不能用于检测原始类型。 |