函数包装是一个的用来封装函数功能的技巧。
如果想要继承或者创建一个新的函数的时候,通过函数包装可直接实现。
最有价值的一个场景是:在我们想要重写(override)一些已经存在的函数的情况下, 并且可以保持在原始函数中那些有用的部分可以在被包装后仍然有效。
另外一个普遍的场景是:兼容不同的浏览器。
例如在Opera浏览器中,有一个获取title属性的bug。
Prototype类库采用了函数包装这个技术来对付这个bug。
为了防止在readAttribute()函数中,过多的出现if/else这样的代码快(这是一个比较丑陋,并且不是一个特别好的分割逻辑的方式)。
Prototype选择利用函数包装(function wrapping)来重写(override)那个老的方法,但是原始函数中的那些正常的逻辑还应该有效。
function wrap(object, method, wrapper){ var fn = object[method]; //#1 return object[method] = function(){ //#2 return wrapper.apply(this, [fn.bind(this)].concat( Array.prototype.slice.call(arguments))); }; }; // Example adapted from Prototype if (Prototype.Browser.Opera){ //#3 wrap(Element.Methods, "readAttribute", function(original, elem, attr){ return attr == 'title' ? elem.title : original(elem, attr); }); }
//#1 缓存原始函数
//#2 返回新的包装函数
//#3 实现函数包装
我们来分析一下wrap()函数是如何工作的。
在传入wrapp()的参数中包括:
1.一个基础的对象(object)
2.这个基础对象想要被包装的方法(method)
3.新的包装函数(wrapper)
首先,我们将原始方法保存在变量fn中(#1);
我们在后面会通过匿名函数的闭包(anonymous function‟s closure)来调用它。
然后我们用一个新的函数来重写这个方法(#2)
这个新的函数执行了之前传进来的wrapper函数(通过一个闭包),并且传给它一个重新构造的参数列表.
在构建这个参数列表时,我们想让第一个参数是我们正在重写的原始函数(original function),
这样一来,我们创建一个数组来连接原始函数(原始函数的上下文已经被绑定,通过bind()函数,我们之前在例5.3中创建的,同样适用于包装函数),然后将原始参数追加到这个数组中。
在apply()函数中,如我们所见,用这个数组作为了他的参数列表。
结论就是,wrap()函数是一个可以重用的函数,我们可以用一种比较隐蔽的方式, 来重写任何对象的方法中的已有的功能。 这又是一个彰显闭包威力的案例。