JavaScript 修炼之路 --- 原型对象与原型链(以小见大)

210 阅读8分钟

写在开头:
个人“才疏学浅”,小白一枚,下述内容均为学习过程中的笔记总结与个人感悟,若有错误之处敬请斧正

关于标题:标题取为修炼之路一是个人曾经是修仙类小说爱好者,二是偶然间看到过杨逸飞大神所著JavaScript百炼成仙一书有感而发

关于本片文章:作本文的初衷意在记录个人学习理解原型链相关内容的过程,同时也希望通过自己的理解,将其输出出来,以供各位同仁共同学习,因此如若文中出现有漏洞或者错误,还请大家及时指正,以防误导后来者,在此聊表感谢!


开篇之言

疑惑

在学习到javascript的构造函数时,接触到了原型对象以及原型链的内容,在学习过程中,始终对这个原型链的指向问题抱有很大的疑惑。一个对象、一个函数他的原型链到底应该指向谁?其原型对象又到底是什么?为什么有些时候对象中有些方法明明没有添加为何能够使用?

图片.png

逛论坛摸鱼顺带解决下问题

于是乎,我就带着这些一丢丢丢丢丢丢丢丢的问题,摸鱼半天,终于薅回了几根掉下来的头发,也算是理解了一点点原型链的来由了

话不多说,来,笔墨纸砚伺候!🤔

概念

原型对象(或称原型)--- prototype

MDN:原型是一种在开发生命周期的早期显示应用程序或产品的外观和行为的模型。

说人话就是:原型对象就是一种模版,这种模版具有继承性,当你创建了一个对象(此处注意:万物皆对象,并不单指{}!),就会产生一个哲学问题:即我从哪里来?而这个继承性就让这个对象能够拥有来处的模板的方法!

暂时还没看懂?没关系,我们接着向下看,后面将会详细解释这个哲学问题

原型链 --- __ proto __

注:此处可能有兼容性的说法,但与今天主题无关,暂以__proto__来使用,并且了解到其在浏览器控制台上体现为[[prototype]]即可!

MDN未做具体概念陈述,而是放在了继承与原型链一章中,其大致概念也就是通过对对象调用__proto__方法来查找这个对象从哪里来,即追根溯源!

说人话就是:你创建了背着我从哪里来?的哲学问题的对象,会以__proto__的方式来调查,最后将会得到这个对象的祖宗十八代的关系网

继承与原型链

以下是MDN中的关于继承与原型链的描述

对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 __ proto __ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__ proto __ ),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

尽管这种原型继承通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。

由此,我们可以知道原型链的存在其实就是为了继承,在我们创建一个对象时,这个对象可能有一些自己的私有属性或者方法,但仍然有很多属性和方法是在其他的对象中是相同的。

以小见大,破解原型

例如我们可以看下面这个例子:

let arr = [1,2,3,4]; //创建一个数组对象

console.dir(arr); // 查看这个数组对象的属性和方法,打印结果见下图1-1

arr.push(5); // 使用push方法给arr添加内容

console.log(arr); //此处打印[1,2,3,4,5]

图片.png

我们知道数组的数据类型其实是对象'object'(此处有迷惑的同学可以使用typeof arr 来进行验证),也就是说数组其实就是对象的一种特殊情况,它相比较于普通的对象而言,它具有有序性,可以通过角标--“即有序的属性”--去访问我们的数组里的内容,那么同样的他作为一种特殊的对象,那么必然也会具有可使用的方法。

我们还知道,一个对象,只有你具有这个属性,或者具有这个方法,你才能让这个对象对其进行访问或使用,当这个对象中不存在你想访问或者想用的方法时,系统将会返回undefined或者报错说明你访问的方法不是一个函数。

那么问题来了,很明显,在图1-1中我们打印了arr这个对象,其中我们能见到的只有0-4的内容以及一个私有属性length,以及暂时还不知道是个啥的[[prototype]],我们并未见到push这个方法的出现,那为何arr却能够使用呢?

我想大家应该已经猜到了,答案应该就在这个暂时还不知道是个啥的[[prototype]]的里面!

来,我们来看看这个里面到底有些什么,上图1-2!

图片.png

从图中我们可以看出来,[[prototype]]展开以后有一系列的方法,而这些方法是从Array这个构造函数里来的,此时我们可以知道__proto__(即[[prototype]])的作用就是将你创建的这个arr这个数组对象指向了他的构造函数Array的prototype,因此arr可以使用Array的prototype里的方法,这也就是javascript的继承表现之一。

为了再次验证,我们来打印一下arr的构造函数Array,看看他的内部具体有些什么:

图片.png

从图1-3我们可以看到,Array 内部包含有蓝色框框内的私有属性(也就是说,这些写在prototype外部的属性和方法,是arr无法访问和使用的)、红色框框内的prototype(原型对象,这个里面的东西就是arr能够继承使用的)、红色框框内的[[protype]](说明Array也是具有原型链的,其链接到了function的prototype)。[[Scopes]] 这个不是今天的主题内容,在此省略,有兴趣的朋友可自行查阅。

其实,了解到上面这些以后我们,就大致清楚了,js使用__proto__可以让我们的对象具有其父元素(暂且这么叫)中的prototype的属性和方法。以上内容图解如下(构造函数的后续链接内容就不做图一一展示了,同时也建议大家自行打印查看后续__proto__链接内容来验证!):

图片.png

结论

如上图,如果你比较细心,一定可以发现一个问题,就是,arr没有prototype,而arr、Array、Function、Object、prototype都具有__proto__!

因此总结以下结论:

1.prototype只有函数才具有

2.__ proto __ :只要是对象,都具有原型链(函数从某种意义上讲也是对象!)

3.__ proto __ 的指向是上一级父元素的prototype!

4.Object的prototype最终的__proto__指向是null!

5.Function的__proto__指向的是其自身的prototype!

一句话解释原型对象与原型链

此处做一个开放话题吧,确实是因为鄙人学识不够,思考良久,想不出如何使用一句话来总结,诚心欢迎各位大佬参与讨论,咱们评论区见!!!

参考文献

注:文章不分好坏,排名无先后顺序,仅为学习过程中的记录,且此处记录的是个人认为能够帮助理解原型链的非常好的文章,还有个别零散资料,暂且就不放进来了!

1.2019 面试准备 - JS 原型与原型链 --- jsliang

2.MDN 继承与原型链

3.JavaScript 世界万物诞生记 --- manxisuo

4.这可能是掘金讲「原型链」,讲的最好最通俗易懂的了,附练习题 --- Sunshine_Lin

5.面不面试的,你都得懂原型和原型链 --- 尼克陈

结束语

如上述各位大佬文章里面所述,其实__proto__ 和 prototype 的叫法有很多,大家选择性的以自己能理解的去记就可以了,本文中讲prototype仍然叫为原型对象,是因为补充理解文末结论中的prototype也具有__proto__!另外,本文仅是以一个数组的小案例映射了一下原型链的相关指向问题,还有很多地方是没有囊括到的,后期本文也将会持续更新,尽量补充完整,尽情期待!


码字不易,如若对你有帮助,帮忙给个一键三连,孩子求求了🥺~