你有没有想过为什么咱们能够在字符串、数组或对象上应用诸如.length
、.split()
、.join()
这些内置办法呢?咱们素来没有明确指定过它们,它们到底是从哪里来的呢?当初别说“哈哈,没人晓得,这就是神奇的JavaScript????????♂️”。这实际上是因为一种叫做原型继承(prototypal inheritance)的玩意儿。它很棒,而且咱们用到它的次数比意识到它存在的次数要多得多!
咱们常常要创立很多雷同类型的对象。假如咱们有一个网站,在这个网站上,人们能够浏览狗!
对每一只狗,咱们都须要对象来示意它!???? 咱们用不着每次都写一个新对象,而是用一个结构器函数(我晓得你在想什么,稍后我将介绍ES6类!),用new
关键字创立Dog实例(不过,本文并非是要解释结构器函数,所以我不想谈太多)。
每只狗都有名字(name)、种类(breed)、色彩(color)以及一个bark()函数!
当咱们创立Dog
结构器函数时,它并不是咱们创立的惟一对象。咱们还主动创立了另一个对象,称为prototype(原型)!默认状况下,这个对象蕴含一个constructor属性,它只是对原始结构器函数的援用,在本例中是Dog
。
图1.当咱们创立一个结构器函数时,同时也创立了一个prototype对象。结构器的prototype有一个对原始结构器函数的援用。
Dog结构器函数上的prototype
属性是不可枚举的,也就是说,当咱们试图拜访对象的属性时,它是不会呈现。但它仍然存在!
好吧,那么为什么咱们会有这个属性对象呢?首先,咱们来创立一些咱们想展现的狗。为简略起见,我叫它们dog1
和dog2
。dog1
是Daisy,一只可恶的彩色拉布拉多犬!dog2
是Jack,一只无畏的红色杰克罗素犬!????

上面咱们把dog1
输入到控制台,并开展其属性!
咱们能够看到增加的属性,如name
、breed
、color
和bark
。然而__proto__
属性是什么玩意!它是不可枚举的,也就是说当咱们试图获取对象的该属性时,它通常不会呈现。上面咱们把它开展!????
哇哦,它看起来就像Dog.prototype
对象!你猜怎么着,__proto__
就是对Dog.prototype
对象的一个援用。这就是原型继承的目标:结构器的每个实例都能够拜访结构器的原型!????
图3.实例也蕴含一个__proto__属性,这是对实例的结构器的原型的援用,在本例中是Dog.prototype。
为什么这很酷呢?有时咱们有一些所有实例都共享的属性。比方本例中的bark
函数:它对每个实例都是完全相同的,那么与其每次创立一个新的dog时都创立一个新函数,每次都耗费内存,还不如将其增加到Dog.prototype
对象!????
图4.咱们能够通过将属性增加到所有实例都能够共享的原型上,而不是每次创立该属性的新正本。
每当咱们试图拜访实例上的属性时,引擎首先在本地搜寻,看看该属性是否在对象自身上定义。不过,如果找不到咱们要拜访的属性,那么引擎就会通过__proto__
属性沿着原型链遍历!
图5.当试图拜访一个对象上某个属性时,引擎首先在本地搜寻。而后,通过__proto__
属性沿着原型链遍历。
当初这只是一个步骤,但它能够蕴含几个步骤!如果持续往下看,咱们就可能会留神到,当开展__proto__
对象时,并没有蕴含一个显示Dog.prototype
的属性。Dog.prototype
自身是一个对象,也就是说它实际上是Object
结构器的一个实例!这意味着Dog.prototype
也蕴含一个__proto__
属性,这个属性是对Object.prototype
的一个援用!
最初,咱们就有了所有内置办法来自何方的答案:它们在原型链上!????
比方.toString()
办法。它是在dog1
对象上本地定义的吗?嗯,不是的。。它是定义在dog1.__proto__
的援用,即Dog.prototype
对象上的吗?也不是!它是定义在Dog.prototype.__proto__
的援用,即Object.prototype
对象上的吗?是的!????????
图6.原型链能够有几个步骤。比方,Dog.prototype自身是个对象,因此继承来自内置的Object.prototype的属性。
当初,咱们刚刚用过了结构器函数(function Dog() { ... }
),它依然是无效的JavaScript。不过,ES6实际上为结构器函数以及解决原型引入了一种更简略的语法:类!
类只是结构器函数的语法糖。其工作机制还是一样的!
咱们用class
关键字编写类。类有一个constructor
函数,它基本上就是咱们用ES5语法编写的结构器函数!咱们要增加到原型中的属性是在类主体自身上定义的。
图7.ES6引入了类,类是结构器函数的语法糖。
类的另一个益处是,咱们能够很容易地继承其余类。
假如咱们要展现几只雷同种类的狗,即吉娃娃狗(chihuahua)!不论咋样,吉娃娃仍然是狗。为简略起见,咱们当初只保留一个name
属性给Dog类。不过这些吉娃娃也能够做些特地的事件,它们的叫声很小(smallBark),它们的叫声不是Woof!
,而是Small woof!
。????
在继承的类中,咱们能够应用super
关键字拜访父类的结构器。父类的结构器冀望的参数,咱们必须传递给super
:在本例中是name
。myPet
既能够拜访Chihuahua.prototype
,又能够拜访Dog.prototype
(并且因为Dog.prototype
是个对象,又能够主动拜访Object.prototype
)。
图8.原型继承在类与ES5结构器上的工作机制是一样的。
因为Chihuahua.prototype
有smallBark
函数,而Dog.Prototype
有bark
函数,因而在myPet
上,咱们既能够拜访smallBark
,也能够拜访bark
!
当初咱们能够料想得到,原型链不会永远继续上来。最终有一个原型等于null
的对象:在本例中就是Object.prototype
对象!如果咱们尝试拜访在本地或原型链上找不到的属性,就会返回undefined
。

图9.咱们能够调用从被继承的类的继承的办法。原型链在__proto__的值为null的时候完结。
只管我在这里用结构器函数和类解释了将原型增加到对象的所有内容,然而将原型增加到对象还有一种办法,就是用Object.create()
办法。用这个办法,咱们能够创立一个新对象,并能够精确指定该对象的原型!????????
为此,咱们将已有对象作为参数传递给Object.create
办法。该对象就是咱们创立的对象的原型!
上面输入咱们刚刚创立的me
对象。
咱们没有向对象me
增加任何属性,它仅蕴含不可枚举的__proto__
属性!__proto__
属性援用了咱们定义为原型的对象:person
对象,它有一个name
和一个age
属性。因为person
对象是一个对象,因而person
对象上的__proto__
属性值就是Object.prototype
(不过为了使更容易浏览,我没有在gif上开展该属性!)。
心愿你当初理解了为什么原型继承在JavaScript的美好世界中如此重要!如有疑难,请随时与我分割!????
原文 by Lydia Hallie:https://dev.to/lydiahallie/javascript-visualized-prototypal-inheritance-47co
本文由博客一文多发平台 OpenWrite 公布!