JavaScript 繼承
JavaScript 繼承是經常被深入考察的話題,以下是一些常見的問題:
基礎概念問題
- JavaScript 中有哪些實現(xiàn)繼承的方式?
原型鏈繼承
構造函數(shù)繼承(借用構造函數(shù))
組合繼承(原型鏈+構造函數(shù))
原型式繼承
寄生式繼承
寄生組合式繼承
ES6 Class 繼承
- 原型鏈繼承的優(yōu)缺點是什么?
優(yōu)點:簡單,能繼承父類原型上的屬性和方法
缺點:所有實例共享原型屬性;無法向父類構造函數(shù)傳參
- 構造函數(shù)繼承的優(yōu)缺點是什么?
優(yōu)點:可以在子類中向父類傳遞參數(shù);避免了引用屬性共享問題
缺點:不能繼承父類原型上的方法;方法都在構造函數(shù)中定義,無法復用
深入技術問題
- 組合繼承有什么問題?如何優(yōu)化?
問題:會調用兩次父類構造函數(shù)
優(yōu)化:使用寄生組合式繼承
- 寄生組合式繼承為什么是最佳實踐?
只調用一次父類構造函數(shù)
避免在子類原型上創(chuàng)建不必要的屬性
保持原型鏈不變
- ES6 Class 繼承和 ES5 繼承有什么區(qū)別?
Class 繼承使用
extends和superClass 繼承實質上是語法糖,底層還是基于原型鏈
Class 繼承有更嚴格的語法檢查
代碼實現(xiàn)問題
- 手寫一個寄生組合式繼承的實現(xiàn)
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name) {
this.name = name;
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
- 解釋下面代碼的輸出結果
function Parent() {
this.name = 'parent';
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {
this.name = 'child';
}
Child.prototype = new Parent();
const child = new Child();
child.sayName(); // 輸出什么?為什么?
實際應用問題
- 如何實現(xiàn)多重繼承?
- JavaScript 本身不支持多重繼承,但可以通過混入(Mixin)模式模擬
-
在大型項目中,你會選擇哪種繼承方式?為什么?
通常會選擇 ES6 Class 繼承,因為語法清晰,易于維護
如果需要更精細控制,可能會選擇組合繼承或寄生組合式繼承
-
如何判斷一個對象是否是另一個對象的原型?
使用
Object.prototype.isPrototypeOf()或
instanceof操作符
準備這些問題時,最好能夠結合實際代碼示例來解釋,展示你對 JavaScript 繼承機制的深入理解。
function Parent() {
this.name = 'parent';
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {
this.name = 'child';
}
Child.prototype = new Parent();
const child = new Child();
child.sayName(); // 輸出什么?為什么?
這段代碼的輸出結果是:
"child"
代碼解析:
-
Parent構造函數(shù):
定義了一個實例屬性
this.name = 'parent'在
Parent.prototype上添加了一個方法sayName(),用于打印this.name
-
Child構造函數(shù):
定義了一個實例屬性
this.name = 'child'將
Child.prototype設置為new Parent(),即Child繼承Parent的原型鏈
-
const child = new Child():
執(zhí)行
Child構造函數(shù),this.name被賦值為'child'child的原型 (__proto__) 指向Parent的實例(new Parent())
-
child.sayName():
child本身沒有sayName方法,所以沿著原型鏈查找在
Parent.prototype上找到sayName并調用sayName()中的this指向child實例,所以this.name是'child'(而不是Parent的'parent')
關鍵點:
this的指向:sayName()被child調用,所以this指向child實例,而不是Parent實例。原型鏈查找:
child本身沒有sayName,所以會去Child.prototype(即new Parent())上找,再往上到Parent.prototype找到sayName。
如果修改代碼:
Child.prototype = Parent.prototype; // 直接繼承 Parent.prototype,而不是 new Parent()
const child = new Child();
child.sayName(); // 仍然輸出 "child"
- 這樣
Child.prototype直接指向Parent.prototype,不再執(zhí)行new Parent(),但this仍然指向child,所以this.name還是'child'。
總結:
原型鏈繼承 的關鍵是
Child.prototype = new Parent(),使得Child的實例可以訪問Parent原型上的方法。this指向調用者,所以child.sayName()打印的是child的name,而不是Parent的name。