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高級程序設計(第三版)》
相關文章
相關標籤/搜索