装饰器是ES2016 stage-2的一个草案,但是在babel的支持下,已被广泛使用,有点类似java里面的注解。
提案地址Class and Property Decorators
如果我们要在我们项目中使用最新的stage-2的装饰器提案怎么做呢?
Preset: babel-preset-stage-1
Plugins: babel-plugin-transform-decorators, babel-plugin-transform-decorators-legacy
First Pull Request: babel/babylon#587 by @peey
Babylon Label: Spec: Decorators
代码如下:
@frozen class Foo {
@configurable(false) @enumerable(true) method() {}
}
function frozen(constructor, parent, elements) {
return {
constructor,
elements,
finisher(constructor) {
Object.freeze(constructor.prototype)
Object.freeze(constructor)
}
}
}
function configurable(configurable) {
return decorator;
function decorator(previousDescriptor) {
return {
...previousDescriptor,
descriptor: {
...previousDescriptor.descriptor,
configurable
}
}
}
}
function enumerable(enumerable) {
return decorator;
function decorator(previousDescriptor) {
return {
...previousDescriptor,
descriptor: {
...previousDescriptor.descriptor,
enumerable
}
}
}
}
babel的更多提案大家可以参考:https://github.com/babel/proposals
说了那么多,我们直接上代码。
首先我们创建一个叫decorator-demo的项目:
decorator-demo
demo
index.html//demo入口文件
lib //babel编译完毕后的文件
src //工程源文件
demo1.js //demo测试入口
babel.config.js //babel配置文件
package.json //项目清单
入口文件引用一个编译好的demo1.js文件
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<script src="../lib/demo1.js">script>
body>
html>
利用装饰器修改name属性值为“yasin”
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
class Person {
@defineName name;
}
function defineName(target, property, descriptor) {
delete descriptor.initializer;
return {
...descriptor,
value: "yasin"
};
}
document.write(new Person().name);
babel配置文件,不懂的小伙伴可以查看babel官网,也可以参考我之前写的一篇babel的文章 babel源码解析一
module.exports = {
"presets": [
["@babel/env", {"modules": false}]
],
"plugins": [
["@babel/plugin-proposal-decorators", {"legacy": true}],
["@babel/proposal-class-properties", {"loose": true}]
]
};
babel、preset和插件的具体使用方法我就不在这里介绍了
装饰器使用可以参考@babel/plugin-proposal-decorators官网:
https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy
{
"name": "decorator-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-decorators": "^7.10.1",
"@babel/preset-env": "^7.10.2"
}
}
首先在根目录执行npm install
npm install
在根目录执行babel编译
$ npx babel ./src/demo1.js -o ./lib/demo1.js
执行完毕后会在lib目录下面看到一个编译过后的demo1.js文件:
var _class, _descriptor, _temp;
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
var Person = (_class = (_temp = function Person() {
_classCallCheck(this, Person);
_initializerDefineProperty(this, "name", _descriptor, this);
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [defineName], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class);
function defineName(target, property, descriptor) {
delete descriptor.initializer;
return _objectSpread(_objectSpread({}, descriptor), {}, {
value: "yasin"
});
}
document.write(new Person().name);
哈哈,反正我是看不懂写了啥
直接浏览器打开我们的demo/index.html:
可以看到页面上出现了我们的“yasin”字段。
demo2.js:
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
@defineClass
class Person {
}
function defineClass(target) {
console.log(target)
}
编译过后demo2.js:
var _class;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
var Person = defineClass(_class = function Person() {
_classCallCheck(this, Person);
}) || _class;
function defineClass(target) {
console.log(target);
}
源码还是很简单的吧,也就是把函数Person当参数传给了defineClass方法
我们试着给Person的原型上加一个name属性“yasin”:
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
@defineClass
class Person {
}
function defineClass(target) {
target.prototype.name="yasin";
}
document.write(new Person().name);
重新编译运行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LjqhuPUv-1592134958078)(/Users/yinqingyang/doc/h5/study/高级程序设计/前端框架系列之decorator/屏幕快照 2020-06-14 下午6.46.46.png)]
demo1.js:
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
class Person {
@defineName name;
}
function defineName(target, property, descriptor) {
delete descriptor.initializer;
return {
...descriptor,
value: "yasin"
};
}
document.write(new Person().name);
编译过后的源码:
var Person = (_class = (_temp = function Person() {
_classCallCheck(this, Person);
_initializerDefineProperty(this, "name", _descriptor, this);
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [defineName], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class);
function defineName(target, property, descriptor) {
delete descriptor.initializer;
return _objectSpread(_objectSpread({}, descriptor), {}, {
value: "yasin"
});
}
document.write(new Person().name);
源码还是比较好懂的吧,就不一一解析了。
demo3.js:
/**
* @author YASIN
* @version [React-Native V01, 2020/6/14]
* @date 2020/6/14
* @description Person
*/
class Person {
@defineMethod
say(msg){
console.log('hello '+msg);
}
}
function defineMethod(target,property,descriptor) {
Object.defineProperty(target, "name", {
configurable: true,
enumerable: true,
value: "yasin"
});
const origin=descriptor.value;
return {
...descriptor,
value:function(msg){
console.log('my name is '+this.name);
return origin.call(this,...arguments);
}
}
}
new Person().say("world");
可以看到,我们给Person的原型定义了一个name属性:
Object.defineProperty(target, "name", {
configurable: true,
enumerable: true,
value: "yasin"
});
然后重写了say方法:
const origin=descriptor.value;
return {
...descriptor,
value:function(msg){
console.log('my name is '+this.name);
return origin.call(this,...arguments);
}
}
最后编译运行:
可以在输出栏console中看到
demo3.js:47 my name is yasin
demo3.js:31 hello world