關於js繼承中"prototype模式"的constructor重定向淺析

近來在看阮一峯的網絡日誌,確實寫得好,非常通俗易懂,對我們這些小白受益匪淺!

JavaScript的繼承一直是一大難點,初涉會花費非常多的時間,我也如此。在摸索了許多天之後,逐漸有了一些頭緒。

但是關於constructor屬性重定向這個問題上,我還是存在疑問。

在測試過程中,發現測試的結果與阮一峯老師的結論有些出入,在此記錄。

首先粘上阮一峯老師的這篇博文:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

同時爲了方便閱讀,在此將部分內容摘抄:

1、阮一峯老師的"prototype模式"講解

現在有一個"動物"對象的構造函數,以及一個"貓"對象的構造函數。
function Animal(){
  this.species = "動物";
}
function Cat(name,color){
  this.name = name;
  this.color = color;
}
要使“貓”繼承“動物”,使用prototype模式的話,就要這樣:
"貓"的prototype對象,指向一個Animal的實例,那麼所有"貓"的實例,就能繼承Animal了。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動物
代碼的第一行,我們將Cat的prototype對象指向一個Animal的實例。
Cat.prototype = new Animal();
它相當於完全刪除了prototype 對象原先的值,然後賦予一個新值。但是,第二行又是什麼意思呢?
Cat.prototype.constructor = Cat;
原來,任何一個prototype對象都有一個constructor屬性,指向它的構造函數。如果沒有"Cat.prototype = new Animal();"這一行,Cat.prototype.constructor是指向Cat的;加了這一行以後,Cat.prototype.constructor指向Animal。
alert(Cat.prototype.constructor == Animal); //true
更重要的是,每一個實例也有一個constructor屬性,默認調用prototype對象的constructor屬性(此處文字加粗,是一會討論的重點)
alert(cat1.constructor == Cat.prototype.constructor); // true
因此,在運行"Cat.prototype = new Animal();"這一行之後,cat1.constructor也指向Animal!
alert(cat1.constructor == Animal); // true

這顯然會導致繼承鏈的紊亂(cat1明明是用構造函數Cat生成的),因此我們必須手動糾正,將Cat.prototype對象的constructor值改爲Cat。這就是第二行的意思。

這是很重要的一點,編程時務必要遵守。下文都遵循這一點,即如果替換了prototype對象,

o.prototype = {};
那麼,下一步必然是爲新的prototype對象加上constructor屬性,並將這個屬性指回原來的構造函數。
o.prototype.constructor = o;

2、關於自己對constructor屬性的理解

首先, 阮老師說:”每個實例也有一個constructor屬性,默認調用prototype對象的constructor屬性“,我在這一點比較疑問。請看下面的代碼:
function Animal(){
    this.species = "動物";
  }
var animal = new Animal();
console.info( "constructor" in animal );//true
console.info( animal.hasOwnProperty("constructor") );//false
console.info( animal.__proto__.hasOwnProperty("constructor") );//true
從上面的代碼很明顯的看出,新new出來的實例是沒有constructor屬性的,當訪問實例的constructor屬性時,根據作用域鏈的原則,會到其prototype對象中去找。
那麼其實當訪問實例的constructor屬性時,實質上訪問的是其prototype的constructor。
所以初步得到的結論是:實例對象並沒有自己的constructor屬性!

那麼,下面阮老師代碼的第二行中的constructor到底是誰的呢?
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;


上面的代碼我們可以如下轉換:
var animal = new Animal();
Cat.prototype = animal;
Cat.prototype.constructor = Cat;
此時我們再輸出:
console.info("constructor" in animal);//true
console.info( animal.hasOwnProperty("constructor") );//true
console.info( animal.__proto__.hasOwnProperty("constructor") );//true
此時的animal竟然多出了constructor屬性(注意:這裏的animal一直是一個實例)。
到此,我做了一個大膽的猜測:當執行Cat.prototype.constructor = Cat時,其實是手動給Cat.prototype指向的實例新增了一個constructor屬性。

爲了驗證這個猜想,我再次用Firefox的Web控制檯查看每次輸出的animal中的屬性,如下:
首先輸出單個animal:
function Animal(){
  this.species = "動物";
}
var animal = new Animal();
console.info(animal);
控制檯輸出數據:
可以看到animal中除了自己的species屬性外沒有constructor屬性。
然後改寫阮老師代碼:
function Animal(){
	this.species = "動物";
}
function Cat(){
	this.name="大黃";
}

var animal = new Animal();
Cat.prototype = animal;
Cat.prototype.constructor = Cat;

console.info(animal);
控制檯輸出數據:

此處可以看到animal多了一個constructor屬性,並指向Cat。

由此可論證:constructor屬性並非每個實例固有,而是在爲了防止繼承鏈混亂而給已成爲其他對象的prototype的實例人爲加上的。
所以,其實並非阮老師說的有偏差,而是說的不夠細緻,以使我們這些小白容易掉到坑裏。

最後再重新闡述一下阮老師代碼中關於constructor重定向的問題。
當令Cat.prototype = animal,然後訪問新new出來的實例cat的cat.constructor時,由於cat實例中沒有,根據作用域鏈原則向上查找,查找其prototype(也就是新new出來的animal),而animal中也沒有,繼續向上查找,找到Animal.prototype,則其返回Animal的構造方法。
這是我們不希望看到的,因爲cat是通過Cat構造方法new出來的,而constructor卻返回了Animal的構造方法。
則爲了避免這種情況,我們將cat的原型Cat.prototype,也就是animal新增一個constructor屬性,並將其強制指向Cat,那麼一切返回最初我們希望的狀態。cat.constructor能很好的反應其是Cat,而非Animal。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章