深入理解class和装饰器(上)

深入理解class和装饰器

class 的出现大大简化了 javascript 中类的写法,而装饰器又是 class 里面非常实用的功能,但是老实说,它们都是语法糖,并没有引入新的功能,那它们的原理是怎样的呢?本文来一一探究。通过本文,您可以学到:

  1. class 语法糖的原理是什么?
  2. super 的原理是什么?有什么注意事项?
  3. 装饰器的原理是什么?
  4. vue-class-component 是怎么实现 vue 的 class 写法的?
  5. vue-property-decorator 是怎么实现 watch 装饰器的?

class 语法糖

我们首先用 class 的写法写一个 demo:

class A {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(this.name);
  }

  static move() {
    console.log('move');
  }
}

class B extends A {
  constructor() {
    this.a = 1;
    super();
    this.b = 2;
  }

  hello() {
    console.log('hello');
  }

  static go() {
    console.log('go');
  }
}

通过分析 babel 打包后的代码,其实可以简化成下面这样:

var A = /*#__PURE__*/function () {
  function A(name) {
    this.name = name
  }

  A.prototype.say = function say() {
    console.log(this.name)
  }

  A.move = function move() {
    console.log('move')
  }

  return A
}()

var B = /*#__PURE__*/function (_A) {
  var _super = function _createSuperInternal() {
    return _A.apply(this, arguments) || this
  }

  function B() {
    var _this;

    // _this.a = 1; // 这里会出现 _this 未定义,导致报错,所以不能在 super 之前绑定实例属性
    _this = _super.call(this)
    console.log(_this)
    _this.b = 2;
    return _this;
  }

  // 这里其实等价于:
  // B.prototype = Object.create(_A.prototype)
  // B.prototype.constructor = B
  B.prototype = Object.create(_A.prototype, {
    constructor: {
      value: B,
      writable: true,
      configurable: true
    }
  })

  B.prototype.hello = function hello() {
    console.log('hello');
  }

  B.go = function go() {
    console.log('go');
  }

  return B;
}(A);

可以看到:

  1. class 语法糖原理其实就是使用 constructor 作为构造函数,然后在构造函数的 prototype 上面绑定实例方法,并且直接在构造函数上面绑定静态方法
  2. class 的继承就是组合继承的形式。

关于 super

我们从上面可以看到,super 其实是内部创建的一个方法,它使用构造函数继承的方法来继承实例属性。但由于 _this 其实是由 super 返回的,所以如果在 super 之前绑定实例属性的话,_this 还未定义,导致报错。所以在 super 之前不能绑定实例属性

那这里的 _this 为什么不直接用自己的 this 呢?

我们来思考这样一种场景,就是构造函数里面有返回值

class C {
  constructor() {
    return {a:2}
  }
}

上面的类会被编译成:

var C = /*#__PURE__*/function () {
  function C(name) {
    return {a:2}
  }

  return C
}()

我们在实例化这个类的时候,其实得到的是构造函数的返回值,即{a:2}这个对象。所以如果父类是这种返回对象的形式的话,子类在继承的时候,就必须在这个返回值上面绑定实例属性,而不是在自己的this上面绑定实例属性。

原文地址:https://www.cnblogs.com/yangzhou33/p/13875917.html