还是简单的问题:基“类”方法调用。下面的代码给出了一个十分极端的例子(使用了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有上面的全部代码)