讓 JavaScript 區(qū)別于其它語言的是什么?原型繼承!
只有了解了原型繼承,你才能說你懂 JS,原型影響著對象的工作方式。原型繼承經(jīng)常會在面試中被問到,因為這個面試官可以看出你對 JS 的了解程度。
本文,主要是幫助大家能夠更好的理解 JS 中的原型。
1.簡介
JavaScript 僅具有基本類型,null,undefined和object。JS 和Java或PHP等語言相反,沒有類的概念可以用作創(chuàng)建對象的模板。
對象是可組合的結(jié)構(gòu),由多個屬性組成:鍵和值對。
例如,以下對象cat包含2個屬性:
- const cat = { sound: 'Meow!', legs: 4 };
由于我想在其他對象中重用legs屬性,因此讓我們將legs屬性提取到專門的對象pet中
- const pet = { legs: 4 };
- const cat = { sound: 'Meow!' };
看起來還不錯!
但是我仍然想讓cat擁有l(wèi)egs的屬性。如何將cat與pet聯(lián)系起來?
繼承可以幫助我們!
2. 原型對象
在 JS 中,一個對象可以繼承另一個對象的屬性。繼承屬性的對象被稱為 prototype,也就是原型。
按照改定義,我們可以讓pet作為 cat的原型,該原型將繼承l(wèi)egs屬性。使用對象字面量創(chuàng)建對象時,也可以使用特殊屬性__proto__設(shè)置所創(chuàng)建對象的原型。
- const pet = { legs: 4 };
- const cat = { sound: 'Meow!', __proto__: pet };
- cat.legs; // => 4
cat 對象現(xiàn)在從原型pet繼承了legs 。現(xiàn)在,我們可以使用cat.legs,其值為4。
另一方面,sound 屬性是一個自有屬性,因為它是直接在對象上定義的。
JavaScript 原型繼承本質(zhì):對象可以從其他對象(原型)繼承屬性。
你可能想知道:為什么首先需要繼承?
繼承解決了數(shù)據(jù)和邏輯重復(fù)的問題。通過繼承,對象可以共享屬性和方法。
- const pet = { legs: 4 };
- const cat = { sound: 'Meow!', __proto__: pet };
- const dog = { sound: 'Bark!', __proto__: pet };
- const pig = { sound: 'Grunt!', __proto__: pet };
- cat.legs; // => 4
- dog.legs; // => 4
- pig.legs; // => 4
cat,dog和pig 都重復(fù)使用了屬性legs。
注意:__proto__已過時,但為了簡單起見,我使用它。在生產(chǎn)環(huán)境中,建議使用Object.create()。
2.1 自有與繼承的屬性
如果對象自己的屬性和繼承屬性名稱一樣,JS 會優(yōu)先選擇自有屬性。
在以下示例中,chicken對象具有自己的屬性legs,但還繼承了具有相同名稱legs的屬性:
- const pet = { legs: 4 };
- const chicken = { sound: 'Cluck!', legs: 2, __proto__: pet };
- chicken.legs; // => 2
cat 對象從原型pet繼承了legs ?,F(xiàn)在,您可以使用屬性訪問器cat.legs,其值為4。
chicken.legs的值為2。JavaScript在繼承上選擇自有屬性legs 。
如果刪除自有屬性,則 JS 會選擇繼承的屬性!
- const pet = { legs: 4 };
- const chicken = { sound: 'Cluck!', legs: 2, __proto__: pet };
- chicken.legs; // => 2
- delete chicken.legs;
- chicken.legs; // => 4
3.隱式原型
創(chuàng)建對象時,未明確設(shè)置原型,JS 會為我們創(chuàng)建的對象類型分配一個隱式原型。
我們再來看看pet對象
- const pet = { legs: 4 };
- pet.toString(); // => `[object Object]`
pet只有一個屬性legs,但是,我們可以調(diào)用方法pet.toString()。這個toString()哪里來的?
創(chuàng)建pet 對象后,JS 為其分配了一個隱式原型對象。pet從這個隱式原型中繼承了toString()方法:
Object.getPrototypeOf() 方法返回指定對象的原型(內(nèi)部[[Prototype]]屬性的值)。
4. 原型鏈
我們再創(chuàng)建tail 對象,讓他也成為pet的原型:
- const tail = { hasTail: true };
- const pet = { legs: 4, __proto__: tail };
- const cat = { sound: 'Meow!', __proto__: pet };
- cat.hasTail; // => true
cat從它的原型pet繼承了legs 的屬性。但是cat也從其原型的原型tail 繼承了hasTail 。
訪問屬性myObject.myProp時,JS 會在myObject自身的屬性內(nèi),對象的原型,原型的原型中依次搜索myProp,以此類推,直到遇到null作為原型。
換句話說,JavaScript在原型鏈中尋找繼承的屬性。
5. 但 JavaScript有類
從剛開始講的 JS 只有對象,沒有類,你可能就已經(jīng)感到困惑,你在說什么鬼。這可能是因為在 ES6 中 你已經(jīng)開始使用class關(guān)鍵字了。
例如,你可以編寫一個Pet類:
- class Pet {
- legs = 4;
- constructor(sound) {
- this.sound = sound;
- }
- }
- const cat = new Pet('Moew!');
- cat.legs; // => 4
- cat instanceof Pet; // => true
并在實例化該類時創(chuàng)建cat 。
其實 ,JS 中的class 語法是原型繼承之上的語法糖。
上面的基于類的代碼片段等效于以下內(nèi)容:
- const pet = {
- legs: 4
- };
- function CreatePet(sound) {
- return { sound, __proto__: pet };
- }
- CreatePet.prototype = pet;
- const cat = CreatePet('Moew!');
- cat.legs; // => 4
- cat instanceof CreatePet; // => true
CreatePet.prototype = pet賦值是使cat instanceof CreatePet值為true所必需的。
6.總結(jié)
在JavaScript中,對象從其他對象(原型)繼承屬性,這就是原型繼承的一個概念。
JS 在對象的原型中尋找繼承的屬性,也在原型的原型中尋找繼承的屬性,等等。
雖然最初的原型繼承看起來很笨拙,但是理解它之后,我們會喜歡它的簡單性和可能性。
作者:Dmitri Pavlutin 譯者:前端小智 來源:sitepoint
原文:https://dmitripavlutin.com/javascript-prototypal-inheritance/
本文轉(zhuǎn)載自微信公眾號「大遷世界」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系大遷世界公眾號。