原文链接: 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 查看控制台结果。
装饰器修饰的位置不同,所得的参数有所不同。并且有参数的装饰器与无参数的装饰器也有所区别。 装饰器可修饰内容如下:
调用装饰器时,参数结构对比如下:
与 TypeScript 以前之前的装饰器的区别在于,声明的装饰器方法的参数,为 Descriptor 类型。
参数 | 值 | 说明 |
---|---|---|
kind | class | 标识,用于说明当前修饰的内容 |
elements | [Descriptor] | 描述符组成的数组,描述类中所有的元素 |
其中 elements 中对象与 method 和 fields 相同,后面介绍。
修饰 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); } } 复制代码