美文网首页
前端框架系列之(装饰器Decorator)

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

作者: vv_小虫虫 | 来源:发表于2020-06-22 21:32 被阅读0次

    简介:

    装饰器是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 //项目清单
    
    在这里插入图片描述

    index.html:

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

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </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:

    在这里插入图片描述

    可以看到页面上出现了我们的“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
    

    相关文章

      网友评论

          本文标题:前端框架系列之(装饰器Decorator)

          本文链接:https://www.haomeiwen.com/subject/lonffktx.html