前端框架系列之(装饰器Decorator)

简介:

装饰器是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

Demo:

说了那么多,我们直接上代码。

项目目录:

首先我们创建一个叫decorator-demo的项目:

decorator-demo
	demo 
		index.html//demo入口文件
	lib //babel编译完毕后的文件
  src //工程源文件
  	demo1.js //demo测试入口
  babel.config.js //babel配置文件
  package.json //项目清单

前端框架系列之(装饰器Decorator)_第1张图片

index.html:

入口文件引用一个编译好的demo1.js文件


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <script src="../lib/demo1.js">script>
body>
html>

demo1.js:

利用装饰器修改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.config.js:

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里面用到了:

  • @babel/preset-env:会根据当前环境自动做babel转换es5代码需要
  • @babel/plugin-proposal-decorators: babel装饰器插件
  • @babel/proposal-class-properties: babel类属性插件

babel、preset和插件的具体使用方法我就不在这里介绍了

装饰器使用可以参考@babel/plugin-proposal-decorators官网:

https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy

package.json:

{
  "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:

前端框架系列之(装饰器Decorator)_第2张图片

可以看到页面上出现了我们的“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);
  • target: Person的原型对象 Person.prototype
  • property: 属性名称’name’
  • descriptor: name属性的描述对象,可通过Object.getOwnPropertyDescriptor获取

编译过后的源码:

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");
  • target: Person的原型对象 Person.prototype
  • property: 属性名称’name’
  • descriptor: name属性的描述对象,可通过Object.getOwnPropertyDescriptor获取

可以看到,我们给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

你可能感兴趣的:(js基础)