Ever wondered why we can use built-in methods such as .length
, .split()
, .join()
on our strings, arrays, or objects? We never explicitly specified them, where do they come from? Now don’t say”It’s JavaScript lol no one knows, it’s magic 🧚🏻♂️”, it’s actually because of something called prototypal inheritance. It’s pretty awesome, and you use it more often than you realize!
有没有想过为什么我们可以在字符串,数组或对象上使用诸如
.length
,.split()
,.join()
之类的内置方法?我们从未明确指定它们,它们来自何处?现在不要说 “这是 JavaScript 没有人知道,这是神奇的 🧚🏻♂️”,这实际上是由于所谓的 原型继承.它非常棒,而且您使用它的次数比您想像的要多!
We often have to create many objects of the same type. Say we have a website where people can browse dogs!
For every dog, we need object that represents that dog! 🐕 Instead of writing a new object each time, I’ll use a constructor function (I know what you’re thinking, I’ll cover ES6 classes later on!) from which we can create Dog instances using the new
keyword (this post isn’t really about explaining constructor functions though, so I won’t talk too much about that).
我们经常必须创建许多相同类型的对象.假设我们有一个网站,人们可以浏览狗! 对于每只狗,我们都需要代表那只狗的物体! 🐕 不用每次都编写一个新对象,我将使用构造函数(我知道您在想什么,稍后将介绍 ES6 类!),我们可以使用
new
关键字从中创建 Dog ** 实例 **(本文并不是真正要解释构造函数,因此,我不会对此进行过多讨论.)
Every dog has a name, a breed, a color, and a function to bark!
每只狗都有名字,品种,颜色和吠叫功能!
function Dog(name, breed, color) {
this.name = name;
this.breed = breed;
this.color = color;
this.bark = function () {
return 'Woof!';
};
}
When we created the Dog
constructor function, it wasn’t the only object we created. Automatically, we also created another object, called the prototype! By default, this object contains a constructor property, which is simply a reference to the original constructor function, Dog
in this case.
当我们创建
Dog
构造函数时,它并不是我们创建的唯一对象.自动地,我们还创建了另一个对象,称为 _原型_!默认情况下,此对象包含一个 构造函数 属性,该属性只是对原始构造函数(在这种情况下为Dog
)的引用.
The prototype
property on the Dog constructor function is non-enumerable, meaning that it doesn’t show up when we try to access the objects properties. But it’s still there!
Okay so.. Why do we have this property object? First, let’s create some dogs that we want to show. To keep it simple, I’ll call them dog1
and dog2
. dog1
is Daisy, a cute black Labrador! dog2
is Jack, the fearless white Jack Russell 😎
Dog 构造函数上的
prototype
属性是不可枚举的,这意味着当我们尝试访问对象属性时,该属性不会显示.但它仍然在那里! 好吧.. 为什么会有这个 property 对象?首先,让我们创建一些要展示的狗.为简单起见,我将它们分别称为dog1
和dog2
.dog1
是黛西,一个可爱的黑色拉布拉多犬!dog2
是杰克,无所畏惧的白色杰克罗素㹴 😎
const dog1 = new Dog('Daisy', 'Labrador', 'black');
const dog2 = new Dog('Jack', 'Jack Russell', ' white');
Let’s log dog1
to the console, and expand its properties!
让我们将
dog1
输出到控制台,并展开其属性!
We see the properties we added, like name
, breed
, color
, and bark
.. but woah what is that __proto__
property! It’s non-enumerable, meaning that it usually doesn’t show up when we try to get the properties on the object. Let’s expand it! 😃
我们看到了我们添加的属性,例如
name
,breed
,color
和bark
.. 但是,哇,__proto__
属性是什么!它是不可枚举的,这意味着当我们尝试获取对象的属性时,通常不会显示它.让我们展开它! 😃
Woah it looks exactly like the Dog.prototype
object! Well guess what, __proto__
is a reference to the Dog.prototype
object. This is what prototypal inheritance is all about: each instance of the constructor has access to the prototype of the constructor! 🤯
哇,看起来就像
Dog.prototype
对象一样!好吧,猜猜__proto__
是对Dog.prototype
对象的引用.这就是原型继承的全部内容:构造函数的每个实例都可以访问构造函数的原型! 🤯
So why is this cool? Sometimes we have properties that all instances share. For example the bark
function in this case: it’s the exact same for every instance, why create a new function each time we create a new dog, consuming memory each time? Instead, we can add it to the Dog.prototype
object! 🥳
那么为什么这很酷?有时我们拥有所有实例共享的属性.例如,在这种情况下,
bark
函数:每个实例都完全相同,为什么每次创建新狗时都创建一个新函数,却每次都要消耗内存?相反,我们可以将其添加到Dog.prototype
对象中! 🥳
Whenever we try to access a property on the instance, the engine first searches locally to see if the property is defined on the object itself. However, if it can’t find the property we’re trying to access, the engine walks down the prototype chain through the __proto__
property!
每当我们尝试访问实例上的属性时,引擎都会首先在本地搜索以查看该属性是否在对象本身上定义.但是,如果找不到我们要访问的属性,则引擎擎通过
__proto__
属性沿着原型链走下去!
Now this is just one step, but it can contain several steps! If you followed along, you may have noticed that I didn’t include one property when I expanded the __proto__
object showing Dog.prototype
. Dog.prototype
itself is an object, meaning that it’s actually an instance of the Object
constructor! That means that Dog.prototype
also contains a __proto__
property, which is a reference to Object.prototype
!
现在这只是一个步骤,但可以包含多个步骤!如果您照做的话,您可能会注意到,当我展开显示
Dog.prototype
的__proto__
对象时,我没有包含一个属性.Dog.prototype
本身是一个对象,这意味着它实际上是Object
构造函数的一个实例!这意味着Dog.prototype
还包含__proto__
属性,该属性是对Object.prototype
的引用!
Finally, we have an answer to where all the built-in methods come from: they’re on the prototype chain! 😃
For example the .toString()
method. Is it defined locally on the dog1
object? Hmm no.. Is it defined on the object dog1.__proto__
has a reference to, namely Dog.prototype
? Also no! Is it defined on the object Dog.prototype.__proto__
has a reference to, namely Object.prototype
? Yes! 🙌🏼
最后,我们对所有内置方法的来源都有一个解析:它们位于原型链上! 😃 例如
.toString()
方法.它是在dog1
对象上本地定义的吗?嗯.是否在对象dog1.__ proto__
上定义了它的引用,即Dog.prototype
?也没有!它是否在对象Dog.prototype.__ proto__
所引用的对象Object.prototype
上定义?是的! 🙌🏼
Now, we’ve just been using constructor functions (function Dog() { ... }
), which is still valid JavaScript. However, ES6 actually introduced an easier syntax for constructor functions and working with prototypes: classes!
现在,我们一直在使用构造函数 (
function Dog() { ... }
),该函数仍然是有效的 JavaScript.但是,ES6 实际上为构造函数和原型使用了一种更简单的语法:类!
Classes are only syntactical sugar for constructor functions. Everything still works the same way!
We write classes with the class
keyword. A class has a constructor
function, which is basically the constructor function we wrote in the ES5 syntax! The properties that we want to add to the prototype, are defined on the classes body itself.
我们使用
class
关键字编写类.一个类具有构造函数,基本上就是我们用 ES5 语法编写的构造函数!我们要添加到原型的属性是在类主体本身上定义的.
Another great thing about classes, is that we can easily extend other classes.
Say that we want to show several dogs of the same breed, namely Chihuahuas! A chihuahua is (somehow… 😐) still a dog. To keep this example simple, I’ll only pass the name
property to the Dog class for now instead of name
, breed
and color
. But these chihuahuas can also do something special, they have a small bark. Instead of saying Woof!
, a chihuahua can also say Small woof!
🐕
In an extended class, we can access the parent class’constructor using the super
keyword. The arguments the parent class’ constructor expects, we have to pass to super
: name
in this case.
关于类的另一个好处是,我们可以轻松扩展其他类. 假设我们要展示几只相同品种的狗,即奇瓦瓦狗!奇瓦瓦狗(某种程度上……)仍然是一只狗.为了使这个示例简单,我现在仅将
name
属性传递给 Dog 类,而不是name
,breed
和color
.但是这些吉娃娃也可以做一些特别的事情,它们的树皮很小.吉娃娃还可以说 “小汪汪”,而不是说Small woof!
🐕 在扩展类中,我们可以使用super
关键字访问父类的构造函数.父类的构造函数期望的参数,在这种情况下,我们必须传递给super
:name
.
myPet
has access to both the Chihuahua.prototype
and Dog.prototype
(and automatically Object.prototype
, since Dog.prototype
is an object).
myPet
可以访问Chihuahua.prototype
和Dog.prototype
(并自动访问Object.prototype
,因为Dog.prototype
是对象).
Since Chihuahua.prototype
has the smallBark
function, and Dog.prototype
has the bark
function, we can access both smallBark
and bark
on myPet
!
Now as you can imagine, the prototype chain doesn’t go on forever. Eventually there’s an object which prototype is equal to null
: the Object.prototype
object in this case! If we try to access a property that’s nowhere to be found locally or on the prototype chain, undefined
gets returned.
由于
Chihuahua.prototype
具有smallBark
功能,而Dog.prototype
具有bark
功能,因此我们可以在myPet
上访问smallBark
和树皮! 现在,您可以想象,原型链不会永远持续下去.最终有一个原型等于null
的对象:在这种情况下为Object.prototype
对象!如果我们尝试访问在本地或原型链上找不到的属性,则会返回undefined
.
Although I explained everything with constructor functions and classes here, another way to add prototypes to objects is with the Object.create
method. With this method, we create a new object, and can specify exactly what the prototype of that object should be! 💪🏼
We do this, by passing an existing object as argument to the Object.create
method. That object is the prototype of the object we create!
尽管我在这里用构造函数和类解释了所有内容,但是将原型添加到对象的另一种方法是使用
Object.create
方法.使用此方法,我们可以创建一个新对象,并可以精确指定该对象的原型! 💪🏼 为此,我们将现有对象作为参数传递给Object.create
方法.该对象是我们创建的对象的原型!
Let’s log the me
object we just created.
We didn’t add any properties to the me
object, it simply only contains the non-enumerable __proto__
property! The __proto__
property holds a reference to the object we defined as the prototype: the person
object, which has a name
and an age
property. Since the person
object is an object, the value of the __proto__
property on the person
object is Object.prototype
(but to make it a bit easier to read, I didn’t expand that property in the gif!)
我们没有向
me
对象添加任何属性,它仅包含不可枚举的__proto__
属性!__proto__
属性持有对我们定义为原型的对象的引用:person
对象,它具有name
和age
属性.由于person
对象是一个对象,因此person
对象上__proto__
属性的值是Object.prototype
(但为了使其更易于阅读,我没有在 gif 中扩展该属性!)
Hopefully, you now understand why prototypal inheritance is such an important feature in the wonderful world of JavaScript! If you have questions, feel free to reach out to me! 😊
希望您现在了解为什么原型继承在 JavaScript 的奇妙世界中如此重要!如果您有任何疑问,请随时与我联系! 😊