一切都要从公司里的一位老哥给我看的一段代码提及。。。javascript
@controller('/user') @auth @post('/login') async userLogin = (name, pass) => { @required // ... }
如下为对话:html
我:这不是修饰器吗(由于以前看到过@这个东西)老哥:还不错嘛,知道是修饰器,那你知道这一段想表达什么意思吗java
我:这是路由?(一脸懵逼,可是看到了/user和post还有/login,内心想难道这是路由)api
老哥:稳!async
我:震惊了,还可以这样写路由。不行,回去我要好好看看这个破@函数
由此开始了修饰器的学习~~~
嘤嘤嘤~~post
首先说明,修饰器在JavaScript中还处于提议阶段,目前还不可以被大部分环境支持,而且以后还有可能会改变。若是想要使用该特性请用Babel进行转码或者使用JavaScript的超集TypeScript
在ES6中增长了类的相关定义和操做(好比class和extends),这样方便了咱们单个类的操做,可是当咱们想要在多个不一样类之间共享、复用一些方法的时候,会发现变得不那么优雅,因此decorator被提了出来。学习
小小的demo:以@做为标识符,既能够做用于类,也能够做用于类属性ui
@decorator class Cat {} class Dog { @decorator run() {} }
既然decorator与类相关,咱们先了解一下类(这里只是简单介绍,想要详细了解的,请自行查阅相关资料,推荐阮一峰的ES6入门)this
class Cat { constructor(name) { this.name = name } say () { console.log("miao ~") } }
这实际上是一个语法糖,具体的实现是经过Object.defineProperty()
方法来操做的,该方法语法以下:
Object.defineProperty(obj, prop, descriptor) -> obj: 要在其上定义属性的对象 -> prop: 要定义或修改的属性的名称 -> descriptor:要被定义或修改的属性描述符 返回:传递给该方法的对象(即obj)
因此上面那个Cat的代码实际上在执行时是这样的:
function Cat() {} Object.defineProperty(Cat.prototype, "say", { value: function() {console.log("miao ~")}, // 该属性对应的值 enumerable: false, // 为true时,才可以出如今对象的枚举属性中 configurable: true, // 为true时,该属性描述符才可以被改变 writable: true // 为true时,value才能被赋值运算符改变 }) // 返回Cat.prototype
function isAnimal(target) { target.isAnimal = true return target // 返回的是传递给该函数的对象 } @isAnimal class Cat { // ... } console.log(Cat.isAnimal) // true
上面的代码基本等同于:
Cat = isAnimal(function Cat() {})
function readonly(target, name, descriptor) { descriptor.writable = false return descriptor } class Cat { @readonly say () { console.log("miao ~") } } let kitty = new Cat() kitty.say = function () { console.log("wow ~") } kitty.say() // miao ~
经过将descriptor属性描述符的writable设置为false,使得say方法只读,后面对它进行的赋值操做不会生效,调用的依旧是以前的方法。
有木有以为readonly()方法的参数似曾相识?它和上文介绍ES6中的类中提到的Object.defineProperty()
是同样的。
其实修饰器在做用于属性的时候,其实是经过Object.defineProperty
进行扩展和封装的。因此上面的代码其实是这样的:
let descriptor = { value: function() { console.log("miao ~") }, enumerable: false, configurable: true, writable: true } descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor Object.defineProperty(Cat.prototype, "say", descriptor)
当修饰器做用于类时,咱们操做的对象是类自己,当修饰器做用于类属性时,咱们操做的对象既不是类自己也不是类属性,而是它的描述符(descriptor)。
固然了,你也能够直接在target上进行扩展和封装。
function fast(target, name, descriptor) { target.speed = 20 let run = descriptor.value() descriptor.value = function() { run() console.log(`speed ${this.speed}`) } return descriptor } class Rabbit { @fast run() { console.log("running~") } } let bunny = new Rabbit() bunny.run() // running ~ // speed 20 console.log(bunny.speed) // 20
让咱们回到文章开始讲到的代码,它怎么阅读呢
@controller("/api/user") export class userController { @post("/add") @required({ body: ["telephone", 'key1'] }) async addUser(ctx, next) {} @get("/userlist") async userList(ctx, next) {} }
让咱们先看@controller("/api/user")
const symbolPrefix = Symbol('prefix') export const controller = path => target => (target.prototype[symbolPrefix] = path)
做用是将/api/user
做为prefixPath,此时target为userController {}
,在该target的原型上设置path
再接着看@post("/add")
// 如下代码省略了部分细节 ... // export const post = path => (target, name, descriptor) => {} routerMap.set({ target: target, ...conf }, target[name]) // name为addUser for(let [conf, func] of routerMap) { // conf为{target, path} 该target为userController {},path为@post()传递进来的参数 let prefixPath = conf.target[symbolPrefix] // 为 /api/user let routePath = prefix + path // 为 /api/user/add } // 获得了api路径(routePath),也获得了该api路径所执行的方法(func) // get同理 ...
原理基本上都是相似的,处理修饰器传递的参数,获得本身想要的结果。
额,经历过上面的知识了解,应该能大概够理解这段代码了吧~
修饰器容许你在类和方法定义的时候去注释或者修改它。修饰器是一个做用于函数的表达式,它接收三个参数 target、 name 和 descriptor , 而后可选性的返回被装饰以后的 descriptor 对象。
你也能够叠加使用,就像这样
@post("/add") @required({ body: ["telephone", 'key1'] }) async xxx {}