我相信读到本文标题的人基本上是懵 B 的,我实在想不出更好的表述方法。简单说,我想实现这么一个功能:
假设有一个对象 foobar,他的方法支持链式调用。比如这样:
var foobar = new Foobar();
foobar.segment(1).fault(2);
注意 segment
和 fault
并不一定返回 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?想不出来。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。