一篇文章理解JS繼承——原型鏈/構造函數/組合/原型式/寄生式/寄生組合/Class extends

免費幫忙內推阿里等各大IT公司的崗位,有興趣能夠帶簡歷加微信angeltune
說實在話,之前我只須要知道「寄生組合繼承」是最好的,有個祖傳代碼模版用就行。最近由於一些事情,幾個星期以來一直心心念念想整理出來。本文以《JavaScript高級程序設計》上的內容爲骨架,補充了ES6 Class的相關內容,從我認爲更容易理解的角度將繼承這件事敘述出來,但願你們能有所收穫。

1. 繼承分類

先來個總體印象。如圖所示,JS中繼承能夠按照是否使用object函數(在下文中會提到),將繼承分紅兩部分(Object.create是ES5新增的方法,用來規範化這個函數)。前端

其中,原型鏈繼承和原型式繼承有同樣的優缺點,構造函數繼承與寄生式繼承也相互對應。寄生組合繼承基於Object.create, 同時優化了組合繼承,成爲了完美的繼承方式。ES6 Class Extends的結果與寄生組合繼承基本一致,可是實現方案又略有不一樣。編程

下面立刻進入正題。segmentfault

clipboard.png

2. 繼承方式

上圖上半區的原型鏈繼承,構造函數繼承,組合繼承,網上內容比較多,本文不做詳細描述,只指出重點。這裏給出了我認爲最容易理解的一篇《 JS中的繼承(上)》。若是對上半區的內容不熟悉,能夠先看這篇文章,再回來繼續閱讀;若是已經比較熟悉,這部分能夠快速略過。另,上半區大量借用了yq前端的一篇繼承文章[1]。

2.1 原型鏈繼承

核心:將父類的實例做爲子類的原型微信

SubType.prototype = new SuperType() 
// 全部涉及到原型鏈繼承的繼承方式都要修改子類構造函數的指向,不然子類實例的構造函數會指向SuperType。
SubType.prototype.constructor = SubType;

優勢:父類方法能夠複用
缺點:函數

  • 父類的引用屬性會被全部子類實例共享
  • 子類構建實例時不能向父類傳遞參數

2.2 構造函數繼承

核心:將父類構造函數的內容複製給了子類的構造函數。這是全部繼承中惟一一個不涉及到prototype的繼承。性能

SuperType.call(SubType);

優勢:和原型鏈繼承徹底反過來。優化

  • 父類的引用屬性不會被共享
  • 子類構建實例時能夠向父類傳遞參數

缺點:父類的方法不能複用,子類實例的方法每次都是單首創建的。this

2.3 組合繼承

核心:原型式繼承和構造函數繼承的組合,兼具了兩者的優勢。spa

function SuperType() {
    this.name = 'parent';
    this.arr = [1, 2, 3];
}

SuperType.prototype.say = function() { 
    console.log('this is parent')
}

function SubType() {
    SuperType.call(this) // 第二次調用SuperType
}

SubType.prototype = new SuperType() // 第一次調用SuperType

優勢:prototype

  • 父類的方法能夠被複用
  • 父類的引用屬性不會被共享
  • 子類構建實例時能夠向父類傳遞參數

缺點:
調用了兩次父類的構造函數,第一次給子類的原型添加了父類的name, arr屬性,第二次又給子類的構造函數添加了父類的name, arr屬性,從而覆蓋了子類原型中的同名參數。這種被覆蓋的狀況形成了性能上的浪費。

2.4 原型式繼承

核心:原型式繼承的object方法本質上是對參數對象的一個淺複製。
優勢:父類方法能夠複用
缺點:

  • 父類的引用屬性會被全部子類實例共享
  • 子類構建實例時不能向父類傳遞參數
function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
ECMAScript 5 經過新增 Object.create()方法規範化了原型式繼承。這個方法接收兩個參數:一 個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象。在傳入一個參數的狀況下, Object.create()與 object()方法的行爲相同。——《JAVASCript高級編程》

因此上文中代碼能夠轉變爲

var yetAnotherPerson = object(person); => var yetAnotherPerson = Object.create(person);

2.5 寄生式繼承

核心:使用原型式繼承得到一個目標對象的淺複製,而後加強這個淺複製的能力。
優缺點:僅提供一種思路,沒什麼優勢

function createAnother(original){ 
    var clone=object(original);    //經過調用函數建立一個新對象
    clone.sayHi = function(){      //以某種方式來加強這個對象
        alert("hi");
    };
    return clone;                  //返回這個對象
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

2.6 寄生組合繼承

剛纔說到組合繼承有一個會兩次調用父類的構造函數形成浪費的缺點,寄生組合繼承就能夠解決這個問題。

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); // 建立了父類原型的淺複製
    prototype.constructor = subType;             // 修正原型的構造函數
    subType.prototype = prototype;               // 將子類的原型替換爲這個原型
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
// 核心:由於是對父類原型的複製,因此不包含父類的構造函數,也就不會調用兩次父類的構造函數形成浪費
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
}

優缺點:這是一種完美的繼承方式。

2.7 ES6 Class extends

核心: ES6繼承的結果和寄生組合繼承類似,本質上,ES6繼承是一種語法糖。可是,寄生組合繼承是先建立子類實例this對象,而後再對其加強;而ES6先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修改this。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

ES6實現繼承的具體原理:

class A {
}

class B {
}

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 繼承 A 的靜態屬性
Object.setPrototypeOf(B, A);

ES6繼承與ES5繼承的異同:
相同點:本質上ES6繼承是ES5繼承的語法糖
不一樣點:

  • ES6繼承中子類的構造函數的原型鏈指向父類的構造函數,ES5中使用的是構造函數複製,沒有原型鏈指向。
  • ES6子類實例的構建,基於父類實例,ES5中不是。

3. 總結

  • ES6 Class extends是ES5繼承的語法糖
  • JS的繼承除了構造函數繼承以外都基於原型鏈構建的
  • 能夠用寄生組合繼承實現ES6 Class extends,可是仍是會有細微的差異

參考文章:

[1]《js繼承、構造函數繼承、原型鏈繼承、組合繼承、組合繼承優化、寄生組合繼承》[2]《JavaScript高級編程》

相關文章
相關標籤/搜索