基於Node的web服務器開發中使用decorator對請求進行權限校驗和數據格式的處理是一個看起來比較漂亮的寫法,這裏正好整理一下對javascript中的decorator的理解。javascript
decorator的概念在其餘語言中早有存在,在javascript中目前(2017/09/17)還處於stage 2階段,基本肯定會進入正式的ECMA規範了。可是目前還不能直接使用,只能使用babel來進行語法的轉換。html
官方的說法是:java
Decorators make it possible to annotate and modify classes and properties at design time.git
大概的意思就是在運行時改變類或者類的屬性。github
首先看下裝飾器常見的兩種使用:web
function readonly(target, name, descriptor) {
discriptor.writable = false;
return discriptor;
}
class Cat {
@readonly
say() {
console.log("meow ~");
}
}
var kitty = new Cat();
kitty.say = function() {
console.log("woof !");
}
kitty.say() // meow ~
複製代碼
function isAnimal(target) {
target.isAnimal = true;
return target;
}
@isAnimal
class Cat {
...
}
console.log(Cat.isAnimal); // true
複製代碼
ES6中的類實際上就是一個語法糖,本質上是構造函數,類的屬性的定義使用的是 Object.defineProperty() 用一個簡單的栗子來理解以下:服務器
class Cat {
say() {
console.log("meow ~");
}
}
function Cat() {}
Object.defineProperty(Cat.prototype, "say", {
value: function() { console.log("meow ~"); },
enumerable: false,
configurable: true,
writable: true
});
複製代碼
細心的小夥伴已經發現了babel
Object.defineProperty(obj, prop, descriptor)
複製代碼
接收的參數和做用於類的屬性的時候裝飾器函數的接收的參數很像。函數
能夠知道做用於類的屬性的時候的裝飾器函數接收的參數就是上述ES6中的類定義屬性時候使用Object.defineProperty時接收的參數,如出一轍...ui
本質上也就是說裝飾器在做用於類的屬性的時候,其實是經過 Object.defineProperty 來對原有的descriptor進行封裝:
descriptor:
經過處理descriptor能夠改變原有屬性。 被裝飾的屬性的定義在實際上執行的是如下的代碼:
let descriptor = {
value: function() {
console.log("meow ~");
},
enumerable: false,
configurable: true,
writable: true
};
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
Object.defineProperty(Cat.prototype, "say", descriptor);
複製代碼
也就是說,上面的那個@readonly其實就是
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
複製代碼
的語法糖,要注意的是,裝飾器執行的時間是在屬性定義的時候,也就是被裝飾的屬性在定義後就是已經被裝飾器處理過的不同的屬性了。
裝飾一個類的時候類自己本質上是一個函數,沒有descriptor,target是這個函數自己。
function isAnimal(target) {
target.isAnimal = true;
return target;
}
@isAnimal
class Cat {
...
}
console.log(Cat.isAnimal); // true
複製代碼
也就是說,上面的@isAnimal其實就是作了下面這件事
Cat = isAnimal(function Cat() { ... });
複製代碼
在瞭解了兩種狀況下裝飾器本質上作了什麼以後,順帶能夠看出,裝飾器函數執行的時間:
function log(message) {
return function() {
console.log(message);
}
}
console.log('before class');
@log('class Bar')
class Bar {
@log('class method bar');
bar() {}
@log('class getter alice');
get alice() {}
@log('class property bob');
bob = 1;
}
console.log('after class');
let bar = {
@log('object method bar')
bar() {}
};
複製代碼
輸出結果:
before class class method bar class getter alice class property bob class Bar after class object method bar 複製代碼
能夠看出裝飾器在定義時就執行了,也就對應着官方的那句話:
Decorators make it possible to annotate and modify classes and properties at design time.
在類和類的屬性定義的時候就對它們進行了"裝飾"。
以上大體的說了下javascript的裝飾器的原理和使用,可是還有一些細節有待進一步的深刻。
TBD
參考資料