在面向對象(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());
複製代碼