关于javascript:图解JavaScript原型继承

76次阅读

共计 3094 个字符,预计需要花费 8 分钟才能阅读完成。


你有没有想过为什么咱们能够在字符串、数组或对象上应用诸如 .length.split().join() 这些内置办法呢?咱们素来没有明确指定过它们,它们到底是从哪里来的呢?当初别说“哈哈,没人晓得,这就是神奇的 JavaScript????????‍♂️”。这实际上是因为一种叫做原型继承(prototypal inheritance)的玩意儿。它很棒,而且咱们用到它的次数比意识到它存在的次数要多得多!

咱们常常要创立很多雷同类型的对象。假如咱们有一个网站,在这个网站上,人们能够浏览狗!

对每一只狗,咱们都须要对象来示意它!???? 咱们用不着每次都写一个新对象,而是用一个结构器函数(我晓得你在想什么,稍后我将介绍 ES6 类!),用 new 关键字创立 Dog实例(不过,本文并非是要解释结构器函数,所以我不想谈太多)。

每只狗都有名字(name)、种类(breed)、色彩(color)以及一个 bark()函数!


当咱们创立 Dog 结构器函数时,它并不是咱们创立的惟一对象。咱们还主动创立了另一个对象,称为 prototype(原型)!默认状况下,这个对象蕴含一个constructor 属性,它只是对原始结构器函数的援用,在本例中是 Dog

图 1. 当咱们创立一个结构器函数时,同时也创立了一个 prototype 对象。结构器的 prototype 有一个对原始结构器函数的援用。

Dog 结构器函数上的 prototype 属性是不可枚举的,也就是说,当咱们试图拜访对象的属性时,它是不会呈现。但它仍然存在!

好吧,那么为什么咱们会有这个属性对象呢?首先,咱们来创立一些咱们想展现的狗。为简略起见,我叫它们 dog1dog2dog1是 Daisy,一只可恶的彩色拉布拉多犬!dog2是 Jack,一只无畏的红色杰克罗素犬!????



上面咱们把 dog1 输入到控制台,并开展其属性!

咱们能够看到增加的属性,如 namebreedcolorbark。然而 __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.prototypesmallBark函数,而 Dog.Prototypebark函数,因而在 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 公布!

正文完
 0