动态

详情 返回 返回

ES5、ES6 如何實現繼承

完整高頻題庫倉庫地址:https://github.com/hzfe/aweso...

完整高頻題庫閲讀地址:https://febook.hzfe.org/

相關問題

  • 關於 ES5 和 ES6 的繼承問題
  • 原型鏈概念

回答關鍵點

原型鏈繼承 構造函數繼承 ES6 類繼承

繼承是指子類型具備父類型的屬性和行為,使代碼得以複用,做到設計上的分離。JavaScript 中的繼承主要通過原型鏈和構造函數來實現。常見的繼承方法有:ES6 中 class 的繼承、原型鏈繼承、寄生組合式繼承等。

知識點深入

1. 原型鏈

原型鏈的本質是拓展原型搜索機制。每個實例對象都有一個私有屬性 \_\_proto\_\_。該屬性指向它的構造函數的原型對象 prototype。該原型對象的 \_\_proto\_\_ 也可以指向其他構造函數的 prototype。依次層層向上,直到一個對象的 \_\_proto\_\_ 指向 null。根據定義,null 沒有原型,並作為這個原型鏈中的最後一個環節。

當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或直到這個鏈表結束(Object.prototype.__proto__ === null)。

2. 原型鏈繼承

原型鏈繼承的思想:一個引用類型繼承另一個引用類型的屬性和方法

function SuperType() {
  this.b = [1, 2, 3];
}

function SubType() {}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

var sub1 = new SubType();
var sub2 = new SubType();

// 這裏對引用類型的數據進行操作
sub1.b.push(4);

console.log(sub1.b); // [1,2,3,4]
console.log(sub2.b); // [1,2,3,4]
console.log(sub1 instanceof SuperType); // true

優點:

  1. 父類新增原型方法/原型屬性,子類都能訪問到。
  2. 簡單、易於實現。

缺點:

  1. 無法實現多繼承。
  2. 由於原型中的引用值被共享,導致實例上的修改會直接影響到原型。
  3. 創建子類實例時,無法向父類構造函數傳參。

3. 構造函數繼承

構造函數繼承的思想:子類型構造函數中調用父類的構造函數,使所有需要繼承的屬性都定義在實例對象上

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

SuperType.prototype.say = function () {
  console.log("HZFE");
};

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

var sub1 = new SubType();
var sub2 = new SubType();

// 傳遞參數
var sub3 = new SubType("Hzfe");

sub1.say(); // 使用構造函數繼承並沒有訪問到原型鏈,say 方法不能調用

console.log(sub3.name); // Hzfe

sub1.b.push(4);

// 解決了原型鏈繼承中子類實例共享父類引用屬性的問題
console.log(sub1.b); // [1,2,3,4]
console.log(sub2.b); // [1,2,3]
console.log(sub1 instanceof SuperType); // false

優點:

  1. 解決了原型鏈繼承中子類實例共享父類引用屬性的問題。
  2. 可以在子類型構造函數中向父類構造函數傳遞參數。
  3. 可以實現多繼承(call 多個父類對象)。

缺點:

  1. 實例並不是父類的實例,只是子類的實例。
  2. 只能繼承父類的實例屬性和方法,不能繼承原型屬性和方法。
  3. 無法實現函數複用,每個子類都有父類實例函數的副本,影響性能。

4. 組合繼承(偽經典繼承)

組合繼承的思想:使用原型鏈實現對原型屬性和方法的繼承,借用構造函數實現對實例屬性的繼承

function SuperType(name) {
  this.name = name;
  this.a = "HZFE";
  this.b = [1, 2, 3, 4];
}

SuperType.prototype.say = function () {
  console.log("HZFE");
};

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

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

優點:

  1. 可以繼承實例屬性/方法,也可以繼承原型屬性/方法。
  2. 不存在引用屬性共享問題。
  3. 可傳參
  4. 函數可複用

缺點:

  1. 調用了兩次父類構造函數(耗內存),生成了兩份實例。

5. 寄生組合式繼承

寄生組合式繼承的思想:借用構造函數來繼承屬性,使用混合式原型鏈繼承方法

// 在函數內部,第一步創建父類原型的一個副本,第二部是為創建的副本添加 constructor 屬性,
// 從而彌補因重寫而失去的默認的 constructor 屬性。最後一步,將新創建的對象(即副本)賦值給予類型的原型。
function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype); // 創建對象
  prototype.constructor = subType; // 增強對象
  subType.prototype = prototype; // 指定對象
}

function SuperType(name) {
  this.name = name;
}

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

function SubType(name, num) {
  SuperType.call(this, name);
  this.num = num;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayNum = function () {
  console.log(this.num);
};

優點:

  1. 只調用了一次 SuperType 構造函數,避免了在 SubType.prototype 上創建不必要的屬性。
  2. 能夠正常使用 instanceof 和 isPrototypeOf()。

缺點:

  1. 實現較為複雜

6. ES6 中 class 的繼承

ES6 中引入了 class 關鍵字, class 可以通過 extends 關鍵字實現繼承,還可以通過 static 關鍵字定義類的靜態方法,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。需要注意的是:class 關鍵字只是原型的語法糖, JavaScript 繼承仍然是基於原型實現的。

class Pet {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  showName() {
    console.log("調用父類的方法");
    console.log(this.name, this.age);
  }
}

// 定義一個子類
class Dog extends Pet {
  constructor(name, age, color) {
    super(name, age); // 通過 super 調用父類的構造方法
    this.color = color;
  }

  showName() {
    console.log("調用子類的方法");
    console.log(this.name, this.age, this.color);
  }
}

優點:

  1. 清晰方便

缺點:

  1. 不是所有的瀏覽器都支持 class。

參考資料

  1. JS 實現繼承的幾種方式
  2. 阮一峯 ES6 入門之 class 的繼承
  3. 《JavaScript 高級程序設計》
  4. 《你不知道的 JavaScript》
user avatar
0 用户, 点赞了这篇动态!

发布 评论

Some HTML is okay.