仿函数(Functor)是 C++ 里面一个重要的概念,简而言之就是使用重载了 operator() 运算符的对象模仿函数的行为,带来的收益是仿函数可以携带自身状态,普通的 C++ 函数不是对象,做不到这一点。
js 中的函数本身就是对象,可以携带自身状态,另外还有 curry 化等函数式编程的方法让函数缓存状态,基本上没有仿函数存在的必要。最简单的你可以这样写:
function foobar() { return foobar.a; } foobar.a = 1; var b = foobar(); // b=>1
这样,foobar 就携带了自身状态 a,并且可以在函数体重访问 a。
但是这里有一个问题:函数体中 foobar.a 这一句是利用闭包实现的,其中 foobar 这个引用被写死了,从效果上看 foobar 成了一个单例 。如果我想要多个 foobar 实例怎么办呢?
以及,我比较喜欢 this 指针,而不是闭包。面对这种情况,我更喜欢这样的写法:
// 伪代码 function foobar() { return this.b; } foobar.setB = function (val) { this.b = val } var foo = new foobar; foo.setB(1); var b = foo(); // b=>1
那么怎么实现呢?我之前写了一篇文章,里面说 js 不容易实现类似的概念。但是当时我没细想,今天试了一下其实变动一下接口,还是能实现类似效果的。
基本的原理就是这样:
function f() {...} var functor = f.bind(f);
让一个函数 bind 它自己,这样它不就能用 this 访问自己了吗?但是这里还有个问题,bind 的返回结果并不是 f 自身而是另一个函数,functor 的持有者在外部访问不到 f。所以这里还要用 js 的新 api defineProperty 处理一下,使得对 functor 的某些属性访问,转移到 f 上去。
完整的实现如下:
function makeFunctor(fn, props) { function thisFn() { return fn.apply(this, Array.prototype.slice.call(arguments)); } var ret = thisFn.bind(thisFn); for (var key in props) { if (!props.hasOwnProperty(key)) { continue; } Object.defineProperty(ret, key, { configurable : true, enumerable : true, get : function () { return thisFn[key]; }, set : function (value) { thisFn[key] = value; } }); ret[key] = props[key]; } return ret; }
通过 makeFunctor,我们可以通过一个函数 fn 创建很多个 functor,每一个都有自身的状态,互不影响。并且在 fn 中我们可以使用 this 访问自身状态。比如:
function hello () { alert('Hello, ' + this.name); } hello.create = function () { makeFunctor(hello, { name : 'Tom' }); } var ftHello = hello.create(); var ftHello2 = hello.create(); ftHello(); // Hello, Tom' ftHello.name = 'Jack'; ftHello(); // Hello, Jack' ftHello2(); // Hello, Tom'
最后,这只是个脑洞!每个语言都有自身的规律和方法论。不要真的在项目里这么写,除非你的项目目的就是创造漂亮的语法。