我們先看一段傳統的繼承代碼:
復制代碼 代碼如下:
//定義超類
function Father(){
this.name = "父親";
}
Father.prototype.theSuperValue = ["NO1","NO2"];
//定義子類
function Child(){
}
//實現繼承
Child.prototype = new Father();
//修改共享數組
Child.prototype.theSuperValue.push("修改");
//創建子類實例
var theChild = new Child();
console.log(theChild.theSuperValue); //["NO1","NO2","修改"]
//創建父類實例
var theFather = new Father();
console.log(theFather.theSuperValue); //["NO1","NO2","修改"]
通過上面的代碼,我們注意“加紅”的代碼,子類Child的原型對象是父類Father的一個實例(new Father()),我們在這裡是調用new Father()對象中的theSuperValue屬性,因為new Father()對象中沒有此屬性(只有name屬性),因此會沿著原型鏈向它的原型對象(Father.prototype)中去找,找到後發現是一個數組,而且是引用類型,此時我們往此數組中添加一個字符串“修改”。
之後,我們新建了Child的實例對象theChild,當theChild調用theSuperValue屬性時,首先它自己裡面沒有此屬性,就會去它的原型對象(new Father)中去找,可惜這裡也沒有,接著會到new Father()的原型中去找,OK,在Father.prototype中找到了這個數組,發現是["NO1","NO2","修改"]。
再接著,我們創建了Father的實例對象theFather,同上,我們在Father.prototype中找到了這個引用類型的數組["NO1","NO2","修改"]。(當然,數組都是引用類型的!)
通過上面的贅述,本來已經理解原型鏈概念的朋友覺得是廢話連篇,其實我也是呵呵,接下來我們再看一個相似的例子:
復制代碼 代碼如下:
//定義超類
function Father() {
this.name = "父親";
}
Father.prototype.theSuperValue = ["NO1", "NO2"];
//定義子類
function Child() {
}
//實現繼承
Child.prototype = new Father();
//修改共享數組
Child.prototype.theSuperValue = ["我是覆蓋代碼"]
//創建子類實例
var theChild = new Child();
console.log(theChild.theSuperValue);
//創建父類實例
var theFather = new Father();
console.log(theFather.theSuperValue);
我們看一下上面的代碼,我用一種比較特別的紫色標注了此段代碼與上段代碼的小小區別,但結果卻發生了“巨大”變化,見下面的截圖:
為什麼我說是巨大變化,是因為我們從“重用公共屬性”過渡到“覆蓋公共屬性,建立自己特色屬性”上來!我這裡是用數組演示的,其實第二種情況常常用在Function中,用子類的方法來覆蓋父類的方法。
在第二段代碼中,我們需要關注的是紫色代碼前的“=”號,它是賦值操作符。如果我們對Child.prototype及new Father()對象調用這個賦值操作符時,我們就在這個對象上“新建”了一個屬性,當在下面的theChild實例上調用theSuperValue時,返回的當然是新屬性值["我是覆蓋代碼"]。
但當我們新創建一個父類實例theFather對象時,調用該對象上的theSuperValue屬性,我們就會發現對象上並沒有啊,這是為什麼呢?因為我們剛才覆蓋的是Father對象new Father();而不是Father類,所以,通過Fater()構造函數創建的新對象theFather並不包含新建的屬性,當然,接下來的事情大家都明白,就是沿著原型鏈向上找,OK,在Father.prototype中找到了,就是我們一開始定義的那個數組。
通過上面兩個例子,我們在JS中使用原型提供的繼承功能時,尤其是利用子對象操作原型方法、對象時,切記“=”號賦值與引用調用這兩種不同的操作,因為他們會帶來完全不同的結果。