臨時起的興趣,想寫一篇關于ts decorator的文章,就花小半天整理了一下...
這東西,在ES2017裡好像也有... 文檔的話看
這裡。
因為臨時,也沒想寫太多文字介紹,帶少許文字說明直接開撸代碼吧。
本文通過ts編譯後的decorator代碼解釋一番裝飾器是什麼?能做什麼?有什麼好處?
實作代碼
編譯後代碼是這樣的,帶注釋:
var __decorate =
(this && this.__decorate) ||
function(decorators, target, key, desc) {
// c 參數長度
// r ? c < 3 則是target,否則先判斷desc為null的話則将desc取target的key屬性的描述,再否則便是desc了
// d 預留使用
var c = arguments.length,
r =
c < 3
? target
: desc === null
? (desc = Object.getOwnPropertyDescriptor(target, key))
: desc,
d;
// 下面文字解釋,這僅是個甩鍋的行為
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
r = Reflect.decorate(decorators, target, key, desc);
// 循環 decorators 并每次指派給 d,并且判斷值
else
for (var i = decorators.length - 1; i >= 0; i--)
if ((d = decorators[i]))
// c < 3 ,用 r 作為 decorators[i] 的入參執行;
// c > 3 ,target, key, r 作為 decorators[i] 的入參執行;
// c === 3,target, key 作為 decorators[i] 的入參執行。
// 如果執行無傳回結果, r = r;。
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
// 如果 c > 3 && r , 修改 target ,傳回 r
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
從代碼裡可以看出,最終結果要麼是用decorator執行target,進而改變一些什麼東西;要麼就是使用Object.defineProperty來對target來做操作,代碼就幾行,用處确不小...具體的執行過程結合下面的兩個例子會更容易了解。
值得一提的是,關于代碼裡的Reflect原本以為是這個
sec-reflect-object裡的方法,但可惜不是;
然後猜測是Typescript的實作,翻了
Typescript/tsc.js的代碼(如果打不開連結就從 node_modules 下看吧),發現也不是,再去查 stackoverflow 的解釋,是這樣的
what-is-reflect-decorate-in-js-code-transpiled-from-ts大緻說是ts希望把這個鍋甩給ES來補,到時候ts的else裡的代碼便是polyfill了
案例
以下面的 decorator 和 class 作為例子解釋
// ts 代碼
function show(target: any) {
console.log(target);
target.prototype.showMe = (name: string) => {
console.log("show me :", name);
};
}
interface IShow {
showMe?(name: string): any;
}
@show
class Show implements IShow {
showMe(name: string) {}
}
const shoow = new Show();
shoow.showMe("ys");
// 編譯後的js
// decorator ,簡單的列印,并且修改方法
function show(target) {
console.log(target);
target.prototype.showMe = function(name) {
console.log("show me :", name);
};
}
// class Shoow
var Shoow = (function() {
function Shoow() {}
Shoow.prototype.showMe = function(name) {};
// decorators 為[show],target 為 Shoow
Shoow = __decorate([show], Shoow);
return Shoow;
})();
var shooow = new Shoow();
shooow.showMe("ys");
// output : show me : ys
了解一下執行步驟:
- decorators = [show],target = Shoow,
- c = 2,r = target{Shoow},d = undefined
- 不存在 Reflect,走循環,隻循環一次
- d = show,r = show(target{Shoow}),r 沒傳回結果,是以 r 還是 r , r = target{Shoow}
- return 結果: c = 2, 是以傳回 false
- 執行後無傳回值,但是在執行show(target{Shoow})的時候将showMe方法改掉了,于是執行結果符合預期
一個不夠?再來一個?這次在裡面傳回一個函數試試?
// ts代碼
function logger1(config?) {
return function(target, key: string, descriptor: PropertyDescriptor) {
const _value = descriptor.value;
if (typeof _value === "function") {
descriptor.value = (...args) => {
console.log(`logger1-begin : ${config.level}`);
const res = _value.apply(target, args);
console.log(`logger1-end`);
return res;
};
}
return descriptor;
};
}
function logger2(config?) {
return function(target, key: string, descriptor: PropertyDescriptor) {
const _value = descriptor.value;
if (typeof _value === "function") {
descriptor.value = (...args) => {
console.log(`logger2-begin : ${config.level}`);
const res = _value.apply(target, args);
console.log(`logger2-end`);
return res;
};
}
return descriptor;
};
}
interface IShow {
showMe?(name: string): any;
}
class Show implements IShow {
@logger1({ level: "info" })
@logger2({ level: "error" })
showMe(name: string) {
console.log("show me :", name);
}
}
const shoow = new Show();
shoow.showMe("ys");
// output 這裡手動加個縮進,這時候showMe方法已經經過多次包裹
// logger1-begin : info
// logger2-begin : error
// show me : ys
// logger2-end
// logger1-end
再來看看執行步驟:
- decorators = [logger1, logger2],target = Shoow,key = "showMe",desc = null 注意,這裡是為null,不是為undefined
- c = 4,r = target{Shoow},d = undefined
- 第一次循環取 d = logger1,r = logger1(target, key, r),因為 return 存在值,r = logger1 的傳回函數,這時候 descriptor.value 被第一次重寫
- 第二次循環取 d = logger2,r = logger2(target, key, r),又因為 return 存在值,r = logger2 的傳回函數,這時候 descriptor.value 被第二次重寫
- return 結果: 因為 c > 3,r 存在值,執行 Object.defineProperty(target, key, r)來重寫對象屬性并且傳回 r (r為重寫的結果)
- 經過 2 次重寫 showMe 屬性值,執行結果符合預期
歡樂
裝飾器給你帶來什麼歡樂?簡單列幾個最明顯的優點,其他在運用中各自提取每日份的快樂去吧...
- 業務和功能之間的解耦(比如日志)
// 日常
dosomething(){
consol.log('start')
// some thing
console.log('end')
}
// 使用裝飾器
@logger(logConfig?)
dosomething();
- 代碼結構清晰(特别針對多層HOC後的React元件)
// 日常多層HOC
class MyComponent extends Component{
// ..
}
connect(mapFn)(
MyHoc(someMapFn)(
Form.create(fieldsMapFn)(MyComponent)
)
)
// 使用裝飾器
@connect(mapFn)
@MyHoc(someMapFn)
@FormFields(fieldsMapFn)
class MyComponent extends Component{
// ..
}
export default MyComponent;
最後
AOP,了解一下