前面谈到了javascript的类式继承。这篇继续部分类式继承,及一些现代继承。
类式继承模式-代理构造函数
这种模式通过断开父对象与子对象之间原型之间的直接链接关系,来解决上次说到的共享一个原型所带来的问题,而且同时可以继续原型链带来的好处。
代码:
function inherit(C,P){ var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); }
可以看到,在这里面有一个空构造函数F(),充当了子对象和父对象之间的一个代理,F()的原型指向父对象的原型。子对象的原型是一个空白函数实例。这个模式与默认原型继承模式有一些不同,是因为这里的子对象仅继承了原型的属性。这个情况比较理想,也更加可取,因为原型是放置复用功能的位置。在这种模式中,父构造函数添加到this的成员都不会被继承。示意图如下:
可以创建一个子对象:
var kid = new Child();
访问kid.say()时,在上图对象3中没有找到方法,需查询原型链。对象4也没有,对象1中有,可以调用这个方法。在实际应用中,可以添加一个指向父构造函数原型对象的引用以备使用:
function inherit(C,P){ var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; }
下面还需要重置构造函数指针,以便使用:
function Parent(){} function Child(){} inherit(Child,Parent); var kid = new Child(); kid.constructor.name;//"Parent kid.constructor === Parent;//true
function inherit(C,P){ var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; }
这里还可以做的一个优化是避免每次需要继承时都创建代理构造函数,仅创建一次代理函数,并修改它的原型,这个已经可以达到目的了。在具体实现上,可以使用立即调用函数并且在闭包中存储代理函数:
var inherit = (fucntion(){ var F = function(){}; return function(C,P){ F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; }; })();
现代继承-对象的原型继承
这个是一种无类继承模式,不涉及类,这里的对象都是继承子其它对象。可以用这个方式考虑:有一个想要复用的对象,并且创建的第二个对象要从第一个对象中获取内容。
代码:
var parent = { name:"Papa" }; var child = object(parent); child.name;//"Papa"
parent是以对象字面量方式创建的一个父对象,需要创建一个与parent有相同属性和方法的child对象,其中的object()函数实现如下,与类式继承的代理构造函数类似:
function object(o){ function F(){} F.prototype = o; return new F(); }
下图显示了使用这个模式的原型链。child最初是一个空对象,没有自身的属性,同时又通过_proto_链接而具有父对象的全部功能。
在这个模式中,不需要使用对象字面量来创建父对象,其实也可以使用构造函数创建,如果使用构造函数,自身的属性和构造函数的原型属性都将被继承:
function Person(){ this.name = "Adam";//own property } Person.prototype.getName = function(){ return this.name; }; var papa = new Person(); var kid = object(papa); kid.getName();//"Adam"
其实可以选择只继承现有构造函数的原型对象,如下:
function Person(){ this.name = "Adam";//own property } Person.prototype.getName = function(){ return this.name; }; var kid = object(Person.prototype); typeof kid.getName();//function typeof kid.name;//undefined
在ES5中,原型继承模式已成为该语言的一部分,是通过Object.create()来实现的:
var child = Object.create(parent);
Object.create()接受另外一个参数,可传入一个对象,这个对象的属性将作为新对象的自身属性:
var child = Object.create(parent,{ age:{value:2} )}; child.hasOwnProperty("age");//true
现代继承-通过复制属性实现继承
下面是extend()的一个例子:
function extend(parent,child){ var i; child = child||{}; for (i in parent){ if(parent.hasOwnProperty(i)){ child[i] = parent[i]; } } return child; }
使用方法如下:
var dad = {name:"Adam"}; var kid = extend(dad); kid.name;//"Adam"
上面实现的是浅复制,深复制需要进行属性检查,如果复制的是一个对象或一个数组,在使用浅复制的时候,如果改变了子对象的属性,且该属性是一个对象,父对象也会被修改,这个情况可能会导致意外:
var dad = { counts:[1,2,3], reads:{paper:true} }; var kid = extend(dad); kid.counts.push(4); dad.counts.toString();//"1,2,3,4" dad.reads === kid.reads;//true
实现深复制,需要修改extend(),判断每个属性是对象还是数组:
function extendDeep(parent,child){ var i, toStr = Object.prototype.toString, astr = "[object Array]"; child = child||{}; for (i in parent){ if(parent.hasOwnProperty(i)){ if(typeof parent[i] === "object"){ child[i] = (toStr.call(parent[i]) === astr?) [] : {}; extendDeep(parent[i],child[i]); }else{ child[i] = parent[i]; } } } return child; }
测试:
var dad = { counts:[1,2,3], reads:{paper:true} }; var kid = extendDeep(dad); kid.counts.push(4); kid.counts.toString();//"1,2,3,4" dad.counts.toString();//"1,2,3" dad.reads === kid.reads;//false kid.reads.paper = false; dad.reads.paper;//true
这种模式应用广泛,例如FireBug有一个extend()方法可以实现浅复制,Jquery中的extend()可以实现深复制。