# 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
上图可以看出用 instanceof 判断出 dog 的构造函数是 animal,这个结果是意料之中的,但为什么 Object 也是 dog 的构造函数呢?
因为构造函数 animal 的 prototype 也是一个对象,对象就有__proto__属性,就会沿着原型链一直往上找,直到__proto__:Object 结束,所以才会有这样的结果。
# constructor
上边名词介绍中写到 constructor 返回的是创建此对象的函数的引用。
# 总结
- instanceof 找到的是实例在原型链中所有的构造函数,不容易找到直接创建实例的构造函数;
- constructor 找到的是构造函数只有一个,就是直接创建这个实例的构造函数,所以用 constructor 找实例的构造函数更严谨。
# constructor 和 instanceof 的详细区别与作用
# 一、constructor
我们创建的每个函数都有一个 prototype(原型)对象,这个属性是一个指针,指向一个对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性是一个指向 prototype 属性所在函数的指针。
function Person () {} | |
console.log(Person.prototype) |
打印结果如下:
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针,指向构造函数的原型对象。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) |
打印结果如下:
因为 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) |
打印结果如下:
访问 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)) |
打印结果如下:
我们发现 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)) |
打印结果如下:
我们可以看到 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的实例,处于原型链的末端,而它没有原型
。