js類式繼承的實現 非常重要
在開始擺弄程式碼之前,應該搞清楚使用繼承的目的和能帶來什麼好處。一般來說,在設計類的時候,我們希望能減少重複性的程式碼,並且儘量弱化類之間的耦合。而要做到這兩者都兼顧是很難的,我們需要根據具體的條件和環境下決定我們應該採取什麼方法。根據我們對物件導向語言中繼承的瞭解,繼承會帶類直接的強耦合,但js由於其特有的靈活性,可以設計出強耦合和弱耦合,高效率和低效率的程式碼。而具體用什麼,看情況。
下面提供js實現繼承的三種方法:類式繼承,原型繼承,摻元類。這裡先簡述類式繼承,後兩種在往後的隨便中簡述,請多多關注、指導,謝謝。
類式繼承。
js類式繼承的實現依靠原型鏈來實現的。什麼是原型鏈?js中物件有個屬性prototy,這個屬性返回物件型別的引用,用於提供物件的類的一組基本功能。
貌似對prototype有印象,對了,我們經常這樣用程式碼。
1
2
3
4
5
6
7
8
9
|
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成員。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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; }; |
繼承關係就建立了,我們這樣呼叫它
1
2
|
var c
= new Chinese( "liyatang" , "China" ); alert(c.getName()); //
liyatang |
於是類式繼承就這樣完成了。難道真的完成了嘛,用firebug在alert那裡設斷點,會發現原來的Person.prototype被修改了,新增了getNation方法。
這是因為在上面的程式碼Chinese.prototype = Person.prototype; 這是引用型別,修改Chinese同時也修改了Person。這本身就是不能容忍的,且使類之間形成強耦合性,這不是我們要的效果。
我們可以另起一個物件或例項化一個例項來弱化耦合性。
1
2
3
4
5
6
|
//第一種 //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。我們還需要新增以下程式碼
1
2
3
4
|
//對這裡的if條件不需要細究,知道Chinese.prototype.constructor
= Chinese就行 if (Chinese.prototype.constructor
== Object.prototype.constructor){ Chinese.prototype.constructor
= Chinese; } |
整理全部程式碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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()); |
如果可以把繼承的程式碼放在一個函式裡,方便程式碼複用,最後整理程式碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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函式又有新的看法。之前在討論如何設定原型鏈時提出了兩種方法
1
2
3
4
5
6
|
//第一種 //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類裡呼叫父類的建構函式了。這樣也符合物件導向的思路。
所以,還是力薦用第二種方法。
第一次這樣寫有關技術類的文章,基本是按自己的思路鋪展開來,難免會有一些沒有考慮到的地方和解釋的不清楚的地方,望留言反饋,謝謝。
相關文章
- JS原型繼承和類式繼承JS原型繼承
- js 原型鏈實現類的繼承JS原型繼承
- js的繼承實現JS繼承
- JS中繼承的實現JS中繼繼承
- 從babel實現es6類的繼承來深入理解js的原型及繼承Babel繼承JS原型
- js--如何實現繼承?JS繼承
- 類的繼承_子類繼承父類繼承
- JS 繼承的 六 種實現方式JS繼承
- js實現繼承的三種方式JS繼承
- js實現繼承的幾種方式JS繼承
- UML類圖(上):類、繼承和實現繼承
- JSP模板繼承功能實現JS繼承
- js建構函式的繼承JS函式繼承
- 類的繼承繼承
- js 使用建構函式和原型鏈實現繼承操作JS函式原型繼承
- js實現繼承的方法以及優缺點JS繼承
- Web前端------JS高階繼承的實現方式Web前端JS繼承
- 繼承的實現方式繼承
- JavaScript實現類的private、protected、public、static以及繼承JavaScript繼承
- js如何是利用apply實現繼承JSAPP繼承
- prototype實現繼承繼承
- 實現JavaScript繼承JavaScript繼承
- js實現繼承的幾種方式和對比JS繼承
- JavaScript實現繼承的方式JavaScript繼承
- 實現一個無法被繼承的C++類繼承C++
- js繼承JS繼承
- ES6 -> Javascript的類與繼承在Babel的實現JavaScript繼承Babel
- JS中的繼承JS繼承
- javascript類繼承JavaScript繼承
- 子類繼承父類的建構函式(方法)嗎?繼承函式
- Javascript如何實現繼承JavaScript繼承
- Python類的繼承Python繼承
- 類的繼承圖解繼承圖解
- Java的類與繼承Java繼承
- Swift—類的繼承-備Swift繼承
- 類的繼承和派生繼承
- java關於繼承父類,需要實現父類中的構造方法Java繼承構造方法
- Java集合為什麼設計為:實現類繼承了抽象類,同時實現抽象類實現的介面Java繼承抽象