原型链

正如我们之前所了解的,JavaScript中的每个函数都有一个名为prototype的对象属性。该函数被new操作符调用时会创建出一个对象,并且该对象中会有一个指向其原型对象的秘密链接(在某些环境中,该链接名为__proto__),我们就可以在新建的对象中调用相关原型对象的方法和属性。

而原型对象自身也具有对象固有的普遍特征,因此本身也包含了指向其原型的链接。由此就形成了一条链,我们称之为原型链。

如图所示,在对象A的一系列属性中,有一个叫做__proto__的隐藏属性,它指向了另一个对象B(对象A的构造函数的原型)。而B的__proto__属性又指向了对象C,以此类推,直至链条末端的Object对象,该对象时JavaScript中的最高级父对象(没有给原型进行定义的函数,默认值Object)。

正因为有了这些技术,我们才可以在某个属性不在对象A中而在对象B中时,依然将它当做A的属性来访问(基于对象的继承)。同样的,如果对象B中也没有该属性,还可以继续到对象C中去寻找。这就是继承的作用,它能使每个对象都能访问其继承链上的任何一个属性。

原型链示例

首先我们定义三个构造器函数

function Shape(){
    this.name = 'shape';
    this.toString = function(){ return this.name; };
}
function TwoDShape(){
    this.name = '2D shape';
}
function Triangle(side,height){
    this.name = 'Triangle';
    this.side = side;
    this.height = height;
    this.getArea = function(){return this.side * this.height/2;};
}

接下来,就是我们施展继承魔法的代码了:

TwoDShape.prototype = new Shape();
Triangle.prototype = new TwoDShape();

我们将对象new Sharpe()直接创建在TwoDShape构造函数的prototype属性中,下面我们来测试一下目前为止所实现的内容,先创建一个Triangle对象,然后调用它的getArea()方法:

尽管对象my中并没有属于自己的toString()方法,但我们依然可以调用它所继承的方法。并且该方法toString()显然是与my对象紧密绑定在一起的。

  • 接下来,我们关注一下JavaScript引擎在my.toString()被调用时究竟做了哪些事
  • 首先,它会遍历my对象中的所有属性,但没有找到一个叫做toString()的方法。
  • 接着再去查看my.__proto__所指向的对象,该对象是my构造函数的原型,就是new TwoDShape()所创建的实体。
  • 显然,JavaScript引擎在遍历TwoDShape实体的过程中依然不会找到toString()方法。然后,它又会继续检查该实体的__proto__属性。这时候,该__proto__属性所指向的实体是由new Shape()所创建的。
  • 终于,在new Shape()所创建的实体中找到了toString()方法。
  • 最后,该方法就会在my对象中被调用,并且其this也指向了my

通过instanceof操作符,我们可以验证my对象同时是上述三个构造器的实例:

同样,当我们以my参数调用这些构造器原型的isPropertypeOf()方法时,结果也是如此:

将共享属性迁移到原型中去

当我们用某一个构造器创建对象时,其属性就被添加到this中去。例如,在上面的实例中,Shape()构造器是这样定义的:

function Shape(){
    this.name = 'shape';
}

这种实现意味着每当我们用new Shape()新建对象时,每个实体都会有一个全新的name属性,并在内存中拥有自己独立的存储空间。而事实上,我们也可以选择将name属性添加到所有实体所共享的原型对象中去:

function Shape(){}
Shape.prototype.name = 'shape';

这样一来,每当我们再用new Shape()新建对象时,新对象中就不再含有属于自己的name属性了,而是被添加进了该对象的原型中。虽然这样做通常会更有效率,但这也只是针对对象实体中的不可变属性而言的,另外,这种方式也同样使用于对象中的共享性方法。

修改后的代码:

function Shape(){}
Shape.prototype.name='Shape';
Shape.prototype.toString=function(){return this.name};

function TwoDShape(){}
TwoDShape.prototype=new Shape();
TwoDShape.prototype.constructor=TwoDShape;
TwoDShape.prototype.name='2D Shape';

function Triangle(side,height){
    this.side = side;
    this.height = height;
}
Triangle.prototype=new TwoDShape();
Triangle.prototype.constructor=Triangle;
Triangle.prototype.name='Triangle';
Triangle.prototype.getArea=function(){return this.side*this.height/2;};

在我们完成相关的继承关系设定后,对这些对象的constructor属性进行相应的重置是一个非常好的习惯。另外,我们通常会在对原型对象进行扩展前,先完成相关对象的关系构建,因为后面的新内容(name='')有时会抹掉我们继承来的东西。

 

posted on 2015-01-29 11:22  凡一二三  阅读(496)  评论(0编辑  收藏  举报