今天小五对 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 的设计者要怎么做?
他找到了 Java 和 JS 的共同点就是两者都有构造函数, Java的 new 的过程内部其实调用了构造函数。但是 JS 是没有“类”的概念的,于是 JS 就把new 一个“类”设计成了 new 一个构造函数,于是构造函数成为了一个实例对象的原型对象。
3.为什么要设计原型对象?
上述这样的原型设计有一个致命的缺点就是无法共享公共属性。
因为我们知道,每 new 一个对象,生成的实例是两个不同的对象。所以共有的属性也不是共享的。
所以要设计一个对象专门用来存储对象共享的属性,那么我们叫它「原型对象」。
4.什么是原型对象?
要想让构造函数生成的所有实例对象都能够共享属性,那么我们就给构造函数加一个属性叫做prototype,用来指向原型对象,我们把所有实例对象共享的属性和方法都放在这个构造函数的prototype属性指向的原型对象中,不需要共享的属性和方法放在构造函数中。
这里有一点疑惑就是,我们知道对象可以设置属性,函数也可以设置属性吗?对于初学者来说是比较懵逼的,那我们可以稍微的简单说一下:
JavaScript 中的函数拥有对象的所有能力,也因此可被称作为任意其他类型对象来对待。当我们说函数是第一类对象的时候,就是说函数也能够对象的一些功能,比如添加属性,函数当做参数传递等。
所以说,实例对象一旦通过构造函数创建,就会自动给实例对象赋值上原型对象上共享的属性或方法。说清楚一点就是该对象属性都指向了原型对象的属性值。
5.对象和函数在原型链关系?
小结
这里我们在纵观全图,总结几条定义你比对着图去找。
1、所有的实例的_proto_都指向该构造函数的原型对象(prototype)。
2、所有的函数(包括构造函数)是Function的实例,所以所有函数的 _proto_的都指向Function的原型对象。
3、所有的原型对象(包括 Function的原型对象)都是Object的实例,所以_proto_都指向 Object(构造函数)的原型对象。而 Object构造函数的 _proto_指向 null。
4、Function构造函数本身就是 Function 的实例,所以_proto_指向Function的原型对象。
全篇文章的精华都在最后的总结部分,前边的所有分解讲解是为了让你理解这些函数对象以及原型对象之间的关系,这关系都是固定的,谁指向谁,都是写死额,只要你记住了他们的关系,这张图就理解的差不多了,能够理解完这张图,你的原型和原型链已经了解的很扎实了,但是还需要做一些面试题巩固一下。