instanceof原理


一、instanceof原理:

作用:

①用于判断某个实例是否属于某构造函数

②在继承关系中用来判断一个实例是否属于它的父类型或者祖先类型的实例

查找构造函数的原型对象是否在实例对象的原型链上,如果在返回true,如果不在返回false。

说白了,只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。

var obj = new Object()
obj instanceof Object // true

如果没有发生继承关系,这个逻辑自然是没有疑惑的。

但是,利用原型继承,切断了原来的prototype的指向,而指向了一个新的对象,这时instanceof又是如何进行判断的呢?

注:这里要严格一个问题,有些说法是检测目标的__proto__与构造函数的prototype相同即返回true,这是不严谨的,检测的一定要是对象才行,如下:

let num = 1
num.__proto__ === Number.prototype // true
num instanceof Number // false

num = new Number(1)
num.__proto__ === Number.prototype // true
num instanceof Number // true

num.__proto__ === (new Number(1)).__proto__ // true

上面例子可以看出,1与new Number(1)几乎是一样的,只是区别在于是否封装成对象,所以instanceof的结果是不同的。

本文通过代码与画图分析instanceof的底层原理,借此复习原型链的知识。


二、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__;  
    }
}

案例1:未发生继承关系时

function Person(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}

function Student(score){
    this.score = score;
}

var per = new Person("小明"20, "男");

var stu = new Student98);

console.log(per instanceof Person);  // true

console.log(stu instanceof Student);  // true

console.log(per instanceof Object);  // true

console.log(stu instanceof Object);  // true

1、下图1是未发生继承关系时的原型图解:

图1  未发生继承关系时的原型图解

2、instanceof的工作流程分析

首先看per instanceof Person

function instance_of(L, R) { // L即per ;  R即Person
   var O = R.prototype; //O为Person.prototype

   L = L.__proto__;       // L为per._proto_

   while (true) {    //执行循环
        if (L === null)   //不通过
            return false;   

        if (O === L)       //判断:Person.prototype ===per._proto_?
            return true;  //如果等于就返回true,证明per是Person类型

        L = L.__proto__;                   
   }
}         

执行 per instanceof Person ,通过图示看出Person.prototype ===per.*proto*是成立的,所以返回true,证明引用per是属于构造函数Person的。

接下来再看 per instanceof Object

function instance_of(L, R) { //L即per ;  R即Object        
    var O = R.prototype;  //O为Object.prototype        

    L = L.__proto__;    // L为per._proto_             

    while (true) {     //执行循环                   
        if (L === null){  //不通过                            
            return false;  
        }                   

        if (O === L){   //Object .prototype === per._proto_?  不成立**
            return true; 
        }                         

        L = L.__proto__; //令L为 per._proto_ ._proto_ ,**
        //即图中Person.prototype._proto_指向的对象

        //接着执行循环,

        //到Object .prototype === per._proto_ ._proto_  ?

        //成立,返回true
    }
}

案例2:发生继承关系时

function Person(name,age,sex){

    this.name = name;

    this.age = age;

    this.sex = sex;

}

function Student(name,age,sex,score){

    Person.call(this,name,age,sex);  

    this.score = score;

}

Student.prototype = new Person();  // 这里改变了原型指向,实现继承

var stu = new Student("小明",20,"男",99); //创建了学生对象stu

console.log(stu instanceof Student);    // true

console.log(stu instanceof Person);    // true

console.log(stu instanceof Object);    // true

1、下图2是发生继承关系后的原型图解

图2  发生继承关系后的原型图解

2、instanceof的工作流程分析

首先看 stu instanceof Student

function instance_of(L, R) { //L即stu ;  R即Student
var O = R.prototype;  //O为Student.prototype,现在指向了per

L = L.__proto__;    //L为stu._proto_,也随着prototype的改变而指向了per

while (true) {    //执行循环
        if (L === null){  //不通过
            return false;   
        }
        if (O === L){    //判断: Student.prototype ===stu._proto_?
            return true;  //此时,两方都指Person的实例对象per,所以true
        }
        L = L.__proto__;                   

    }
} 

所以,即使发生了原型继承,stu instanceof Student 依然是成立的。

接下来看 stu instanceof Person ,instanceof是如何判断stu继承了Person

function instance_of(L, R) { // L即stu ;  R即Person        
    var O = R.prototype; // O为Person.prototype     

    L = L.__proto__;   //L为stu._proto_,现在指向的是per实例对象

    while (true) {   // 执行循环                   
        if (L === null){   //不通过                            
            return false;                    
        }
        if (O === L){    //判断:   Person.prototype === stu._proto_ ?      
            return true;   //此时,stu._proto_ 指向per实例对象,并不满足
        }
        L = L.__proto__;  //令L=  stu._proto_._proto_,执行循环

    }                     //stu._proto_ ._proto_,看图示知:

}                      //指的就是Person.prototype,所以也返回true

stu instanceof Person 返回值为true,这就证明了stu继承了Person。

stu instanceof Object 也是同理的,根据图示即可轻易得出。

三、和typeof的区别

  1. typeof操作符返回一个字符串,表示未经计算的操作数的类型。

就这么几种类型:number、boolean、string、object、undefined、function、symbol。

语法: typeof [参数],如:

let obj = 1
typeof obj // 'number'
typeof 1 // "number"
typeof '1' // "string"
typeof true // "boolean"
typeof Symbol(1) // "symbol"
typeof {} // "object"
typeof [] // "object",小坑,并非为array,而是object
typeof function(){} // "function"
typeof undefined // "undefined"
typeof null // "object",出名的坑

对于typeof null=="object"的问题,仅仅typeof无解,记住有这么个坑即可。

而关于 typeof array=="object"的问题,建议使用:Array.isArray([]) // true来判断即可。

  1. instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

涉及的构造函数有这些基础类型:String、Number、Boolean、Undefined、Null、Symbol;

复杂类型:Array,Object;

其他类型:Function、RegExp、Date。

语法:[对象] instanceof [构造函数],如:

let obj = new Object()
obj instanceof Object // true

注意左侧必须是对象(object),如果不是,直接返回false,具体见基础类型。

基础类型

let num = 1
num instanceof Number // false

num = new Number(1)
num instanceof Number // true

明明都是num,而且都是1,只是因为第一个不是对象,是基本类型,所以直接返回false,而第二个是封装成对象,所以true。

这里要严格注意这个问题,有些说法是检测目标的__proto__与构造函数的prototype相同即返回true,这是不严谨的,检测的一定要是对象才行,如:

let num = 1
num.__proto__ === Number.prototype // true
num instanceof Number // false

num = new Number(1)
num.__proto__ === Number.prototype // true
num instanceof Number // true

num.__proto__ === (new Number(1)).__proto__ // true

上面例子可以看出,1与new Number(1)几乎是一样的,只是区别在于是否封装成对象,所以instanceof的结果是不同的。

string、boolean等,这些基础类型一样的。

new String(1)与String(1)是不同的,new是封装成对象,而没有new的只是基础类型转换,还是基础类型,如下:

new String(1) // String {"1"}
String(1) // "1"

其他基础类型一样的。

复杂类型,比如数组与对象,甚至函数等,与基础类型不同。

复杂类型

let arr = []
arr instanceof Array // true
arr instanceof Object // true
Array.isArray(arr) // true

首先,字面量是直接生成构造函数的,所以不会像基本类型一样两种情况,这个可以放心用。

但是上面那个问题,当然,基础类型也会有这个问题,就是与Object对比。没办法,Object在原型链的上层,所以都会返回true,如下:

(new Number(1)) instanceof Object // true

由于从下往上,比如你判断是Number,那就没必要判断是不是Object了,因为已经是Number了

Array一个道理,不过还是建议使用isArray来专门处理数组判断。

new Object(){}就不介绍了,一样的情况。

其他类型

let reg = new RegExp(//)
reg instanceof RegExp // true
reg instanceof Object // true

let date = new Date()
date instanceof Date // true
date instanceof Object // true

除了Function,都一样,具体Function如下:

function A() {}
let a = new A()
a instanceof Function // false
a instanceof Object // true
A instanceof Function // true

这里要注意,function A() {}相当于let A; A = function() {},然后分析:

  1. a是new出来,所以是经过构造,因此已经是对象,不再是函数,所以false。
  2. a是经过构造的对象,返回ture没问题。
  3. 如上所述,A是个函数,因此没什么概念上的问题。但是要知道A.__proto__Function.prototypeƒ () { [native code] },这是与object以后处于原型链上层的存在,而且与object平级,检测如下:
let obj = {}
obj.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
obj.__proto__.prototype // undefined

let A = function() {}
A.__proto__ // ƒ () { [native code] }
A.__proto__.prototype // undefined

总结:

typeof 在对值类型number、string、boolean 、undefined、以及引用类型的function的反应是精准的;但是,对于对象{ } 、数组[ ] 、null 都会返回 object,但对 NaN 返回的是number类型

为了弥补这一点,instanceof 从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录