最近在学习代码复用模式(code reuse pattern),印象特别深刻的是“借用和设置原型”,也就是先借用构造函数,然后设置子构造函数的原型使其指向一个构造函数创建的一个新实例。代码如下:
function Parent(name){ this.name = name } Parent.prototype.say = function(){ return this.name; }
子类实现:
function Child(){ Parent.apply(this,arguments) } Child.prototype = new Parent()
这样写的优点在于代码运行后的结果对象能够获得父对象本身的成员的副本以及父对象原型上的功能。同时子对象也能将任意参数传递到父对象。但是这种模式有一个缺点,父构造函数被调用了两次(见红色代码)。不同于上面的那种需要调用两次父类构造函数,共享原型做了如下改动:可复用成员应该转移到原型中而不是放置到this,可以将子对象的原型设置成为与父对象的原型相同。
function inherit(child,parent){ child.prototype = parent.prototype; }
这样设置后,所有对象就共享了同一个原型,但是这也是一个缺点,如果在继承链下方的某处存在一个子对象或者孙子对象修改了原型,它将会影响到祖先对象。
那么,怎么样做才能断开父对象与子对象的原型之间的直接链接关系,从而解决共享同一个原型所带来的问题,而且同时能继续受益原型带来的好处。
临时构造函数解决了这个问题。请看代码:
function inherit(child,parent){ var f = function(){}; f.prototype = parent.prototype; child.prototype = new f(); }
在上面的代码中,定义了一个空的函数,该函数充当了子对象和父对象之间的代理。f()的prototype指向了父对象的原型,子对象的原型则指向了空白函数的实例。这种情况通常来说是很好,因为原型正是放置可复用功能的位置,而且父构造函数添加到this的任何成员都不会被继承。
但是需要注意一点:请看如下代码:
function parent(name){ this.name = name; } parent.prototype.arr = [1,2,3]; //引用类型 parent.prototype.age = 12; //基本类型 function child(){ parent.apply(this,arguments); } function inherit(child,parent){ var f = function(){}; f.prototype = parent.prototype; child.prototype = new f(); } inherit(child,parent)
var ch = new child("pp");
//打印属性如下
ch.name; // "pp"
ch.age //12
ch.arr //[1, 2, 3]
修改属性如下:
ch.age = 20;
ch.age; //20;
ch.arr.push(4);
ch.arr; //[1,2,3,4]
通过如上的修改,可以发现,子类修改原型上的基本属性类型值的时候,并没有影响到祖先原型,但是当我们往子类原型的引用类型值上做修改的时候,祖先原型相应的改变了。我们知道当我们创建一个实例的时候,并调用相应的方法或者属性的时候,会按照原型链进行搜索,也就是先会搜索实例上的属性或者方法,当不存在的时候,再在原型上搜索。当我们修改ch.age = 20;实际上,并没有修改,而是在ch实例上我们创建了一个age属性,它覆盖了(没有删除)原型上的age属性。
但是当我们修改原型上的数组类型值时,由于数组是引用类型访问,所以修改子类继承的数组时,祖先原型也会相应的改变。