javascript图解之原型链和原型继承、类

本文深入解析JavaScript中的原型继承机制,探讨构造函数、原型对象及原型链的作用,揭示内置方法的来源,以及如何利用原型实现属性和方法的共享。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


有没有想过为什么我们可以在字符串、数组或对象上使用诸如 .length 、 .split() 、 .join() 之类的内置方法?我们从来没有明确指出它们,它们来自哪里?现在不要说“这是 JavaScript 内部实现,哈哈没人知道,这是魔法”,这实际上是因为一种叫做原型继承的东西。太棒了,而且你用得比你意识到的还要多!
我们经常要创建许多相同类型的对象。假设我们有一个人们可以浏览关于 Dog 的网站!
对于每一只狗,我们需要一个代表它的对象!🐕 我不会每次都编写一个新对象,而是使用构造函数(我知道你在想什么,稍后我将介绍 ES6 类!)从中我们可以使用new关键字创建 Dog 实例(这篇文章并不是真的要解释构造器函数,所以我不会过多地讨论这个问题)。
每只 dog 都有 name 、 breed 、 color 和 bark 的属性功能!

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

Dog 构造函数的 prototype 属性是不可枚举的,这意味着当我们试图访问对象属性时它不会出现。但它还在那里!
好吧,那么。。为什么我们有这个 prototype 对象?首先,让我们创造一些我们想展示的 dog 实例。为了简单起见,我将它们称为dog1 和dog2 。 dog1 是黛西,一只可爱的黑色拉布拉多犬! dog2 是杰克,无畏的白杰克罗素😎

让我们将 dog1 记录到控制台,并展开查看其属性!

 我们可以看到我们添加的属性,如 name 、 breed 、 color 和 bark 。但还有个 __proto__ 那是什么东西!它是不可枚举的,这意味着当我们试图获取对象的属性时,它通常不会出现。让我们打开查看它!😃

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

 为什么这么酷?有时我们拥有所有实例共享的属性。例如这个例子中的 bark 函数:对于每个实例来说都是一样的,为什么每次创建一个新的函数,每次都消耗内存?相反,我们可以将其添加到 Dog.prototype 对象中!🥳

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

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

最后,我们找到了所有内置方法的来源:它们都在原型链上!😃
例如 .toString() 方法。它是在 dog1 对象上本地定义的吗?嗯,不。。它是否定义在对象 dog1.__proto__ 有一个引用,即Dog.prototype ?也不是!它是否定义在对象 Dog.prototype.__proto__ 的一个引用,即 Object.prototype ?对!🙌🏼

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

类只是构造函数的语法糖。一切照旧!

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

类的另一个优点是,我们可以轻松地扩展其他类。
说我们要展示几个同一品种的 dog ,即吉娃娃!吉娃娃(不知怎么地。。。😐) 还是只狗。为了简单起见,我现在只将 name 属性传递给 Dog 类,而不是 name 、 breed 和 color 。但是这些吉娃娃也可以做一些特别的事情,它们有一个 smallBark 。而不是说bark !,一只吉娃娃也能 smallBark !🐕
在扩展类中,我们可以使用 super 关键字访问父类的构造函数。父类构造函数需要的参数,在本例中必须传递给 super:name。

myPet 可以访问 Chihuahua.prototype 和 Dog.prototype (以及 automatically Object.prototype ,因为 Dog.prototype 是一个对象)。

 因为 Chihuahua.prototype 有 smallBark 功能,而 Dog.prototype 有 bark 功能,所以我们可以访问 myPet上 的 smallBark 和bark !
现在你可以想象,原型链不会永远延续下去。最终会有一个 prototype 等于 null 的对象:本例中 的Object.prototype 对象!如果我们试图访问在本地或原型链上找不到的属性,则返回 undefined 。

尽管我在这里用构造函数和类解释了一切,但是向对象添加原型的另一种方法是使用 Object.create 方法。通过这个方法,我们创建了一个新的对象,并且可以指定该对象的原型应该是什么!💪🏼
我们通过将现有对象作为参数传递给 Object.create 方法来实现这一点。那个参数对象就是我们创造的对象的原型

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

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


希望您现在能够理解为什么原型继承在 JavaScript 的奇妙世界中是如此重要的特性!如果你有问题,请随时联系我!😊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值