有没有想过,为何咱们可使用内置的方法,例如.length,.split(),.join()咱们的字符串,数组或对象?咱们从未明确指定它们,它们来自何处?如今,不要说“这是JavaScript,没人知道,这是魔术🧚🏻♂️”,这其实是因为所谓的原型继承
。它很是棒,并且您使用它的次数比您想象的要多!javascript
咱们常常必须建立许多相同类型的对象。假设咱们有一个网站,人们能够浏览狗!java
对于每只狗,咱们都须要表明那只狗的对象!🐕我将使用构造函数(我知道您在想什么,稍后将介绍ES6类!),而不是每次都编写一个新对象。咱们可使用new建立Dog实例(本文并非真的要解释构造函数,所以,我不会对此进行过多讨论)。git
每只狗都有名字,品种,颜色和吠叫功能!github
建立Dog构造函数时,它不是咱们建立的惟一对象。咱们还自动建立了另外一个对象,称为原型(prototype
)!默认状况下,此对象包含一个构造函数(constructor
),该属性只是对原始构造函数的引用,在本例中是Dog。数组
Dog的prototype属性是不可枚举的,这意味着当咱们尝试访问对象的属性时,该属性不会显示。可是它仍然在那里!函数
好吧..为何会有这个属性对象?首先,让咱们建立一些咱们想要展现的狗。为简单起见,我将其称为dog1和dog2。dog1是黛西,一个可爱的黑色拉布拉多犬!dog2是杰克,无所畏惧的白人杰克罗素😎oop
咱们到控制台打印dog1,并展开其属性!post
咱们看到咱们添加,如性能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的奇妙世界中如此重要!若有疑问,请随时与我联系!😊