资讯专栏INFORMATION COLUMN

实现一个奇怪的需求:如何将一串 js 链式调用存储在一个函数或对象中,以备未来调用?

jerry / 2004人阅读

摘要:简单说,我想实现这么一个功能假设有一个对象,他的方法支持链式调用。本来是立即执行的代码,通过的包装,成了一个函数,我可以把存储起来在任意时刻调用,相当于执行检查。

我相信读到本文标题的人基本上是懵 B 的,我实在想不出更好的表述方法。简单说,我想实现这么一个功能:

假设有一个对象 foobar,他的方法支持链式调用。比如这样:

var foobar = new Foobar();

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

注意 segmentfault 并不一定返回 this,但是要返回一个同类型对象,否则 foobar.segment(1).fault(2).segment(3).fault(4) 这样的代码就可能不合法。这是我特别添加的约束,满足这一条下面的文章和才有意义

现在我想实现一个包装函数 makePolicy,实现这样的语法(假设 Foobar 所有方法都支持链式调用):

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)这句代码。如果没有这个需求,那直接这样写好了:

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) 检查。

需求讲清楚了,剩下的就是开脑洞了。对照下面的代码梳理一下思路:

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(...) 即可执行相应功能。

第二,将 policy 设计为一个 Policy 类的实例,因为 policy 可能会有很多方法,这些方法是在 makePolicy 函数中从输入类原型上按照名字一个一个扒下来的,比较适合放在 prototype 中。以及,根据输入类的不同,Policy 应该在 makePolicy 中动态生成,然后立即 new 一个实例返回,这样我们可以随时生成任意类的 policy 包装。

综合以上思考,我们要实现的接口改为这样:

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?想不出来。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87805.html

相关文章

  • 第一话·彻底搞清数据结构·逻辑结构&存储结构

    摘要:数据元素数据元素是数据的基本单位,在计算机中通常作为整体处理。数据项数据项是构成数据元素的不可分割的最小单位数据对象数据对象是性质相同的数据元素的集合,是数据的子集。   L:大仙,我刚刚入门数据结构大门,老是分不清逻辑结构和存储结构 ?:行吧小子,那今天就带你好好区分这两种结构的区...

    oogh 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程出现和 5 种更好 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    maochunguang 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程出现和 5 种更好 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    浠ラ箍 评论0 收藏0
  • JavaScript 未来:它还少些什么?

    摘要:例如通过哈希表映射需要一个操作来检查值是否相等,另一个操作用于创建哈希码。如果使用哈希码,则对象应该是不可变的。模式匹配提案目前处于第阶段。在本文,我们研究其中的智能管道另一个提议被称为。更强大,更重量级,并附带自己的数据结构。 翻译:疯狂的技术宅原文:http://2ality.com/2019/01/fut... 本文首发微信公众号:jingchengyideng欢迎关注,每天...

    layman 评论0 收藏0
  • javascript 回调函数 整理

    摘要:回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。参考理解回调函数理解与使用中的回调函数这篇相当不错回调函数 为什么写回调函数 对于javascript中回调函数 一直处于理解,但是应用不好的阶段,总是在别人家的代码中看到很巧妙的回调,那时候会有wow c...

    xiaowugui666 评论0 收藏0

发表评论

0条评论

jerry

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<