開個腦洞,如何使用 javascript 實現“仿函式”(Functor)?

Y3G發表於2019-05-10

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`

最後,這只是個腦洞!每個語言都有自身的規律和方法論。不要真的在專案裡這麼寫,除非你的專案目的就是創造漂亮的語法。

相關文章