javascript中類式繼承和原型式繼承的實現方法和區別

來源:互聯網
上載者:User

標籤:記憶體位址   tor   構造   error   而且   屬性   extend   注意   繼承   

在所有物件導向的編程中,繼承是一個重要的話題。一般說來,在設計類的時候,我們希望能減少重複性的代碼,並且盡量弱化對象間的耦合(讓一個類繼承另一個類可能會導致二者產生強耦合)。關於“解耦”是程式設計中另一個重要的話題,本篇重點來看看在javascript如何?繼承。

其它的物件導向程式設計語言都是通過關鍵字來解決繼承的問題(比如extend或inherit等方式)。但是javascript中並沒有定義這種實現的機制,如果一個類需要繼承另一個類,這個繼承過程需要程式員自己通過編碼來實現。

一、類式繼承的實現

1、建立一個類的方式:

 1 //定義類的建構函式 2 function Person(name) { 3     this.name = name || ‘預設姓名‘; 4 } 5 //定義該類所有執行個體的公用方法 6 Person.prototype.getName = function() { 7     return this.name; 8 } 9 10 var smith = new Person(‘Smith‘);11 var jacky = new Person(‘Jacky‘);12 13 console.log( smith.getName(), jacky.getName() ); //Smith Jacky

2、繼承這個類:這需要分兩個步驟來實現,第1步是繼承父類建構函式中定義的屬性,第2步是繼承父類的prototype屬性

//定義類的建構函式function Person(name) {    this.name = name || ‘預設姓名‘;}//定義該類所有執行個體的公用方法Person.prototype.getName = function() {    return this.name;}function Author(name, books) {    //繼承父類建構函式中定義的屬性    //通過改變父類建構函式的執行內容來繼承    Person.call(this, name);    this.books = books;}//繼承父類對應的方法Author.prototype = new Person(); //Author.prototype.constructor === PersonAuthor.prototype.constructor = Author; //修正修改原型鏈時造成的constructor丟失Author.prototype.getBooks = function() {    return this.books;};//測試var smith = new Person(‘Smith‘);var jacky = new Author(‘Jacky‘, [‘BookA‘, ‘BookB‘]);console.log(smith.getName()); //Smithconsole.log(jacky.getName()); //Jackyconsole.log(jacky.getBooks().join(‘, ‘)); //BookA, BookBconsole.log(smith.getBooks().join(‘, ‘)); //Uncaught TypeError: smith.getBooks is not a function

從測試的結果中可以看出,Author正確繼承了Person,而且修改Author的原型時,並不會對Person產生影響。這其中的關鍵一句就是 Author.prototype = new Person(),要與Author.prototype = Person.prototype區分開來。前者產生了一個執行個體,這個執行個體有Person.prototype的副本(這裡先這麼理解,後面有更詳細的解析)。後者是指將兩者的prototype指向同一個原型對象。

那麼,這也意味著每次繼承都將產生一個父類的副本,肯定對記憶體產生消耗,但為了類式繼承這個記憶體開銷必須得支付,但還可以做得更節省一點:Author.prototype = new Person()這一句其實多執行了建構函式一次(而這一次其實只需在子類建構函式中執行即可),尤其是在父類的建構函式很龐大時很耗時和記憶體。修改一下繼承的方式,如下:

Author.prototype = (function() {    function F() {}    F.prototype = Person.prototype;    return new F();})();

如上所示的代碼,new時,去掉了對父類的建構函式的調用,節省了一次調用的開銷。

3、類式繼承顯著的特點每一次執行個體化對象時,子類都將執行一次父類的建構函式。如果E繼承了D,D繼承了C,C繼承了B,B繼承了A,在執行個體化一個E時,一共要經過幾次建構函式的調用呢?

/*繼承方法的函數*/function extend(son, father) {    function F() {}    F.prototype = father.prototype;    son.prototype = new F();    son.prototype.constructor = son;}//A類function A() {    console.log(‘A()‘);}A.prototype.hello = function() {    console.log(‘Hello, world.‘);}//B類function B() {    A.call(this);    console.log(‘B()‘);}extend(B, A);//C類function C() {    B.call(this);    console.log(‘C()‘);}extend(C, B);//D類function D() {    C.call(this);    console.log(‘D()‘);}extend(D, C);//E類function E() {    D.call(this);    console.log(‘E()‘);}extend(E, D);//建立一個E的執行個體var e = new E(); //A() B() C() D() E()e.hello(); //hello, world.

5次,這還只是執行個體化一個E時調用的次數。所以,我們應該儘可能的減少繼承的層級。但這並不是說不要使用這種類式繼承,而是應該根據自己的應用場合決定採用什麼方法。

二、原型式繼承

1、先來看一段代碼:我們先將之前類式繼承中的繼承prototype那一段改成另一個函數clone,然後通過字面量建立一個Person,最後讓Author變成Person的複製體。

//這個函數可以理解為複製一個對象function clone(object) {    function F() {}    F.prototype = object;    return new F();}var Person = {    name: ‘Default Name‘;    getName: function() {        return this.name;    }}//接下來讓Author變為Person的複製體var Author = clone(Person);

問一個問題:clone函數裡的new F()為這個執行個體開闢記憶體空間來儲存object的副本了嗎?

按我之前的理解,回答是肯定的。但是,當我繼續將代碼寫下去的時候,奇怪的事情發生了,代碼如下:

//接下來讓Author變為Person的複製體var Author = clone(Person);Author.books = [];Author.getBooks = function() {    return this.books.join(‘, ‘);}//增加一個作者Smithvar Smith = clone(Author);console.log(Smith.getName(), Smith.getBooks()); //Default NameSmith.name = ‘Smith‘;Smith.books.push(‘<<Book A>>‘, ‘<<Book B>>‘); //作者寫了兩本書console.log(Smith.getName(), Smith.getBooks()); //Smith <<Book A>>, <<Book B>>//再增加一個作者Jackyvar Jacky = clone(Author);console.log(Jacky.getName(), Jacky.getBooks()); // Default Name <<Book A>>, <<Book B>>

當我們繼續增加作者Jacky時,奇怪的現象發生了!!Jacky的名字依然是Default Name,但是他居然也寫兩本與Smith一樣的書?Jacky的書都還沒push呢。到了這裡,我想到了引用對象的情況(引用一個對象時,引用的是該對象的記憶體位址),發生這樣的現象,問題肯定出在clone()函數中的new F()這裡。

事實上,這個clone中的new F()確實返回了一個新的對象,該對象擁有被複製對象的所有屬性。但這些屬性保留的是對被複製對象中相應屬性的引用,而非一個完全獨立的屬性副本。換句話說,新對象的屬性被複製的對象的屬性指向同一個記憶體位址(學過C語言的同學應該明白指標類型,這裡意義差不多)。

那麼為什麼上面的代碼中,Jacky的書與Smith的書相同了,為什麼Jacky的名字卻不是Smith而是Default Name呢?這就是Javascript中繼承的機制所在,當Smith剛剛繼承自Author時,他的屬性保留了對Author的屬性的引用,一旦我們顯示的對Smith的屬性重新賦值時,Javascritp引擎就會給Smith的該屬性重新劃分記憶體空間來儲存相應的值,由於重新劃分了內址地址,那麼對Smith.name的改寫就不會影響到Author.name去了。這就很好的解釋了前面的那個問題——為什麼Jacky的名字卻不是Smith而是Default Name。

2、基於原型繼承

通過前面的情況分析,可以看出基於原型繼承的方式更能節約記憶體(只有在需要時候才開闢新的記憶體空間)。但要注意:基於原型繼承時,對象的屬性一定要重新賦值後(重新劃分記憶體)再去引用該屬性。對於對象的方法,如果有不同的處理方式,我們只需重新定義即可。

下面將前一段代碼做一個完整、正確的範例出來,以說明原型繼承的特點和使用方式:

//這個函數可以理解為複製一個對象function clone(object) {    function F() {}    F.prototype = object;    return new F();}var Person = {    name: ‘Default Name‘,    getName: function() {        return this.name;    }}//接下來讓Author變為Person的複製體var Author = clone(Person);Author.books = [];Author.getBooks = function() {    return this.books.join(‘, ‘);}//增加一個作者Smithvar Smith = clone(Author);Smith.name = ‘Smith‘;Smith.books = [];Smith.books.push(‘<<Book A>>‘, ‘<<Book B>>‘); //作者寫了兩本書console.log(Smith.getName(), Smith.getBooks()); //Smith <<Book A>>, <<Book B>>//再增加一個作者Jackyvar Jacky = clone(Author);Jacky.name = ‘Jacky‘;Jacky.books = [];Jacky.books.push(‘<<Book C>>‘, ‘<<Book D>>‘);console.log(Jacky.getName(), Jacky.getBooks()); // Jacky <<Book C>>, <<Book D>>
三、類式繼承與原型式繼承的區別與相式之處

1、類式繼承中:使用建構函式初始化對象的屬性,通過調用父類的建構函式來繼承這些屬性。通過new 父類的prototype來繼承方法。

2、原型式繼承中:去掉了建構函式,但需要將對象的屬性和方法寫一個{}裡申明。準確的說,原型式繼承就是類式繼承中繼承父類的prototype方法。

javascript中類式繼承和原型式繼承的實現方法和區別

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.