javascript框架之继承机制3
继续上一部分,现在我们的实的构造器initialize很完美了,甚至连类式super这样语法糖都不用了,就自动实例了父类。我们转而看一看其属性与方法的继承。许多类库都是一个for...in循环再加一些判定实现原型属性拷贝,或根据这些判定把某些属性揪出来加工一下再放进去。又如,我们要对Array的模板进行扩展,做成一个新类Array2,直接继承后,在有些浏览器中Array2可能有forEach方法,可能没有,如果没有,我们才添加自己实现的forEach方法。因此条件过滤非常重要的。在mootools中,生成类都带有一些类方法(alias与implement),以供更进一步的加工。
var copy = function(a,b,c){ var l = arguments.length; if(a && b && l == 2){ for(var p in b) a[p] = b[p]; }else if(a && b && l == 3 ){ a[b] = c; } return a; };
上面的方法名副其实,就是用于单纯的复制。
var override = function(a,b,filter,scope){ var i, scope = scope || window; for(i in b) { if(filter.call(i,a,b)) a[i] = b[i]; } };
override 为有选择地复制,如果我们不想覆盖原生函数,只需要这样:
override(Array,{/**/},function(p,a,b){ return !(p in a) })
有了以上方法,我们就可以设置更为复杂的语法糖,如实例属性的getter与setter,它们在java早已用annotation搞定了,在ruby中它们的设置也非常简单,我们没有道理放弃如此诱人的东西。不过,在javascript中我们无法利用注释来实现,因为可恨的火狐在编译时把注释全部去掉。ruby那种实现,相当于让人设计另一套语法。因此,我们还是交由类工厂实现,把它们全部变成原型方法。过程如下,首先取得我们定义的构造方法,然后取得其参数,再取得父类构造器的参数,然后转化为两个数组,如果子类参数数组的长度大于父类的,说明它定义了新的属性,我们把这些新属性提出来,然后把它们首字母大写前面加上set与get,对应的函数用eval生成即可。
//获取函数的参数,以字符串数组形式返回 var argumentNames = function(fn) { var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } //首字母大写函数 var capitalize = function(str){ return str.replace(/\S+/g, function(a){ return a.charAt(0).toUpperCase() + a.slice(1); }); }; //用于提取两个数组不同的部分 var difference = function(arrayA,arrayB){ var len = Math.max(arrayA.length, arrayB.length), i,p, result=[], diff = {}; for(i=0;i<len;i++){ if(i<arrayA.length) diff[ arrayA[i] ] = diff[arrayA[i]] ? 2 : 1; if(i<arrayB.length) diff[ arrayB[i] ] = diff[arrayB[i]] ? 2 : 1; }; for(p in diff) if(diff[p] ==1 ) result.push(p); return result; };
在火狐等浏览器中,公开了一个叫__proto__的内部变量,它为实例对象如我们的i的属性。通常实例都有一个construtor属性,它其实也是prototype上。但prototype为类的属性,实例只能访问prototype上的属性。
var i = new IndiaTiger("印度虎",2,"印度"); var a = new Array alert(i.prototype)//undefined,实例不能直接访问类的prototype属性 alert(a.prototype)//undefined alert(i.constructor)//IE不能访问,其他能 --> function IndiaTiger(){[variant code]} alert(a.constructor)//IE不能访问,其他能 --> function Array(){[native code]} alert(i.__proto__.klassname)//IE不能访问,其他能 IndiaTiger alert(i.__proto__.constructor)//IE不能访问,其他能 --> function IndiaTiger(){[variant code]} alert(a.__proto__.constructor)//IE不能访问,其他能 --> function Array(){[native code]}
这简单,我们为我们生产的类的prototype上添加一个__proto__属性即可。这样另一条原型链在我们的继承体系中就修复了!
if(!+"\v1" || window.opera){ klass.prototype.__proto__ = klass.prototype;//让类实例可以访问类的prototype }
我们来看另一种语法糖,它见于Prototype.js1.6版。
//以下为Prototype的类继承 var Person = Class.create({ initialize: function(name) { this.name = name; }, say: function(message) { return this.name + ': ' + message; } }); // when subclassing, specify the class you want to inherit from var Pirate = Class.create(Person, { // redefine the speak method say: function($super, message) {//★★★★注意看是如何重写父类的方法 return $super(message) + ', yarr!'; } }); var john = new Pirate('Long John'); john.say('ahoy matey'); // -> "Long John: ahoy matey, yarr!"
其实就是Ruby那一套东西,目的是达到最大的复用,类继承是整体的复用,方法的super是局部的复用(我也不知怎样叫它)。子类的同名方法把父类的方法用裹其中,大大减少方法的代码长度。我们看一下相应的ruby代码:
class A #定义父类 def a #定义方法 相当于javascript的 function a(){document.write("a 1")}; p 'A a method' end end aa = A.new #创建实例 var aa = new A; aa.a # aa.a();输出 "A a method" #>ruby a.rb #"A a method" #>Exit code: 0
像ruby这种一切皆对象的语言中,实现继续轻而易举!
class A #定义父类 def a p 'A a method' end end class B < A#定义子类,让类B继承类A def a p 'B a method start' super p 'B a method end' end end b = B.new b.a #>ruby a.rb #"B a method start" #"A a method" #"B a method end" #>Exit code: 0
因此我们应该明白Prototype的$super就是执行与父类的同名方法。如何调用它呢?我们可以为$super方法传入arguments,通过arguments我们在内部就可以找到此方法实体(那个arguments.callee),为了方便找到父类的同类方法,我们在它直接设置到方法上。
DOMは人間の使う物ではない!