JavaScript 繼承

今天整理面試題的時候看見一道題叫講一下繼承,雖然繼承之前也看過書,也在用,可是竟然沒法總結性、系統地回答這個問題,因而趕忙把《JavaScript設計模式》扒拉出來看看。程序員

爲何須要繼承

在設計類的時候,可以減小重複性代碼,而且能儘可能弱化對象間的耦合。能夠在現有類的基礎上進行設計並充分利用已經具有的各類方法,而對設計的修改也更爲輕鬆。
繼承一共有三種方式:類式繼承、原型式繼承、摻元類面試

類式繼承

JavaScript能夠被裝扮成使用類式繼承的語言。經過用函數來聲明類、用關鍵字 new 來建立實例,JavaScript中的對象也能模仿Java或C++中的對象。
首先創造一個簡單的類聲明,及對該類的實例化:設計模式

// 建立 Person 類
function Person (name) {
    this.name = name;
}

Person.prototype.getName = function () {
    return this.name;
};

// 實例化 Person 類
var reader = new Person ('John Smith');
reader.getName();   // John Smith

建立繼承 Person 的類 Author :數組

// 建立繼承 Person 的類 Author
function Author (name,books) {
    Person.call(this,name);
    this.book = books;
}

// 派生子類
Author.prototype = new Person();
Author.prototype.constructor = Author;
Author.prootype.getBooks = function () {
    return this.book;
};

在默認狀況下,全部原型對象都會自動得到一個 constructor (構造函數)屬性,這個屬性是一個指向 prototype 屬性所在函數的指針。
爲了簡化類的聲明。能夠把派生子類的整個過程包裝在一個名爲 extend 的函數中,做用是基於一個給定的類結構建立一個新的類。函數

// extend 函數
function extend(subClass,superClass) {
    var F = function() {};
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
    
    // 確保超類的 constructor 屬性被設置正確
    subClass.superclass = superClass.prototype;
    if(superClass.prototype.constructor == Object.prototype.constructor) {
        superClass.prototype.constructor = superClass;
    }
}

那麼 Author 的繼承能夠改寫爲:學習

function Author (name,books) {
    Author.superClass.contructor.call(this,name);
    this.books = books;
}
extend(Author,Person);   // 調用 extend 函數

Author.prootype.getBooks = function () {
    return this.book;
};

原型式繼承

原型式繼承和類式繼承大相徑庭。在學習原型式繼承時,最好忘掉本身關於類和實例的一切知識,只從對象的角度來考慮。
在使用原型式繼承時,不須要用類來定義對象的結構,只需直接建立一個對象便可。這個對象隨後能夠被新的對象重用,這得益於原型鏈查找的工做機制,該對象被稱爲原型對象。
下面使用原型式繼承來從新設計 Person 和 Authorthis

// Person 原型對象
var Person = {
    name: 'default name',
    getName: function() {
        return this.name;
    }
};

Person 如今是一個對象字面量,其中定義了全部類 Person 對象都要具有的屬性和方法,併爲他們提供了默認值。clone 函數能夠用來建立新的類 Person 對象,該函數會建立一個空對象,而該對象的原型對象被設置爲 Person。prototype

// Author 原型對象
var Author = clone(Person);
Author.books = [];   // Default value
Author.getBooks = function () {
    return this.books;
};

一個克隆並不是原型對象的一份徹底獨立的副本,它只是一個以那個對象爲原型對象的空對象。對繼承而來的成員有讀和寫的不對等性:設計

var authorClone = clone(Author);
alert(authorClone.name);   // default name (鏈接到 Person.name)
authorClone.name = 'new name';
alert(authorClone.name);   // new name (鏈接到 authorClone.name)

authorClone.books.push('new book');   // 在這裏,想authorClone.books數組添加
                                      // 新元素其實是把這個元素添加到
                                      // Author.books數組中。
authorClone.books = [];
authorClone.books.push('new book');

這也就說明了爲何必須爲經過引用傳遞的數據類型的屬性建立新的副本。在以上例子中,向authorClone.books數組添加新元素其實是把這個元素添加到Author.books數組中。這可不是什麼好事,由於對那個值的修改不只會影響到 Author,並且會影響到全部機場了Author但還未改寫那個屬性的默認值的對象。在改變全部那些數組和對象的成員以前,必須先爲其建立新的副本。指針

相似繼承和原型式繼承的對比

包括JavaScript程序員在內的整個程序員羣體對類式繼承都比較熟悉。原型式繼承更能節約內存。在原型鏈中查找成員的方式使得全部克隆出來的對象都共享每一個屬性和惟一一份實例,只有在直接設置了某個克隆出來的對象的屬性和方法時,狀況纔會有所變化。而相似繼承方式中建立的每個對象在內存中都有本身的一套屬性的副本。

相關文章
相關標籤/搜索