
Decorator (装饰器模式)
在面向对象(OOP)的设计模式中,Decorator 被称为装饰模式。OOP 的装饰模式需要通过继承和组合来实现。
通过装饰器动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活;它允许向一个现有的对象添加新的功能,同时又不改变其结构。
Javascript 中的 Decorator 源于 python 之类的语言。
A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.
def decorator(func):
print("decorator")
return func
def func():
print('func')
func = decorator(func)
func()
@decorator
def func2():
print("func2")
func2()
点击 python 在线运行环境 查看运行结果。
这里的 @decorator
就是装饰器,利用装饰器给目标方法执行前打印出" decorator"
,并且并没有对原方法做任何的修改。
配置环境
decorator 还在草案阶段,所以需要 babel 支持下面给出几种方式
babel-在线编辑环境 (需要打开 F12 )
使用 babel 编译并执行
npm install --save-dev @babel/core \
@babel/cli \
@babel/preset-env \
@babel/plugin-proposal-decorators \
@babel/plugin-proposal-class-properties
.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
npx babel test.js | node -
在 node 环境下运行
在上述配置的基础上再执行以下命令
npm install --save-dev @babel/register @babel/polyfill
新建index.js
require("@babel/register")();
require("@babel/polyfill");
require('./test')
执行命令
node index
Javascript 中的 Decorator
从 Class 看起
es6
中的 class 可以使用 Object.defineProperty
实现,代码如下:
class Shopee {
isWho() {
console.log("One of the largest e-commerce companies in Southeast Asia");
}
}
function Shopee() {}
Object.defineProperty(Shopee.prototype, "isWho", {
value: function() {
console.log("One of the largest e-commerce companies in Southeast Asia");
},
enumerable: false,
configurable: true,
writable: true
});
new Shopee().isWho();
ES7 Decorator
在 ES7 中的 Decorator 可以用来装饰 类 || 类方法 || 类属性
。
修饰类
function isAnimal(target) {
target.isAnimal = true;
return target;
}
@isAnimal
class Cat {}
console.log(Cat.isAnimal); // true
如果把 decorator 作用到类上,则它的第一个参数 target
是 类本身
。
所以针对 class 的 decorator ,return 一个 target 即可。
那么当一个类有多个装饰器是怎么样的呢?
function dec_1(target) {
target.value = 1;
console.log("dec_1");
return target;
}
function dec_2(target) {
target.value = 2;
console.log("dec_2");
return target;
}
@dec_1
@dec_2
class Target {}
console.log(Target.value);
// dec_1
// dec_2
// 1
decorator 的执行顺序是 dec_2 -> dec_1
,且修改的目标属性是同一个属性时最后执行的会覆盖前一个,通过 babel 转译得到如下代码:
var _class;
function dec_1(target) {
target.value = 1;
console.log("dec_1");
return target;
}
function dec_2(target) {
target.value = 2;
console.log("dec_2");
return target;
}
let Target =
dec_1((_class = dec_2((_class = class Target {})) || _class)) || _class;
console.log(Target.value);
decorator 修饰 class 的本质就是函数的嵌套,可以从两个方面来看:
- 如果代码中函数的嵌套层级过多,导致类似 callback 或者 .then 时的死亡嵌套,可以使用 decorator 展开,变成平级的结构。
- class 使用 extend ,在多个不同类之间共享或者扩展一些方法或者行为的时候 ,层级结构会变得复杂,很难一眼就看出该 class 实际拥有了哪些方法,哪些行为已经被扩展或修改。使用 decorator 可以更加优雅地解决这个事情。
修饰类属性 || 类方法
我们利用修饰器使该方法不可写
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class FE {
@readonly
say() {
console.log("javascipt");
}
}
var leo = new FE();
leo.say = function() {
console.log("C++");
};
leo.say();
// javascipt
我们将以上代码使用 ES5 实现 :
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
function FE() {}
let descriptor = {
value: function() {
console.log("javascipt");
},
enumerable: false,
configurable: true,
writable: true
};
descriptor = readonly(FE.prototype, "say", descriptor) || descriptor;
Object.defineProperty(FE.prototype, "say", descriptor);
var leo = new FE();
leo.say = function() {
console.log("C++");
};
leo.say();
从上述代码可以看出,对于修饰类方法的 decorator 形参和 Object.defineProperty
的属性值一致
Object.defineProperty(object, propertyname, descriptor)
/**
* 装饰者
* @param {Object} 类为实例化的工厂类对象
* @param {String} name 修饰的属性名
* @param {Object} desc 描述对象
* @return {descr} 返回一个新的描述对象
*/
function decorator(target,name,desc){}
根据 ES7 decorate-constructor ,Decorator function 可以不需要 return target/descriptor, 但是建议在书写中带上默认的 return。
decorator 为什么没有支持 function
在 babel 中尝试使用 decorator 装饰方法会的到以下报错。

看一段代码
var decorator = function(){
conslo.log(decorator)
}
@decorator
function target(){}
// js 存在变量的提升,会得到一下代码
var decorator
@decorator
function target(){}
decorator = function(){
conslo.log(decorator)
}
// 当 decorator 执行时,decorator 还是 undefined
由于 Javascript 中的变量提升问题,导致 decorator 的实现会变得比较复杂。
尤其在使用模块化编程时, var some-decorator = required('./some-decorator')
使用这个 some-decorator 修饰 function ,必然存在变量提升。
也许后续会出现修正 js 中变量提升的写法,类似于:
@deco let f() {}
@deco const f() {}
......
对 Decorator 传参
const dec = skill => target => {
target.skill = skill;
return target;
};
@dec("nodejs")
class FE {}
console.log(FE.skill);
实践
React-redux
class MyReactComponent extends Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
connect(mapStateToProps, mapDispatchToProps)
的调用会 return
一个 function (target){}
所以我们可以将 connect 函数简写成 decorator
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends Component {}
core-decorators.js
使用 npm install core-decorators --save
,然后使用上述"配置环境" 中的 2、3 点。
- @autobind
class Person {
getPerson() {
return this;
}
}
let person = new Person();
const { getPerson } = person;
console.log(getPerson() === person); // false
console.log(person.getPerson() === person); // true
由于 const { getPerson } = person;
将 getPerson
指向了全局,所以 getPerson() === person
为 false
, 我们使用 autobind
。
import { autobind } from "core-decorators";
@autobind
class Person {
getPerson() {
return this;
}
}
let person = new Person();
const { getPerson } = person;
console.log(getPerson() === person); // true
-
@readonly 可以使 property or method 只读。
-
@override 可以检测改方法是否是重写的方法,方法名和参数名与父级保持一致,为重写。
import { override } from 'core-decorators';
class Parent {
speak(first, second) {}
}
class Child extends Parent {
@override
speak() {}
// SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
}
// or
class Child extends Parent {
@override
speaks() {}
// SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
//
// Did you mean "speak"?
}
- @deprecate 可标记该方法已被丢弃
import { deprecate } from 'core-decorators';
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
facepalmHarder() {}
}
let person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
//
mixin-decorator
koa2 decorator
提供 github 使用 koa-with-decorator。
登录检验
// decorate 和 convert 会被复用 只写一次
const decorate = (args, middleware) => {
let [target, key, descriptor] = args;
target[key].unshift(middleware);
return descriptor;
};
const convert = middleware => (...args) => decorate(args, middleware);
export const auth = convert(async (ctx, next) => {
if (!ctx.session.user) {
return (ctx.body = {
success: false,
code: 401,
err: "登录信息失效,重新登录"
});
}
await next();
});
路由装饰
const isArray = c => (_.isArray(c) ? c : [c]);
const symbolPrefix = Symbol("prefix");
// 存储所有路由信息
const routerMap = new Map()
// 为什么使用 target[key] ?
const router = conf => (target, key, descriptor) => {
routerMap.set({
target: target,
...conf
}, target[key])
}
const controller = path => target => (target.prototype[symbolPrefix] = path)
const get = path => router({
method: 'get',
path: path
})
@get @auth 的使用
const router = new Router();
const app = new Koa();
@controller('/admin')
export class adminController {
@get('/movie/list')
@auth
async getMovieList (ctx, next) {
console.log('admin movie list')
const movies = await getAllMovies()
ctx.body = {
success: true,
data: movies
}
}
for (let [conf, controller] of routerMap) {
const controllers = isArray(controller);
let prefixPath = conf.target[symbolPrefix];
const routerPath = prefixPath + conf.path;
router[conf.method](routerPath, ...controllers);
}
app.use(router.routes());
app.use(router.allowedMethods());
总结
- decorator 的用法使无限嵌套的函数的写法变得优雅。使代码变成了平级的状态。
- 改变多个class extend 的问题(类似 mixin)。
- 在 core-decorators 的使用可以看出,decorator 还有一个注注解的作用,代码一目了然。
- 不会对原有代码进行侵入,减少修改代码地成本。