不了解原型继承,你就不了解JavaScript。原型极大地影响了对象在JavaScript中的工作方式。
原型继承在编码面试中经常被问到,因为这种知识是衡量你对JavaScript了解程度的一个指标。
本指南将帮助你轻松理解JavaScript中的原型继承。
1.简介
JavaScript只有原始类型,null ,undefined 和对象。一个对象的大世界。在JavaScript中,与Java或PHP等语言相反,没有类的概念,作为创建对象的模板。
一个对象是一个可组合的结构,由多个属性组成:键和值对。
例如,下面这个对象cat ,包含2个属性:
javascript
const cat = { sound: 'Meow!', legs: 4 };
由于我想在其他对象中重复使用legs 属性,让我们把legs 属性提取到一个专门的对象pet :
javascript
const pet = { legs: 4 };
const cat = { sound: 'Meow!' };
这看起来更好。
但是我仍然希望在cat 上有legs 属性。你怎么能把cat 和pet 联系起来呢?
继承可以帮助你!
2.原型对象
在JavaScript中,一个对象可以继承另一个对象的属性。继承这些属性的对象被称为原型。
按照这个例子,你可以把pet 作为cat 的原型,然后它将继承 legs 的属性。
当使用对象字面意义创建一个对象时,你也可以使用特殊的属性__proto__ 来设置所创建对象的原型。
让我们使用__proto__ ,使pet 成为cat 的原型:
javascript
const pet = { legs: 4 };
const cat = { sound: 'Meow!', __proto__: pet };
cat.legs; // => 4
sound 属性,在另一边,是一个自己的属性,因为它直接定义在对象上。

JavaScript中原型继承的本质:对象可以继承其他对象的属性--原型。
你可能在想:为什么首先需要继承?
继承解决了数据和逻辑重复的问题。通过继承,对象可以共享属性和方法。
例如,你可以很容易地重复使用legs 属性来创建更多的宠物。
javascript
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 都重复使用属性legs 。
注意:__proto__ 已经被废弃了,但为了简单起见,我还是用它。在生产代码中,建议使用Object.create()。
2.1 自己的属性与继承的属性
如果一个对象有一个自己的属性和一个具有相同名称的继承属性,那么JavaScript总是选择自己的属性。
在下面的例子中chicken 对象有一个自己的属性legs ,但也继承了一个同名的属性legs 。
javascript
const pet = { legs: 4 };
const chicken = { sound: 'Cluck!', legs: 2, __proto__: pet };
chicken.legs; // => 2
chicken.legs 评估为legs 。JavaScript选择了自己的属性2 (也就是2 )而不是继承的 legs(也就是 4)。

如果你删除了自己的属性,那么JavaScript就会选择继承的那一个。
javascript
const pet = { legs: 4 };
const chicken = { sound: 'Cluck!', legs: 2, __proto__: pet };
chicken.legs; // => 2
delete chicken.legs;
chicken.legs; // => 4
3.隐式原型
当你创建一个对象时,如果没有明确设置原型,JavaScript就会为你创建的对象类型指定一个隐式原型。
让我们再看一下pet 对象:
javascript
const pet = { legs: 4 };
pet.toString(); // => `[object Object]`
pet 只有一个属性legs ,但是,你可以调用方法 pet.toString() 。 toString()是从哪里来的?
当你创建了pet 对象后,JavaScript给它分配了一个隐式原型对象。从这个隐式原型pet 继承了toString() 方法:
javascript
const pet = { legs: 4 };
const petPrototype = Object.getPrototypeOf(pet);
pet.toString === petPrototype.toString; // => true
Object.getPrototypeOf(object)是一个实用的函数,用于返回一个对象的原型。
4.原型链
让我们再深入一点,创建一个对象tail ,使其也成为pet 的原型:
javascript
const tail = { hasTail: true };
const pet = { legs: 4, __proto__: tail };
const cat = { sound: 'Meow!', __proto__: pet };
cat.hasTail; // => true
cat 继承了其直接原型 pet的属性 。但是legs 也从它的原型-- cat 的原型继承了 hasTail!
当访问一个属性myObject.myProp ,JavaScript会在myObject 自己的属性里面寻找myProp ,然后在对象的原型中寻找,然后在原型的原型中寻找,以此类推,直到遇到null 这个原型。
换句话说,JavaScript在原型链中寻找继承的属性。
5.但是JavaScript有类!?
关于JavaScript只有对象的说法,你可能会感到困惑。你可能已经在JavaScript中使用了class 这个关键字!
例如,你可以写一个类Pet :
javascript
class Pet {
legs = 4;
constructor(sound) {
this.sound = sound;
}
}
const cat = new Pet('Moew!');
cat.legs; // => 4
cat instanceof Pet; // => true
并在实例化该类时创建一个cat 。
秘密在于,JavaScript中的class 语法是在原型继承之上的语法糖。
上述基于class 的代码片段等同于以下内容:
javascript
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。
当使用class-es工作时,你可以完全忘记原型。
6.总结
在JavaScript中,对象继承了其他对象的属性--原型。这就是原型继承的概念。
JavaScript在对象的原型中寻找继承的属性,同时也在原型的原型中寻找,以此类推,在原型链中寻找。
虽然原型继承一开始看起来很笨拙,但当理解了它之后,你可以享受它的简单性和可能性。对象从对象继承属性--还有什么比这更简单的呢?
有关于原型继承的问题吗?请在下面的评论中提问!