关于函数的Wrapper

前些天在写后端的角色鉴权,访问权限在 Controller 处定义,原本的想法是使用装饰器实现,后来通过了 Builder 的方式完成。
大致的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Builder {
constructor (router) {
this.router = router
this.options = {}
}

handler (fn) {
if (typeof fn !== 'function') {
throw new Error('Handler must be a function')
}
this.options.handler = fn
return this
}

role (action, indexRole) {

}
}

通过 Builder 构造出 Controller,handler 指定了需要执行的函数,role 的功能是对已有 handler 进行包装(目前是顺序敏感的,必须放在 handler 之后,包装成对 handler 的处理函数估计可以解决),加入角色校验的相关代码,这里就涉及到了函数的Wrapper。

1
2
3
4
this.options.handler = function (ctx, next) {
// 角色校验的实现
return this.options.handler()
}

这段代码乍一看没什么问题,实际上形成了一个闭包,function还没有执行,this.options.handler还未确定,所以成了递归函数。

那么解决方式有两种:

1
2
3
4
5
6
7
// 方法1
let origin = this.options.handler

this.options.handler = function (ctx, next) {
// 角色校验的实现
origin(ctx, next)
}

先保存原有的handler,之后定义一个wrapper对原有handler进行包装。

1
2
3
4
5
6
7
8
// 方法2
this.options.handler = new Proxy(this.options.handler, {
apply (target, thisArg, argumentsList) {
const [ctx, next] = argumentsList
// 角色校验的实现
return target.apply(thisArg, argumentsList)
}
})

Proxy是ES6加入的新特性,能通过handler对象(和前文的handler不一样)的规则对已有target进行改造,整体上是一个代理模式,这里使用的apply实际上是对target函数的包装。