摘要:使用裝飾器的方法很簡單在裝飾器名前加字符,寫在想要裝飾的方法上,類似寫注釋的方式裝飾器實際上是一個函數(shù),入?yún)樗b飾的方法,返回值為裝飾后的方法。經(jīng)過裝飾過的方法,它依然按照原來的方式執(zhí)行,只是額外執(zhí)行了附件的裝飾器函數(shù)的功能。
讓我來深入地了解一下TypeScript對于裝飾器模式的實現(xiàn),以及反射與依賴注入等相關特性。
在Typescript的源代碼中,可以看到裝飾器能用來修飾class,property,method,parameter:
declare type ClassDecorator =(target: TFunction) => TFunction | void; declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; declare type MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor ) => TypedPropertyDescriptor | void; declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
接下來深入地了解一下每種裝飾器:
方法裝飾器首先來根據(jù)上面的標識,實現(xiàn)一個名為log的方法裝飾器。使用裝飾器的方法很簡單:在裝飾器名前加@字符,寫在想要裝飾的方法上,類似寫注釋的方式:
class C { @log foo(n: number) { return n * 2; } }
裝飾器實際上是一個函數(shù),入?yún)樗b飾的方法,返回值為裝飾后的方法。在使用之前需要提前實現(xiàn)這個裝飾器函數(shù),如下:
function log(target: Function, key: string, descriptor: any) { // target === C.prototype // key === "foo" // descriptor === Object.getOwnPropertyDescriptor(C.prototype, "foo") // 保存對原方法的引用,避免重寫 var originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { // 將“foo”函數(shù)的參數(shù)列表轉化為字符串 var a = args.map(a => JSON.stringify(a)).join(); // 調用 foo() 并獲取它的返回值 var result = originalMethod.apply(this, args); // 將返回的結果轉成字符串 var r = JSON.stringify(result); // 打印日志 console.log(`Call: ${key}(${a}) => ${r}`); // 返回調用 foo 的結果 return result; } // 返回已編輯的描述符 return descriptor; }
該裝飾器函數(shù)包含三個參數(shù):
target:所要修飾的方法。
key:被修飾方法的名字。
descriptor:屬性描述符,如果為給定可以通過調用Object.getOwnPropertyDescriptor()來獲取。
我們觀察到,類C中使用的裝飾器函數(shù)log并沒有顯式的參數(shù)傳遞,不免好奇它所需要的參數(shù)是如何傳遞的?以及該函數(shù)是如何被調用的?
TypeScript最終還是會被編譯為JavaScript執(zhí)行,為了搞清上面的問題,我們來看一下TypeScript編譯器將類C的定義最終生成的JavaScript代碼:
var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; Object.defineProperty(C.prototype, "foo", __decorate([ log ], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo"))); return C; })();
而為添加裝飾器所生成的JavaScript代碼如下:
var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; return C; })();
對比兩者發(fā)現(xiàn)使用裝飾的不同,只是在類定義中,多了如下代碼:
Object.defineProperty( __decorate( [log], // 裝飾器 C.prototype, // target:C的原型 "foo", // key:裝飾器修飾的方法名 Object.getOwnPropertyDescriptor(C.prototype, "foo") // descriptor ); );
通過查詢MDN文檔,可以知悉defineProperty的作用:
Object.defineProperty()方法可直接在一個對象上定義一個新的屬性,或者修改對象上一個已有的屬性,然后返回這個對象。
TypeScript編譯器通過defineProperty方法重寫了所修飾的方法foo,新方法的實現(xiàn)是由函數(shù)__decorate返回的,那么問題來了:__decorate函數(shù)在哪聲明的呢?
掘地三尺不難找到,來一起把玩一下:
var __decorate = this.__decorate || function (decorators, target, key, desc) { if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { return Reflect.decorate(decorators, target, key, desc); } switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); } };
第一行使用了或操作符(||),以確保如果函數(shù)__decorate已被創(chuàng)建,他將不會被重寫。
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
第二行是一個條件語句,使用了JavaScript的一個新特性:元數(shù)據(jù)反射。這個主題后續(xù)再展開講述,下面我們先聚焦觀察下該新特性的兼容方案:
switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); }
此處__decorate函數(shù)接受了4個參數(shù),所以case 4將被執(zhí)行。平心而論這塊代碼有點生澀,沒關系掰開揉碎了看。
reduceRight方法接受一個函數(shù)作為累加器和數(shù)組的每個值(從右到左)將其減少為單個值。
為了方便理解,上面的代碼重寫如下:
[log].reduceRight(function(log, desc) { if(log) { return log(C.prototype, "foo", desc); } else { return desc; } }, Object.getOwnPropertyDescriptor(C.prototype, "foo"));
可以看到當這段代碼執(zhí)行的時候,裝飾器函數(shù)log被調用,并且參數(shù)C.prototype,"foo",previousValue也被傳入,如此之前的問題現(xiàn)在可以解答了。
經(jīng)過裝飾過的foo方法,它依然按照原來的方式執(zhí)行,只是額外執(zhí)行了附件的裝飾器函數(shù)log的功能。
const c = new C(); const r = c.foo(23); // "Call: foo(23) => 46" console.log(r); // 46
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/101503.html
摘要:可見參數(shù)裝飾器函數(shù)需要個參數(shù)被裝飾類的原型,裝飾參數(shù)所屬的方法名,參數(shù)的索引。參數(shù)裝飾器不應當用來修改構造器方法或屬性的行為,它只應當用來產(chǎn)生某種元數(shù)據(jù)。一旦元數(shù)據(jù)被創(chuàng)建,我們便可以用其它的裝飾器去讀取它。 之前已經(jīng)分別介紹了方法裝飾器、屬性裝飾器和類裝飾器,這篇文章我們來繼續(xù)關注這些話題: 參數(shù)裝飾器 裝飾器工廠 我們將圍繞以下這個例子,來探討這些概念: class Person...
摘要:值得注意的是,的返回值復寫了原始的構造函數(shù),原因是類裝飾器必須返回一個構造器函數(shù)。原始構造函數(shù)的原型被復制給的原型,以確保在創(chuàng)建一個的新實例時,操作符如愿以償,具體原因可參考鄙人另一篇文章原型與對象。 上一篇文章中,我們討論了TypeScript源碼中關于方法裝飾器的實現(xiàn),搞明白了如下幾個問題: 裝飾器函數(shù)是如何被調用的? 裝飾器函數(shù)參數(shù)是如何傳入的? __decorate函數(shù)干了...
摘要:慶幸的是,已經(jīng)支持反射機制,來看看這個特性吧元數(shù)據(jù)反射可以通過安裝包來使用元數(shù)據(jù)反射的若要使用它,我們需要在中設置為,同時添加的引用,同時加載文件。復雜類型序列化的團隊為復雜類型的元數(shù)據(jù)序列化做出了努力。 本篇內(nèi)容包括如下部分: 為什么JavaScript中需要反射 元數(shù)據(jù)反射API 基本類型序列化 復雜類型序列化 為什么JavaScript中需要反射? 關于反射的概念,摘自百度百...
摘要:看了這一章,發(fā)現(xiàn)原來是裝飾器,又一新知識。期間,裝飾器會做一些額外的工作。書中介紹了模塊中的三個裝飾器。另一個是,這個裝飾器把函數(shù)結果保存了起來,避免傳入相同參數(shù)時重復計算。疊放不奇怪,裝飾器返回的就是函數(shù)或可調用對象。 在 Web 框架 Flask 中,最??吹降幕蛟S是以@app.route開頭的那行代碼。由于還是剛接觸 Flask,所以對這種語法還不熟悉??戳诉@一章,發(fā)現(xiàn)原來是裝飾...
摘要:希望引以為戒鄭傳裝飾模式如果你了解,你肯定聽過裝飾器模式。在面向對象中,裝飾模式指動態(tài)地給一個對象添加一些額外的職責。就增加一些功能來說,裝飾模式比生成子類更為靈活。 漫談 如果作為一個Python入門,不了解Python裝飾器也沒什么,但是如果作為一個中級Python開發(fā)人員,如果再不對python裝飾器熟稔于心的話,那么可能并沒有量變積累到質變。 我以前也看過很多講python 裝...
閱讀 761·2023-04-26 01:30
閱讀 3314·2021-11-24 10:32
閱讀 2201·2021-11-22 14:56
閱讀 2000·2021-11-18 10:07
閱讀 569·2019-08-29 17:14
閱讀 636·2019-08-26 12:21
閱讀 3117·2019-08-26 10:55
閱讀 2956·2019-08-23 18:09