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は人間の使う物ではない!
浙公网安备 33010602011771号