部落客在閱讀大量面經文章時發現無論是大廠面試還是小廠面試,無論是社招還是校招,隻要考查JS基礎,繼承、原型、原型鍊都是繞不開的話題,是以本次希望和大家一起學習必須掌握的七種繼承方式,部落客将細緻的分析每一種繼承方式的原理、優缺點并給出線上的實作方式,這不僅僅對面試有幫助,還對我們了解JS的運作機制有幫助,讓我們一起加油吧~順便給個贊哦!
1:原型鍊繼承
原理
原型鍊繼承的原理是利用原型對象和執行個體之間的關系實作繼承,實作這種繼承的關鍵在于讓子類的原型對象指向新建立的父類執行個體。
實作代碼
// 1:原型鍊繼承
function Father() {
this.name = 'justin';
}
Father.prototype.getName = function () {
return this.name
}
function Child() { }
Child.prototype = new Father();
const child = new Child();
console.log(child.getName());
優缺點
- 優點:執行個體可以繼承的屬性包括:執行個體的構造函數的屬性,父類構造函數的屬性,父類原型對象上的屬性。
- 缺點:一個執行個體修改了原型對象上的屬性,另一個執行個體的原型屬性也會被修改。新執行個體無法向父類構造函數傳參。
下面這個例子展示了,原型鍊繼承的缺點
大廠面試必須掌握的六種繼承方式,你會嗎?
線上實作
codeSandBox 2. 構造函數繼承
構造函數繼承的核心在于:在子類構造函數中通過父類構造函數.call(this)來實作繼承。
// 2:構造函數繼承
function Father() {
this.name = 'justin';
this.say = {haha: 111}
}
function Child(age) {
Father.call(this);
this.age = age;
}
const child1 = new Child(10);
const child2 = new Child(20);
child1.say.haha = 222;
console.log(child1);
console.log(child2);
- 可以繼承多個構造函數屬性(通過多次call的調用)
- 解決了原型鍊繼承中執行個體共享引用類型的問題
- 在子執行個體中可以向父執行個體中傳參
- 隻繼承了父類構造函數的屬性,沒有繼承父類原型對象上的屬性。
- 無法實作父類構造函數的複用,每次都要重新調用
3. 組合繼承(組合指的是組合了原型鍊和構造函數的繼承方式)
結合了原型鍊和構造函數的繼承方式,一是通過在子類構造函數中讓父類構造函數調用call修改this指向,二是讓子類構造函數的原型對象指向父類構造函數的執行個體。
// 3:組合繼承(組合指的是組合了原型鍊繼承和構造函數繼承)
function Father(age) {
this.colors = ['red','pink'];
this.age = age;
}
Father.prototype.say = () => '你好';
function Child(name,age) {
// 構造函數的方式
Father.call(this,age);
this.name = name;
}
// 原型鍊
Child.prototype = new Father();
Child.prototype.constructor = Child;
const child1 = new Child('張三',20);
const child2 = new Child('李四',25);
Child.prototype
child1
child1.colors.push('black');
console.log(child1.colors);
console.log(child2.colors);
console.log(child1.say());
- 可以繼承父類構造函數上的屬性和原型對象上的屬性。
- 可以傳參。
- 每個新執行個體引入的構造函數屬性是私有的。
- 調用了兩次父類構造函數。
- 子類執行個體上的屬性,同時存在于原型鍊上和子例身上,造成原型鍊污染。
4: 原型式繼承
利用一個空函數作為中介,讓這個中介的原型對象指向需要繼承的父類對象,然後傳回這個函數的執行個體,即可完成原型式繼承。
// 4:原型式繼承
function createObj(o) {
function F() { };
F.prototype = o;
return new F();
}
const obj = {
name: 'justin',
friends: [1, 2, 3, 4]
}
// 方式1
const m1 = createObj(obj);
const m2 = createObj(obj);
// 方式2
const m3 = Object.create(obj);
console.log(m1.name); //justin
console.log(m2.name); //justin
m1.friends.push(666);
console.log(m1.friends); // [1,2,3,4,666]
console.log(m2.friends); // [1,2,3,4,666]
- 類似于複制一個對象,用函數來包裝。
- 無法向父類傳參。
- 父類的引用類型被子類共享。
codeSandBox線上實作 5:寄生式繼承
寄生式繼承是在原型式繼承的基礎上進行了一次增強,也就是通過增加一個函數,然後添加屬性實作繼承。
// 5:寄生式繼承
function objCopy(o) {
function F() {};
F.prototype = o;
return new F();
}
function enhanceObj(o) {
const clone = objCopy(o);
clone.say = function() {
return 'hi';
}
return clone;
}
const obj = {
name: 'justin',
colors: [1,2,3]
}
const m1 = enhanceObj(obj);
const m2 = enhanceObj(obj);
console.log(m1.name); //justin
console.log(m1.colors); //[1,2,3]
console.log(m1.say()); //hi
m1.colors.push(777)
console.log(m2.colors); // [1,2,3,777]
- 增強了原型式繼承的能力。
-
- 父類構造函數的引用類型對象被子類執行個體共享。
6:寄生組合式繼承
寄生組合式繼承結合了構造函數繼承、寄生式繼承,讓子類的構造函數的原型對象指向原型式繼承傳過來的執行個體,同時這個執行個體的構造函數也指向子類構造函數,别忘了子類構造函數中還需要父類構造函數通過call改變this指向。
// 6:寄生組合式繼承
function Father(name) {
this.name = name;
this.colors = [1,2,3];
}
Father.prototype.say = function() {
return 'hi';
}
function Child(name,age) {
Father.call(this,name);
this.age = age;
}
function createObj(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(Child,Father) {
// 先構造一個指向父類構造函數原型對象的對象
const prototype = createObj(Father.prototype);
// 讓這個對象的構造函數指向Child
prototype.constructor = Child;
Child.prototype = prototype;
}
inheritPrototype(Child,Father);
const child1 = new Child('justin',666);
const child2 = new Child('心飛揚',777);
console.log(child1.colors); //[1,2,3]
console.log(child2.colors); //[1,2,3]
child1.colors.push(666);
console.log(child1.colors); // [1,2,3,666]
console.log(child2.colors); // [1,2,3]
- 子類構造函數可以向父類傳參。
- 隻調用一次父類構造函數。
- 父類的引用類型屬性不會被子類共享。
參考連結