JS繼承有哪些,你能否手寫其中一兩種呢?

引言

JS系列暫定 27 篇,從基礎,到原型,到異步,到設計模式,到架構模式等,

本篇是 JS系列中第 3 篇,文章主講 JS 繼承,包括原型鏈繼承、構造函數(shù)繼承、組合繼承、寄生組合繼承、原型式繼承、 ES6 繼承,以及 多繼承與 new 。

ES5 繼承

先定義一個父類

function SuperType () {
  // 屬性
  this.name = 'SuperType';
}
// 原型方法
SuperType.prototype.sayName = function() {
  return this.name;
};

一、 原型鏈繼承

基本思想

將父類的實例作為子類的原型

// 父類
function SuperType () {
  this.name = 'SuperType'; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType () {
  this.subName = "SubType"; // 子類屬性
};

SubType.prototype = new SuperType(); // 重寫原型對象,代之以一個新類型的實例
// 這里實例化一個 SuperType 時, 實際上執(zhí)行了兩步
// 1,新創(chuàng)建的對象復制了父類構造函數(shù)內的所有屬性及方法
// 2,并將原型 __proto__ 指向了父類的原型對象

SubType.prototype.saySubName = function () { // 子類原型方法
  return this.subName;
}

// 子類實例
let instance = new SubType();

// instanceof 通過判斷對象的 prototype 鏈來確定對象是否是某個類的實例
instance instanceof SubType; // true
instance instanceof SuperType; // true

// 注意
SubType instanceof SuperType; // false
SubType.prototype instanceof SuperType ; // true

[圖片上傳失敗...(image-732830-1664760989907)]

特點:

利用原型,讓一個引用類型繼承另一個引用類型的屬性及方法

優(yōu)點:

繼承了父類的模板,又繼承了父類的原型對象

缺點:

  • 可以在子類構造函數(shù)中,為子類實例增加實例屬性。如果要新增原型屬性和方法,則必須放在 SubType.prototype = new SuperType('SubType'); 這樣的語句之后執(zhí)行。

  • 無法實現(xiàn)多繼承

  • 來自原型對象的所有屬性被所有實例共享

    // 父類
    function SuperType () {
      this.colors = ["red", "blue", "green"];
      this.name = "SuperType";
    }
    // 子類
    function SubType () {}
    
    // 原型鏈繼承
    SubType.prototype = new SuperType();
    
    // 實例1
    var instance1 = new SubType();
    instance1.colors.push("blcak");
    instance1.name = "change-super-type-name";
    console.log(instance1.colors); // ["red", "blue", "green", "blcak"]
    console.log(instance1.name); // change-super-type-name
    // 實例2
    var instance2 = new SubType();
    console.log(instance2.colors); // ["red", "blue", "green", "blcak"]
    console.log(instance2.name); // SuperType
    
    

    [圖片上傳失敗...(image-118bdf-1664760989907)]

    注意:更改 SuperType 引用類型屬性時,會使 SubType 所有實例共享這一更新。基礎類型屬性更新則不會。

  • 創(chuàng)建子類實例時,無法向父類構造函數(shù)傳參,或者說是,沒辦法在不影響所有對象實例的情況下,向超類的構造函數(shù)傳遞參數(shù)

更多面試題解答參見 前端手寫面試題詳細解答

二、 構造繼承

基本思想:

在子類型的構造函數(shù)內部調用父類型構造函數(shù)。

注意:

  • 函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對象,所以這里使用 apply/call 來實現(xiàn)。

  • 使用父類的構造函數(shù)來增強子類實例,等于是復制父類的實例屬性給子類(沒用到原型)

// 父類
function SuperType (name) {
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType () {
  // 調用 SuperType 構造函數(shù)
  SuperType.call(this, 'SuperType'); // 在子類構造函數(shù)中,向父類構造函數(shù)傳參
  // 為了保證子父類的構造函數(shù)不會重寫子類的屬性,需要在調用父類構造函數(shù)后,定義子類的屬性
  this.subName = "SubType"; // 子類屬性
};
// 子類實例
let instance = new SubType(); // 運行子類構造函數(shù),并在子類構造函數(shù)中運行父類構造函數(shù),this綁定到子類

[圖片上傳失敗...(image-b91367-1664760989907)]

優(yōu)點:

解決了1中子類實例共享父類引用對象的問題,實現(xiàn)多繼承,創(chuàng)建子類實例時,可以向父類傳遞參數(shù)

缺點:

  • 實例并不是父類的實例,只是子類的實例
  • 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
  • 無法實現(xiàn)函數(shù)復用,每個子類都有父類實例函數(shù)的副本,影響性能

三. 組合繼承

顧名思義,組合繼承就是將原型鏈繼承與構造函數(shù)繼承組合在一起,從而發(fā)揮兩者之長的一種繼承模式。

基本思想:

使用原型鏈繼承使用對原型屬性和方法的繼承,通過構造函數(shù)繼承來實現(xiàn)對實例屬性的繼承。這樣既能通過在原型上定義方法實現(xiàn)函數(shù)復用,又能保證每個實例都有自己的屬性。

通過調用父類構造,繼承父類的屬性并保留傳參的優(yōu)點,然后通過將父類實例作為子類原型,實現(xiàn)函數(shù)復用

// 父類
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType (name, subName) {
  // 調用 SuperType 構造函數(shù)
  SuperType.call(this, name); // ----第二次調用 SuperType----
  this.subName = subName;
};

// ----第一次調用 SuperType----
SubType.prototype = new SuperType(); // 重寫原型對象,代之以一個新類型的實例

SubType.prototype.constructor = SubType; // 組合繼承需要修復構造函數(shù)指向
SubType.prototype.saySubName = function () { // 子類原型方法
  return this.subName;
}

// 子類實例
let instance = new SubType('An', 'sisterAn')
instance.colors.push('black')
console.log(instance.colors) // ["red", "blue", "green", "black"]
instance.sayName() // An
instance.saySubName() // sisterAn

let instance1 = new SubType('An1', 'sisterAn1')
console.log(instance1.colors) //  ["red", "blue", "green"]
instance1.sayName() // An1
instance1.saySubName() // sisterAn1

[圖片上傳失敗...(image-7f4a11-1664760989907)]

第一次調用 SuperType 構造函數(shù)時,SubType.prototype 會得到兩個屬性namecolors;當調用 SubType 構造函數(shù)時,第二次調用 SuperType 構造函數(shù),這一次又在新對象屬性上創(chuàng)建了 namecolors,這兩個屬性就會屏蔽原型對象上的同名屬性。

// instanceof:instance 的原型鏈是針對 SuperType.prototype 進行檢查的
instance instanceof SuperType // true
instance instanceof SubType // true

// isPrototypeOf:instance 的原型鏈是針對 SuperType 本身進行檢查的
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true

[圖片上傳失敗...(image-c3b6ec-1664760989907)]

優(yōu)點:

彌補了方式2的缺陷,可以繼承實例屬性/方法,也可以繼承原型屬性/方法,不存在引用屬性共享問題,可傳參,可復用

缺點:

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

四. 寄生組合繼承

在組合繼承中,調用了兩次父類構造函數(shù),這里 通過通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點

主要思想:

借用 構造函數(shù) 繼承 屬性 ,通過 原型鏈的混成形式 來繼承 方法

// 父類
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType (name, subName) {
  // 調用 SuperType 構造函數(shù)
  SuperType.call(this, name); // ----第二次調用 SuperType,繼承實例屬性----
  this.subName = subName;
};

// ----第一次調用 SuperType,繼承原型屬性----
SubType.prototype = Object.create(SuperType.prototype)

SubType.prototype.constructor = SubType; // 注意:增強對象

let instance = new SubType('An', 'sisterAn')

[圖片上傳失敗...(image-31d0a6-1664760989908)]

優(yōu)點:

  • 只調用一次 SuperType 構造函數(shù),只創(chuàng)建一份父類屬性
  • 原型鏈保持不變
  • 能夠正常使用 instanceofisPrototypeOf

五. 原型式繼承

實現(xiàn)思路:

實現(xiàn)思路就是將子類的原型設置為父類的原型

// 父類
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

/** 第一步 */
// 子類,通過 call 繼承父類的實例屬性和方法,不能繼承原型屬性/方法
function SubType (name, subName) {
  SuperType.call(this, name); // 調用 SuperType 的構造函數(shù),并向其傳參 
  this.subName = subName;
}

/** 第二步 */
// 解決 call 無法繼承父類原型屬性/方法的問題
// Object.create 方法接受傳入一個作為新創(chuàng)建對象的原型的對象,創(chuàng)建一個擁有指定原型和若干個指定屬性的對象
// 通過這種方法指定的任何屬性都會覆蓋原型對象上的同名屬性
SubType.prototype = Object.create(SuperType.prototype, { 
  constructor: { // 注意指定 SubType.prototype.constructor = SubType
    value: SubType,
    enumerable: false,
    writable: true,
    configurable: true
  },
  run : { 
    value: function(){ // override
      SuperType.prototype.run.apply(this, arguments); 
          // call super
          // ...
    },
    enumerable: true,
    configurable: true, 
    writable: true
  }
}) 

/** 第三步 */
// 最后:解決 SubType.prototype.constructor === SuperType 的問題
// 這里,在上一步已經指定,這里不需要再操作
// SubType.prototype.constructor = SubType;

var instance = new SubType('An', 'sistenAn')

[圖片上傳失敗...(image-c586ea-1664760989908)]

多繼承

如果希望能 多繼承 ,可使用 混入 的方式

// 父類 SuperType
function SuperType () {}
// 父類 OtherSuperType
function OtherSuperType () {}

// 多繼承子類
function AnotherType () {
    SuperType.call(this) // 繼承 SuperType 的實例屬性和方法
    OtherSuperType.call(this) // 繼承 OtherSuperType 的實例屬性和方法
}

// 繼承一個類
AnotherType.prototype = Object.create(SuperType.prototype);

// 使用 Object.assign 混合其它
Object.assign(AnotherType.prototype, OtherSuperType.prototype);
// Object.assign 會把  OtherSuperType 原型上的函數(shù)拷貝到 AnotherType 原型上,使 AnotherType 的所有實例都可用 OtherSuperType 的方法

// 重新指定 constructor
AnotherType.prototype.constructor = AnotherType;

AnotherType.prototype.myMethod = function() {
     // do a thing
};

let instance = new AnotherType()

最重要的部分是:

  • SuperType.call 繼承實例屬性方法
  • Object.create() 來繼承原型屬性與方法
  • 修改 SubType.prototype.constructor的指向

ES6 繼承

首先,實現(xiàn)一個簡單的 ES6 繼承:

class People {
    constructor(name) {
        this.name = name
    }
    run() { }
}

// extends 相當于方法的繼承
// 替換了上面的3行代碼
class Man extends People {
    constructor(name) {
        // super 相當于屬性的繼承
        // 替換了 People.call(this, name)
        super(name)
        this.gender = '男'
    }
    fight() { }
}

核心代碼

extends 繼承的核心代碼如下,其實現(xiàn)和上述的寄生組合式繼承方式一樣

function _inherits(subType, superType) {
    // 創(chuàng)建對象,Object.create 創(chuàng)建父類原型的一個副本
    // 增強對象,彌補因重寫原型而失去的默認的 constructor 屬性
    // 指定對象,將新創(chuàng)建的對象賦值給子類的原型 subType.prototype
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: { // 重寫 constructor
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

繼承的使用場景

  • 不要僅僅為了使用而使用它們,這只是在浪費時間而已。
  • 當需要創(chuàng)建 一系列擁有相似特性的對象 時,那么創(chuàng)建一個包含所有共有功能的通用對象,然后在更特殊的對象類型中繼承這些特性。
  • 應避免多繼承,造成混亂。

注: 考慮到JavaScript的工作方式,由于原型鏈等特性的存在,在不同對象之間功能的共享通常被叫做 委托 - 特殊的對象將功能委托給通用的對象類型完成。這也許比將其稱之為繼承更為貼切,因為“被繼承”了的功能并沒有被拷貝到正在“進行繼承”的對象中,相反它仍存在于通用的對象中。

JS系列暫定 27 篇,從基礎,到原型,到異步,到設計模式,到架構模式等,

本篇是 JS系列中第 3 篇,文章主講 JS 繼承,包括原型鏈繼承、構造函數(shù)繼承、組合繼承、寄生組合繼承、原型式繼承、 ES6 繼承,以及 多繼承與 new 。

ES5 繼承

先定義一個父類

function SuperType () {
  // 屬性
  this.name = 'SuperType';
}
// 原型方法
SuperType.prototype.sayName = function() {
  return this.name;
};

一、 原型鏈繼承

基本思想

將父類的實例作為子類的原型

// 父類
function SuperType () {
  this.name = 'SuperType'; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType () {
  this.subName = "SubType"; // 子類屬性
};

SubType.prototype = new SuperType(); // 重寫原型對象,代之以一個新類型的實例
// 這里實例化一個 SuperType 時, 實際上執(zhí)行了兩步
// 1,新創(chuàng)建的對象復制了父類構造函數(shù)內的所有屬性及方法
// 2,并將原型 __proto__ 指向了父類的原型對象

SubType.prototype.saySubName = function () { // 子類原型方法
  return this.subName;
}

// 子類實例
let instance = new SubType();

// instanceof 通過判斷對象的 prototype 鏈來確定對象是否是某個類的實例
instance instanceof SubType; // true
instance instanceof SuperType; // true

// 注意
SubType instanceof SuperType; // false
SubType.prototype instanceof SuperType ; // true

[圖片上傳失敗...(image-f78f86-1664760989908)]

特點:

利用原型,讓一個引用類型繼承另一個引用類型的屬性及方法

優(yōu)點:

繼承了父類的模板,又繼承了父類的原型對象

缺點:

  • 可以在子類構造函數(shù)中,為子類實例增加實例屬性。如果要新增原型屬性和方法,則必須放在 SubType.prototype = new SuperType('SubType'); 這樣的語句之后執(zhí)行。

  • 無法實現(xiàn)多繼承

  • 來自原型對象的所有屬性被所有實例共享

    // 父類
    function SuperType () {
      this.colors = ["red", "blue", "green"];
      this.name = "SuperType";
    }
    // 子類
    function SubType () {}
    
    // 原型鏈繼承
    SubType.prototype = new SuperType();
    
    // 實例1
    var instance1 = new SubType();
    instance1.colors.push("blcak");
    instance1.name = "change-super-type-name";
    console.log(instance1.colors); // ["red", "blue", "green", "blcak"]
    console.log(instance1.name); // change-super-type-name
    // 實例2
    var instance2 = new SubType();
    console.log(instance2.colors); // ["red", "blue", "green", "blcak"]
    console.log(instance2.name); // SuperType
    
    

    [圖片上傳失敗...(image-3d5578-1664760989908)]

    注意:更改 SuperType 引用類型屬性時,會使 SubType 所有實例共享這一更新?;A類型屬性更新則不會。

  • 創(chuàng)建子類實例時,無法向父類構造函數(shù)傳參,或者說是,沒辦法在不影響所有對象實例的情況下,向超類的構造函數(shù)傳遞參數(shù)

更多面試題解答參見 前端手寫面試題詳細解答

二、 構造繼承

基本思想:

在子類型的構造函數(shù)內部調用父類型構造函數(shù)。

注意:

  • 函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對象,所以這里使用 apply/call 來實現(xiàn)。

  • 使用父類的構造函數(shù)來增強子類實例,等于是復制父類的實例屬性給子類(沒用到原型)

// 父類
function SuperType (name) {
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType () {
  // 調用 SuperType 構造函數(shù)
  SuperType.call(this, 'SuperType'); // 在子類構造函數(shù)中,向父類構造函數(shù)傳參
  // 為了保證子父類的構造函數(shù)不會重寫子類的屬性,需要在調用父類構造函數(shù)后,定義子類的屬性
  this.subName = "SubType"; // 子類屬性
};
// 子類實例
let instance = new SubType(); // 運行子類構造函數(shù),并在子類構造函數(shù)中運行父類構造函數(shù),this綁定到子類

[圖片上傳失敗...(image-6cccd7-1664760989908)]

優(yōu)點:

解決了1中子類實例共享父類引用對象的問題,實現(xiàn)多繼承,創(chuàng)建子類實例時,可以向父類傳遞參數(shù)

缺點:

  • 實例并不是父類的實例,只是子類的實例
  • 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
  • 無法實現(xiàn)函數(shù)復用,每個子類都有父類實例函數(shù)的副本,影響性能

三. 組合繼承

顧名思義,組合繼承就是將原型鏈繼承與構造函數(shù)繼承組合在一起,從而發(fā)揮兩者之長的一種繼承模式。

基本思想:

使用原型鏈繼承使用對原型屬性和方法的繼承,通過構造函數(shù)繼承來實現(xiàn)對實例屬性的繼承。這樣既能通過在原型上定義方法實現(xiàn)函數(shù)復用,又能保證每個實例都有自己的屬性。

通過調用父類構造,繼承父類的屬性并保留傳參的優(yōu)點,然后通過將父類實例作為子類原型,實現(xiàn)函數(shù)復用

// 父類
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType (name, subName) {
  // 調用 SuperType 構造函數(shù)
  SuperType.call(this, name); // ----第二次調用 SuperType----
  this.subName = subName;
};

// ----第一次調用 SuperType----
SubType.prototype = new SuperType(); // 重寫原型對象,代之以一個新類型的實例

SubType.prototype.constructor = SubType; // 組合繼承需要修復構造函數(shù)指向
SubType.prototype.saySubName = function () { // 子類原型方法
  return this.subName;
}

// 子類實例
let instance = new SubType('An', 'sisterAn')
instance.colors.push('black')
console.log(instance.colors) // ["red", "blue", "green", "black"]
instance.sayName() // An
instance.saySubName() // sisterAn

let instance1 = new SubType('An1', 'sisterAn1')
console.log(instance1.colors) //  ["red", "blue", "green"]
instance1.sayName() // An1
instance1.saySubName() // sisterAn1

[圖片上傳失敗...(image-f731bd-1664760989908)]

第一次調用 SuperType 構造函數(shù)時,SubType.prototype 會得到兩個屬性namecolors;當調用 SubType 構造函數(shù)時,第二次調用 SuperType 構造函數(shù),這一次又在新對象屬性上創(chuàng)建了 namecolors,這兩個屬性就會屏蔽原型對象上的同名屬性。

// instanceof:instance 的原型鏈是針對 SuperType.prototype 進行檢查的
instance instanceof SuperType // true
instance instanceof SubType // true

// isPrototypeOf:instance 的原型鏈是針對 SuperType 本身進行檢查的
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true

[圖片上傳失敗...(image-95d056-1664760989908)]

優(yōu)點:

彌補了方式2的缺陷,可以繼承實例屬性/方法,也可以繼承原型屬性/方法,不存在引用屬性共享問題,可傳參,可復用

缺點:

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

四. 寄生組合繼承

在組合繼承中,調用了兩次父類構造函數(shù),這里 通過通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點

主要思想:

借用 構造函數(shù) 繼承 屬性 ,通過 原型鏈的混成形式 來繼承 方法

// 父類
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

// 子類
function SubType (name, subName) {
  // 調用 SuperType 構造函數(shù)
  SuperType.call(this, name); // ----第二次調用 SuperType,繼承實例屬性----
  this.subName = subName;
};

// ----第一次調用 SuperType,繼承原型屬性----
SubType.prototype = Object.create(SuperType.prototype)

SubType.prototype.constructor = SubType; // 注意:增強對象

let instance = new SubType('An', 'sisterAn')

[圖片上傳失敗...(image-183b5a-1664760989908)]

優(yōu)點:

  • 只調用一次 SuperType 構造函數(shù),只創(chuàng)建一份父類屬性
  • 原型鏈保持不變
  • 能夠正常使用 instanceofisPrototypeOf

五. 原型式繼承

實現(xiàn)思路:

實現(xiàn)思路就是將子類的原型設置為父類的原型

// 父類
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
  return this.name;
};

/** 第一步 */
// 子類,通過 call 繼承父類的實例屬性和方法,不能繼承原型屬性/方法
function SubType (name, subName) {
  SuperType.call(this, name); // 調用 SuperType 的構造函數(shù),并向其傳參 
  this.subName = subName;
}

/** 第二步 */
// 解決 call 無法繼承父類原型屬性/方法的問題
// Object.create 方法接受傳入一個作為新創(chuàng)建對象的原型的對象,創(chuàng)建一個擁有指定原型和若干個指定屬性的對象
// 通過這種方法指定的任何屬性都會覆蓋原型對象上的同名屬性
SubType.prototype = Object.create(SuperType.prototype, { 
  constructor: { // 注意指定 SubType.prototype.constructor = SubType
    value: SubType,
    enumerable: false,
    writable: true,
    configurable: true
  },
  run : { 
    value: function(){ // override
      SuperType.prototype.run.apply(this, arguments); 
          // call super
          // ...
    },
    enumerable: true,
    configurable: true, 
    writable: true
  }
}) 

/** 第三步 */
// 最后:解決 SubType.prototype.constructor === SuperType 的問題
// 這里,在上一步已經指定,這里不需要再操作
// SubType.prototype.constructor = SubType;

var instance = new SubType('An', 'sistenAn')

[圖片上傳失敗...(image-f0ba96-1664760989908)]

多繼承

如果希望能 多繼承 ,可使用 混入 的方式

// 父類 SuperType
function SuperType () {}
// 父類 OtherSuperType
function OtherSuperType () {}

// 多繼承子類
function AnotherType () {
    SuperType.call(this) // 繼承 SuperType 的實例屬性和方法
    OtherSuperType.call(this) // 繼承 OtherSuperType 的實例屬性和方法
}

// 繼承一個類
AnotherType.prototype = Object.create(SuperType.prototype);

// 使用 Object.assign 混合其它
Object.assign(AnotherType.prototype, OtherSuperType.prototype);
// Object.assign 會把  OtherSuperType 原型上的函數(shù)拷貝到 AnotherType 原型上,使 AnotherType 的所有實例都可用 OtherSuperType 的方法

// 重新指定 constructor
AnotherType.prototype.constructor = AnotherType;

AnotherType.prototype.myMethod = function() {
     // do a thing
};

let instance = new AnotherType()

最重要的部分是:

  • SuperType.call 繼承實例屬性方法
  • Object.create() 來繼承原型屬性與方法
  • 修改 SubType.prototype.constructor的指向

ES6 繼承

首先,實現(xiàn)一個簡單的 ES6 繼承:

class People {
    constructor(name) {
        this.name = name
    }
    run() { }
}

// extends 相當于方法的繼承
// 替換了上面的3行代碼
class Man extends People {
    constructor(name) {
        // super 相當于屬性的繼承
        // 替換了 People.call(this, name)
        super(name)
        this.gender = '男'
    }
    fight() { }
}

核心代碼

extends 繼承的核心代碼如下,其實現(xiàn)和上述的寄生組合式繼承方式一樣

function _inherits(subType, superType) {
    // 創(chuàng)建對象,Object.create 創(chuàng)建父類原型的一個副本
    // 增強對象,彌補因重寫原型而失去的默認的 constructor 屬性
    // 指定對象,將新創(chuàng)建的對象賦值給子類的原型 subType.prototype
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: { // 重寫 constructor
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

繼承的使用場景

  • 不要僅僅為了使用而使用它們,這只是在浪費時間而已。
  • 當需要創(chuàng)建 一系列擁有相似特性的對象 時,那么創(chuàng)建一個包含所有共有功能的通用對象,然后在更特殊的對象類型中繼承這些特性。
  • 應避免多繼承,造成混亂。

注: 考慮到JavaScript的工作方式,由于原型鏈等特性的存在,在不同對象之間功能的共享通常被叫做 委托 - 特殊的對象將功能委托給通用的對象類型完成。這也許比將其稱之為繼承更為貼切,因為“被繼承”了的功能并沒有被拷貝到正在“進行繼承”的對象中,相反它仍存在于通用的對象中。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容