图解:什么是JS原型和原型链?转载本文请联系小鹿动漫学习编程公众号。刚开始学习JS语言的继承机制prototype和prototypechain的时候,对这种设计机制是看不懂的。再加上之前对Java继承的理解,让我在学习JS继承机制的设计上又跨了一步。在大坑,很多知识点前期都是死记硬背,无法真正理解它的设计思路。JS中继承机制的思想可以说是学习JS的一个核心思想,可以说是JS中的命脉。往往这些复杂而抽象的继承关系,以及专业术语和代名词确实成为了初学者的绊脚石。当我真正理解它的设计思想时,其实并没有那么复杂,我觉得很简单。在写这篇关于JS原型和原型链的文章之前,我在谷歌上搜索了一下,检索到了大量关于JS原型和原型链的好评度很高的文章。他们中的大多数人都围绕着“这是什么?”展开。学者们缺乏对JS继承的设计和实现的语境关联性,准确理解还是有难度的。我们首先要明白,学习这块内容知识,要知道设计师的“为什么要做这个”远比“怎么做”更重要。这是掌握这部分内容的关键。今天小鹿就对JS的继承机制做一个系统的总结。站在设计师的角度,将复杂的设计思路以动画形式呈现,将零散的知识点系统化,让你一篇文章了解JS的继承机制(原型和原型链)。思维导图1.JS的发展历史要想贯彻JS的核心设计思想,还得从JS的诞生说起。1.1JavaScript为什么诞生?比较成熟的浏览器是Netscape发布的。早年间,浏览器只能浏览网页内容,不能进行用户交互。比如我们登录,输入用户名和密码,浏览器无法判断用户是否真的输入了,但是服务器会判断。如果没有输入,则返回错误提示用户。这种设计是浪费时间和服务器资源。为了解决这个问题,Netscape需要开发一种运行在浏览器中的脚本语言,可以用来简单地执行用户输入验证等操作。当时最流行的语言是面向对象的Java编程语言。Netscape将其命名为JavaScript是为了借助Java传播浏览器脚本语言。事实上,两者之间没有任何关系。1.2存在的问题JS中的数据类型设计受当时Java流行的影响,都是对象类型。这时候,我们遇到了问题。对象必然涉及到继承机制,那么JS的继承机制是不是应该设计成和Java一样呢?还是有别的设计思路?2.JS继承的设计思想。JS的开发者认为,如果设计的时候像Java一样有“类”的概念,那不就变成像Java一样完全面向对象的编程语言了吗?最后,他们决定自己设计。一种继承机制,但它的设计思想还是采用了Java的一些特点。2.1生成对象一般情况下,Java生成对象的过程是通过类通过new生成实例对象的过程。但是JS中没有类,那么JS的设计者该怎么办呢?他发现Java和JS的共同点是都有构造函数。Java的new进程其实就是调用了构造函数。但是JS没有“类”的概念,所以JS设计了一个新的“类”作为新的构造函数,于是构造函数就变成了一个实例对象的原型对象。3、为什么要设计原型对象?上述原型设计的一个致命缺点是不能共享公共属性。因为我们知道对于每个新对象,生成的实例都是两个不同的对象。所以共享属性也不共享。所以专门设计一个对象来存储对象共享的属性,那么我们称它为“原型对象”。4.什么是原型对象?为了让构造函数生成的所有实例对象共享属性,那么我们在构造函数中添加一个名为prototype的属性,用于指向原型对象。我们共享所有实例对象共享的属性和方法,它们都放在构造函数的原型属性指向的原型对象中,不需要共享的属性和方法放在构造函数中。这里有点疑问,我们知道对象可以设置属性,函数也可以设置属性吗?对于初学者来说,是比较迷惑的,那么我们可以简单地说:JavaScript中的函数具有对象的所有能力,因此也可以将其视为任何其他类型的对象。当我们说函数是一等对象时,就是说函数也可以执行对象的一些功能,比如添加属性,将函数作为参数传递等等。因此,一旦通过构造函数创建了实例对象,就会自动为实例对象分配原型对象上共享的属性或方法。说清楚一点,对象属性都指向原型对象的属性值。5.原型链中对象和函数的关系?上图反映了原型链中对象和函数的关系。如果你觉得上图看起来很混乱,没关系。刚开始学习原型链的时候,我什至不知道上面这张图是什么“清明上河图”。小鹿将分解并逐步解释。看这张图很简单。是的,很简单。我们文章开头也说了什么是原型对象。说白了就是构造函数的一个原型属性,这个属性指向原型对象。其实我们并没有提到一些connection的属性,只提到了prototype属性,剩下的属性会用下图补齐。我们只需要将这张图片打印到大脑中即可。我们来分析一下上图。首先,我们必须声明一条狗的构造函数并定义它的名称和重量属性(私有属性)。同时,我们上面提到的每一个构造函数都会有一个prototype属性。这个原型指向原型对象,原型对象放置了对象共享的属性。但是注意原型对象中有一个constructor属性,它指向回构造器。我们使用new构造函数生成了两个狗的对象实例,一个叫豆豆,一个叫贝贝。这两个是两个不同的对象,名字和权值都不同,但是他们共享原型对象上的属性类型。它们的共同点是犬齿。在所有的JS对象中,只要是对象,都会有一个内置的属性叫_proto_,这个属性是系统自动生成的。只要你创建了一个对象,这个对象就会有这个属性。_proto_属性指向原型对象。通过上面的分布解释,我们明白了构造函数、对象实例和原型对象之间的关系。一句话总结:构造函数的原型指向原型对象,原型对象有一个构造函数属性指向构造函数,而构造函数生成的每个实例对象都有一个_proto_属性,指向原型对象。是的,原型制作就是这么简单。但是你会发现原型也是一个对象。你说只要是对象,都会有一个_proto_属性指向自己构造函数的原型对象。没错,如果想知道原型对象的_proto_属性指向谁,就需要知道是哪个构造函数创建了原型对象?我们知道所有的JS对象都继承了一个对象,叫做Object。可以理解为Object的构造器创造万物。他们的关系如下,和上面一样。让我们理解上面总结的句子。但是上图中有一个问题,Object的构造函数原型对象也是一个对象,它肯定也有一个_proto_属性,为什么指向null呢?我们是拿上面总结的那句话来说的,_proto_属性指向自己构造函数的原型对象,谁是自己的构造函数呢?是Object构造函数,那么Object构造函数的原型是谁呢?当然是它自己(如图),所以_proto_指向null。上面的关系如果不仔细整理的话,真的是乱七八糟的,尤其是初学者,但是如果像小鹿这样整理的话,再乱的关系,看不懂就多看几篇吧。6.原型链我们还有一个问题没有解决,原型链是什么?知道原型是什么了,那什么是原型链呢?顾名思义,肯定是一个链,既然每个对象都有一个_proto_属性指向原型对象,那么原型对象也有_proto_指向原型对象的原型对象,直到上图中指向null,到达原型链的顶部。别忘了,我们还没有看懂上面的图,我们是从上到下看懂这张图的。第一张图分解,上面小鹿画的图的关系和这张是一样的。仔细对比了一下,很简单,第一张图就这样解决了。我们继续拆分来看第二张图。为什么第二张图还是那么眼熟?这不就是小路上面分析的Object关系图吗?恩,那就对了。第三张图稍微绕了个弯,但是汤不是药。听听小璐的分析。还是很眼熟,只是函数换成了Function,f变成了大写的F,这里涉及到一个知识点。在JS中,所有的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的原型对象。整篇文章的精髓在最后的总结部分。前面所有的分解,都是为了让你明白这些函数对象和原型对象之间的关系。这个关系是固定的,谁指向谁就写死。只要记住他们的关系,就差不多能看懂这张图了。看懂这张图后,你对原型和原型链的理解就扎实了,但还是需要做一些面试题来巩固。
