我不得不赞叹的methodize和curry
其实先前就有听闻它核心的设计模式--大名鼎鼎的Functional Programing。不过真正去接触之后才真的发现,原来确实,真的,与传统的以对象作为第一型的的模式相比,它有着另一番天地。良好的使用这种模式不仅可以保证不污染基元(基函数),而且通常还可以达到很多出人意料的便利和效果。。。
其实Functional的思想在javascript这个领域里也不是最近才兴起,一直以来都有被运用。prototype.js里面一直都有methodize和curry的方法,jquery里面没有明显的提出这样类似的理念和方法,可是冥冥之中不难发现,它基于jquery返回的列表里面对所有元素的批量操作,都是基于each的,而恰恰这个each或者是类似的map,其实本来就是Functional典型的应用。
函数包装,这个概念最近又重新开始流行起来了...我想,光是基于这个结果,其实就应该恭喜QWrap团队了... :)
月影MM说:QWrap的methodize的最大价值就在于能把一个静态函数变换成为一个对象的方法甚至是一个Wrap的方法,这样就从纯静态设计上去规避对核心的污染,而在使用的时候把必要的“污染”交给了框架的retouch机制。
咱们先不论他们的retouch是怎么怎么的。先看看它的这样的一种理念到底能在我们编码的时候带来什么样的便利,通过函数来组合函数的方式究竟会有多巧妙。
我前几天纠结了一个小问题,问题说得现实一点,就拿使用jQuery的人来说,比如我用$去选择我想要的dom对象,那么获取到的结果必然是一个包含匹配的dom元素的一个列表。哪怕是取到的元素只有一个,依旧是[HtmlElement],对于这样的一个返回结果。那么我们就已然逃不出“你使用了一个jquery提供的方法,就很难再完全的从jquery里面抽离出来”的这个怪圈了...因为你面对这样一个对象集合,你不再能用htmlElement原生的方法去做一些事
if (!!$('#test')) {
//todo
}
类似这样的东西都不再行得通(好在jquery想的够周到,所有dom原生的方法基本都被他重写了一遍,可以用它的方法在它的世界里横行无阻...不过垄断的国度总觉得不够自由!)。而有些时候,我们往往就只需要一个类似于document.getElementById() 这样的简单的东西。那我们改怎么办呢?$('#test')[0] ?? 确实是有够山寨...
恩恩,比如我们现在有个需求,可能和上面的情况还不太一样。需要一个根据id来获取dom对象的一个方法,当只有一个参数的时候,直接返回这个匹配的dom对象,当有多个参数的时候,返回匹配所有参数的对象列表。
那么,接下来,简单的,直观的,我们可能这么做:
var a = arguments;
if (a.length <= 1) return document.getElementById(a[0]);
else {
var ret = [];
for (var i=0; i<a.length; i++) {
ret.push(document.getElementById(a[i]));
}
return ret;
}
}
唉,这样子的方式,总觉得好死气沉沉。而且完全耦合...
月MM的博客里有点东西或许能给我们一点启示:
var _this = this,
slice = Array.prototype.slice;
return function () {
var oldArgs = slice.call(arguments, 0, _this.length),
moreArgs = slice.call(arguments, _this.length);
var ret = _this.apply(this, oldArgs);
if (moreArgs.length > 0) {
ret = fn.call(this, arguments.callee, ret, moreArgs);
}
return ret;
}
}
//暂以取id为例
var _$;
(function () {
function getMore(fn, ret, args) {
return [ret].concat(fn.apply(this, args));
}
_$ = function (id) {
return document.getElementById(id);
}.addParams(getMore);
})();
// eg.
<div id="a"></div>
<div id="b"></div>
_$('a') // HtmlDivElement[id=a]
_$('a', 'b') //[HtmlDivElement[id=a], HtmlDivElement[id=b]]
这是多么神奇又有用的一件事。我第一次看到这种实现方式的时候,心里的确忍不住一赞啊...
给函数一个包装器,这个包装器的作用在getMore里可以看到,通过迭代求解的方式让多余的参数发挥作用。
于是乎,在不污染函数基元,亦即function(id){return document.getElementById(id)} 的前提下,扩展除了_$这个新函数。通过函数来组合函数,这确实很让人兴奋。
【函数重载?】
回头看看这样的东西,虽然它的本质不是通过参数的不同来执行不同的函数体,但确实是像走在重载的边缘。
js由于是弱类型的语言,函数类型无法通过类型区分,自然也不允许同名函数共存。 但依旧可以通过简单的词法分析模拟出类似的功能。说到这里,前两天刚好碰巧看到了LC同学用了类似的方式对函数作了词法分析,通过函数参数的长度和类型,进行模式匹配,执行不同的函数体。思路其实都是和QWrap一脉相承。百度团队的LC同学和Cat Chen都做了类似的说明:
我相信Functional的设计模式已经在他们团队里流淌了很久了....
【我不得不说的后续...】
就拿上面的重载的例子来说,事事都有两面性,把这种模式匹配单独拿出来之后,在某些特殊的时候反而很难达到自己想要的易用性。举个简单的例子,就拿animate来说。通常为了保证良好的通用性,会给这个方法较完善的配置: config, durationTime, tween, ease, callback,通常这样几个配置是必需的,config用来配置变化的属性和值如{left:500, width:100}之类,durationTime,变换持续时间,tween和ease都是缓动选择,callback回调。
面对这样的配置,我希望的结果是,为了保证良好的易用性,除了config这种必须得参数外,其余参数个数随意,参数位置随意,那如果真正按照如此的模式匹配的话,我需要多少个模式呢?这样的匹配又必然会造成多少冗余的代码呢?
所以,我很同意LC中肯的那句(当然这种形式有利有弊,使用者自己权衡吧)——