是什么造就了JavaScript——原型继承性介绍

65 阅读5分钟

不了解原型继承,你就不了解JavaScript。原型极大地影响了对象在JavaScript中的工作方式。

原型继承在编码面试中经常被问到,因为这种知识是衡量你对JavaScript了解程度的一个指标。

本指南将帮助你轻松理解JavaScript中的原型继承。

1.简介

JavaScript只有原始类型,nullundefined 和对象。一个对象的大世界。在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 属性。你怎么能把catpet 联系起来呢?

继承可以帮助你!

2.原型对象

在JavaScript中,一个对象可以继承另一个对象的属性。继承这些属性的对象被称为原型

按照这个例子,你可以把pet 作为cat原型,然后它将继承 legs 的属性。

当使用对象字面意义创建一个对象时,你也可以使用特殊的属性__proto__ 来设置所创建对象的原型。

让我们使用__proto__ ,使pet 成为cat 的原型:

javascript

const pet = { legs: 4 };
const cat = { sound: 'Meow!', __proto__: pet };
cat.legs; // => 4

sound 属性,在另一边,是一个自己的属性,因为它直接定义在对象上。

Inherited property from the prototype in JavaScript

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)。

Own vs inherited property in JavaScript

如果你删除了自己的属性,那么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!

Prototypes chain in JavaScript 当访问一个属性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在对象的原型中寻找继承的属性,同时也在原型的原型中寻找,以此类推,在原型链中寻找。

虽然原型继承一开始看起来很笨拙,但当理解了它之后,你可以享受它的简单性和可能性。对象从对象继承属性--还有什么比这更简单的呢?

有关于原型继承的问题吗?请在下面的评论中提问!