JavaScript---谈谈对象系统和对原型的理解

我们都知道JavaScript是一门基于对象的编程语言,那么究竟什么是基于对象?

我们先来谈谈JavaScript对象系统的设计思路,JavaScript采用了一种“冷门”的方式来描述对象,那就是原型。面向对象的三大特性是“封装、继承和多态”,可是整个JavaScript系统都是对象,函数也是对象,变量也是对象,却无法利用现有的对象模板来产生新的对象类型,所以JavaScript只有封装的概念,继承和多态可能就谈不上了。不过,JavaScript有它自己实现“继承”的方式,那就是原型链。直到ES6,引入了“类”的概念,才更加贴近Java、C++等面向对象语言的描述思路。

原型

原型的理解,其实不是单纯的继承,我们用通俗的话讲,一辆车子在车厂被造出来,需要一个模板,那么这个模板就是这辆车的原型,如果是“宝马模板”,那么这辆车就是宝马(或者说这辆车的类型是宝马)。

我们先来看一个例子,理解一下基本的变量查找规则在原型链中是如何实现的。

function foo() {} //函数声明式,构造函数
foo.prototype.z=3;  //在foo原型对象添加z属性
var obj = new foo();  //造出对象obj
obj.y=2;  //在obj上添加属性x和y
obj.x=1;

prototype表示原型对象,如果我们在obj里面查找z属性,找不到,他会沿着proto去查找,proto指向原型对象,也就是foo.prototype,如果在foo.prototype中查找不到就继续沿着原型链查找,直到object.prototype。
JavaScript---谈谈对象系统和对原型的理解
但是事情并不是这么简单。我们之前说到一个概念,原型相当于“模板”。原型链的原型对象指向,其实是基于这些模板的。比如说,obj这个对象,他是由foo这个构造函数构造出来的,那么obj这个对象原型对象,就是foo.prototype(而不是foo)。这是因为,obj这辆车是foo牌的,而foo牌的车的模板,是foo.prototype
(注意foo.prototype本身也是一个对象,所以有proto,才能连接成原型链)

JavaScript---谈谈对象系统和对原型的理解
说到这里,可能有点懵,我们来看这幅图,最终总结一下。
小明和小红这两个人是Student类型的人,他们是由Student构造函数(也就是图中上面的那一块)构造出来的,Student构造函数具有一个属性叫做prototype。

而Student类型的原型对象即Student.prototype(也就是图中下面的那一块),所以小明和小红的proto都指向原型对象Student.prototype(也就是红色的箭头)。

原型对象可以具有一个属性叫做constructor,指向它自己的构造函数。

我们回到文章的开头的那辆车子,理清这些关系:

  • 车子的proto指向“宝马牌”(原型对象prototype)
  • 宝马牌用“构造机器”来造车(构造函数constructor)
  • 构造机器贴上了标签,说明了机器是用来造宝马车的(属性prototype)

原型链

所以原型链到底是怎么形成的,应该已经清楚了:
原型链由proto指向原型对象prototype来进行衔接

绝大多数对象的原型链末端是Object.prototype(构造函数是Object(),它具有属性prototype,也就是原型对象)。

问题又来了,为什么说是绝大多数???JavaScript的对象系统不是应该只有一个源头object.prototype吗?

其实不是的,我们来了解一个函数:
Object.create()

The Object.create() method creates a new object, using an existing object as the prototype of the newly created object.
A TypeError exception if the propertiesObject parameter is null or a non-primitive-wrapper object.

MDN第一句话意思就是说这个方法传入一个现有的原型来构造对象。第二句话的意思是如果参数不是对象或者null,会报TypeError。也就是说传入的参数可以是一个对象,也可以是null。所以,对象的原型链末端也可能是null,而不一定是object.prototype!