實現一個奇怪的需求:如何將一串 js 鏈式調用存儲在一個函數或對象中,以備將來調用?

我相信讀到本文標題的人基本上是懵 B 的,我實在想不出更好的表述方法。簡單說,我想實現這麼一個功能:javascript

假設有一個對象 foobar,他的方法支持鏈式調用。好比這樣:java

var foobar = new Foobar();

foobar.segment(1).fault(2);

注意 segmentfault 並不必定返回 this,可是要返回一個同類型對象,不然 foobar.segment(1).fault(2).segment(3).fault(4) 這樣的代碼就可能不合法。這是我特別添加的約束,知足這一條下面的文章和纔有意義git

如今我想實現一個包裝函數 makePolicy,實現這樣的語法(假設 Foobar 全部方法都支持鏈式調用):github

var policy = makePolicy(Foobar).segment(1).fault(2);
var foobar = new Foobar();

policy(foobar); // 等效於調用 foobar.segment(1).fault(2)

這裏比較難實現的,就是 makePolicy(Foobar).segment(1).fault(2)這句代碼。若是沒有這個需求,那直接這樣寫好了:segmentfault

var policy = function (that) {
    var newThat = Foobar.prototype.segment.call(that, 1);
    Foobar.prototype.fault.call(newThat , 2);
};
var foobar = new Foobar();

policy(foobar); // 等效於調用 foobar.segment(1).fault(2)

之因此有這樣的需求,主要是爲了完善 js 參數檢查器(這篇文章)中的邏輯鏈接的功能。爲了方便使用,我想寫出這樣的接口:數組

// 檢查 param 是否在區間(1,3) 與 (2,4) 的交集內
check(param, 'param').and(check.policy.gt(1).lt(3), check.policy.gt(2).lt(4));

// 檢查 param 是否在區間(1,2) 與 (3,4) 的並集內
check(param, 'param').or(check.policy.gt(1).lt(2), check.policy.gt(3).lt(4));

function myCheck(obj) {
    return obj.length > 4;
}

// 檢查 param 是不是數組而且長度大於 4
check(param, 'param').and(check.policy.is('array'), myCheck);

// 檢查 param 是否*不是*[1,3]之間的偶數(即2)
check(param, 'param').not.and(
    check.policy.is('number').not.lt(1).not.gt(3),
    function (obj) {
        return obj % 2 === 0;
    });

上面的代碼中,check.policy.gt(1).lt(3) 就是我想實現的語法功能。原本 check(a).gt(1).lt(3) 是當即執行的代碼,經過 policy 的包裝,var fn = check.policy.gt(1).lt(3) 成了一個函數,我能夠把 fn 存儲起來在任意時刻調用,至關於執行 gt(1).lt(3) 檢查。app

需求講清楚了,剩下的就是開腦洞了。對照下面的代碼梳理一下思路:函數

var policy = makePolicy(Foobar).segment(1).fault(2);
var foobar = new Foobar();

policy(foobar); // 等效於調用 foobar.segment(1).fault(2)

首先,我實驗了一下,policy 的語法設想實際上很難實現,由於 js 中沒有方便的語法表達「函數類」、「函數實例」這樣的概念,因此 policy 不適合設計爲一個函數,妥協一下,把 policy 設計爲一個 包含 exec 方法的對象,調用 policy.exec(...) 便可執行相應功能。this

第二,將 policy 設計爲一個 Policy 類的實例,由於 policy 可能會有不少方法,這些方法是在 makePolicy 函數中從輸入類原型上按照名字一個一個扒下來的,比較適合放在 prototype 中。以及,根據輸入類的不一樣,Policy 應該在 makePolicy 中動態生成,而後當即 new 一個實例返回,這樣咱們能夠隨時生成任意類的 policy 包裝。prototype

綜合以上思考,咱們要實現的接口改成這樣:

var policy = makePolicy(Foobar);
var functor = policy.segment(1).fault(2); // fn 存儲了鏈式調用的路徑和參數
var foobar = new Foobar();

functor.exec(foobar); // 等效於調用 foobar.segment(1).fault(2)

下面是簡化的實現代碼:

/**
 * 生成 policy
 * @param proto 要生成 policy 的類原型
 * @return 生成的 policy 實例
 */
function makePolicy(proto) {
    function Policy(fn, prev) {
        this.fn_ = fn;
        this.prev_ = prev;
    }

    Policy.prototype.exec = function (that) {
        var myThat = that;
        var prev = this.prev_;
        var fn = this.fn_;

        if (prev) {
            myThat = prev.exec(that);
        }

        return fn(myThat);
    };

    for (var key in proto) {
        if (proto.hasOwnProperty(key)) {
            Policy.prototype[key] = (function (fnName) {
                return function () {
                    var self = this;
                    var args = Array.prototype.slice.call(arguments, 0);

                    return new Policy(function (that) {
                        return proto[fnName].apply(that, args);
                    }, self);
                }
            })(key);
        }
    }
    
    return new Policy();
}

由上面的代碼可知,當咱們在鏈式調用函數時,順序是從左到右。而 policy 在運行時,是先調用最右邊的 policy 而後經過 prev_ 指針一路回溯到最左邊,而後再從左到右執行下來。

有了上面的實現,js 參數檢查器(這篇文章)的功能差很少就完整了,有興趣的同窗能夠在這裏看到具體實現。不過目前的實現仍然有許多限制,在目前的基礎上,咱們其實能夠實現更加通用,無所謂是否是鏈式調用的 policy 語法,等我作出來在寫文章彙報。

最後請教一個問題:policy 這個名字是我瞎起的,這樣的功能應該叫什麼名字呢?roadmap?blueprint?想不出來。

相關文章
相關標籤/搜索