ES6 中 class 的那些事

1,022 阅读4分钟

ES6 中的 class 有哪些特性呢?下面通过 babel 在线转换 将 class 转换成 ES5 语法,来分析 class 的特性。

class 代码:

class HelloES6 {
  a = 'hello';
  
  constructor() {
    this.b = 'ES6';
  }
  
  sayToES6() {
    console.log(`${this.a} ${this.b}`);
  }
  
  static sayToWorld() {
    console.log('hello world');
  }
}

通过 babel 在线转换 转换而成的 ES5 代码:

"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 HelloES6 =
  /*#__PURE__*/
  (function() {
    function HelloES6() {
      _classCallCheck(this, HelloES6);

      _defineProperty(this, "a", "hello");

      this.b = "ES6";
    }

    _createClass(
      HelloES6,
      [
        {
          key: "sayToES6",
          value: function sayToES6() {
            console.log("".concat(this.a, " ").concat(this.b));
          }
        }
      ],
      [
        {
          key: "sayToWorld",
          value: function sayToWorld() {
            console.log("hello world");
          }
        }
      ]
    );

    return HelloES6;
  })();

分析转换而成的 ES5 代码:

  1. 转换而成的 ES5 代码,首先声明代码是运行在严格模式下,然后声明了 5 个函数,_instanceof、_classCallCheck、_defineProperties、_createClass、_defineProperty,最后声明了一个变量 HelloES6,这个变量的值是一个立即函数的返回值,这个返回值就是 HelloES6 类对应的构造函数,所以 HelloES6 类不存在变量提升,如果在 HelloES6 类申明之前访问 HelloES6 类,HelloES6 类的值是 undefined。
  2. 这个立即执行函数,首先声明了一个函数 HelloES6,这个函数就是 HelloES6 类对应的构造函数,然后执行了 _createClass 函数,最后返回函数 HelloES6,赋值给变量 HelloES6。_createClass 的作用有 2 个,第一个,根据 HelloES6 类中是否声明方法,来决定是否执行 _defineProperties 函数,第二个,根据 HelloES6 类中是否声明静态方法,来决定是否执行 _defineProperties 函数。
  3. _defineProperties 函数的作用有 2 个,第一个,将 HelloES6 类中声明的方法,声明到构造函数 HelloES6 的原型 prototype 对象上,第二个,将 HelloES6 类中通过 static 声明的静态方法,声明到构造函数 HelloES6 上。注意,_defineProperties 函数在构造函数 HelloES6 和构造函数 HelloES6 原型 prototype 对象上声明方法的时候,同时默认将这些方法对应数据属性中的 enumerable 特性设置为了 false,所以这些方法都是不可遍历的。

小结:通过分析转换而成的 ES5 代码可知,1、class 类采用严格模式,2、class 类就是一个构造函数,3、class 类中声明的方法,其实是声明到构造函数的原型 prototype 对象上,并且这些方法不可遍历,4、class 类中通过 static 声明的静态方法,其实是声明到构造函数上,并且这些方法不可遍历。

通过转换而成的 ES5 代码,分析 new HelloES6 类:

当 new HelloES6 类时:

  1. 执行 _classCallCheck 函数,_classCallCheck 函数根据 _instanceof 函数的返回结果,决定是否抛出错误。抛出这个错误的时机就是没有用 new 使用 HelloES6 类,而是把 HelloES6 类当做普通函数使用。
  2. 执行 _instanceof 函数,_instanceof 函数的作用是,如果 HelloES6 类是通过 new 来使用的,返回 true,如果 HelloES6 被当做普通函数使用,返回 false。
  3. 执行 _defineProperty 函数,_defineProperty 函数的作用是将定义在 HelloES6 类中最顶层的属性 a 声明到 HelloES6 的实例上。注意,通过 _defineProperty 函数的定义,可以看出,如果在类中最顶层声明多个同名属性,以最后一次声明为准。
  4. 执行 this.b = "ES6",相当于是在执行 HelloES6 类中 constructor 函数的内容。
  5. 结束。

小结:通过转换而成的 ES5 代码,分析 new HelloES6 类可知,1、class 类必须通过 new 来调用,否则报错,2、在 class 类中最顶层声明的属性,其实是声明到了 class 类的实例上,并且声明多个同名属性,以最后一次声明为准。

总结

  1. class 类采用严格模式。
  2. class 类就是一个构造函数。
  3. class 类中声明的方法,其实是声明到构造函数的原型 prototype 对象上,并且这些方法不可遍历。
  4. class 类中通过 static 声明的静态方法,其实是声明到构造函数上,并且这些方法不可遍历。
  5. class 类必须通过 new 来调用,否则报错。
  6. 在 class 类中最顶层声明的属性,其实是声明到了 class 类的实例上,并且声明多个同名属性,以最后一次声明为准。