instanceof为何会失真——constructor指向的改变

首先,对constructor属性有如下几点了解:浏览器

  1. constructor属性是原型对象具备的属性,指向经过prototype户型连接它的构造函数
  2. 因为实例对象继承自原型对象,因此实例对象中也具备constructor属性,指向与原型对象中的constructor同样;
  3. 其实构造函数(不管是原生的仍是自定义的)也有constructor属性,它们通通指向原生的Function构造函数,就连Function本身的构造函数也是它本身

1、发现

对于一些公共的属性和方法,我么能够经过原型对象,把它们定义在构造函数的外部,使构造函数成为一个空函数:函数

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};

可是,这样的话,每增长一个公共的属性或方法都得写上Person.prototype。为了减小重复的书写,更常见的作法是用一个包含全部属性和方法的对象字面量来重写整个原型对象性能

function Person(){}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

通过这样重写以后,从视觉上更好的封装原型的功能,咱们至关于把Person.prototype设置成了一个以对象字面量形式建立的新对象。
可是,这样设置有一个问题:constructor属性再也不指向person了this

var friend = new Person();
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false

咱们能够看到,instanceof表示friend仍然是Person的实例,可是constructor却代表friend的构造函数(父类)再也不是Person了,constructor与instanceof的结果不一致这就形成了instanceof的失真。那么对象的constructor到底指向谁呢?prototype

2、解答

var friend = new Person();
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true

没错,constructor 属性指向了Object,也就是说经过这种对象字面量方式改变原型对象以后,原型/实例对象的constructor属性指向了Object,它们的构造函数(父类)变成了Object。
这是为何呢?
每建立一个函数就会同时建立它的prototype对象,这个对象也会自动得到constructor属性。而经过对象字面量形式改写原型对象,本质上算是彻底重写了默认的原型对象,也便是说咱们写了一个新的对象,它是个新对象,所以它的constructor属性也就变成了新对象的constructor属性,指向Object构造函数,再也不指向Person函数了。
所以,经过constructor操做符还能返回正确的结果,可是经过instanceof不能准确肯定对象的类型设计

var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true

3、带来的后果&根本缘由

经过改写原型对象的方式改变属性和方法,不只使instanceof操做符失真,若是实例对象定义在修改以前,还会致使实例对象没法访问新加的属性和方法:3d

var friend = new Person();
Person.prototype.sayHi = function(){
    alert("hi");
};
friend.sayHi(); //"hi"(没有问题!)

这段代码显示,经过使用Person.prototype.……的方式逐个向原型对象上添加的属性,能够被实例对象成果访问;可是,经过改写原型对象的形式添加新属性和方法并不是如此:指针

function Person(){}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName(); //error

显然,经过改写原型对象的方式,实例对象不能访问新添加的属性和方法。致使这一现象的根本缘由和instanceof操做符失真的缘由一致:constructor的指向发生了变化
请参考如下图示:code

简单来说,调用构造函数时,会为实例添加一个指向最初原型的_proto_指针,重写原型对象成为一个新对象,就等于:对象

  1. 切断了构造函数与最初原型之间的联系,切断后constructor默认指向Object,但能够自定义修改;
  2. 切断了新原型与以前已经存在的任何实例对象之间的联系,即新原型不是任何已有实例的原型对象
  3. 实例对象的_proto_属性指向的仍然是原有的原型对象。

4、小结

1.常规写法,可是比较啰嗦,每次都要重复写Person.prototype

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
//...

2.使用对象字面量方式改写原型对象,可是这样会改变constructor的指向

function Person(){}
Person.prototype = {
    name : "Nicholas",
    age : 29,
  //...
};

3.若是 constructor 的值真的很重要,能够像下面这样特地将它设置回适当的值。

function Person(){}
Person.prototype = {
    constructor : Person,
    name : "Nicholas",
    age : 29,
  //...
};

在代码中特地包含一个 constructor属性,并将它的值设置为Person,从而确保了经过该属性可以访问到适当的值

4.可是以上述形式从新指定constructor属性,会使得constructor属性的[[Enumerable]]特性被设置为 true,也就是变成了可枚举类型的属性,可是原生的constructor属性是不可枚举类型,[[Enumerable]]特性为 false,所以可以使用下列语句修改它的[[Enumerable]]属性,仍符合原生的设定:

function Person(){}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
//重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});
Reference:
  1. 《JavaScript高级程序设计(第三版)》
相关文章
相关标签/搜索