基本原理解析

修饰类

一个例子

@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源码阅读