一、裝飾器簡介
注:官方ES6入門叫修飾器,本文以裝飾器贅述。
通常在 Java 項?中?到如下圖所示的代碼,下圖中涉及“@PostMapping”和“@ResponseBody" 在Java中通常解釋為”注解“。通過這種編程?式,可以很?便地將?些邏輯封裝起來,如:
Controller 的請求?法(get、post 等)和路由,參數(shù)的序列化等等。
如果不使?注解,那么上述這些邏輯就需要開發(fā)者??去實現(xiàn)了。

裝飾器是一種用于修改類的語法,它可以在不修改原始代碼的情況下增強其功能。
裝飾器可以實現(xiàn)AOP(面向切面編程)的功能
可見裝飾器存在以下優(yōu)點:
- 減少冗余代碼
- 提高代碼擴展性
二、JavaScript中的裝飾器
2.1 裝飾器演練
2.1.1 裝飾器瀏覽器環(huán)境下如何運行
由于@ 符號后面的標識符被解析為一個裝飾器。然而,裝飾器目前還是處于實驗階段,不在所有的 JavaScript 環(huán)境中都被支持,此時需要babel轉(zhuǎn)換。
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
創(chuàng)建一個.babelrc文件
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
運行下列指令打包輸出轉(zhuǎn)義后的代碼,在html文件中引入
npx babel test.js --out-file dist.js
2.1.2 簡單演示
@test
class TestClass {
constructor() {
console.log('test ok')
}
}
function test(t) {
t.isTest = true
}
console.log(TestClass.isTest) // true
此時按照上述引入后,可以直接打印出結(jié)果。
上述代碼中'@test' 就是一個裝飾器,修改testClass類,在它上添加了靜態(tài)屬性test。
因此裝飾器基本行為如下:
@decorator
class A {}
-------------------
// 等同于
class A {}
A = decorator(A) || A
裝飾器也可以接受參數(shù)(在包裝一層函數(shù))或者在原型對象上操作
@test(1)
class TestClass {
constructor() {}
}
@test('加參數(shù)ok')
class BooClass {
constructor() {}
}
function test(t) {
return function(target) {
target.isTest = t
}
}
console.log(TestClass.isTest) // true
console.log(BooClass.isTest) // true
@test(1)
class TestClass {
constructor() {}
}
function test(t) {
return function(target) {
target.prototype.isTest = t
}
}
let a = new TestClass()
console.log(a.isTest) // 1
2.2 裝飾器使用場景
2.2.1 類場景使用
1)類方法裝飾器
屬性裝飾器表達式會在運行時當作函數(shù)被調(diào)用,傳入下列3個參數(shù):
- 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
- 成員的名字
- 成員的描述對象
注:屬性描述符不會做為參數(shù)傳入屬性裝飾器,這與TypeScript是如何初始化屬性裝飾器的有關(guān)。
function readonly(target, name, descriptor) {
// descriptor值類似Object.prototype原型
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// }
descriptor.writable = false;
return descriptor;
}
class Person {
@readonly
name() {
console.log("~~~");
}
}
const p = new Person();
p.name() // ~~~
p.name = () => {
console.log('111')
} // dist.js:43 Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Person>'
將當前類設(shè)置為只讀,等同于Object.defineProperty使用。類似包含可枚舉,凍結(jié)等
2)類屬性裝飾器
屬性裝飾器表達式會在運行時當作函數(shù)被調(diào)用,傳入下列2個參數(shù):
- 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
- 成員的名字
注:屬性描述符不會做為參數(shù)傳入屬性裝飾器,這與TypeScript是如何初始化屬性裝飾器的有關(guān)
class MyTestableClass {
@testable
name;
constructor(name) {
this.name = name;
}
}
function testable(target, name) {
console.log(arguments)
}

3)復合裝飾器
注:一個方法有多個修飾器,該方法會先從外到內(nèi)進入修飾器,接著由內(nèi)向外執(zhí)行?!居悬c類似棧,先進后出】
function dec(id) {
console.log('進入前:',id)
// 若不寫這段,會報錯
// Uncaught TypeError: decorator is not a function
return (target, property, descriptor) => {
console.log('進入后:', id)
}
}
class Example {
@dec(1)
@dec(2)
method() {}
}
// 進入前: 1
// 進入前: 2
// 進入后: 2
// 進入后: 1
2.2.3 無法用于函數(shù)
無法用于函數(shù),是因為函數(shù)存在函數(shù)提升
let count = 0
const add = () => {
count++
}
@add
const foo = () => {}
console.log(foo.add())
實際操作時,發(fā)現(xiàn)babel轉(zhuǎn)義或者vscode強限制住裝飾器只能支持

2.3 應用場景
關(guān)于裝飾器可以便于我們封裝一些工具場景,比如埋點、日志、發(fā)布等自動化場景。