【小试牛刀】Stage-2 装饰器初探

原文链接: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


<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 查看控制台结果。

装饰器参数详解

装饰器修饰的位置不同,所得的参数有所不同。并且有参数的装饰器与无参数的装饰器也有所区别。 装饰器可修饰内容如下:

  • 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);
  }
}
复制代码

相关内容推荐

装饰器应用:

  • Midway
  • bound

参考资料:

  • defineProperty MDN
  • TypeScript 中的装饰器
  • proposal-decorators
  • Decorators proposal
  • MobX详解(二):ES7 装饰器 decorator

下一篇带大家动手实现一个装饰器,敬请期待。

转载于:https://juejin.im/post/5c52930c5188254d855d6693

你可能感兴趣的:(【小试牛刀】Stage-2 装饰器初探)