# 可视化的JavaScript —— (五)原型继承

【五】JavaScript Visualized: Prototypal Inheritance (opens new window)

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

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

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

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

image

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

9howj4i3zvlgun3svppp

Dog的构造函数属性prototype是不可枚举的,这意味着它不会显示出来,当我们试图访问该对象的属性。但它是存在的!

好的.. 为什么我们会有这个属性对象?首先,让我们创建一些我们想要展示的Dog。为简单起见,我将它们称为dog1dog2dog1是黛西,一只可爱的黑色拉布拉多!dog2是杰克,无所畏惧的白人杰克罗素😎

image

让我们输出dog1到控制台,并展开查看它的属性!

tt4yfoz8ckmxfofv3f9v

我们看到了我们添加的属性,比如name, breed, color, 和bark.. 但是哇,那个__proto__属性是什么!它是不可枚举的,这意味着当我们尝试获取对象的属性时,它通常不会出现。让我们展开它!

dye57pcku5cfaz0er60c

哇!它看起来和Dog.prototype一模一样。__proto__是对Dog.prototype对象的引用。这就是原型继承的全部内容:构造函数的每个实例都可以访问构造函数的原型!

t6kiav029gl2e0hv1xct

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

59nlnyqioosaowj09xn8

每当我们尝试访问实例上的属性时,引擎首先在局部作用域搜索以查看该属性是否在对象本身上定义。然而,如果它找不到我们试图访问的属性,引擎就会通过属性__proto__沿着原型链继续查找!

fabyyjot1s78mttyzzk8

现在这只是一个步骤,它可以包含多个步骤!如果您持续跟进,您可能已经注意到,当我展开__proto__显示Dog.prototype时. Dog.prototype本身是一个对象,这意味着它实际上是Object构造函数的一个实例!这意味着Dog.prototype它还包含一个__proto__属性,它指向Object.prototype!

8vk5w6loliot818f2lcd

最后,我们对所有内置方法的来源有了一个答案:它们在原型链上!

例如.toString()方法:

它是在dog1对象上局部定义的吗?嗯没有...

它是否定义在对象上dog1.__proto__引用的对象上,即Dog.prototype?也没有!

是否定义在Dog.prototype.__proto__引用的对象上,即Object.prototype?是的。

fpt5nndkbq5kau0nqeqj

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

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

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

qnbqubcipqjl5pb3i8ds

类的另一个好处是我们可以轻松扩展其他类。

假设我们要展示几只相同品种的狗狗,即吉娃娃!吉娃娃仍然是一只狗狗。为了让这个例子保持简单,我现在只将name属性传递给 Dog 类,而不是name,breedcolor。但是这些吉娃娃也可以做一些特别的事情,它们的吠声很小。除了说Woof!,吉娃娃还可以说Small woof!🐕

在扩展类中,我们可以使用super关键字访问父类的构造函数。父类的构造函数期望传给子类的参数,在这种情况下,我们必须传递给super:name

image

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

qija16dju8t5j1ksy0ps

由于Chihuahua.prototypesmallBark功能,Dog.prototypebark功能,我们就可以同时访问smallBarkbarkmyPet

现在你可以想象,原型链不会永远持续下去。最终有一个原型等于null的Object.prototype对象:在这种情况下的对象!如果我们尝试访问在局部作用域或原型链上找不到的属性,则会返回undefined。

image

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

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

image

让我们输出刚刚创建的对象me

6zzt8zpy85gtitxmpwi9

我们没有向me对象添加任何属性,它只包含不可枚举的__proto__属性!__proto__属性保存我们定义为原型对象的引用:person对象,它具有一个name和一个age属性。由于person也是一个对象,person对象上的__proto__属性值是Object.prototype(但为了更容易阅读,我没有在 gif 中展开该属性!)

【完】

上次更新: 1/8/2023, 9:08:35 AM