Functor
仿函式(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 實現
那麼怎麼實現呢?我之前寫了一篇文章,裡面說 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`
最後,這只是個腦洞!每個語言都有自身的規律和方法論。不要真的在專案裡這麼寫,除非你的專案目的就是創造漂亮的語法。