JavaScript中的instanceof运算符
概要
在Javascript开发过程中,我们经常会涉及原型和原型链的概念,并且会使用instanceof运算符来判断变量的类型,判断的过程又涉及原型和原型链的概念。为了更好的了解和应用该运算符,本文通过分析原型和原型链,再来模拟其实现,从而揭开其中的原理。
基本概念
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
为了理解上述概念,我们首先搞清楚什么是原型,什么是原型链接。
原型(prototype): 函数的一个属性,是一个对象
原型链(proto):Object对象的一个属性,也是一个Object对象。
对象的__proto__保存着该对象构造函数的prototype
基于上述概念,instanceof运算符的本质就是检查目标对象是被哪个方法构造的,因为存在继承关系,所以是在整个原型链上查找。也就是检测该对象是什么类型的。
JS中的class是function的语法糖,所以上述概念依然适用。
概念解析
原型链解析
我们看如下代码:
function A(){this.a = 10}var a = new A();a.__proto__ === A.prototype && console.log("a is instance of A");a instanceof A && console.log("a is instance of A");a.__proto__.__proto__ === Object.prototype && console.log("a is instance of Object");a instanceof Object && console.log("a is instance of Object");a.__proto__.__proto__ .__proto__ === null && console.log("Object prototype's prototype is null");
运行结果如下:
我们从原型链的角度分析一下上面的结果:
对象a 实际上就是下面的值。
a = { a:10, __proto__: { // A.prototype __proto__:{ // Object.prototype __proto__ : null } }}
严格说,把 __proto__理解成原型链是不准确的,它实际是链条的节点。多个__proto__即多个原型对象组成了原型链。
- a 是被A方法构造出来的。所以a的__proto__等于A.prototype,因此a instanceof A 是真
- 按照定义a的__proto__是一个Object对象,也就是被Object构造出来的,因此a.proto.proto 等于Object.prototype
- Object的prototype对象还有__proto__属性,但是为空,即已经到了链条的最底部。
将function换成class可以得到相同的结果,function只是class的语法糖,所以运行结果是一样的,请参考下面的代码,不再赘述。
class A {constructor(){}}var a = new A();a.__proto__ === A.prototype && console.log("a is instance of A");a instanceof A && console.log("a is instance of A");a.__proto__.__proto__ === Object.prototype && console.log("a is instance of Object");a instanceof Object && console.log("a is instance of Object");a.__proto__.__proto__ .__proto__ === null && console.log("Object prototype's prototype is null");
运行结果如下:
instanceof的其他例子
var date = new Date();var isDate = date instanceof Date;var isArray = date instanceof Array;isDate && console.log(`date object is Date type.`);isArray && console.log(`date object is Array.`);
运行结果如下:
只打印出了“date object is Date type.” 原因很简单,再date对象的原型链上,每一个__proto__ 都不等于Array.prototype, 即date对象并不是被Array构造方法构造出来的。
Date, Array, Object到底是什么
为了搞清楚Date,Array,Object这些JS内置类型,我们执行下面的代码:
console.log(typeof Date)console.log(typeof Array)console.log(typeof Object)
执行结果如下:
它们都是函数,更准确说都是构造函数,他们都是被Function构造出来的。
下面我们来看里一个问题,为什么Date,Array,Object亦或我们自定义的类型,都有__proto__属性?
JS并不像OOP语言那样,类和对象具有严格的界限,对象不能再作为类去定义新的对象。JS中的对象或实例的概念具有相对性,只要有构造函数,即可以构造对象。
如果我们写成var date = new Date(),这就说明构造date对象或实例,要调用Date函数,Date是一个构造函数,需要用new调用。
但是Date为什么也有__proto__属性呢,因为它是被Function构造出来的。因为Date它也有构造方法,它的构造方法是Function,相对于Function,Date只是一个它构造的实例,所以下面代码可以返回为真。
function A(){this.a = 10}console.log(A.__proto__ === Function.prototype)console.log(Date.__proto__ === Function.prototype)console.log(Array.__proto__ === Function.prototype)console.log(Object.__proto__ === Function.prototype)
所以我们通过instanceof,也可以得到相同的结果,因为这也是符合instanceof运行符的定义的。
function A(){this.a = 10}A instanceof Function && console.log("A is instance of Function");Date instanceof Function && console.log("Date is instance of Function");Array instanceof Function && console.log("Array is instance of Function");Object instanceof Function && console.log("Object is instance of Function");
instanceof 运算符代码重写
基于我们上面对原型,原型链的讨论,我们重写instanceof运算符的代码如下:
function isInstanceOf(obj,ctor){ var proto = Object.getPrototypeOf(obj); while(proto != null){ if (proto === ctor.prototype){ return true; } proto = Object.getPrototypeOf(proto); } return false;}
因为并不是所有浏览器支持__proto__属性,所以采用Object.getPrototypeOf代替它。
代码验证:
function A(){}function B(){}var a = new A();console.log("Array is instance of Function");console.log(isInstanceOf(Array,Function));console.log(Array instanceof Function );console.log("a is instance of A");console.log(isInstanceOf(a,A));console.log(a instanceof A);console.log("a is not instance of B");console.log(isInstanceOf(a,B));console.log(a instanceof B);console.log("a is instance of Object");console.log(isInstanceOf(a,Object));console.log(a instanceof Object);