有没有想过,为什么我们可以使用内置的方法,例如.length
,.split()
,.join()
我们的字符串,数组或对象?我们从未明确指定它们,它们来自何处?现在,不要说“这是JavaScript,没人知道,这是神奇的♂♂️”,这实际上是因为有一种称为原型继承的东西。它非常棒,而且您使用它的次数比您想象的要多!
我们经常必须创建许多相同类型的对象。假设我们有一个网站,人们可以浏览狗!
对于每只狗,我们都需要代表该狗的物体!🐕不用每次都编写一个新对象,我将使用构造函数(我知道您在想什么,稍后将介绍ES6类!),我们可以使用该关键字创建Dog 实例new
(本文为不过,我并不是真的要解释构造函数,因此,我不会对此进行过多讨论。
每只狗都有名字,品种,颜色和吠叫功能!
创建Dog
构造函数时,它不是我们创建的唯一对象。我们还自动创建了另一个对象,称为原型!默认情况下,此对象包含一个构造函数属性,Dog
在这种情况下,该属性只是对原始构造函数的引用。
prototype
Dog构造函数的属性是不可枚举的,这意味着当我们尝试访问对象的属性时,该属性不会显示。但是它仍然在那里!
好吧..为什么会有这个属性对象?首先,让我们创建一些要展示的狗。为简单起见,我将其称为dog1
和dog2
。dog1
是黛西,一个可爱的黑色拉布拉多犬!dog2
是杰克,无所畏惧的白人杰克罗素😎
让我们登录dog1
到控制台,并扩展其属性!
我们看到我们添加,如性能name
,breed
,color
,和bark
..但哇那是什么__proto__
性质!它是不可枚举的,这意味着当我们尝试获取对象的属性时,通常不会显示它。让我们扩展它!😃
哇,看起来就像Dog.prototype
物体!好吧,猜猜__proto__
是对Dog.prototype
对象的引用。这就是原型继承的全部内容:构造函数的每个实例都可以访问构造函数的原型!🤯
那么为什么这很酷?有时我们拥有所有实例共享的属性。例如bark
在这种情况下的函数:每个实例都完全相同,为什么每次创建新狗时都创建一个新函数,每次都消耗内存?相反,我们可以将其添加到Dog.prototype
对象中!🥳
每当我们尝试访问实例上的属性时,引擎都会首先在本地搜索以查看该属性是否在对象本身上定义。但是,如果找不到我们要访问的属性,引擎将沿着该__proto__
属性沿着原型链走下去!
现在这只是一个步骤,但是可以包含多个步骤!如果继续进行下去,您可能会注意到,在扩展__proto__
显示为的对象时,我没有包含一个属性Dog.prototype
。Dog.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
属性狗类,而不是现在的name
,breed
和color
。但是这些吉娃娃也可以做些特别的事情,它们的树皮很小。Woof!
吉娃娃不用说也可以说Small woof!
🐕
在扩展类中,我们可以使用super
关键字访问父类的构造函数。父类的构造函数期望的参数,在这种情况下,我们必须传递给super
:name
。
myPet
可以访问Chihuahua.prototype
和Dog.prototype
(并且自动访问Object.prototype
,因为Dog.prototype
是一个对象)。
由于Chihuahua.prototype
有smallBark
功能,Dog.prototype
有bark
功能,我们就可以同时访问smallBark
和bark
上myPet
!
现在,您可以想象,原型链不会永远持续下去。最终有一个原型等于null
的Object.prototype
对象:在这种情况下就是对象!如果我们尝试访问在本地或原型链上找不到的属性,undefined
则会返回。
尽管我在这里用构造函数和类解释了所有内容,但是将原型添加到对象的另一种方法是使用Object.create
方法。使用此方法,我们可以创建一个新对象,并可以确切指定该对象的原型!💪🏼
通过将现有对象作为参数传递给Object.create
方法,我们可以做到这一点。该对象是我们创建的对象的原型!
让我们记录me
刚刚创建的对象。
我们没有向该me
对象添加任何属性,它仅包含不可枚举的__proto__
属性!该__proto__
属性包含对我们定义为原型的person
对象的引用:该对象具有name
和age
属性。由于person
对象是对象,因此对象上的__proto__
属性值person
是Object.prototype
(但为了使其更容易阅读,我没有在gif中扩展该属性!)
希望您现在了解了为什么原型继承在JavaScript的奇妙世界中如此重要!如有疑问,请随时与我联系!😊