实现一个奇怪的需求:如何将一串 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?想不出来。

相关文章
相关标签/搜索