在设计javascript的继承体系时,有一个重要需求,方法链。通俗地说,说是在方法中调用父类的同名方法。类似java的this.super().method()。如何把父类的同名方法包装到子类中呢?这就要用到wrapper函数。之所以叫wrapper,而不是wrap,因为它比wrap更加wrapper。比如像Ext那种深度继承的结构中,如果父类没有找祖父,祖父没有找曾祖父,沿着原型链层层上溯,以获取它所需要的功能。此外,wrapper函数在jQuery也有应用,好像分为三种wrapAll,wrapinner,wrap,专门用来对付IE的table或其他DOM。可能还有其他用处,不管了,先看如何实现它。
一个普通的函数
var greeting = function(world){ return "hello " + world +"!"; }; alert(greeting("world"));
我们把它塞进更深一层的作用域,非bind函数。
var wrap= function(fn){ return function(){ return fn.apply(null,arguments); }; };
但这只是延迟了它的执行时间而已。上面函数中的null,也可以换成window。
var wrap= function(fn){ return function(){ return fn.apply(window,arguments); }; };
因为总要人去调用函数的,null没有此能力,就由window上。现在我们就是要在这个位置上做文章,把换成this。如果没有进一步的改进,这里的this还是window的替身。下面就开始复杂了,先分解一下写法,降低阅读难度,就像jQuery那样把它掰成三部分:
var wrapper= function(fn){//这里改一下名。 var temp = function(){ return fn.apply(this,arguments); }; return temp; };
//fn为原函数 //temp为改装了的函数 //wrapper为包装工厂,只运作一次 //wrap为改装了的函数的属性,它的参数和wrapper一样为函数,但能运行无数次, //把原改装了的函数内置为新增函数的内部函数。 var wrapper= function(fn){ var temp = function(){ return fn.apply(this,arguments); }; temp.wrap = function(callback){ var prev = fn; fn = function(){ return callback.apply(prev,arguments); }; }; return temp; };
这样就可以如下神奇效果:
可以看出,this总是为原来的同名函数(p),也就是说,我们可以称之为父方法,这样也super关键字或者相关的代替品也不用了,轻松调用原来覆盖了父类的方法。因此jQuery同学单是用它来处理DOM真是大材小用。Prototype同学就做得不错了,不过代码写得比较艰涩:
wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } },
它的方法链设计:
for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); } this.prototype[property] = value; }