JavaScript中繼承的七種方式對比總結(jié)
昨天我們聊了原型鏈,知道了JS對象之間是怎么“攀親戚”的。今天咱們來聊聊繼承——也就是怎么讓一個對象“認祖歸宗”,繼承另一個對象的屬性和方法。從最原始的手動操作,到ES6優(yōu)雅的class語法,這中間有好幾種姿勢,每種都有自己的脾氣。今天一次性給你盤清楚。
前言
繼承在JS里就像“房產(chǎn)繼承”——你想把老爹的房子傳給孩子,但又不想直接把房子拆了重新蓋。不同的繼承方式,就像是不同的“過戶”手段,有的簡單粗暴,有的精細巧妙,有的會留下后遺癥。
今天我們就來盤點JS里實現(xiàn)繼承的七種方式,從最基礎(chǔ)的到最完善的,讓你在面試官問“JS繼承有哪些方式”時,能從容應(yīng)對,還能說出各自的優(yōu)缺點。
一、原型鏈繼承:最原始的“血脈相連”
這是JS里最基礎(chǔ)的繼承方式,核心就是讓子類的原型指向父類的實例。
function Animal() {
this.name = '動物';
this.colors = ['黑色', '白色'];
}
Animal.prototype.eat = function() {
console.log('吃東西');
};
function Dog() {}
// 關(guān)鍵一步:讓Dog的原型指向Animal的實例
Dog.prototype = new Animal();
const dog1 = new Dog();
const dog2 = new Dog();
dog1.colors.push('金色');
console.log(dog2.colors); // ['黑色', '白色', '金色'] —— 哎呀,被改了!
優(yōu)點:實現(xiàn)了方法的繼承,寫起來簡單。
缺點:
- 引用類型的屬性會被所有實例共享,一個改了大家都改。
- 無法向父類構(gòu)造函數(shù)傳參。
- 子類實例的構(gòu)造函數(shù)被“篡改”成了Animal。
這種繼承就像家族企業(yè),祖宗留下的財產(chǎn)(比如房產(chǎn)證)大家共用,一個孫子把房子賣了,其他孫子都沒了。
二、構(gòu)造函數(shù)繼承:借雞生蛋
為了解決引用共享和傳參問題,誕生了“借用構(gòu)造函數(shù)”的方式,在子類構(gòu)造函數(shù)里調(diào)用父類構(gòu)造函數(shù)。
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
Animal.prototype.eat = function() {
console.log('吃東西');
};
function Dog(name) {
Animal.call(this, name); // 借調(diào)父類構(gòu)造函數(shù)
}
const dog1 = new Dog('旺財');
const dog2 = new Dog('來福');
dog1.colors.push('金色');
console.log(dog1.colors); // ['黑色', '白色', '金色']
console.log(dog2.colors); // ['黑色', '白色'] —— 沒被影響!
console.log(dog1.eat); // undefined —— 父類原型上的方法沒繼承到
優(yōu)點:
- 解決了引用共享問題,每個實例有自己的屬性副本。
- 可以向父類傳遞參數(shù)。
缺點:
- 只能繼承父類實例屬性,繼承不到父類原型上的方法。
- 方法都在構(gòu)造函數(shù)里定義,每次創(chuàng)建實例都會創(chuàng)建新方法,浪費內(nèi)存。
這就像“借錢不借地”,你把老爹的現(xiàn)金拿來了,但老爹的祖宅(原型上的方法)沒拿到。
三、組合繼承:取長補短
把原型鏈繼承和構(gòu)造函數(shù)繼承結(jié)合起來,各取所長。
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
Animal.prototype.eat = function() {
console.log('吃東西');
};
function Dog(name) {
Animal.call(this, name); // 第二次調(diào)用父類
}
Dog.prototype = new Animal(); // 第一次調(diào)用父類
Dog.prototype.constructor = Dog; // 修正構(gòu)造函數(shù)指向
const dog1 = new Dog('旺財');
const dog2 = new Dog('來福');
dog1.colors.push('金色');
console.log(dog1.colors); // ['黑色', '白色', '金色']
console.log(dog2.colors); // ['黑色', '白色']
dog1.eat(); // 吃東西 —— 方法也繼承到了
優(yōu)點:
- 解決了引用共享問題。
- 可以向父類傳參。
- 繼承了父類原型上的方法。
缺點:父類構(gòu)造函數(shù)被調(diào)用了兩次,造成了一定的性能浪費和屬性冗余(實例上有,原型上也有)。
組合繼承是JS里最常用的繼承方式,雖然有小瑕疵,但足夠好用。ES6的class本質(zhì)上就是它的語法糖。
四、原型式繼承:Object.create的雛形
這種方式不涉及構(gòu)造函數(shù),直接通過一個對象創(chuàng)建另一個對象。
const animal = {
name: '動物',
colors: ['黑色', '白色'],
eat() {
console.log('吃東西');
}
};
function createObject(obj) {
function F() {}
F.prototype = obj;
return new F();
}
const dog1 = createObject(animal);
const dog2 = createObject(animal);
dog1.colors.push('金色');
console.log(dog2.colors); // ['黑色', '白色', '金色'] —— 又被共享了
ES5提供了Object.create()方法,就是干這個的。
const dog1 = Object.create(animal); const dog2 = Object.create(animal);
優(yōu)點:不需要構(gòu)造函數(shù),直接基于已有對象創(chuàng)建新對象。
缺點:引用類型屬性還是會被共享。
這種繼承就像“克隆人”,克隆體共享同一個原型,一個改了大家都改。
五、寄生式繼承:給繼承加個包裝
在原型式繼承的基礎(chǔ)上,給新對象添加方法。
function createDog(original) {
const clone = Object.create(original);
clone.bark = function() {
console.log('汪汪汪');
};
return clone;
}
const animal = { name: '動物', eat() { console.log('吃東西'); } };
const dog = createDog(animal);
dog.bark(); // 汪汪汪
優(yōu)點:可以在不修改原始對象的情況下添加新功能。
缺點:和原型式繼承一樣,引用共享問題依然存在;而且方法每次創(chuàng)建都會重新生成,沒法復(fù)用。
六、寄生組合繼承:最完美的姿勢
組合繼承的缺點是調(diào)用了兩次父類構(gòu)造函數(shù)。寄生組合繼承解決了這個問題,它被認為是JS繼承的“最佳實踐”。
function Animal(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
Animal.prototype.eat = function() {
console.log('吃東西');
};
function Dog(name) {
Animal.call(this, name); // 只調(diào)用一次父類
}
// 核心:用Object.create代替new Animal()
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('汪汪汪');
};
const dog1 = new Dog('旺財');
const dog2 = new Dog('來福');
dog1.colors.push('金色');
console.log(dog2.colors); // ['黑色', '白色'] —— 不受影響
dog1.eat(); // 吃東西
dog1.bark(); // 汪汪汪
優(yōu)點:
- 父類構(gòu)造函數(shù)只調(diào)用一次。
- 原型鏈干干凈凈,沒有冗余屬性。
- 既有自己的屬性副本,又繼承了原型方法。
寄生組合繼承是目前最理想的繼承實現(xiàn)方式,也是ES6 class 背后做的事情。
七、ES6 Class:語法糖的終極形態(tài)
ES6引入了class關(guān)鍵字,讓繼承寫起來像其他語言一樣優(yōu)雅。
class Animal {
constructor(name) {
this.name = name;
this.colors = ['黑色', '白色'];
}
eat() {
console.log('吃東西');
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 調(diào)用父類構(gòu)造函數(shù)
this.breed = breed;
}
bark() {
console.log('汪汪汪');
}
}
const dog1 = new Dog('旺財', '土狗');
const dog2 = new Dog('來福', '金毛');
dog1.colors.push('金色');
console.log(dog2.colors); // ['黑色', '白色'] —— 完美
dog1.eat(); // 吃東西
dog1.bark(); // 汪汪汪
優(yōu)點:
- 語法清晰,易讀易寫。
- 底層就是寄生組合繼承,性能好。
- 支持
super關(guān)鍵字方便調(diào)用父類方法。
缺點:本質(zhì)還是原型那一套,但語法糖已經(jīng)夠甜了。
八、各種繼承方式對比總結(jié)
| 繼承方式 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|
| 原型鏈繼承 | 簡單 | 引用共享、不能傳參 | 基本不用 |
| 構(gòu)造函數(shù)繼承 | 解決引用共享、能傳參 | 不能繼承原型方法 | 基本不用 |
| 組合繼承 | 兩者優(yōu)點都有 | 調(diào)用兩次父類 | 以前常用 |
| 原型式繼承 | 基于已有對象創(chuàng)建 | 引用共享 | 簡單對象復(fù)用 |
| 寄生式繼承 | 可添加新功能 | 引用共享 | 臨時增強對象 |
| 寄生組合繼承 | 完美 | 寫法稍復(fù)雜 | ES6之前的首選 |
| ES6 class | 語法優(yōu)雅、標(biāo)準(zhǔn) | 需要轉(zhuǎn)譯(老環(huán)境) | 現(xiàn)代開發(fā)首選 |
九、實際開發(fā)中怎么選
無腦選ES6 class。除非你還要兼容IE這種古董,否則直接用class和extends就完事了。不僅代碼量少,而且不容易踩坑。
如果你好奇class底層干了啥,或者要寫一些高階的繼承場景(比如混入multiple inheritance),那寄生組合繼承的手寫實現(xiàn)還是值得掌握的。
function inherit(child, parent) {
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
這個工具函數(shù),就是寄生組合繼承的核心。
十、總結(jié):從“手動擋”到“自動擋”
JS的繼承演進史,其實就是一部從“手動擋”到“自動擋”的發(fā)展史:
- 原型鏈繼承是手動擋,操作復(fù)雜容易出事。
- 組合繼承是自動擋,但油耗(性能)稍高。
- 寄生組合繼承是CVT,平順又高效。
- ES6 class是智能駕駛,你只管踩油門,剩下的交給它。
無論哪種方式,底層都是原型鏈那一套。掌握了原型,繼承的七種姿勢不過是排列組合。以后面試官再問“JS繼承有哪些方式”,你可以從容地把這七種娓娓道來,順便告訴他:“但實際開發(fā),我選ES6 class。”
明天我們將進入JavaScript的另一個核心領(lǐng)域——異步編程,從回調(diào)地獄到Promise,再到async/await,帶你徹底理清JS的異步世界。
以上就是JavaScript中繼承的七種方式對比總結(jié)的詳細內(nèi)容,更多關(guān)于JavaScript繼承的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實現(xiàn)通過滑塊改變網(wǎng)頁顏色
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)通過滑塊改變網(wǎng)頁顏色,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
Bootstrap的Carousel配合dropload.js實現(xiàn)移動端滑動切換圖片
這篇文章主要介紹了bootstrap的Carousel配合dropload.js實現(xiàn)移動端滑動切換圖片,實現(xiàn)方法非常簡單,具有參考借鑒價值,需要的朋友可以參考下2017-03-03
javascript 文字上下間隔滾動的代碼 符合WEB標(biāo)準(zhǔn) 腳本之家修正版
javascript 文字上下間隔滾動的代碼 符合WEB標(biāo)準(zhǔn) 腳本之家修正版,這里提供了兩個版本,第二個在firefox下運行有些問題大家可以修改下,第一個的高度問題,已經(jīng)修正,其實就是簡單的加了css樣式。2009-12-12
JavaScript本地數(shù)據(jù)存儲sessionStorage與localStorage使用詳解
這篇文章主要介紹了JavaScript本地數(shù)據(jù)存儲sessionStorage與localStorage使用,localStorage:永久存儲在本地,適合保存在本地的數(shù)據(jù)。sessionStorage:會話級的存儲,敏感帳號一次登陸2022-10-10
Java中int與integer的區(qū)別(基本數(shù)據(jù)類型與引用數(shù)據(jù)類型)
這篇文章主要介紹了int與integer的區(qū)別(基本數(shù)據(jù)類型與引用數(shù)據(jù)類型),簡單的說 int 是基本數(shù)據(jù)類型,integer 是引用數(shù)據(jù)類型,具體區(qū)別詳解大家參考下本文2017-02-02

