typescript中的decorator
最近在做一些node上的中介軟體,不可避免的用到了typescript中的註解,而在ts中,註解有另外一個叫法:decorator,於是花了一點時間搞明白了在ts中註解是如何執行的,下面做一個簡單的記錄。
為何decorator
作為ts中的註解,decorator可以分為四類:
- Class Decorator
- Method Decorator
- Accessor Decorator
- Property Decorator
- Parameter Decorator
分別對應以下五種被註解的型別:
- 類
- 方法
- 存取器
- 變數
- 引數
具體的大家可以參考這份文件 。
decorator是如何執行的
下面我們通過一個簡單的例子,來了解一下ts中的decorator是如何執行的。我們以上文中提到的Method Decorator為例,其他幾個都是大同小異的。
先定義一個簡單的註解:
export function annotation(param) { return function test(target: Function, key: string, value: any) { console.log(param); return function (...args: any[]) { return value.value.apply(this, args); }; } }
再使用一下該註解:
class Clz { @annotation("test") foo(n: number) { return n * 2; } } console.log(new Clz().foo(10));
下面是執行程式之後的輸出:
test 20
我們來看一下編譯好之後的js檔案:
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 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); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); var annotation_1 = require("./annotation"); var Clz = /** @class */ (function () { function Clz() { } Clz.prototype.foo = function (n) { return n * 2; }; __decorate([ annotation_1.annotation("test") ], Clz.prototype, "foo", null); return Clz; }()); console.log(new Clz().foo(10));
我們關注下面這個最關鍵的函式:
__decorate([ annotation_1.annotation("test") ], Clz.prototype, "foo", null);
有次我們知道,當我們使用Decorator來註解一個方法的時候,ts會將其通過一個__decorate函式去進行執行,其中包括4個引數:
- 一個註解方法陣列,在例子中就是annotation方法
- 類的原型
- 被註解的方法
- 描述
接下去我們就看一下__decorate具體的邏輯,我們將其格式化一下以便於閱讀:
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 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); } else { for (var i = decorators.length - 1; i >= 0; i--) { if (d = decorators[i]) { r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; } } } return c > 3 && r && Object.defineProperty(target, key, r), r; };
首先會有一個判斷,用以保證該方法不會被初始化多次:
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { }
接著會去判斷改方法的入參長度,我們以文中的例子為例,長度為4,並且desc為null,所以我們會為desc進行賦值:
desc = Object.getOwnPropertyDescriptor(target, key)
接著我們會走到else的程式碼塊裡,通過for迴圈取出我們的註解,並且還是判斷入參的長度去給r這個變數賦值,帶入我們的入參為4,我們可以知道賦值程式碼如下:
r = d(target, key, r);
而這個d就是我們的註解方法。最後將其賦值給target的key方法。帶入實參我們可以知道,target就是類的原型,而key就是被註解的方法,所以我們由此可以知道,整個__decorate就是將被註解的方法進行重新的賦值,指向我們的註解。
通過上面的原始碼分析我們可以知道,一旦一個方法被註解了,那麼它的邏輯將不會按照原有的程式碼運行了,而是會被重新賦值到註解方法中。帶入文中的例子就是說:Clz的foo方法會被重新賦值給anntation方法的返回值。
下面我們按順序來執行一遍整個邏輯:
//test.js var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { //step2 給desc賦值 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); } else { //step3遍歷取出註解方法 for (var i = decorators.length - 1; i >= 0; i--) { if (d = decorators[i]) { r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; } } } //step5給被註解的方法重新賦值,值為註解的方法 return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); var annotation_1 = require("./annotation"); var Clz = /** @class */ (function () { function Clz() { } Clz.prototype.foo = function (n) { return n * 2; }; //step1 執行__decorate方法 __decorate([ annotation_1.annotation("test") ], Clz.prototype, "foo", null); return Clz; }()); //step6 執行外部呼叫方法 console.log(new Clz().foo(10)); //annotation.js Object.defineProperty(exports, "__esModule", { value: true }); function annotation(param) { return function test(target, key, value) { //step4 執行註解方法 console.log(param); return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return value.value.apply(this, args); }; }; } exports.annotation = annotation;
註解的使用場景
在ts中,註解的使用場景還是十分多見的,比如依賴注入,aop等等。如果你有過java-web的開發經驗,那麼這個特性會對你起很大的幫助~