裝飾器(Decorator)可聲明在類及其成員(例如屬性、方法等)之上,為它們提供一種標注,用于分離復雜邏輯或附加額外邏輯,其語法形式為@expression。expression是一個會在運行時被調用的函數,它的參數是被裝飾的聲明信息。假設有一個@sealed裝飾器,那么可以像下面這樣定義sealed()函數。
~~~
function sealed(target) {
//...
}
~~~
  有兩種方式可以開啟裝飾器,第一種是在輸入命令時添加--experimentalDecorators參數,如下所示,其中--target參數不能省略,它的值為“ES5”。
~~~
tsc default.ts --target ES5 --experimentalDecorators
~~~
  第二種是在tsconfig.json配置文件中添加experimentalDecorators屬性,如下所示,對應的target屬性也不能省略。
~~~
{
compilerOptions: {
target: "ES5",
experimentalDecorators: true
}
}
~~~
## 一、類裝飾器
  類裝飾器用于監聽、修改或替換類的構造函數,并將其作為類裝飾器唯一可接收的參數。當裝飾器返回undefined時,延用原來的構造函數;而當裝飾器有返回值時,會用它來覆蓋原來的構造函數。下面的示例會通過類裝飾器封閉類的構造函數和原型,其中@sealed聲明在類之前。
~~~
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
~~~
  在經過TypeScript編譯后,將會生成一個\_\_decorated()函數,并應用到Person類上,如下所示。
~~~
var Person = /** @class */ (function() {
function Person(name) {
this.name = name;
}
Person = __decorate([sealed], Person);
return Person;
})();
~~~
  注意,類裝飾器不能出現在.d.ts聲明文件和外部類之中。
## 二、方法裝飾器
  方法裝飾器聲明在類的方法之前,作用于方法的屬性描述符,比類裝飾器還多一個重載限制。它能接收三個參數,如下所列:
  (1)對于靜態成員來說是類的構造函數,而對于實例成員則是類的原型對象。
  (2)成員的名字,一個字符串或符號。
  (3)成員的屬性描述符,當輸出版本低于ES5時,該值將會是undefined。
  當方法裝飾器返回一個值時,會覆蓋當前方法的屬性描述符。下面是一個簡單的例子,方法裝飾器的第一個參數是Person.prototype,第二個是“cover”,調用getName()方法得到的將是“freedom”,而不是原先的“strick”。
~~~
class Person {
@cover
getName(name) {
return name;
}
}
function cover(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function() {
return "freedom";
};
return descriptor;
}
let person = new Person();
person.getName("strick"); //"freedom"
~~~
## 三、訪問器裝飾器
  訪問器裝飾器聲明在類的訪問器屬性之前,作用于相應的屬性描述符,其限制與類裝飾器相同,而接收的三個參數與方法裝飾器相同。并且還需要注意一點,TypeScript不允許同時裝飾一個成員的get和set訪問器,只能應用在第一個訪問器上。
  以下面的Person類為例,定義了一個訪問器屬性name,當訪問它時,得到的將是“freedom”,而不是原先的“strick”。
~~~
class Person {
private _name: string;
@access
get name() {
return this._name;
}
set name(name) {
this._name = name;
}
}
function access(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.get = function() {
return "freedom";
};
return descriptor;
}
let person = new Person();
person.name = "strick";
console.log(person.name); //"freedom"
~~~
## 四、屬性裝飾器
  屬性裝飾器聲明在屬性之前,其限制與訪問器裝飾器相同,但只能接收兩個參數,不存在第三個屬性描述符參數,并且沒有返回值。仍然以下面的Person類為例,定義一個name屬性,并且在@property裝飾器中修改其值。
~~~
class Person {
@property
name: string;
}
function property(target: any, key: string) {
Object.defineProperty(target, key, {
value: "freedom"
});
}
let person = new Person();
person.name = "strick";
console.log(person.name); //"freedom"
~~~
## 五、參數裝飾器
  參數裝飾器聲明在參數之前,它沒有返回值,其限制與方法裝飾器相同,并且也能接收三個參數,但第三個參數表示裝飾的參數在函數的參數列表中所處的位置(即索引)。下面用一個例子來演示參數裝飾器的用法,需要與方法裝飾器配合。
~~~
let params = [];
class Person {
@func
getName(@required name) {
return name;
}
}
~~~
  在@func中調用getName()方法,并向其傳入params數組中的值,@required用于修改指定位置的參數的值,如下所示。
~~~
function func(target: any, key: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function () {
return method.apply(this, params);
};
return descriptor;
}
function required(target: any, key: string, index: number) {
params[index] = "freedom";
}
~~~
  當實例化Person類,調用getName()方法,得到的將是“freedom”。
~~~
let person = new Person();
person.getName("strick"); //"freedom"
~~~
## 六、裝飾器工廠
  裝飾器工廠是一個能接收任意個參數的函數,用來包裹裝飾器,使其更易使用,它能返回上述任意一種裝飾器函數。接下來改造方法裝飾器一節中的cover()函數,接收一個字符串類型的value參數,返回一個方法裝飾器函數,如下所示。
~~~
function cover(value: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function() {
return value;
};
return descriptor;
};
}
~~~
  在將@cover作用于類中的方法時,需要傳入一個字符串,如下所示。
~~~
class Person {
@cover("freedom")
getName(name) {
return name;
}
}
~~~
## 七、裝飾器組合
  將多個裝飾器應用到同一個聲明上時,既可以寫成一行,也可以寫成多行,如下所示。
~~~
/****** 一行 ******/
@first @second desc
/****** 多行 ******/
@first
@second
desc
~~~
  這些裝飾器的求值方式與復合函數類似,先由上至下依次執行裝飾器,再將求值結果作為函數,由下至上依次調用。例如定義兩個裝飾器工廠函數,如下代碼所示,在函數體和返回的裝飾器中都會打印一個數字。
~~~
function first() {
console.log(1);
return function(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(2);
};
}
function second() {
console.log(3);
return function(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(4);
};
}
~~~
  將它們先后聲明到類中的同一個方法,如下代碼所示。根據求值順序可知,先打印出1和3,再打印出4和2。
~~~
class Person {
@first()
@second()
getName(name) {
return name;
}
}
~~~
*****
> 原文出處:
[博客園-TypeScript躬行記](https://www.cnblogs.com/strick/category/1561745.html)
[知乎專欄-TypeScript躬行記](https://zhuanlan.zhihu.com/pwts2019)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020