如果曾经使用过 Python,尤其是 Django 的话,应该对 装饰器
的概念有些许的了解。在函数前加 @user_login
这样的语句就能判断出用户是否登录。
装饰器可以说是解决了不同类之间共享方法的问题(可以看做是弥补继承的不足)。
A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.
这句话可以说是对装饰器的非常漂亮的解释。
在未来的 JavaScript 中也引入了这个概念,并且 babel 对他有很好的支持。如果你是一个疯狂的开发者,就可以借助 babel 大胆使用它。
装饰器目前在浏览器或者 Node 中都暂时不支持,需要借助 babel 转化为可支持的版本
按照官网的 说明 安装:
npm install --save-dev babel-cli babel-preset-env
在 .babelrc
中写入:
{ "presets": ["env"] }
如果不装插件执行 babel-node a.js
或者 babel a.js > b.js
的话都会提示:
SyntaxError: a.js: Decorators are not officially supported yet in 6.x pending a proposal update. However, if you need to use them you can install the legacy decorators transform with: npm install babel-plugin-transform-decorators-legacy --save-dev and add the following line to your .babelrc file: { "plugins": ["transform-decorators-legacy"] } { The repo url is: https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy.
按照说明,安装 babel-plugin-transform-decorators-legacy 插件:
npm install babel-plugin-transform-decorators-legacy --save-dev
.babelrc :
{ "presets": ["env"], "plugins": ["transform-decorators-legacy"] }
这样准备工作就完成了。
babel-cli 安装会有 babel 和 babel-node 的工具生成,通过 babel a.js > b.js 可以转化 JS 版本为低版本 JS,通过 babel-node a.js 可以直接执行 JS
function decorateArmour(target, key, descriptor) { const method = descriptor.value; let moreDef = 100; let ret; descriptor.value = (...args)=>{ args[0] += moreDef; ret = method.apply(target, args); return ret; } return descriptor; } class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻击力 this.hp = hp; // 血量 } toString(){ return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`当前状态 ===> ${tony}`); // 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3
装饰器接收三个参数,这三个参数和 Object.defineProperty() 基本保持一致,分别表示:
再看上面的代码:
Man {}
这个类 init()
Object.defineProperty()
一样: {value: [Function], writable: true, enumerable: false, configurable: true}
descriptor.value = (...args)=>
中的 args 是一个数组,分别对应 def、atk、hp,给 def + 100,然后再执行 method
(即被装饰的函数),最后返回 descriptor
。
这样就给 init
函数包装了一层。
有时候,需要给装饰器传参数:
function decorateArmour(num) { return function(target, key, descriptor) { const method = descriptor.value; let moreDef = num || 100; let ret; descriptor.value = (...args)=>{ args[0] += moreDef; ret = method.apply(target, args); return ret; } return descriptor; } } class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour(20) init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻击力 this.hp = hp; // 血量 } toString(){ return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`当前状态 ===> ${tony}`); // 输出:当前状态 ===> 防御力:22,攻击力:3,血量:3
上面两个装饰器都是对类里面的函数进行装饰,改变了类的静态属性;除此之外,还可以对类进行装饰,给类添加方法或者修改方法(通过被装饰类的 prototype):
function decorateArmour(num) { return function(target, key, descriptor) { const method = descriptor.value; let moreDef = num || 100; let ret; descriptor.value = (...args)=>{ args[0] += moreDef; ret = method.apply(target, args); return ret; } return descriptor; } } function addFunc(target) { target.prototype.addFunc = () => { return 'i am addFunc' } return target; } @addFunc class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour(20) init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻击力 this.hp = hp; // 血量 } toString(){ return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`当前状态 ===> ${tony}`) console.log(tony.addFunc()); // 输出:当前状态 ===> 防御力:22,攻击力:3,血量:3 // 输出:i am addFunc
不建议装饰,因为变量提升会产生系列问题
装饰器的使用场景基本都是 AOP 。大多数日志场景都可以使用此种模式,比如这里一个简单的 日志场景 。
对于纯前端来说,也有很多用途,比如实现一个 react 的 lazyload,就可以使用装饰器修饰整个 class。
同时,也有一些库实现了常用的装饰器,比如: core-decorators.js