在面向对象(OOP)的设计模式中,Decorator 被称为装饰模式。OOP 的装饰模式须要经过继承和组合来实现。javascript
经过装饰器动态地给一个对象添加一些额外的职责,就增长功能来讲,装饰器模式比生成子类更为灵活;它容许向一个现有的对象添加新的功能,同时又不改变其结构。java
Javascript 中的 Decorator 源于 python 之类的语言。node
A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.python
def decorator(func):
print("decorator")
return func
def func():
print('func')
func = decorator(func)
func()
@decorator
def func2():
print("func2")
func2()
复制代码
点击 python 在线运行环境 查看运行结果。git
这里的 @decorator
就是装饰器,利用装饰器给目标方法执行前打印出" decorator"
,而且并无对原方法作任何的修改。es6
decorator 还在草案阶段,因此须要 babel 支持下面给出几种方式github
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 -
复制代码
在上述配置的基础上再执行如下命令
npm install --save-dev @babel/register @babel/polyfill
新建index.js
require("@babel/register")();
require("@babel/polyfill");
require('./test')
执行命令
node index
复制代码
es6
中的 class 可使用 Object.defineProperty
实现,代码以下:npm
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 能够用来装饰 类 || 类方法 || 类属性
。编程
function isAnimal(target) {
target.isAnimal = true;
return target;
}
@isAnimal
class Cat {}
console.log(Cat.isAnimal); // true
复制代码
若是把 decorator 做用到类上,则它的第一个参数 target
是 类自己
。json
因此针对 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 的本质就是函数的嵌套,能够从两个方面来看:
咱们利用修饰器使该方法不可写
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。
在 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() {}
......
复制代码
const dec = skill => target => {
target.skill = skill;
return target;
};
@dec("nodejs")
class FE {}
console.log(FE.skill);
复制代码
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 {}
复制代码
使用 npm install core-decorators --save
,而后使用上述"配置环境" 中的 二、3 点。
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"?
}
复制代码
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.
//
复制代码
提供 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
})
复制代码
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());
复制代码