在開始擺弄代碼之前,應該搞清楚使用繼承的目的和能帶來什麼好處。一般來說,在設計類的時候,我們希望能減少重復性的代碼,並且盡量弱化類之間的耦合。而要做到這兩者都兼顧是很難的,我們需要根據具體的條件和環境下決定我們應該采取什麼方法。根據我們對面向對象語言中繼承的了解,繼承會帶類直接的強耦合,但js由於其特有的靈活性,可以設計出強耦合和弱耦合,高效率和低效率的代碼。而具體用什麼,看情況。
下面提供js實現繼承的三種方法:類式繼承,原型繼承,摻元類。這裡先簡述類式繼承,後兩種在往後的隨便中簡述,請多多關注、指導,謝謝。
類式繼承。
js類式繼承的實現依靠原型鏈來實現的。什麼是原型鏈?js中對象有個屬性prototy,這個屬性返回對象類型的引用,用於提供對象的類的一組基本功能。
貌似對prototype有印象,對了,我們經常這樣用代碼。
復制代碼 代碼如下:
var Person = function(){
this.name = "liyatang";
};
Person.prototype = {
//可以在這裡提供Person的基本功能
getName : function(){
return this.name;
}
}
我們把類的基本功能放在prototype屬性裡,表示Person這個對象的引用有XXX功能。
在理解原型後,需要理解下什麼是原型鏈。在訪問對象的某個成員(屬性或方法)時,如果這個成員未見於當前對象,那麼js會在prototype屬性所指的那個對象中查找它,如果還沒有找到,就繼續到下一級的prototype所指的對象中查找,直至找到。如果沒有找到就會返回undifined。
那麼原型鏈給我們什麼提示呢?很容易聯想到,原型鏈意味著讓一個類繼承另一個類,只需將子類的prototype設置為指向父類的一個實例即可。這就把父類的成員綁定到子類上了,因為在子類上查找不到某個成員時會往父類中查找。(以上這兩段用詞不嚴謹,只在用通俗易懂的言語描述)
下面我們需要個Chinese類,需要繼承Person類的name和getName成員。
復制代碼 代碼如下:
var Chinese = function(name, nation){
//繼承,需要調用父類的構造函數,可以用call調用,this指向Chinese
//使Person在此作用域上,才可以調用Person的成員
Person.call(this,name);
this.nation = nation;
};
Chinese.prototype = Person.prototype;
//這裡不可和以前一樣,因為覆蓋掉了prototype屬性
//Chinese.prototype = {
// getNation : function(){
// return this.nation;
// }
//};
//以後的方法都需要這樣加
Chinese.prototype.getNation = function(){
return this.nation;
};
繼承關系就建立了,我們這樣調用它
復制代碼 代碼如下:
var c = new Chinese("liyatang","China");
alert(c.getName());// liyatang
於是類式繼承就這樣完成了。難道真的完成了嘛,用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(){
return this.name;
}
};
var Chinese = function(name, nation){
Person.call(this,name);
this.nation = nation;
};
var F = function(){};
F.prototype = Person.prototype;
Chinese.prototype = F.prototype;
if(Chinese.prototype.constructor == Object.prototype.constructor){
Chinese.prototype.constructor = Chinese;
}
Chinese.prototype.getNation = function(){
return this.nation;
};
var c = new Chinese("liyatang","China");
alert(c.getName());
如果可以把繼承的代碼放在一個函數裡,方便代碼復用,最後整理代碼如下
復制代碼 代碼如下:
function extend(subClass,superClass){
var F = function(){};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.superclass = superClass.prototype; //加多了個屬性指向父類本身以便調用父類函數
if(superClass.prototype.constructor == Object.prototype.constructor){
superClass.prototype.constructor = superClass;
}
}
var Person = function(name){
this.name = name;
};
Person.prototype = {
getName : function(){
return this.name;
}
};
var Chinese = function(name, nation){
Person.call(this,name);
this.nation = nation;
};
extend(Chinese, Person);
Chinese.prototype.getNation = function(){
return this.nation;
};
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類裡調用父類的構造函數了。這樣也符合面向對象的思路。
所以,還是力薦用第二種方法。
第一次這樣寫有關技術類的文章,基本是按自己的思路鋪展開來,難免會有一些沒有考慮到的地方和解釋的不清楚的地方,望留言反饋,謝謝。