完整高頻題庫倉庫地址: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
優點:
- 父類新增原型方法/原型屬性,子類都能訪問到。
- 簡單、易於實現。
缺點:
- 無法實現多繼承。
- 由於原型中的引用值被共享,導致實例上的修改會直接影響到原型。
- 創建子類實例時,無法向父類構造函數傳參。
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
優點:
- 解決了原型鏈繼承中子類實例共享父類引用屬性的問題。
- 可以在子類型構造函數中向父類構造函數傳遞參數。
- 可以實現多繼承(call 多個父類對象)。
缺點:
- 實例並不是父類的實例,只是子類的實例。
- 只能繼承父類的實例屬性和方法,不能繼承原型屬性和方法。
- 無法實現函數複用,每個子類都有父類實例函數的副本,影響性能。
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;
優點:
- 可以繼承實例屬性/方法,也可以繼承原型屬性/方法。
- 不存在引用屬性共享問題。
- 可傳參
- 函數可複用
缺點:
- 調用了兩次父類構造函數(耗內存),生成了兩份實例。
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);
};
優點:
- 只調用了一次 SuperType 構造函數,避免了在 SubType.prototype 上創建不必要的屬性。
- 能夠正常使用 instanceof 和 isPrototypeOf()。
缺點:
- 實現較為複雜
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);
}
}
優點:
- 清晰方便
缺點:
- 不是所有的瀏覽器都支持 class。
參考資料
- JS 實現繼承的幾種方式
- 阮一峯 ES6 入門之 class 的繼承
- 《JavaScript 高級程序設計》
- 《你不知道的 JavaScript》