写在前边
在我初学 JS 语言的继承机制原型和原型链的时候,我一直理解不了这种设计机制,再加上之前原有对 Java继承的理解,在学习 JS 继承机制的设计上踩了一个大坑,很多知识点前期都是死记硬背,无法真正的理解它的设计思想。
JS 中的继承机制思想可以说是学习 JS 的一个核心思想,更可以说是 JS 中的一个命脉,往往这些复杂、抽象的继承关系,以及专业术语、代名词确成为了困扰初学者的绊脚石。当我真正理解它的设计思想时,其实并没有那么复杂,而且觉得非常简单。
在写这篇 JS 的原型和原型链的文章之前,我在谷歌搜索检索了大量的高赞有关 JS 原型和原型链的文章,大部分都是围绕着“是什么”来讲的,导致部分初学者缺少对 JS 继承的设计与实现的前后关联性,还是很难准确的去理解。
我们先要明白,学习这块内容知识要知道设计者“ 为什么这样做 ” 远比 “怎么做的” 重要的多这才是掌握这部分内容的关键。
今天小鹿对 JS 的继承机制要做一个系统的总结,从设计者的角度出发,将复杂的设计思想用动画呈现,将零碎的知识点体系化,争取让你一文搞懂 JS 的继承机制思想(原型和原型链)。
思维导图

1、JS 的发展史
要想贯彻 JS 的核心设计思想,我们要从 JS 的诞生说起。
1.1 为什么会诞生 JavaScript ?
相对比较成熟的浏览器是由网景公司发布的,早些年间,浏览器只能浏览网页内容,而不能进行用户交互。比如我们登录输入用户名和密码,在浏览器是不能进行判断用户是否真正输入了,而是通过服务器来判断,如果没有输入,返回错误提示用户,这种设计非常的浪费时间和服务器资源。

当时最流行的语言是面向对象的Java编程语言 ,网景公司为了能够借助 Java将浏览器脚本语言流传开,所以起名 JavaScript。其实两者没有任何的关系。
1.2 存在的问题
JS 中的数据类型设计受当时 Java流行的影响,都是对象类型,这时候就遇到问题了,有对象必然涉及到继承机制,那么 JS 的继承机制要设计成 Java一样呢?还是另有设计思想?
2、JS 继承的设计思想
JS 的开发者想如果设计成像 Java一样有“类”的概念岂不是和 Java一样成为了一种完全面向对象的编程语言了?最后决定自己设计一种继承机制,但是它的设计思想还是采用了 Java的一些特性。
2.1 生成对象
通常 Java 生成对象是通过 new 的方式,通过类生成一个实例对象的过程。但是 JS 中并没有类,那 JS 的设计者要怎么做?

new
的过程内部其实调用了构造函数。但是 JS 是没有“类”的概念的,于是 JS 就把new
一个“类”设计成了 new
一个构造函数,于是构造函数成为了一个实例对象的原型对象。

3、为什么要设计原型对象?
上述这样的原型设计有一个致命的缺点就是无法共享公共属性。
因为我们知道,每 new
一个对象,生成的实例是两个不同的对象。所以共有的属性也不是共享的,如果我们改动一个对象的 type 属性,但是另一个不会改变,因为这个属性没有共享。

4、什么是原型对象?
要想让构造函数生成的所有实例对象都能够共享属性,那么我们就给构造函数加一个属性叫做prototype
,用来指向原型对象,我们把所有实例对象共享的属性和方法都放在这个构造函数的prototype
属性指向的原型对象中,不需要共享的属性和方法放在构造函数中。
这里有一点疑惑就是,我们知道对象可以设置属性,函数也可以设置属性吗?对于初学者来说是比较懵逼的,那我们可以稍微的简单说一下:
JavaScript 中的函数拥有对象的所有能力,也因此可被称作为任意其他类型对象来对待。当我们说函数是第一类对象的时候,就是说函数也能够对象的一些功能,比如添加属性,函数当做参数传递等。
所以说,实例对象一旦通过构造函数创建,就会自动给实例对象赋值上原型对象上共享的属性或方法。说清楚一点就是该对象属性都指向了原型对象的属性值。

5、对象和函数在原型链关系?

上述的图反映了对象以及函数在原型链中的关系,如果你觉的上边的这张图看懵逼了,没关系,我刚开始学习原型链的时候,根本不知道上边这是什么“清明上河图”,小鹿下面通过一步步的拆分讲解,看这张图就非常简单,没错,非常简单。
我们文章的开头也说了什么是原型对象,说白了就是构造函数的一个 prototype
属性,这个属性就指向原型对象。
其实我们其中一些连接属性没有讲到,只讲到了prototype
属性,下面一张图来将剩下的属性补充完整,我们只要把这张图印到大脑中就可以了。

我们来分析一下上图,首先我们先要声明一个狗的构造函数,定义其名字和体重属性(私有属性),同时每个构造函数我们上边讲到了,都会有一个prototype
属性。

这个prototype
指向的就是原型对象,原型对象放的就是对象共享的属性。但是注意,原型对象里有一个constructor
属性,这个属性又指回了构造函数。

在 JS 所有对象中,只要是对象,都会有一个内置属性叫做_proto_
,而且这个属性是系统自动生成的,只要你创建一个对象,这个对象就有这个属性。这个_proto_
属性指向的是原型对象。
通过上边的分布讲解,我们明白了构造函数与对象实例以及原型对象的关系。
总结为一句话为:
构造函数的 prototype 指向原型对象,原型对象有一个 constructor 属性指回构造函数,每个构造函数生成的实例对象都有一个 proto 属性,这个属性指向原型对象。
没错,原型就是这么简单。但是你会发现,原型也是对象呀,你说只要是对象都会有一个_proto_属性指向自身构造函数的原型对象。
没错,要想知道原型对象的_proto_
属性指向谁,就要知道是哪个构造函数创建了原型对象?
我们知道,所有的 JS 对象的都是继承了一个叫做 Object
的对象。可以理解为 Object
构造函数创造了这个万物,他们的关系如下,和上边是同样的道理,上边总结的那句话好好理解一下。

但是上图中会有一个疑问,Object
构造函数原型对象的也是对象,它肯定也有一个_proto_
属性,为什么会指向null
呢?
我们在拿上述总结的那句话,_proto_
属性指向的是自身构造函数的原型对象,自身的构造函数是谁?是 Object
构造函数,那 Object
构造函数的原型是谁?当然是本身(如图),所以把_proto_
指向了null
。
上边的关系如果不仔细整理的话确实很乱,尤其是对于初学者,但是如果像小鹿这样已整理,再乱的关系把它安排的井井有条,没有理解,就多看几篇文章。
6、原型链
我们还有一个问题没有解决就是原型链?既然我么你知道什么是原型了,原型链是什么?顾名思义,肯定是一条链,既然每个对象都有一个_proto_
属性指向原型对象,那么原型对象也有_proto_指向原型对象的原型对象,直到指向上图中的null
,这才到达原型链的顶端。
不要忘了,上边那种图我们还没有把它理解,我们把图自上而下理解。



Object
的关系图吗?对的,没错。


function
换成了Function
,f 变成了大写的 F,这里涉及到一个知识点就是,在 JS 中,所有的function
函数都是由Function
继承来的,可以说是Function
是所有 function
的祖宗。
那Function
是由谁生产来的?我们看到图中的Function
函数有_proto_
属性了,而且属性指向自己的原型对象,那不就是自己繁衍自己吗?可以这么理解。
小结
这里我们在纵观全图,总结几条定义你比对着图去找。
1、所有的实例的_proto_
都指向该构造函数的原型对象(prototype
)。
2、所有的函数(包括构造函数)是Function
的实例,所以所有函数的_proto_
的都指向Function
的原型对象。
3、所有的原型对象(包括 Function
的原型对象)都是Object的实例,所以_proto_
都指向 Object
(构造函数)的原型对象。而Object
构造函数的 _proto_
指向 null。
4、Function
构造函数本身就是Function
的实例,所以_proto_
指向Function
的原型对象。
全篇文章的精华都在最后的总结部分,前边的所有分解讲解是为了让你理解这些函数对象以及原型对象之间的关系,这关系都是固定的,谁指向谁,都是写死额,只要你记住了他们的关系,这张图就理解的差不多了,能够理解完这张图,你的原型和原型链已经了解的很扎实了,但是还需要做一些面试题巩固一下。
后期会出文章专门讲解怎么解决大厂的这种有关原型和原型链的面试题。
今天的文章就到这里了,今天的内容看起来多,其实总结起来就几句话,还是那句话,原创不易,点个赞就是对原创作者最大的支持,非常感谢。
❤️ 不要忘记留下你学习的脚印 [点赞 + 收藏 + 评论]
文章都看完了,为何不妨点个赞呢?嘻嘻,那就说明你很自私,你怕那么好的文章让别人也看到。开个小小玩笑。
其实我也很自私,我把我的一直以来坚持原创的公众号:「小鹿动画学编程」偷偷给你,里边汇聚了小鹿以动画形式讲解的数据结构与算法、网络原理、Web 等技术文章。

作者Info:
【作者】:小鹿
【原创公众号】小鹿动画学编程
【简介】:和小鹿同学一起用动画的方式从零基础学编程,将Web前端领域、数据结构与算法、网络原理等通俗易懂的呈献给小伙伴。公众号回复 “资料” 送一从零自学资料大礼包!
【转载说明】:转载请说明出处,谢谢合作!~