换种思维,简单解决

还是简单的问题:基“类”方法调用。下面的代码给出了一个十分极端的例子(使用了megamijs):递归的树状调用基“类”不同名方法,下面的是标准输出。

(说明:trace相当于C# Console.WriteLine,derive派生子构造器,method定义方法,__mgBase调用基“类”方法,指定名字。)

var A = Object.derive().method('g', function() {
	trace('a#g')
});
var B = A.derive().method('h', function() {
	trace(' >> in b#h');
	this.__mgBase('g');
	this.__mgBase('g');
	trace(' end b#h')
});
var C = B.derive().method('f', function() {
	trace(' >> in c#f');
	this.__mgBase('h');
	this.__mgBase('g');
	trace(' end c#f')
});

var x = new C;
x.g = function() {
	trace(' >> in own');
	this.__mgBase('f');
	this.__mgBase('h');
	trace('own')
}
x.g();
 >> in own
 >> in c#f
 >> in b#h
a#g
a#g
 end b#h
a#g
 end c#f
 >> in b#h
a#g
a#g
 end b#h
own

传统的方法是构造“基类链”,然后沿着积累链一级一级向上找——Qomo就是这么做的,而且效果很好。但是这样的缺点也很明显,每一次调用都要查找链,效率是一个大问题。

我们注意到每次__mgBase调用的时候,传入的this都是同一个,所以搜索到的__mgBase就也是同一个,于是就出现了“搜链”的状况——假如每次搜索基类时修改一下__mgBase,让它不一样呢?

想法很疯狂!那么就试试吧!

var mgBaseGen = function(bcl, original) {
	return function(nname) {
		var c = bcl,
			rv;
		while (!c.prototype.hasOwnProperty(nname)) c = c.baseConstructor;
		this.__mgBase = mgBaseGen(c.baseConstructor, this.__mgBase);
		try {
			rv = c.prototype[nname].apply(this, Array.prototype.slice.call(arguments, 1));
		} finally {
			this.__mgBase = original;
		};
		return rv;
	};
};

这个神奇的函数干的事情就是生成__mgBase供复写,它传入两个参数——搜索方法的“基类”和基类方法调用之后恢复的原有__mgBase。当__mgBase被调用的时候,它先是搜索基类对应的方法,然后把this的__mgBase复写,最后调用基类方法。等调用完后再把__mgBase改回来,然后return。try-finally的作用是无论基类方法调用的时候产生怎样的错误,都会保证把__mgBase改回来。(就像C#中经常出现的一样,不是吗?)Function的baseConstructor是一个由megamijs维护的属性,它代表基类(例子中,B的baseConstructor === A)。

但是,最早的__mgBase总得有吧?呵呵,先看一个工具方法(不做解释):

var containsOwnValue = function(ob, v) {
	for (var each in ob) if (ob.hasOwnProperty(each) && ob[each] === v) return true;
	return false;
}

接着是第二段好戏——当当当~~:

clz.prototype.__mgBase = function(name) {
	var c = clz, rv, cler = arguments.callee.caller;
	if (!containsOwnValue(this, cler)) {
		while (!containsOwnValue(c.prototype, cler)) c = c.baseConstructor;
		c = c.baseConstructor;
	};
	var original = this.__mgBase;
	this.__mgBase = mgBaseGen(c, original);
	try {
		rv = this.__mgBase.apply(this, arguments);
	} finally {
		this.__mgBase = original;
	};
	return rv;
};

这是Function.prototype.inherits里面的代码,第3-6行检测调用__mgBase的函数到底是对象“自己拥有”(hasOwnProperty)的方法还是原型上的。第六行之后可以保证c的原型上一定没有arguments.callee.caller。然后就是传统的复写,调用(注意这里和上面mgBaseGen生成的函数间的区别)。

总的来说,这种方案几乎可以完美的实现调用基类方法,但它的效率仍然不是很高(主要在第一步查原型使用了反射)。因此,我还是建议,珍惜生命,远离base。

(注:下源码可以svn这里:svn co https://megamijs.svn.sourceforge.net/svnroot/megamijs megamijs,主文件megami.js有上面的全部代码)
posted @ 2010-01-10 13:42  infinte  阅读(254)  评论(0编辑  收藏  举报