本文深入解析 JavaScript 中 instanceof 运算符的用法与底层原理。通过对比 typeof,说明 instanceof 在类型确认上的优势。详细拆解基于原型链的判定逻辑,解释 Object、Function 等内置对象的特殊行为。最后结合 Dojo 框架实例,探讨其在多重继承场景下的局限性及 isInstanceOf 方法的替代方案。
instanceof 运算符简介
instanceof 运算符简介
在 JavaScript 中,通常使用 typeof 运算符来判断变量类型。然而,当处理引用类型时,typeof 存在局限性:无论引用的是哪种具体类型的对象,它统一返回 “object”。为了解决这一识别模糊的问题,ECMAScript 规范引入了 instanceof 运算符。该运算符与 typeof 类似,用于识别当前处理对象的类型,但不同之处在于,instanceof 要求开发者明确指定目标类型,从而进行更精确的类型确认。例如:
清单 1. instanceof 示例
var oStringObject = new String("hello world");
console.log(oStringObject instanceof String); // 输出 "true"
上述代码旨在验证变量 oStringObject 是否为 String 对象的实例。由于 oStringObject 确实是通过 String 构造函数创建的实例,因此表达式返回 “true”。虽然 instanceof 的灵活性不如 typeof,但在 typeof 仅返回 “object” 而无法区分具体类型的场景下,instanceof 提供了必要的类型区分能力。
instanceof 运算符的常规用法:
在常规使用中,instanceof 主要用于判断一个实例是否归属于特定的类或构造函数。例如:
清单 2. instanceof 常规用法
// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)//true
此外,instanceof 在继承关系中具有重要作用,可用于判断实例是否属于其父类或祖先类。例如:
清单 3. instanceof 在继承中关系中的用法
// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承
var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true
上述代码演示了判断单层继承关系中的父类。在涉及多层继承的场景中,instanceof 运算符同样适用,能够沿原型链向上追溯,确认实例与各个祖先类的关系。
instanceof 底层原理分析
你真的了解 instanceof 操作符吗?
通过上述示例,instanceof 的操作看似简单。然而,深入探究其内部机制会发现一些反直觉的现象。
清单 4. instanceof 复杂用法
console.log(Object instanceof Object);//true
console.log(Function instanceof Function);//true
console.log(Number instanceof Number);//false
console.log(String instanceof String);//false
console.log(Function instanceof Object);//true
console.log(Foo instanceof Function);//true
console.log(Foo instanceof Foo);//false
观察上述代码,可能会产生困惑:为什么 Object 和 Function 对自身的 instanceof 判断结果为 true,而其他类(如 Number、String)对自身的判断结果却为 false?要彻底理解这一现象,需要从两个核心维度进行分析:一是 ECMAScript 语言规范对该运算符的定义;二是 JavaScript 的原型继承机制。
清单 5. JavaScript instanceof 运算符代码
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
清单 6. Object instanceof Object
// 为了方便表述,首先区分左侧表达式和右侧表达式
ObjectL = Object, ObjectR = Object;
// 下面根据规范逐步推演
O = ObjectR.prototype = Object.prototype
L = ObjectL.__proto__ = Function.prototype
// 第一次判断
O != L
// 循环查找 L 是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判断
O == L
// 返回 true
清单 7. Function instanceof Function
// 为了方便表述,首先区分左侧表达式和右侧表达式
FunctionL = Function, FunctionR = Function;
// 下面根据规范逐步推演
O = FunctionR.prototype = Function.prototype
L = FunctionL.__proto__ = Function.prototype
// 第一次判断
O == L
// 返回 true
清单 8. Foo instanceof Foo
// 为了方便表述,首先区分左侧表达式和右侧表达式
FooL = Foo, FooR = Foo;
// 下面根据规范逐步推演
O = FooR.prototype = Foo.prototype
L = FooL.__proto__ = Function.prototype
// 第一次判断
O != L
// 循环再次查找 L 是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判断
O != L
// 再次循环查找 L 是否还有 __proto__
L = Object.prototype.__proto__ = null
// 第三次判断
L == null
// 返回 false
Dojo 框架中的多重继承处理
简析 instanceof 在 Dojo 继承机制中的应用
与 Java 类似,原生 JavaScript 不支持多重继承。但在 Dojo 库中,通过 declare 方法声明类时,允许类继承自多个父类。以下以 Dojo 1.6.1 版本为例进行说明。
清单 9. Dojo 中多重继承
dojo.declare("Aoo",null,{});
dojo.declare("Boo",null,{});
dojo.declare("Foo",[Aoo,Boo],{});
var foo = new Foo();
console.log(foo instanceof Aoo);//true
console.log(foo instanceof Boo);//false
console.log(foo.isInstanceOf(Aoo));//true
console.log(foo.isInstanceOf(Boo));//true
在上述示例中,Foo 类同时声明继承自 Aoo 和 Boo。然而,当使用 instanceof 运算符检查 foo 实例是否属于 Boo 时,返回结果为 false。这是因为在 Dojo 的内部实现中,Foo 实际上只直接继承自 Aoo,而 Boo 类的方法和属性是通过 mixin 机制拷贝到 Foo 中的。因此,基于原型链的 instanceof 无法识别这种混合继承关系,返回 false。为此,Dojo 为每个类的实例提供了 isInstanceOf 方法,专门用于检查这种多重继承关系。