【小試牛刀】Stage-2 裝飾器初探

QCLee發表於2019-01-31

原文連結:QC-L/blog

目前 JavaScript 的裝飾器處於提案 Stage-2 階段,且尚未穩定。因此與所有提案一樣,在未來可能會發生變化。本文為個人的思考和見解,如有異議歡迎拍磚。

裝飾器這個概念想必大家並不陌生,筆者最早遇到這個詞是在學習 Java 的時候,主要應用是在 Java 的 Spring 中,其中有個概念叫 AOP(面向切面程式設計)。

當然這個概念在 JavaScript 中也已有其他的應用,比如 TypeScript,MobX 等。社群也有 Redux 相關的解決方案,如 @connect 裝飾器的使用。

這裡給大家帶來的是有關 Babel 7.1.0 中實現的 @babel/babel-plugin-proposal-decorators 相關應用。

環境搭建

由於該提案依賴於 Babel 的提案外掛,因此需要搭建一個簡易的 Babel 編譯環境。

新建目錄,初始化 package.json:

$ mkdir test-decorator && cd test-decorator
$ npm init -y
複製程式碼

在 package.json 中新增 Babel 相關 package:

yarn add -D @babel/cli @babel/core @babel/preset-env
複製程式碼

建立 .babelrc

touch .babelrc
複製程式碼

新增如下配置:

{
  "presets": ["@babel/preset-env"]
}
複製程式碼

在目錄中新增 src/index.js

class TestDecorator {
  method() {}
}
複製程式碼

package.json 中新增 build script 命令

{
  "name": "test-decorator",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1",
+    "build": "babel src -d dist"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.2.2",
    "@babel/preset-env": "^7.3.1"
  }
}

複製程式碼

建立 index.html 檔案,並引入 dist/index.js

<!doctype html>
<html>
<head>
  <title>測試</title>
</head>
<body>
  <script src="./dist/index.js"></script>
</body>
</html>
複製程式碼

執行 yarn build 即可。

裝飾器初窺

1.編寫一個類裝飾器函式

修改 src/index.js:

class TestDecorator {
  method() {}
}
// 類裝飾器函式
function decorator(value) {
  return function(classDescriptor) {
    console.log(classDescriptor);
  }
}	
複製程式碼

2.使用類裝飾器

+ @decorator
class TestDecorator {
  method() {}
}
複製程式碼

3.安裝 Babel 外掛,並修改 .babelrc 檔案:

$ yarn add -D @babel/plugin-proposal-decorators
複製程式碼
{
   "presets": ["@babel/preset-env"],
+  "plugins": [
+    ["@babel/plugin-proposal-decorators", {
+      "decoratorsBeforeExport": true // 用於標識裝飾器所處位置(提案中討論的點)
+    }]
+  ]
}
複製程式碼

注:decoratorsBeforeExport 是必需設定的選項,否則會丟擲異常。為 true 時,會修飾在 export class 上方;為 false 時,會修飾在 export class 前。

// decoratorsBeforeExport: false
export @decorator class TestDecorator {}

// decoratorsBeforeExport: true
@decorator
export class TestDecorator {}
複製程式碼

如不設定 decoratorsBeforeExport 異常如下:

Error: [BABEL] /test-decorator/src/index.js: The decorators plugin requires a 'decoratorsBeforeExport' option, whose value must be a boolean. If you want to use the legacy decorators semantics, you can set the 'legacy: true' option. (While processing: "/test-decorator/node_modules/@babel/plugin-proposal-decorators/lib/index.js")
複製程式碼

4.執行 yarn build,開啟 index.html 檢視控制檯結果。

【小試牛刀】Stage-2 裝飾器初探

裝飾器引數詳解

裝飾器修飾的位置不同,所得的引數有所不同。並且有引數的裝飾器與無引數的裝飾器也有所區別。 裝飾器可修飾內容如下:

  • class
  • class method
  • class fields

呼叫裝飾器時,引數結構對比如下:

  1. 修飾 class

    與 TypeScript 以前之前的裝飾器的區別在於,宣告的裝飾器方法的引數,為 Descriptor 型別。

    引數 說明
    kind class 標識,用於說明當前修飾的內容
    elements [Descriptor] 描述符組成的陣列,描述類中所有的元素

    其中 elements 中物件與 method 和 fields 相同,後面介紹。

  2. 修飾 class method修飾 class fields

    引數 說明 method fields
    kind 標識,用於說明當前修飾的內容 method field
    descriptor 與 Object.defineProperty 中的 descriptor 相同 [object Object] [object Object]
    key 所修飾的 method 或 fileds 的名稱。可以是 String,Symbol 或 Private Name method x
    placement 可選的引數為 "static","prototype" 或者 "own" prototype | static | own prototype | static | own

    其中關於 key 的描述,在提案中有這麼一段。

    For private fields or accessors, the key will be a Private Name--this is similar to a String or Symbol, except that it is invalid to use with property access [] or with operations such as Object.defineProperty. Instead, it can only be used with decorators.

    可能有些小夥伴未了解過 Object.defineProperty,這裡針對 descriptor 介紹下其包含哪些屬性:

    引數 說明 預設值
    configurable true 當且僅當該屬性的 configurable 為 true 時,該屬性描述符才能夠被改變,同時該屬性也能從對應的物件上被刪除 false
    enumerable false 當且僅當該屬性的enumerable為true時,該屬性才能夠出現在物件的列舉屬性中 false
    value method 該屬性對應的值。可以是任何有效的 JavaScript 值(數值,物件,函式等) undefined
    writable true 當且僅當該屬性的writable為true時,value才能被賦值運算子改變。 false
    get undefined 當且僅當該屬性的writable為true時,value才能被賦值運算子改變。 undefined
    set undefined 一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。當屬性值修改時,觸發執行該方法。該方法將接受唯一引數,即該屬性新的引數值。 undefined

    注意: 如果一個描述符不具有 value, writable, get 和 set 任意一個關鍵字,那麼它將被認為是一個資料描述符。如果一個描述符同時有 (value 或 writable) 和 (get 或 set) 關鍵字,將會產生一個異常

如想嘗試的可以自己列印一下,Demo 地址

示例

@classDecoratorArgs(function () {})
@classDecorator
class TestDecorator {
  @filedsDecoratorArgs('fileds')
  @filedsDecorator
  x = 10 // 注意,如果要在 class 中使用 fileds 需要額外引用 @babel/plugin-proposal-class-properties
  @methodDecoratorArgs(20)
  @methodDecorator
  method() {}
}

// 無引數 class decorator
function classDecorator(classDescriptor) {
  console.log(classDescriptor);
}

// 無引數 fileds decorator
function filedsDecorator(filedsDescriptor) {
  console.log(filedsDescriptor);
}

// 無引數 method decorator
function methodDecorator(elementDescriptor) {
  console.log(elementDescriptor);
}

// 有引數 class decorator
function classDecoratorArgs(args) {
  console.log(args);
  return function(classDescriptor) {
    console.log(classDescriptor);
  }
}

// 有引數 fileds decorator
function filedsDecoratorArgs(args) {
  console.log(args);
  return function(filedsDescriptor) {
    console.log(filedsDescriptor);
  }
}

// 有引數 method decorator
function methodDecoratorArgs(args) {
  console.log(args);
  return function(elementDescriptor) {
    console.log(elementDescriptor);
  }
}
複製程式碼

相關內容推薦

裝飾器應用:

參考資料:

下一篇帶大家動手實現一個裝飾器,敬請期待。

相關文章