instanceof与constructor的区别

名词介绍

instanceof 的作用是判断实例对象是否为构造函数的实例,实际上判断的是实例对象的__proto__属性与构造函数的prototype属性是否指向同一引用;

constructor 的作用是返回实例的构造函数,即返回创建此对象的函数的引用。

区别

先贴出代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Instanceof</title>
</head>
<body>
    
</body>
<script>
    // 创建植物plant类
    var plant = function(name,where){
        this.name = name;
        this.where = where;
        document.write(this.name + "喜欢" + this.where + "的环境<br>");
    }
    // 创建动物animal类
    var animal = function(name,food){
        this.name = name;
        this.food = food;
        document.write(this.name + "吃" + this.food + "<br>");
    }
    // new两个实例dog和cat
    var dog = new animal("狗","骨头");
    var cat = new animal("猫","鱼");
    
    // new两个实例greenDill和hangBasket
    var greenDill = new plant("绿萝","湿热");
    var hangBasket = new plant("吊篮","温暖湿热");
</script>
</html>

上边代码中的创建了两个函数,new了四个实例。

instanceof

img

上图可以看出用 instanceof 判断出dog的构造函数是animal,这个结果是意料之中的,但为什么Object也是dog的构造函数呢?

因为构造函数animal的 prototype 也是一个对象,对象就有__proto__属性,就会沿着原型链一直往上找,直到__proto__:Object结束,所以才会有这样的结果。

constructor

上边名词介绍中写到constructor返回的是创建此对象的函数的引用。

img

总结

  • instanceof找到的是实例在原型链中所有的构造函数,不容易找到直接创建实例的构造函数;
  • constructor找到的是构造函数只有一个,就是直接创建这个实例的构造函数,所以用constructor找实例的构造函数更严谨。

constructor和instanceof的详细区别与作用

一、constructor

我们创建的每个函数都有一个prototype(原型)对象,这个属性是一个指针,指向一个对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。

function Person () {}

console.log(Person.prototype)

打印结果如下:

img

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针,指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]],双中括号表示该属性为内部属性,在JavaScript中不能直接访问。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__来访问[[Prototype]],但__proto__的[[Enumerable]]值为false,不可以通过for-in或Object.keys()枚举出来。

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

var person = new Person('Jim', 21)

console.log(person)

console.log(Object.keys(person))

console.log(person.__proto__)

console.log(person.__proto__ === Person.prototype)

打印结果如下:

img

因为constructor属性在构造函数的原型里,并且指向构造函数,那我们就可以利用constructor属性来判断一个实例对象是由哪个构造函数构造出来的,也可以说判断它属于哪个类。

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

function Car () {}

var person = new Person('Jim', 21)

console.log(person.constructor)   //Person

console.log(person.constructor === Person)    //true

console.log(person.constructor === Car)    //  false

但有一点我们是要注意的,当我们将Person.prototype设置为等于一个以对象字面量形式创建的新对象时,constructor属性不再指向Person。因为上述做法把Person默认的prototype覆盖掉,指向Person的constructor就不复存在。

function Person () {}

var person1 = new Person()

Person.prototype = {
  name: 'Jim',
  age: '21'
}

var person2 = new Person()

console.log(person1)

console.log(person2)

console.log(person1.constructor)

console.log(person2.constructor)

打印结果如下:

img

访问person2时,Person.prototype里已经没有了constructor属性,所以会继续沿着原型链往上找到Person.prototype.prototype中的constructor属性,它是指向Object的,因此person2.constructor指向Object。

那如果我们对 Person.prototype重新赋值后希望constructor仍指向Person的话,我们可以在字面对象里加一个constructor属性让它指向Person

function Person () {}

var person1 = new Person()

Person.prototype = {
  constructor: Person,
  name: 'Jim',
  age: '21'
}

var person2 = new Person()

console.log(person1)

console.log(person2)

console.log(person1.constructor)

console.log(person2.constructor)

console.log(Object.keys(Person.prototype))

打印结果如下:

img

我们发现person2.constructor重新指向Person,但同时我们也发现constructor变成了可枚举属性,上文说到constructor属性默认是不可枚举的,即[[Enumerable]]的值为false

我们可以通过Object.defineProperty()把constructor定义为不可枚举属性

function Person () {}

var person1 = new Person()

Person.prototype = {
  name: 'Jim',
  age: '21'
}
// 重设构造函数,只使用与ECMAScript5兼容的浏览器
Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

var person2 = new Person()

console.log(person1)

console.log(person2)

console.log(person1.constructor)

console.log(person2.constructor)

console.log(Object.keys(Person.prototype))

打印结果如下:

img

我们可以看到constructor已经变成了不可枚举属性

二、instanceof

**instanceof** 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。通俗来将就是判断一个实例对象是否由某个构造函数构造而来。

function Person () {}

function Car () {}

var person = new Person()

console.log(person instanceof Person)  //true

console.log(person instanceof Car)   //false

这一点与constructor有着相同的作用,但instanceof相对于constructor更为可靠

function Person () {}

Person.prototype = {
  name: 'Jim',
  age: '21'
}

var person = new Person()

console.log(person instanceof Person)    //true

console.log(person.constructor === Person)    //false

可见Person.prototype对象被重写并不影响instanceof的判断,因为instanceof是根据原型链来判断构造函数的,只要对象实例的原型链不发生变化,instanceof便可以正确判断

function Person () {}

var person = new Person()

console.log(person instanceof Person)  //true

console.log(person instanceof Object)  //true

person.__proto__ = {}

console.log(person instanceof Person)  //false

console.log(person instanceof Object)   //true

instanceof不仅可以判断实例对象直接的构造函数,而且还能判断原型链上所有的构造函数,上面代码中Object在person的原型链中,所以返回true。

当我们把空字面对象{}赋值给person.__proto__后,相当于切断了person原来的原型链(person -> Person -> Object),所以person instanceof Person返回false,那为什么person instanceof Object会返回true呢,因为空字面对象是Object的实例,即原型链修改为(person -> Object)。这里顺带说一下我面试时被问到的一道面试题,就是Object的原型是什么?答案是null,因为Object是万物之源,所以对象都是Object的实例,处于原型链的末端,而它没有原型