5 🎉👨‍👩‍👧‍👧 JavaScript Visualized: Prototypal Inheritance


5 🎉👨‍👩‍👧‍👧 JavaScript Visualized: Prototypal Inheritance

Cover image for 🎉👨‍👩‍👧‍👧 JavaScript Visualized: Prototypal Inheritance

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 对象?首先,让我们创建一些要展示的狗.为简单起见,我将它们分别称为 dog1dog2. 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,colorbark.. 但是,哇,__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,breedcolor.但是这些吉娃娃也可以做一些特别的事情,它们的树皮很小.吉娃娃还可以说 “小汪汪”,而不是说 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.prototypeDog.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 对象,它具有 nameage 属性.由于 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 的奇妙世界中如此重要!如果您有任何疑问,请随时与我联系! 😊


文章作者: Lydia Hallie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Lydia Hallie !
  目录