什么是裝飾器贵白?
裝飾器模式
(Decorator Pattern)
是一種結(jié)構(gòu)型設(shè)計(jì)模式崩泡,旨在促進(jìn)代碼復(fù)用,可以用于修改現(xiàn)有的系統(tǒng)圈浇,希望在系統(tǒng)中為對(duì)象添加額外的功能靴寂,同時(shí)又不需要大量修改原有的代碼百炬。
JS中的裝飾器是ES7中的一個(gè)新語法,可以對(duì)類
庶弃、方法
德澈、屬性
進(jìn)行修飾,從而進(jìn)行一些相關(guān)功能定制, 它的寫法與Java的注解(Annotation)
類似缴守,但是功能有比較大的區(qū)別镇辉。
大家可能聽說過 組合函數(shù) 和 高階函數(shù) 的概念,也可以這么理解村砂。
我們先來看一下以下代碼:
function doSomething(name) {
console.log('Hi, I\'' + name);
}
funtion useLogging(func, name) {
console.log('Starting');
func(name);
console.log('Finished');
}
以上邏輯不難理解屹逛,給原有的函數(shù)加一個(gè)打日志的功能,但是這樣的話色迂,每次都要傳參數(shù)給useLogging
歇僧,而且破壞了之前的代碼結(jié)構(gòu),之前直接doSomething
就好了祸轮,現(xiàn)在要改成useLogging(doSomething, 'Jiang')
侥钳。
那有沒有更好的方式呢,當(dāng)然有啦苦酱。
簡(jiǎn)單裝飾器:
function useLogging(func) {
return function() {
console.log('Starting');
const result = func.apply(this, arguments)
console.log('Done');
return result;
}
}
const wrapped = useLogging(doSomething);
以上代碼返回了一個(gè)新的函數(shù) wrapped , 調(diào)用方式和doSomething
相同给猾,在原來的基礎(chǔ)上能做多一點(diǎn)事情敢伸。
doSomething('angry');
// Hi, I'angry
const wrapped = useLogging(doSomething);
wrapped('angry');
// Starting
// Hi, I'angry
// Done
怎么使用裝飾器?
裝飾器主要有兩種用法:
- 裝飾類方法或?qū)傩?類成員)
class MyClass {
@readonly
method() { }
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
- 裝飾類
@annotation
class MyClass { }
function annotation(target) {
target.annotated = true;
}
類成員裝飾器
類成員裝飾器用來裝飾類里面的屬性尾序、方法躯砰、getter
和setter
琢歇。這個(gè)裝飾器函數(shù)調(diào)用三個(gè)參數(shù)調(diào):
-
target
: 被裝飾的類的原型 -
name
: 被裝飾的類、屬性、方法的名字 -
descriptor
: 被裝飾的類尚揣、屬性快骗、方法的descriptor
塔次,將傳遞給Object.defineProperty
我們來寫幾個(gè)裝飾器名秀,代碼如下:
寫一個(gè)@readonly
裝飾器匕得,簡(jiǎn)單版實(shí)現(xiàn):
class Example {
@log
add(a, b) {
return a + b;
}
@unenumerable
@readonly
name = "alibaba"
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
function unenumerable(target, name, descriptor) {
descriptor.enumerable = false;
return descriptor;
}
function log(target, name, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result: ${result}`);
return result;
} catch (e) {
console.log(`Error: ${e}`);
throw e;
}
}
}
return descriptor;
}
const e = new Example();
// Calling add with [2, 4]
e.add(2, 4);
e.name = 'antd'; // Error
我們可以通過Babel
查看編譯后的代碼,也可以在本地編譯略吨。
npm i @babel/core @babel/cli
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
.babelrc
文件
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", {"loose": true}]
]
}
編譯 ES6 語法輸出到文件
因?yàn)闆]用全局安裝
@babel/cli
, 建議用 npx 命令來執(zhí)行翠忠,或者./node_modules/.bin/babel
乞榨,關(guān)于npx
命令,可以看下官方文檔
npx babel decorator.js --out-file complied.js
編譯后的代碼:
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {};
// 拷貝屬性
Object['ke' + 'ys'](descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object['define' + 'Property'](target, property, desc); desc = null;
}
return desc;
}
_applyDecoratedDescriptor(_class.prototype, "add", [log], Object.getOwnPropertyDescriptor(_class.prototype, "add"), _class.prototype)
Babel 構(gòu)建了一個(gè) _applyDecoratedDescriptor
函數(shù),用于裝飾類成員
Object.getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor()
方法返回指定對(duì)象上一個(gè)自有屬性對(duì)應(yīng)的屬性描述符董虱。(自有屬性指的是直接賦予該對(duì)象的屬性申鱼,不需要從原型鏈上進(jìn)行查找的屬性),不是原型鏈上的這點(diǎn)很關(guān)鍵淫半。
詳情可以查看官方文檔匣砖,這里就不細(xì)說了。
var desc = {};
// 這里對(duì) descriptor 屬性做了一層拷貝
Object['ke' + 'ys'](descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
// 沒有 value 或者 initializer 屬性的对人,都是 get 和 set 方法
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
這里的 initializer 是 Babel 為了配合 decorator 而產(chǎn)生的一個(gè)屬性牺弄,就比方說對(duì)于上面代碼中的 name 屬性宜狐,被編譯成:
_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [unenumerable, readonly], {
configurable: true,
enumerable: true,
writable: true,
initializer: function initializer() {
return "alibaba";
}
})
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
處理多個(gè) decorator 的情況,這里執(zhí)行了slice()和reverse()咱台,所以我們可以得出回溺,一個(gè)類成員有多個(gè)裝飾器,會(huì)由內(nèi)向外執(zhí)行馅而。
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object['define' + 'Property'](target, property, desc); desc = null;
}
return desc;
最后無論是裝飾方法還是屬性瓮恭,都會(huì)執(zhí)行:
Object["define" + "Property"](target, property, desc);
由此可見,裝飾方法本質(zhì)上還是使用 Object.defineProperty() 來實(shí)現(xiàn)的维哈。
類裝飾器
類裝飾器相對(duì)簡(jiǎn)單
function log(Class) {
return (...args) => {
console.log(args);
return new Class(...args);
};
}
@log
class Example {
constructor(name, age) {
}
}
const e = new Example('Graham', 34);
// [ 'Graham', 34 ]
console.log(e);
// Example {}
裝飾器中傳入?yún)?shù):
function log(name) {
return function decorator(Class) {
return (...args) => {
console.log(`Arguments for ${name}: args`);
return new Class(...args);
};
}
}
@log('Demo')
class Example {
constructor(name, age) {}
}
const e = new Example('Graham', 34);
// Arguments for Demo: args
console.log(e);
// Example {}
應(yīng)用
在 React 中阔挠,經(jīng)常會(huì)用到 redux 或者高階組件脑蠕。
class A extends React.Component {}
export default connect()(A);
裝飾器寫法:
@connect()
export default connect()(A);
總結(jié)
Decorator 雖然原理非常簡(jiǎn)單,但是的確可以實(shí)現(xiàn)很多實(shí)用又方便的功能.