从babel看class(上)

class 语法已经出来很长时间了,借助 babel 咱们能够在生产中使用,下面就经过一个例子来看看 babel 是怎么处理 class 的es6

es6

下面是一段代码,咱们分别为实例、静态、实例的原型添加方法和属性数组

class Foo {
  static foo = "Foo";
  foo = "foo";
  static getName() {
    return this.foo;
  }
  getName() {
    return this.foo;
  }
  static get value() {
    return this.foo + "static";
  }
  get value() {
    return this.foo + "example";
  }
}
复制代码

上面用到了staticstatic代表属性或者方法添加到 Class 自己而不是实例上,下面再来对比一下 es5 的写法babel

es5

function Foo() {
  this.name = "foo";
}
Foo.foo = "Foo";
Foo.getName = function() {
  return this.foo;
};
Foo.prototype.getName = function() {
  return this.foo;
};
Object.defineProperty(Foo, "value", {
  configurable: true,
  enumerable: true,
  get() {
    return this.foo + "static";
  }
});
Object.defineProperty(Foo.prototype, "value", {
  configurable: true,
  enumerable: true,
  get() {
    return this.foo + "example";
  }
});
复制代码

上面咱们使用了Object.defineProperty来为对象定义属性,其中 getter/setter 是可选,不过在严格模式下中 getter/setter 必须同时出现,使用 getter/setter 后 value 和 writable 不可出现,不然报错。函数

babel 转码

"use strict";

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, 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;
}

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;
}

var Foo =
  /*#__PURE__*/
  (function() {
    function Foo() {
      _classCallCheck(this, Foo);

      _defineProperty(this, "foo", "foo");
    }

    _createClass(
      Foo,
      [
        {
          key: "getName",
          value: function getName() {
            return this.foo;
          }
        },
        {
          key: "value",
          get: function get() {
            return this.foo + "example";
          }
        }
      ],
      [
        {
          key: "getName",
          value: function getName() {
            return this.foo;
          }
        },
        {
          key: "value",
          get: function get() {
            return this.foo + "static";
          }
        }
      ]
    );

    return Foo;
  })();

_defineProperty(Foo, "foo", "Foo");
复制代码

转码过的代码看起来有点多,不过咱们顺着执行顺序来看,首先var Foo =内部执行了一个_classCallCheck函数,那么它有什么用呢?post

其实这个函数是为了保证 class 必须经过 new 来调用,ES6 规定 class 必须经过 new 调用,否则会显式报错。ui

必须为 new 调用

判断一个函数是否为new调用能够经过 instanceof 来进行判断,例如:this

function F() {
  if (!(this instanceof F)) {
    throw new Error("必须经过new调用");
  }
}
复制代码

在全局做用域下若是不经过 new 调用 this 为 undefinedinstanceof与非对象对比老是返回false;es5

在 ES6 还能够经过new.target来判断spa

function F() {
  if (!new.target) {
    throw new Error("必须经过new调用");
  }
}
复制代码

new.target 指向被 new 调用的构造函数,若是不存在返回 undefinedprototype

上面介绍了两种判断方法再来看下 babel 是怎么处理这一过程的

  1. 将 this 和构造函数传递给_classCallCheck
  2. _classCallCheck将参数传递给_instanceof函数;

_instanceof函数负责了什么事情呢?

其实也是判断函数是否经过 new 调用,不过它同时对Symbol.hasInstance也进行了判断。

Symbol.hasInstance

当其余对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法,

方法定义在类自己

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass(); // true
复制代码

上面演示了一个例子,咱们书接上文看下_instanceof函数内部怎么判断这一过程

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}
复制代码

判断构造函数存在且同时存在 Symbol.hasInstance方法,若是有就执行Symbol.hasInstance(),没有的话就直接执行instanceof判断。

撒花,这里初始判断讲完,下面就是为对象赋值

赋值

咱们继续顺着代码看,发现调用了_defineProperty,它是为对象辅助赋值,转到函数内部咱们看到

if (key in obj) {
  Object.defineProperty(obj, key, {
    value: value,
    enumerable: true,
    configurable: true,
    writable: true
  });
} else {
  obj[key] = value;
}
复制代码

不过为何要经过key in obj来判断呢,其实下面的_defineProperties函数也用到了这一判断,简单来讲就是否是getter/setter就直接把 value 添加到Object.defineProperty的 value 上,不然就直接复制。

再来看代码最后一句也调用了这个函数为 Foo 静态属性赋值,对应的咱们已经走完了对应 class 的静态和实例属性的赋值

class Foo {
  static foo = "Foo";
  foo = "foo";
}
复制代码

在顺着代码往下看发现调用了_createClass函数,同时还把构造函数以及咱们的静态方法和实例方法经过数组传递,咱们转到_createClass函数

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}
复制代码

这一步就是为构造函数和构造函数的prototype属性赋值,再来看_defineProperties函数

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);
  }
}
复制代码

_defineProperties执行的步骤很简单

  1. 遍历数组,取到每次循环的数组下标对象;
  2. 为对象添加enumerableconfigurable属性描述符,注意enumerable赋值为 false,这样作的缘由是类的内部全部定义的方法,都是不可枚举的;
  3. 判断 value 存在,若是存在添加writable描述属性
  4. 经过Object.defineProperty为对象赋值

最后

上面把 class 转化为构造函数讲完了,不过你是否好奇 babel 怎么处理继承的呢?

下一篇文章从babel看class(下)就讲解这一过程,若是喜欢请点赞一下,^_^

相关文章
相关标签/搜索