在開始擺弄程式碼之前,應該先搞清楚使用繼承的目的和能帶來什麼好處。一般來說,在設計類別的時候,我們希望能減少重複性的程式碼,並且盡量弱化類別之間的耦合。而要做到這兩者都兼顧是很難的,我們需要根據具體的條件和環境來決定我們應該採取什麼方法。根據我們對物件導向語言中繼承的了解,繼承會帶類別直接的強耦合,但js由於其特有的靈活性,可以設計出強耦合和弱耦合,高效率和低效率的程式碼。而具體用什麼,看情況。
在理解原型後,需要理解下什麼是原型鏈。在存取對象的某個成員(屬性或方法)時,如果這個成員未見於當前對象,那麼js會在prototype屬性所指的那個對像中查找它,如果還沒有找到,就繼續到下一級的prototype所指的對像中查找,直至找到。如果沒有找到就會回傳undifined。
那麼原型鏈給我們什麼提示呢?很容易聯想到,原型鏈意味著讓一個類別繼承另一個類,只需將子類的prototype設定為指向父類的一個實例即可。這就把父類別的成員綁定到子類別上了,因為在子類別上查找不到某個成員時會往父類別中尋找。 (以上這兩段用詞不嚴謹,只在用簡單易懂的言語描述)
於是類別繼承就這樣完成了。真的完成了嘛,用firebug在alert那裡設斷點,會發現原來的Person.prototype被修改了,加入了getNation方法。
這是因為在上面的程式碼Chinese.prototype = Person.prototype; 這是引用型,修改Chinese同時也修改了Person。這本身就是不能容忍的,且使類別之間形成強耦合性,這不是我們要的效果。
//第一種
//Chinese. prototype = new Person();
//第二種
//var F = function(){};
//F.prototype = Person.prototype;
//Chinese.prototype = F.prototype;
這兩種方法有什麼差別呢。在第二種中加入了一個空函數F,這樣做可以避免創建父類的一個實例,因為有可能父類會比較龐大,而且父類的構造函數會有一些副作用,或者說會執行大量的計算任務。所以力薦第二種方法。
到此,完了嘛,還沒!在物件的屬性prototype下面有個屬性constructor,它保存了對建構特定物件實例的函數的參考。根據這個說法Chiese.prototype.constructor應該等於Chinese,實際上不是。
回憶之前在設定Chiese的原型鏈時,我們把Person.prototype 覆掉了Chiese.prototype。所以此時的Chiese.prototype.constructor是Person。我們還需要加入以下程式碼
//對這裡的if條件不需要細究,知道Chinese.prototype.constructor = Chinese就行
if(Chinese.prototype.constructor == Object.prototype.constructor){
Chinese.prototype.constructor = Chinese;
}
}
>
整理全部程式碼如下
var Person = function( name){
this.name = name;
};
Person.prototype = {
getName : function(){
;
var Chinese = function(name, nation){
Person.call(this,name);
this.nation = nation;};
var F = function🎜>};
var F = function🎜>};
var F = function🎜>};
var F = function (){};
F.prototype = Person.prototype;
Chinese.prototype = F.prototype;
if(Chinese.prototype.constructor == Object.prototype.constructor){
.prototype.constructor = Chinese;
}
Chinese.prototype.getNation = function(){
return this.nation;
Chin};
;alert(c.getName());
如果可以把繼承的程式碼放在一個函數裡,方便程式碼重複使用,最後整理程式碼如下
function extend(subClass,>
function extend(subClass,🎜>
function extend(subClass, superClass) F = function(){};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor =subClass;
subClass.prototype.constructor =subClass; if(superClass.prototype.constructor == Object.prototype.constructor){
🎜> }
}
var Person = function(name){
this.name = name;
};Person.prototype = { return this.name;
}
};
var Chinese = function(name, nation){
var Chinese = function(name, nation){
var Chinese = function(name, nation){ this.nation = nation;
};
extend(Chinese, Person);
Chinese.prototype.getNation = function(){
var c = new Chinese("liyatang","China");
alert(c.getName());
發表後修改:
在一樓的評論下,我對那個extend函數又有新的看法。之前在討論如何設定原型鏈時提出了兩種方法
//第一種
//Chinese. prototype = new Person();
//第二種
//var F = function(){};
//F.prototype = Person.prototype;
//Chinese.prototype = F.prototype;
雖然第二種減少了呼叫父類別的建構子這條路,但在設計Chinese類別時用了Person.call(this,name);這裡也相當於呼叫了父類別的建構子。
然而用第一種方法的話可以減少在Chinese中再寫Person.call(this,name);,這部分程式碼在子類別中往往會被遺忘。不妨把這種功能程式碼放在了extend裡。就只寫
Chinese.prototype = new Person();也達到同樣的目的:耦合不強。
但遺忘的一點是,Chinese.prototype = new Person();這樣寫對嘛。答案是不對!很明顯 new Person()需要傳一個name參數的。我們不可能在extend函數裡做這部分工作,只好在Chinese類別裡呼叫父類別的建構子了。這樣也符合物件導向的思路。
所以,還是力薦用第二種方法。
第一次這樣寫有關技術類的文章,基本是按自己的思路鋪展開來,難免會有一些沒有考慮到的地方和解釋的不清楚的地方,望留言反饋,謝謝。