装饰器

作者: 吴晗君 | 来源:发表于2019-04-07 14:18 被阅读0次

    基本原理解析

    修饰类

    一个例子

    @log1
    @log2
    @log3
    class Person {
      kidCount () { return this.children.length; }
    }
    
    function log1(target) {
      console.log(target)
      console.log(1)
      return descriptor;
    }
    function log2(target) {
      console.log(target)
      console.log(2)
      return descriptor;
    }
    function log3(target) {
      console.log(target)
      console.log(3)
      return descriptor;
    }
    
    let p = new Person()
    console.log(Person)
    

    先用babel转一下,看编译后的结果

    // proposal是提案的意思
    npm i @babel/cli @babel/core @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
    
    // .babelrc
    {
      "presets": [
        "@babel/preset-env"
      ],
      "plugins": [
        ["@babel/plugin-proposal-decorators", { "legacy": true}],
        ["@babel/plugin-proposal-class-properties", { "loose" : true }]
      ]
    }
    

    这是结果

    "use strict";
    
    var _class;
    
    function _classCallCheck(instance, Constructor) {
      if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
      }
    }
    
    function _defineProperties(target, props) {
      for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
      }
    }
    
    function _createClass(Constructor, protoProps, staticProps) {
      if (protoProps) _defineProperties(Constructor.prototype, protoProps);
      if (staticProps) _defineProperties(Constructor, staticProps);
      return Constructor;
    }
    
    var Person =
      log1(
        (_class =
          log2(
            (_class =
              log3(
                (_class =
                  /*#__PURE__*/
                  (function() {
                    function Person() {
                      _classCallCheck(this, Person);
                    }
    
                    _createClass(Person, [
                      {
                        key: "kidCount",
                        value: function kidCount() {
                          return this.children.length;
                        }
                      }
                    ]);
    
                    return Person;
                  })())
              ) || _class)
          ) || _class)
      ) || _class;
    
    function log1(target) {
      console.log(target);
      console.log(1)
      return descriptor;
    }
    
    function log2(target) {
      console.log(target);
      console.log(2)
      return descriptor;
    }
    
    function log3(target) {
      console.log(target);
      console.log(3)
      return descriptor;
    }
    
    var p = new Person();
    console.log(Person);
    
    

    其实就是将类不断传入装饰器。
    修饰器不仅可以修饰类,还可以修饰类的属性。

    修饰类的属性

    举两个例子,分别装饰实例属性和原型属性
    第一个,装饰实例属性

    function readonly (target, name, descriptor) {
      descriptor.writable = false;
      return descriptor;
    }
    class Person {
      name () { return `${this.first} ${this.last}` }
      @readonly
      xxx = 1
      yyy = 2
    }
    
    let p = new Person()
    console.log(p)
    

    这是结果

    "use strict";
    
    var _class, _descriptor, _temp;
    // initializer是插件自身提供的,描述符上并没有这个属性。
    // 只有修饰实例属性的时候用得到,可以在下面看到
    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
      });
    }
    // 检验是否用new运算符初始化,这是this就是instance,必须为构造函数的实例
    function _classCallCheck(instance, Constructor) {
      if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
      }
    }
    // 处理对象描述符,主要有以下几个注意点
    // 1. 默认不可遍历
    // 2. 默认可被删除
    // 3. value和get不能共存,有value就对应writeable默认为true,代表可写。注意:writeable为false时,进行写操作也不会报错,只是不会生效
    function _defineProperties(target, props) {
      for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
      }
    }
    // 给类的原型属性和静态属性赋值
    function _createClass(Constructor, protoProps, staticProps) {
      if (protoProps) _defineProperties(Constructor.prototype, protoProps);
      if (staticProps) _defineProperties(Constructor, staticProps);
      return Constructor;
    }
    // target 是类的原型
    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;
      }
    // 如果是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 set to use loose mode. " +
          "To use proposal-class-properties in spec mode with decorators, wait for " +
          "the next major version of decorators in stage 2."
      );
    }
    // 修饰器函数
    function readonly(target, name, descriptor) {
      descriptor.writable = false;
      return descriptor;
    }
    // 用了一些括号运算符
    var Person = ((_class = ((_temp =
      /*#__PURE__*/
      (function() {
        function Person() {
          _classCallCheck(this, Person);
    
          _initializerDefineProperty(this, "xxx", _descriptor, this);
    
          this.yyy = 2;
        }
    
        _createClass(Person, [
          {
            key: "name",
            value: function name() {
              return "".concat(this.first, " ").concat(this.last);
            }
          }
        ]);
    
        return Person;
      })()),
    _temp)),
    (_descriptor = _applyDecoratedDescriptor(_class.prototype, "xxx", [readonly], {
      configurable: true,
      enumerable: true,
      writable: true,
      initializer: function initializer() {
        return 1;
      }
    })),
    _class);
    var p = new Person();
    console.log(p);
    
    

    首先可以看到,修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。

    我们看转化后的ES5代码,有两个实例属性、原型属性,但只是修饰了用@运算符后边的xxx属性,yyyname都以正常的方式创建在了其各自的位置上。

    我们再比较一下修饰原型属性
    源代码

    class Person {
      @nonenumerable
      get kidCount() { return this.children.length; }
    }
    
    function nonenumerable(target, name, descriptor) {
      descriptor.enumerable = false;
      return descriptor;
    }
    

    编译后的代码

    // 原型属性
    _applyDecoratedDescriptor(
      _class.prototype,
      "kidCount",
      [nonenumerable],
      Object.getOwnPropertyDescriptor(_class.prototype, "kidCount"),
      _class.prototype
    )
    
    // 实例属性
    (_descriptor = _applyDecoratedDescriptor(_class.prototype, "xxx", [readonly], {
      configurable: true,
      enumerable: true,
      writable: true,
      initializer: function initializer() {
        return 1;
      }
    }))
    

    可以看到,修饰原型上的属性时,是用Object. getOwnPropertyDescriptor去取其描述符,在这基础上进行修改。而修饰实例属性的时候不是,这是因为在处理修饰器的时候还没有实例产生呀,只能自己写一个。

    注意点

    普通函数不能用修饰器的原因是函数存在变量提升。

    在redux中的应用

    // 原写法
    eactComponent extends React.Component {}
    
    export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
    
    //用装饰器的写法
    @connect(mapStateToProps, mapDispatchToProps)
    export default class MyReactComponent extends React.Component {}
    

    connect(mapStateToProps, mapDispatchToProps)生成的函数会执行,参数就是MyReactComponent这个类。

    更多应用

    core-decorator源码阅读

    相关文章

      网友评论

          本文标题:装饰器

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