首页 > 代码库 > JavaScript的原型系统是怎样构建起来的

JavaScript的原型系统是怎样构建起来的

  和传统的面向对象语言通过类实现继承的方式不同,JavaScript中不存在传统意义的"类",JavaScript是通过构造函数来实现继承的。JavaScript的构造函数常常被混淆为"类",只是因为它们承担着同样的功能,然而它们实现继承的方式完全不同。类继承通过生成一个类的副本实现继承,构造函数通过原型关联实现继承。类继承的机制是"复制、拷贝",原型继承的机制是"引用、关联"。

 

一、原型是什么

       JavaScript中所有的对象(包括函数)都有一个内部指针[[prototype]]指向对另一个对象的引用。

       如果说对象A的内部特性[[prototype]]指向对象B,那么B就是A的原型。

       同样的,对象B也有一个内部特性[[prototype]],指向另一个对象C,C也有一个内部特性[[prototype]]指向另一个对象D……这就是原型链。

       JavaScript的继承是基于原型链实现的。

       [[prototype]]特性属于内部实现,是不可访问的,但也有浏览器暴露出__proto__属性用于访问原型对象。

 

二、关联继承的本质

       当调用对象的某个属性时,如果对象的自有属性中不存在该属性,那么JavaScript引擎就会沿着该对象的原型链向上查找,直到找到第一个匹配的属性名为止。

       这就是关联继承,与作用域链的工作机制非常类似。基于原型链一脉相承的继承模式是JavaScript对象系统的根本特征。

      

三、函数的prototype属性

       所有的函数(也只有函数)都有一个内置属性prototype,这个属性指向对另一个对象的引用,这个对象也被称为原型。

       这体现了函数作为一等公民的特殊性:函数拥有一个可访问的内部属性prototype,可以显式的设置其原型对象,从而影响原型链的构建形态。对象是没有prototype属性的,但有一个constructor属性,指向其构造函数(JavaScript所有的对象都是由函数构造的)。

       必须严格区分函数的内部属性prototype和对象的内部特性[[prototype]]。原型继承是基于[[prototype]]的,而不是prototype。

       JavaScript整个对象系统都是建立在构造函数的基础之上的,JavaScript提供了九个内置的构造函数,同时我们还可以使用自定义函数作为构造函数。

       内置的构造函数,其原型是JavaScript预设好的,例如:

       Object.getOwnPropertyNames(Object.prototype)返回结果如下:

         ["__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "constructor", "toString", "toLocaleString", "valueOf", "isPrototypeOf", "propertyIsEnumerable", "__proto__"]

       自定义构造函数的原型对象默认是一个仅包含两个属性(constructor和__proto__)的对象,这个原型对象的[[prototype]]指向Object.prototype。

       所有构造函数的原型都可以自定义设置,但一般不去修改内置构造函数。

 

四、关联是如何建立的

1、对象的原型链

       对象的[[prototype]]具体指向谁,取决于对象的创建方式。

       对象直接量的[[prototype]]指向的原型对象是Object.prototype;

       var obj = { }; obj.__proto__ === Object.prototype // true

       通过Object.create( )创建的对象,其[[prototype]]特性指向由第一个参数指定的对象;

       通过构造函数创建的对象,其 [[prototype]] 特性指向构造函数的原型对象。

 

2、函数的原型链

       函数的原型链非常简单,所有函数的[[prototype]]都指向Function.prototype

       Array.__proto__ === Function.prototype // true

       Function.__proto__ === Function.prototype // true

       var f = function () { }; f.__proto__ === Function.prototype // true

 

5、对象系统的构建

       根据以上规则,我们试着从零开始构建JavaScript的对象系统。

       JavaScript的初始环境提供了三个纯粹的独立的对象(Global&Math&JSON)和九个构造函数。

       Global是全局环境,暂且不谈。

       九个构造函数中,Object是所有对象的始祖,其余八个构造函数都是Object的实例。

       Date instanceof Object // true

       ……

       Math和JSON都是对象,也都是Object的实例。

       Math.constructor === Object // true

       Math instanceof Object // true

       因此构建JavaScript对象系统的第一步就是创造一个构造函数Object,以及Object的原型对象Object.prototype。

       然后再创建其余八个构造函数,将它们的原型对象的[[prototype]]指向Object.prototype。为什么不直接指向Object?因为[[prototype]]只能指向对象,而不是构造函数。

 技术分享

       检验一下:

       Function.prototype.__proto__ === Object.prototype // true

       现在原型对象之间的关联有了,函数之间还没有关联,将函数的[[prototype]]都指向Function.prototype后就得到了一个初始的对象系统。

 技术分享      

   自定义的构造函数在创建之初和内置构造函数是同级的,因为自定义构造函数的原型对象的[[prototype]]同样指向Object.prototype,但是我们可以修改构造函数的原型,来改变一下原型链的纵深。

       var obj = new Array; // obj的[[prototype]]指向Array.prototype

       var f = function() {}; // 创建一个函数作为构造函数

       f.prototype = obj; // 修改构造函数的原型

       var o = new f(); // 创建一个f的实例对象

       那么o的原型链是怎么样的呢?

       对象o的原型的[[prototype]]指向构造函数f的prototype对象;

       构造函数f的prototype对象(即对象obj)的[[prototype]]指向Array.prototype;

       Array.prototype的[[prototype]]指向Object.prototype;

       由此可得:

技术分享 

       可以检测一下:

       console.log(o instanceof f); // true

       console.log(o instanceof Array); // true

       console.log(o instanceof Object); // true

注: object instanceof constructor

       instanceof 运算符用来检测 constructor.prototype 是否存在于object 的原型链上。

 

6、道生于无

       所有原型链都汇聚到同一个源头,就是Object.prototype,它也是一个对象,也有[[prototype]]属性,那么Object.prototype.__proto__ === ?答案是null。

   看上去有些玄妙的样子,正所谓天地肇始,化分阴阳:

   技术分享

   九个构造函数其实就是九尾:

       技术分享

 

   由此看来,火影的创作者也学过JavaScript,而JavaScript的创作者学过《易经》。

JavaScript的原型系统是怎样构建起来的