Javascript繼承

Javascript繼承 
一直想對Javascript再次作一些總結,正好最近本身寫了一個小型Js UI庫,總結了一下Js的繼承機制,在網上也看了一些前輩們博客裏的總結,感受分析不是特別全面。這裏僅僅是把本身的學習體會拿出來分享一下,但願對你們學習Javascript有所幫助。 

Javascript自己是從Perl語言的語法演變而來的,本質上是腳本語言,隨着版本的更新逐漸加入的對面向對象的模擬。我認爲Js的面向對象模擬整體上作得仍是不錯的,由於咱們不能盲從任何一種理念,不能純粹的爲了OOP而OOP,咱們須要抓住的是面向對象的好處究竟是什麼?爲了這些優勢去OOP,纔是最明智的選擇,因此說Js作得還不錯。 

Js的繼承在不少書裏面細緻的分了不少種類型和實現方式,大致上就是兩種:對象冒充、原型方式。這兩種方式各有優勢和缺陷,這裏我先列舉出來,再從底層分析區別: 

(一)對象冒充 javascript

JavaScript code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
function  A(name){
     this .name = name;
     this .sayHello =  function (){alert( this .name+」 say Hello!」);};
}
 
function  B(name,id){
     this .temp = A;
     this .temp(name);         //至關於new A();
     delete  this .temp;         //防止在之後經過temp引用覆蓋超類A的屬性和方法
      this .id = id;    
     this .checkId =  function (ID){alert( this .id==ID)};
}



當構造對象B的時候,調用temp至關於啓動A的構造函數,注意這裏的上下文環境中的this對象是B的實例,因此在執行A構造函數腳本時,全部A的變量和方法都會賦值給this所指的對象,即B的實例,這樣子就達到B繼承了A的屬性方法的目的。以後刪除臨時引用temp,是防止維護B中對A的類對象(注意不是實例對象)的引用更改,由於更改temp會直接致使類A(注意不是類A的對象)結構的變化。 

咱們看到了,在Js版本更新的過程當中,爲了更方便的執行這種上下文this的切換以達到繼承或者更加廣義的目的,增長了call和apply函數。它們的原理是同樣的,只是參數不一樣的版本罷了(一個可變任意參數,一個必須傳入數組做爲參數集合)。這裏就以call爲例子,解釋一下用call實現的對象冒充繼承。 

java

JavaScript code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
function  Rect(width, height){
     this .width = width;
     this .height = height;
     this .area =  function (){ return  this .width* this .height;};
}
 
function  myRect(width, height, name){
     Rect .call( this ,width,height);
     this .name = name;
     this .show =  function (){
     alert( this .name+」  with  area:」+ this .area());
     }
}



關於Call方法,官方解釋:調用一個對象的一個方法,以另外一個對象替換當前對象。 
call (thisOb,arg1, arg2…) 

這也是一種對象冒充的繼承,其實在call方法調用的時候發生的事情也是上下文環境變量this的替換,在myRect函數體中this確定是指向類myRect對象的實例了,然而用這個this做爲上下文環境變量調用名字叫Rect方法,即類Rect的構造函數。因而此時調用Rect時候對this的賦值屬性和方法都其實是對一個myRect的對象進行。因此說盡管call和apply並非僅僅爲了繼承而新增的方法,但用它們能夠模擬繼承。 

對象冒充繼承就是這麼一回事,它能夠實現多重繼承,只要重複作這一套賦值的流程就能夠了。不過目前真正大規模使用得並很少,爲何呢?由於它有一個明顯的性能缺陷,這就要說道OO的概念了,咱們說對象是成員+成員方法的集合,構造對象實例的時候,這些實例只須要擁有各自的成員變量就能夠了,成員方法只是一段對變量操做的可執行文本區域而已,這段區域不用爲每一個實例而複製一份,全部的實例均可以共享。如今回到Js利用對象冒充模擬的繼承裏,全部的成員方法都是針對this而建立的,也就是所全部的實例都會擁有一份成員方法的副本,這是對內存資源的一種極度浪費。其它的缺陷好比說對象冒充沒法繼承prototype域的變量和方法就不用提了,筆者認爲前一個致命缺陷就已經足夠。不過,咱們仍是須要理解它,特別是父類的屬性和方法是如何繼承下來的原理,對於理解Js繼承很重要。 

(二)原型方式 
第二種繼承方式是原型方式,所謂原型方式的繼承,是指利用了prototype或者說以某種方式覆蓋了prototype,從而達到屬性方法複製的目的。其實現方式有不少中,可能不一樣框架多少會有一點區別,可是咱們把握住原理,就不會有任何不理解的地方了。看一個例子(某一種實現): 

數組

JavaScript code
 
?
1
2
3
4
5
6
7
8
9
10
function  Person(){
     this .name = 「Mike」;
     this .sayGoodbye =  function (){alert(「GoodBye!」);};
}
 
Person.prototype.sayHello =  function (){alert(」Hello!」);};
 
function  Student(){}
 
Student.prototype =  new  Person();



關鍵是對最後一句Student原型屬性賦值爲Person類構造的對象,這裏筆者解釋一下父類的屬性和方法是如何copy到子類上的。Js對象在讀取某個對象屬性的時候,老是先查看自身域的屬性列表,若是有就返回不然去讀取prototype域(每一個對象共享構造對象的類的prototype域全部屬性和方法),若是找到就返回,因爲prototype能夠指向別的對象,因此Js解釋器會遞歸的去查找prototype域指向對象的prototype域,直到prototype爲自己,查找變成了一種循環,就中止,此時還沒找到就成undefined了。 

這樣看來,最後一句發生的效果就是將父類全部屬性和方法鏈接到子類的prototype域上,這樣子類就繼承了父類全部的屬性和方法,包括name、sayGoodbye和sayHello。這裏與其把最後一句當作一種賦值,不如理解成一種指向關係更好一點。這種原型繼承的缺陷也至關明顯,就是繼承時父類的構造函數時不能帶參數,由於對子類prototype域的修改是在聲明子類對象以後才能進行,用子類構造函數的參數去初始化父類屬性是沒法實現的,以下所示: 

app

JavaScript code
 
?
1
2
3
4
5
6
7
8
9
function  Person(name){
     this .name = name;
}
 
function  Student(name,id){
     this .id = id;
}
 
Student.prototype =  new  Person( this .name);




兩種繼承方式已經講完了,若是咱們理解了兩種方式下子類如何把父類的屬性和方法「抓取」下來,就能夠自由組合各自的利弊,來實現真正合理的Js繼承。下面是我的總結的一種綜合方式: 

框架

JavaScript code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function  Person(name){
     this .name = name;
}
 
Person.prototype.sayHello =  function (){alert( this .name+「say Hello!」);};
 
function  Student(name,id){
     Person.call( this ,name);
     this .id = id;
}
 
Student.prototype =  new  Person();
Student.prototype.show =  function (){
     alert(「Name is:」+  this .name+」 and Id is:」+ this .id);
}



總結就是利用對象冒充機制的call方法把父類的屬性給抓取下來,而成員方法儘可能寫進被全部對象實例共享的prototype域中,以防止方法副本重複建立。而後子類繼承父類prototype域來抓取下來全部的方法。如想完全理清這些調用鏈的關係,推薦你們多關注Js中prototype的constructor和對象的constructor屬性,這裏就很少說了。函數

相關文章
相關標籤/搜索