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

来源:互联网 发布:rrb400软件代号 编辑:程序博客网 时间:2024/05/22 17:57
达人科技 2016-10-16 18:07

和传统的面向对象语言通过类实现继承的方式不同,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]]只能指向对象,而不是构造函数。

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

检验一下:

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

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

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

自定义的构造函数在创建之初和内置构造函数是同级的,因为自定义构造函数的原型对象的[[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;

由此可得:

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

可以检测一下:

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,而JavaScript的创作者学过《易经》。

0 0
原创粉丝点击