類的繼承

本文講述JavaScript中類繼承的實現方式,並比較實現方式的差別。javascript

1、何爲繼承

繼承,是子類繼承父類的特徵和行爲,使得子類對象具備父類的實例域和方法。
繼承是面向對象編程中,不可或缺的一部分。java

1.1 優勢

  • 減小代碼冗餘 父類能夠爲子類提供通用的屬性,而沒必要由於增長功能,而逐個修改子類的屬性
  • 代碼複用 同上
  • 代碼易於管理和擴展 子類在父類基礎上,能夠實現本身的獨特功能

1.2 缺點

  • 耦合度高 若是修改父類代碼,將影響全部繼承於它的子類
  • 影響性能 子類繼承於父類的數據成員,有些是沒有使用價值的。可是,在實例化的時候,已經分配了內存。因此,在必定程度上影響程序性能。

2、例子

例子以圖書館中的書入庫歸類爲例。
如下是簡化後的父類Book(也可稱爲基類)。
目的是經過繼承該父類,產出Computer(計算機)子類。
而且,子類擁有新方法say,輸出本身的書名。es6

function Book(){
    this.name = ''; // 書名
    this.page = 0; // 頁數
    this.classify = ''; // 類型
}
Book.prototype = {
    constructor: Book,
    init: function(option){
        this.name = option.name || '';
        this.page = option.page || 0;
        this.classify = option.classify || '';
    },
    getName: function(){
        console.log(this.name);
    },
    getPage: function(){
        console.log(this.page);
    },
    getClassify: function(){
        console.log(this.classify);
    }
};

接下來會講解子類Computer幾種繼承方式的實現和優化方法。開始飆車~編程

3、實例式繼承

function Computer(){
    Book.apply(this, arguments);
}
Computer.prototype = new Book();
Computer.prototype.constructor = Computer;
Computer.prototype.init = function(option){
    option.classify = 'computer';
    Book.prototype.init.call(this, option);
};
Computer.prototype.say = function(){
    console.log('I\'m '+ this.name);
}

3.1 調用父類構造器進行初始化

function Computer(){
    Book.apply(this, arguments);
}

Computer的構造函數裏,調用父類的構造函數進行初始化操做。使子類擁有父類同樣的初始化屬性。瀏覽器

3.2 將父類的原型傳遞給子類

Computer.prototype = new Book();使用new操做符對父類Book進行實例化,並將實例對象賦值給子類的prototype
這樣,子類Computer就能夠經過原型鏈訪問到父類的屬性。app

3.3 缺點

  • 父類Book的構造函數被執行了2次
    • 一次是在Computer的構造函數裏Book.apply(this, arguments);
    • 一次是在Computer.prototype = new Book();
      這種模式,存在必定的性能浪費。
  • 父類實例化沒法傳參
    Computer.prototype = new Book();,這種實例化方式,沒法讓Book父類接收不固定的參數集合。

4、原型式繼承

function Computer(){
    Book.apply(this, arguments);
}
Computer.prototype = Object.create(Book.prototype);
Computer.prototype.constructor = Computer;
Computer.prototype.init = function(option){
    option.classify = 'computer';
    Book.prototype.init(option);
};
Computer.prototype.say = function(){
    console.log('I\'m '+ this.name);
}

這裏的改進:是使用Object.create(Book.prototype)。它的做用是返回一個繼承自原型對象Book.prototype的新對象。且該對象下的屬性已經初始化。
Object.create生成新對象,並不會調用到Book的構造函數。
這種方式,也能夠經過原型鏈實現繼承。函數

5、Object.create的簡單版兼容

因爲低版本的瀏覽器是不支持Object.create的。因此這裏簡單介紹下兼容版本:性能

Object.create = function(prototype){
    function F(){}
    F.prototype = prototype;
    return new F();
}

原理是定義一個空的構造函數,而後修改其原型,使之成爲一個跳板,能夠將原型鏈傳遞到真正的prototype。優化

6、函數化繼承

上述兩種實現方式,都存在一個問題:不存在私有屬性私有方法。也就是說,存在被篡改的風險。
接下來就用函數化來化解這個問題。this

function book(spec, my){
    var that = {};

    // 私有變量
    spec.name = spec.name || ''; // 書名
    spec.page = spec.page || 0; // 頁數
    spec.classify = spec.classify || ''; // 類型

    var getName = function(){
        console.log(spec.name);
    };
    var getPage = function(){
        console.log(spec.page);
    };
    var getClassify = function(){
        console.log(spec.classify);
    };

    that.getName = getName;
    that.getPage = getPage;
    that.getClassify = getClassify;

    return that;
}

function computer(spec, my){
    spec = spec || {};
    spec.classify = 'computer';
    var that = book(spec, my);

    var say = function(){
        console.log('I\'m '+ spec.name);
    };
    that.say = say;

    return that;
}

var Ninja = computer({name: 'JavaScript忍者祕籍', page: 350});

函數化的優點,就是能夠更好地進行封裝和信息隱藏。
也許有人疑惑爲何用如下這種方式聲明和暴露方法:

var say = function(){
    console.log('I\'m '+ spec.name);
};
that.say = say;

實際上是爲了保護對象自身的完整性。即便that.say被外部篡改或破壞掉,function computer內部的say方法仍然可以正常工做。
另外,解釋下thatspecmy的做用:

  • that是一個公開數據存儲容器,暴露出去的數據接口,都放到這個容器
  • spec是用來存儲建立新實例所需的信息,屬於實例之間共同編輯的數據
  • my是用來存儲父類、子類之間共享的私密數據容器,外部是訪問不到的。

7、ES6繼承

最後,看下現代版ES6的類繼承。不由感慨之前的刀耕火種,是多麼折磨人🌚

class Book {
    constructor(){
        this.name = ''; // 書名
        this.page = 0; // 頁數
        this.classify = ''; // 類型
    }
    init(option) {
        this.name = option.name || '';
        this.page = option.page || 0;
        this.classify = option.classify || '';
    }
    getName() {
        console.log(this.name);
    }
    getPage (){
        console.log(this.page);
    }
    getClassify (){
        console.log(this.classify);
    }
}
class Computer extends Book{
    constructor(...args){
        super(...args);
    }
    init(option) {
        super.init(option);
        this.classify = 'computer';
    }
    say() {
        console.log('I\'m '+ this.name);
    }
}

結語

雖然ES5終究會被淘汰,可是瞭解下其工做原理,仍是頗有必要。由於不少源碼仍是有用到裏面的模式。 附帶的價值就是,ES5的繼承玩到飛起,ES6的繼承就是小菜一碟。

相關文章
相關標籤/搜索