JavaScript中的繼承

前言

做爲 JavaScript 中最重要的內容之一,繼承問題一直是咱們關注的重點。那麼你是否清晰地知道它的原理以及各類實現方式呢html

閱讀這篇文章,你將知道:前端

  • 什麼是繼承
  • 實現繼承有哪幾種方式
  • 它們各有什麼特色

這裏默認你已經清楚的知道構造函數、實例和原型對象之間的關係,若是並非那麼清晰,那麼推薦你先閱讀這篇文章 -- JavaScript 中的原型與原型鏈git

若是文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過github

如下↓web

概念

繼承(inheritance)是面向對象軟件技術當中的一個概念。若是一個類別 B 繼承自 另外一個類別 A ,就把這個 B 稱爲 A的子類 ,而把 A 稱爲 B的父類別 也能夠稱 A是B的超類 。繼承可使得子類具備父類別的各類屬性和方法,而不須要再次編寫相同的代碼 ... 更多)

image

經過這些概念和圖示咱們不難知道繼承能夠在咱們的開發中帶來的便捷,那麼在 JavaScript 中如何去實現繼承呢?segmentfault

繼承實現方式

原型鏈繼承

利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法
function SuperType() {
    this.name = 'tt';
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    this.name = 'oo';
}
SubType.prototype = new SuperType()

var instance = new SubType()

instance.sayName() // oo
instance instanceof SubType // true
instance instanceof SuperType // ture

以上的試驗中,咱們建立了兩個構造函數 SuperTypeSubType ,而且讓 SubType 的原型指向 SuperTypeSubType 也就繼承了 SuperType 原型對象中的方法。因此在建立 instance 實例的時候,實例自己也就具備了 SuperType 中的方法,而且都處在它們的原型鏈中app

SubType.prototype.constructor == SubType // false
SubType.prototype.constructor == SuperType // true

須要注意的是:這個時候 SubType.prototype.constructor 是指向 SuperType 的,至關於重寫了 SubType 的原型對象。函數

用一張圖表示:學習

image

  • SubType.prototype 至關於 SuperType 的實例存在的,因此 SubType.prototype.constructor 就指向 SuperType

原型繼承的特色

優勢:this

  • 簡單、易於實現
  • 父類新增原型方法/原型屬性,子類都能訪問到
  • 很是純粹的繼承關係,實例是子類的實例,也是父類的實例

缺點:

  • 沒法實現多繼承
  • 想要爲子類 SubType 添加原型方法,就必須在 new SuperType 以後添加(會覆蓋)
  • 來自原型對象的全部屬性被全部實例共享(引用類型的值修改會反映在全部實例上面)
  • 建立子類實例時,沒法向父類構造函數傳參

借用構造函數

在子類構造函數的內部調用超類型構造函數,經過 applycall 實現
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'orange', 'black'];
}

function SubType() {
    SuperType.call(this, 'tt');
}

var instance = new SubType()
var instance1 = new SubType()

instance.colors // ['red', 'orange', 'black']
instance.name // tt

instance.colors.push('green');
instance.colors // ['red', 'orange', 'black', 'green']
instance1.colors // ['red', 'orange', 'black']

借用構造函數的特色

優勢:

  • 解決了原型鏈繼承不能傳參的問題
  • 子類實例共享父類引用屬性的問題
  • 能夠實現多繼承(call能夠指定不一樣的超類)

缺點:

  • 實例並非父類的實例,只是子類的實例
  • 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
  • 沒法實現函數複用

組合繼承

僞經典繼承(最經常使用的繼承模式):將原型鏈和借用構造函數的技術組合到一塊兒。使用原型鏈實現對原型屬性和方法的繼承,經過構造函數來實現對實例屬性的繼承
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'orange', 'black'];
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    SuperType.call(this, 'tt');
    this.name = 'oo';
}
// 這裏的 SubType.prototype.constructor 仍是指向 SuperType
SubType.prototype = new SuperType();

var instance = new SubType();
var instance1 = new SubType();

instance.name // oo
instance.sayName() // oo

instance.colors.push('green');
instance.colors // ['red', 'orange', 'black', 'green']
instance1.colors // ['red', 'orange', 'black']

組合繼承的特色

優勢:

  • 能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法
  • 不存在引用屬性共享問題
  • 可傳參
  • 函數可複用

缺點:

  • 調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)

原型式繼承

藉助原型鏈能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型
function obj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

var person = {
    name: 'tt',
    age: 18,
    colors: ['red', 'green']
}

var instance = obj(person);
var instance1 = obj(person);
instance.colors.push('black');
instance.name // tt
instance.colors // ['red', 'green', 'black']
instance1.colors // ['red', 'green', 'black']

建立一個臨時的構造函數,而後將傳入的對象當作這個構造函數的原型對象,最後返回這個臨時構造函數的新實例。實際上,就是對傳入的對象進行了一次淺複製

ES5 經過新增 Object.create() 規範化了原型式繼承

更多 Object.create()語法請點擊 這裏

原型式繼承特色

優勢:

  • 支持多繼承(傳入的對象不一樣)
  • 不須要興師動衆的建立不少構造函數

缺點: 和原型鏈繼承基本一致,效率較低,內存佔用高(由於要拷貝父類的屬性)

寄生式繼承

建立一個僅用於封裝繼承過程的函數,在函數內部對這個對象進行改變,最後返回這個對象
function createAnother(obj) {
    var clone = Object(obj);
    clone.sayHi = function() {
        alert('Hi');
    }
    return clone
}

var person = {
    name: 'tt',
    age: 18,
    friends: ['oo', 'aa', 'cc'],
    sayName() {
        return this.name
    }
}

var instance = createAnother(person)
var instance1 = createAnother(person)

instance.friends.push('yy')

instance.name // 'tt'
instance.sayHi() // Hi
instance.friends // ["oo", "aa", "cc", "yy"]
instance1.friends // ["oo", "aa", "cc", "yy"]

寄生式繼承的特色

優勢:

  • 支持多繼承

缺點:

  • 實例並非父類的實例,只是子類的實例
  • 不能實現複用(與構造函數類似)
  • 實例之間會互相影響

寄生組合繼承

借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。經過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
function inherit(subType, superType) {
    var obj = Object(superType.prototype); // 建立對象
    obj.constructor = subType;  // 指定constructor
    subType.prototype = obj;    // 指定對象
}

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'orange', 'black'];
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    SuperType.call(this, 'tt');
    this.name = 'oo';
}

inherit(SubType, SuperType)

var instance = new SubType()

instance.name // oo
instance.sayName // oo
instance instanceof SubType // true
instance instanceof SuperType // true
SubType.prototype.constructor == SubType // true

寄生組合繼承的特色

堪稱完美,只是實現稍微複雜一點

後記

做爲 JavaScript 最重要的概念之一,對於繼承實現的方式方法以及它們之間的差別咱們仍是頗有必要了解的。

在實現繼承的時候,拷貝 也是一種頗有效的方式,因爲 JavaScript 簡單數據類型與引用類型的存在,衍生出了 淺拷貝深拷貝 的概念,那麼它們又是什麼,怎麼去實現呢

且聽下回分解,哈哈

週末愉快

最後,推薦一波前端學習歷程,不按期分享一些前端問題和有意思的東西歡迎 star 關注 傳送門

參考文檔

JavaScript 高級程序設計

JavaScript實現繼承的幾種方式

相關文章
相關標籤/搜索