图解JavaScript原型继承

image.png
<br />你有没有想过为什么我们可以在字符串、数组或对象上使用诸如.length.split().join()这些内置方法呢?我们从来没有明确指定过它们,它们到底是从哪里来的呢?现在别说“哈哈,没人知道,这就是神奇的JavaScript🧚🏻‍♂️”。这实际上是因为一种叫做原型继承(prototypal inheritance)的玩意儿。它很棒,而且我们用到它的次数比意识到它存在的次数要多得多!

我们经常要创建很多相同类型的对象。假设我们有一个网站,在这个网站上,人们可以浏览狗!

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

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

image.png
<br />当我们创建Dog构造器函数时,它并不是我们创建的唯一对象。我们还自动创建了另一个对象,称为prototype(原型)!默认情况下,这个对象包含一个constructor属性,它只是对原始构造器函数的引用,在本例中是Dog。<br />
p1.gif
<br />图1.当我们创建一个构造器函数时,同时也创建了一个prototype对象。构造器的prototype有一个对原始构造器函数的引用。

Dog构造器函数上的prototype属性是不可枚举的,也就是说,当我们试图访问对象的属性时,它是不会出现。但它依然存在!

好吧,那么为什么我们会有这个属性对象呢?首先,我们来创建一些我们想展示的狗。为简单起见,我叫它们dog1dog2dog1是Daisy,一只可爱的黑色拉布拉多犬!dog2是Jack,一只无畏的白色杰克罗素犬!😎

image.png
<br /><br />下面我们把dog1输出到控制台,并展开其属性!<br />
p2.gif
<br />我们可以看到添加的属性,如namebreedcolorbark。但是__proto__ 属性是什么玩意!它是不可枚举的,也就是说当我们试图获取对象的该属性时,它通常不会出现。下面我们把它展开!😃<br />
p3.gif
<br />哇哦,它看起来就像Dog.prototype对象!你猜怎么着,__proto__就是对Dog.prototype对象的一个引用。这就是原型继承的目的:构造器的每个实例都可以访问构造器的原型!🤯<br />
p4.gif
<br />图3.实例也包含一个proto属性,这是对实例的构造器的原型的引用,在本例中是Dog.prototype。<br />为什么这很酷呢?有时我们有一些所有实例都共享的属性。比如本例中的bark函数:它对每个实例都是完全相同的,那么与其每次创建一个新的dog时都创建一个新函数,每次都消耗内存,还不如将其添加到Dog.prototype对象!🥳<br />
image.gif
<br />图4.我们可以通过将属性添加到所有实例都可以共享的原型上,而不是每次创建该属性的新副本。<br />每当我们试图访问实例上的属性时,引擎首先在本地搜索,看看该属性是否在对象本身上定义。不过,如果找不到我们要访问的属性,那么引擎就会通过__proto__属性沿着原型链遍历!<br />
image.gif
<br />图5.当试图访问一个对象上某个属性时,引擎首先在本地搜索。然后,通过__proto__属性沿着原型链遍历。<br />现在这只是一个步骤,但它可以包含几个步骤!如果继续往下看,我们就可能会注意到,当展开__proto__对象时,并没有包含一个显示Dog.prototype的属性。Dog.prototype本身是一个对象,也就是说它实际上是Object构造器的一个实例!这意味着Dog.prototype也包含一个__proto__属性,这个属性是对Object.prototype的一个引用!<br />
p7.gif
<br />最后,我们就有了所有内置方法来自何方的答案:它们在原型链上!😃

比如.toString()方法。它是在dog1对象上本地定义的吗?嗯,不是的。。它是定义在dog1.__proto__的引用,即Dog.prototype对象上的吗?也不是!它是定义在Dog.prototype.__proto__的引用,即Object.prototype对象上的吗?是的!🙌🏼<br />

<br />图6.原型链可以有几个步骤。比如,Dog.prototype本身是个对象,因而继承来自内置的Object.prototype的属性。<br />现在,我们刚刚用过了构造器函数(function Dog() { ... }),它仍然是有效的JavaScript。不过,ES6实际上为构造器函数以及处理原型引入了一种更简单的语法:类!

类只是构造器函数的语法糖。其工作机制还是一样的!

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

p9.gif
<br />图7.ES6引入了类,类是构造器函数的语法糖。<br />类的另一个好处是,我们可以很容易地继承其他类。

假设我们要展示几只相同品种的狗,即吉娃娃狗(chihuahua)!不管咋样,吉娃娃依然是狗。为简单起见,我们现在只保留一个name属性给Dog类。不过这些吉娃娃也可以做些特别的事情,它们的叫声很小(smallBark),它们的叫声不是Woof!,而是Small woof!。🐕

在继承的类中,我们可以使用super关键字访问父类的构造器。父类的构造器期望的参数,我们必须传递给super:在本例中是name。<br />

image.png
<br />myPet既可以访问Chihuahua.prototype,又可以访问Dog.prototype(并且由于Dog.prototype是个对象,又可以自动访问Object.prototype)。<br />
p10.gif
<br />图8.原型继承在类与ES5构造器上的工作机制是一样的。<br />由于Chihuahua.prototypesmallBark函数,而Dog.Prototypebark函数,因此在myPet上,我们既可以访问smallBark,也可以访问bark

现在我们可以料想得到,原型链不会永远持续下去。最终有一个原型等于null的对象:在本例中就是Object.prototype对象!如果我们尝试访问在本地或原型链上找不到的属性,就会返回undefined。<br />

p11.gif
<br />图9.我们可以调用从被继承的类的继承的方法。原型链在proto的值为null的时候结束。<br />尽管我在这里用构造器函数和类解释了将原型添加到对象的所有内容,但是将原型添加到对象还有一种方法,就是用Object.create()方法。用这个方法,我们可以创建一个新对象,并可以准确指定该对象的原型!💪🏼

为此,我们将已有对象作为参数传递给Object.create方法。该对象就是我们创建的对象的原型!<br />

image.png
<br />下面输出我们刚刚创建的me对象。<br />
p12.gif

我们没有向对象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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349