JavaScript 可视化:原型继承

2025-05-25

JavaScript 可视化:原型继承

.length有没有想过,为什么我们可以在字符串、数组或对象上使用诸如、 之.split()的内置方法.join()?我们从未明确指定过它们,它们从何而来?别说“这是 JavaScript,哈哈,没人知道,这太神奇了🧚🏻‍♂️”,这实际上是因为一种叫做原型继承 的东西。它非常棒,而且你使用它的频率比你意识到的还要高!

我们经常需要创建许多相同类型的对象。假设我们有一个网站,人们可以在这里浏览狗狗!

对于每只狗,我们都需要一个代表它的对象!🐕 我不会每次都写一个新对象,而是使用构造函数(我知道你在想什么,我稍后会介绍 ES6 类!),我们可以使用关键字从中创建 Dog实例new(不过这篇文章并不是真正解释构造函数,所以我就不多说了)。

每只狗都有名字、品种、颜色和吠叫的功能!

当我们创建Dog构造函数时,它并不是我们创建的唯一对象。我们还自动创建了另一个对象,称为原型!默认情况下,此对象包含一个构造函数属性,在本例中,它只是对原始构造函数的引用Dog

Dog 构造函数上的属性prototype是不可枚举的,这意味着当我们尝试访问对象属性时,它不会显示出来。但它仍然在那里!

好吧,那么……我们为什么要有这个属性对象呢?首先,让我们创建一些想要展示的狗狗。为了简单起见,我把它们命名为dog1……是 Daisy,一只可爱的黑色拉布拉多犬!是 Jack,一只勇敢无畏的白色杰克罗素梗犬dog2😎dog1dog2

让我们登录dog1到控制台,并扩展其属性!

我们看到了添加的属性,比如namebreedcolorbark.. 但哇,这是什么__proto__属性!它是不可枚举的,这意味着当我们尝试获取对象的属性时,它通常不会显示出来。让我们扩展它吧!😃

哇,它看起来和那个对象一模一样Dog.prototype!你猜怎么着,它其实__proto__是一个对象的引用Dog.prototype。这就是原型继承的精髓:构造函数的每个实例都可以访问构造函数的原型!🤯

那么为什么这很酷呢?有时我们有一些所有实例都共享的属性。例如bark本例中的函数:它对于每个实例都是完全相同的,为什么每次创建新狗时都要创建一个新函数,每次都消耗内存呢?相反,我们可以将它添加到Dog.prototype对象中!🥳

每当我们尝试访问实例上的属性时,引擎首先会在本地搜索该属性是否在对象本身上定义。但是,如果找不到我们要访问的属性,引擎就会沿着原型链向下查找该__proto__属性!

现在这只是一个步骤,但它可以包含多个步骤!如果你继续往下看,你可能已经注意到,当我展开__proto__对象显示时,我没有包含一个属性Dog.prototypeDog.prototype本身就是一个对象,这意味着它实际上是构造函数的一个实例Object!这意味着Dog.prototype还包含一个__proto__属性,它是对 的引用Object.prototype

终于,我们终于知道所有内置方法的来源了:它们都在原型链上!😃

比如.toString()方法。它是在对象上本地定义的吗dog1?嗯,不是。它是dog1.__proto__在引用它的对象上定义的吗Dog.prototype?同样不是!它是在Dog.prototype.__proto__引用它的对象上定义的吗Object.prototype?是的!🙌🏼

现在,我们刚刚使用了构造函数(function Dog() { ... }),这仍然是有效的 JavaScript。然而,ES6 实际上为构造函数和原型引入了一种更简单的语法:类!

类只是构造函数的语法糖。一切仍然以相同的方式工作!

我们用关键字来编写类class。类有一个constructor函数,它基本上就是我们在 ES5 语法中编写的构造函数!我们想要添加到原型的属性,是在类主体本身中定义的。

类的另一个优点是,我们可以轻松地扩展其他类。

假设我们想展示几只同一品种的狗,也就是吉娃娃!吉娃娃(不知何故……😐)仍然是一只狗。为了简化示例,我name现在只将 属性传递给 Dog 类,而不是namebreedcolor。但这些吉娃娃还可以做一些特别的事情,它们会轻轻地叫。Woof!吉娃娃不仅可以说 ,还可以说Small woof!🐕

在扩展类中,我们可以使用关键字 : 访问父类的构造函数。在本例中super,我们必须将父类构造函数所需的参数传递给super: 。name

myPet可以访问Chihuahua.prototypeDog.prototype(并且自动访问Object.prototype,因为Dog.prototype是对象)。

由于Chihuahua.prototype具有smallBark函数,并且Dog.prototype具有bark函数,我们可以同时访问smallBarkbarkmyPet

现在你可以想象,原型链不会永远延伸。最终会有一个原型等于的对象nullObject.prototype在本例中就是这个对象!如果我们尝试访问一个在本地或原型链上都找不到的属性,undefined就会被返回。


虽然我在这里解释了构造函数和类的全部内容,但另一种为对象添加原型的方法是使用Object.create方法。通过这个方法,我们可以创建一个新对象,并可以精确指定该对象的原型应该是什么!💪🏼

我们通过将一个现有对象作为参数传递给Object.create方法来实现这一点。该对象是我们创建的对象的原型!

让我们记录me刚刚创建的对象。

我们没有给me对象添加任何属性,它只是简单地包含一个不可枚举的__proto__属性!该__proto__属性保存着我们定义为原型的对象的引用:该person对象拥有一个name和一个age属性。由于该对象是一个对象,因此该对象上person该属性的值就是(但为了更容易阅读,我没有在动图中添加该属性!)__proto__personObject.prototype


希望你现在明白了为什么原型继承在精彩的 JavaScript 世界中如此重要!如有任何疑问,请随时联系我!😊

推特 👩🏽‍💻 Instagram 💻GitHub 💡 LinkedIn 📷 YouTube 💌电子邮件
文章来源:https://dev.to/lydiahallie/javascript-visualized-prototypal-inheritance-47co
PREV
🚀⚙️ JavaScript 可视化:JavaScript 引擎
NEXT
⭐️🎀 JavaScript 可视化:Promises 和 Async/Await