12_ ES6 类的使用和转 ES5 源码阅读

认识 class 定义类

我们会发现,按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码不容易理解

  • 在 ES6(ECMAScript2015)新的标准中使用了 class 关键字来直接定义类
  • 但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已
  • 所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系

那么如何使用 class 来定义一个类呢?

  • 可以使用两种方式来声明类: 类声明和类表达式
// 类声明
class Person {}

// 类的表达式
const Student = class {};

类和构造函数的异同

本质上类只是构造函数的一种语法糖写法,所以很大一部分是相同的

class Person {}

const p = new Person();

console.log(Person); // [class Person]
console.log(Person.prototype); // {}
console.log(Person.prototype.constructor); // [class Person]

console.log(p.__proto__ === Person.prototype); // true

console.log(typeof Person); // function

类的构造函数

  • 如果我们希望在创建对象的时候给类传递一些参数,这个时候应该怎么做呢?

    • 每个类都可以有一个自己的构造函数,这个方法的名称是固定的 constructor
    • 当我们通过 new 操作符,操作一个类的时候会调用这个类的构造函数 constructor
    • 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常
  • 当我们通过 new 关键字操作类的时候,会调用这个 constructor 函数,并且执行如下参数:

    1. 在内存中创建一个新的对象(空对象)
    2. 这个对象内部的[[prototype]]属性会被赋值为该类的 prototype 属性
    3. 构造函数内部的 this,会指向创建出来的新对象
    4. 执行构造函数的内部代码(函数体代码)
    5. 如果构造函数没有返回非空对象,则返回创建出来的新对象

类的实例方法

  • 在上面我们定义的属性都是直接放到 this 上,也就意味着它是放到了创建出来的新对象中
    • 在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享
    • 这个时候我们可以直接在类中定义
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  eating() {
    console.log(this.name + " eating Person~");
  }
}

类的访问器方法

我们之前讲对象的属性描述符时有讲过对象可以添加 setter 和 getter 函数的,那么类也是可以的

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  eating() {
    console.log(this.name + " eating Person~");
  }

  set name(newName) {
    console.log("调用了name的setter方法");
    this.name = newName;
  }

  get name() {
    console.log("调用了name的getter方法");
    return this.name;
  }
}

类的静态方法

静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用 static 关键字来定义

要注意一下: 静态方法是定义在构造函数上的,并不是原型

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  eating() {
    console.log(this.name + " eating Person~");
  }

  static studying() {
    console.log(this.name + " studying Person Static~");
  }
}

ES6 类的继承-extends

前面我们讨论了在 ES5 中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的

在 ES6 中新增了使用 extends 关键字,可以很方便的帮助我们实现继承:

class Person {}

class Student extends Person {}

super 关键字

我们会发现在上面的代码中我是用了一个 super 关键字,这个 super 关键字有不同的使用方式:

  • 注意:在子类(派生类)的构造函数中使用 this 或者返回默认对象之前,必须先通过 super 调用父类的构造函数
  • super 的使用位置有三个: 子类的构造函数、实例方法、静态方法
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  eating() {
    console.log(this.name + " eating Person~");
  }

  static studying() {
    console.log(this.name + " studying Person Static~");
  }
}

class Student extends Person {
  constructor(name, age, sno) {
    // 1. 在子类构造函数中使用super
    super(name, age);
    this.sno = sno;
  }

  running() {
    console.log(this.name + "running~");
  }

  static studying() {
    // 2. 在子类静态方法中使用super
    super.studying();
  }

  eating() {
    // 3. 在子类实例方法中使用super
    super.eating();
  }
}

使用 babel 查看转换后的代码

我们可以访问https://www.babeljs.cn/repl这个网站将代码转成 ES5 以前代码的样子:

简单的 class

首先来看一下简单的单纯定义 class

  1. 初看转换后的代码有一点需要注意,代码中这一段/*#__PURE__*/ 将函数声明为纯函数,方便 webpack 再压缩代码的时候做 tree sharking,如果这段函数没有使用到,就会被移除;
  2. 这里还有一个自执行函数,目的是防止当前代码中的局部变量和项目中的其它变量重名
  3. 继续,我们会看到有一个边界处理,作用是什么,作用就是防止再使用时将我们的构造函数直接当成一个函数去调用,这里就用 instanceof 去做了一个判断
  4. 继续,下来就是在构造函数的原型上添加方法,并直接在构造函数上添加静态方法,并返回
// 转换前
class Obj {
  constructor(name) {
    this.name = name;
  }

  eating() {
    console.log("eating");
  }
}

// 转换后
("use strict");

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);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}

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

    this.name = name;
  }

  _createClass(Obj, [
    {
      key: "eating",
      value: function eating() {
        console.log("eating");
      },
    },
  ]);

  return Obj;
})();

继承的转换

再来看一下继承,这块有点复杂,没去细读了

  1. 首先在创建子类的时候调用了_inherits(Stu, _Obj); 这行代码就是实现继承的方法,和之前我们实现寄生组合式继承原理是一致的
  2. 但是这里注意在使用Object.create的时候,直接将 constructor 修改了,将原先两步操作合并了
  3. 注意看下一步_setPrototypeOf(subClass, superClass)这里目的是为了静态方法的继承,因为子类可以直接通过例如Stu.xxx访问父类的静态方法的
  4. 接下来就是创建 super 了,涉及到 reflect,这个没什么印象了
// 转换前
class Obj {
  constructor(name) {
    this.name = name;
  }

  eating() {
    console.log("eating");
  }
}

class Stu extends Obj {
  constructor(name, age) {
    super(name);
    this.age = age;
  }

  running() {
    console.log("running");
  }
}

// 转换后
("use strict");

function _typeof(obj) {
  "@babel/helpers - typeof";
  return (
    (_typeof =
      "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
        ? function (obj) {
            return typeof obj;
          }
        : function (obj) {
            return obj &&
              "function" == typeof Symbol &&
              obj.constructor === Symbol &&
              obj !== Symbol.prototype
              ? "symbol"
              : typeof obj;
          }),
    _typeof(obj)
  );
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true },
  });
  Object.defineProperty(subClass, "prototype", { writable: false });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf
    ? Object.setPrototypeOf.bind()
    : function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
      };
  return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived),
      result;
    if (hasNativeReflectConstruct) {
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this, arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  } else if (call !== void 0) {
    throw new TypeError(
      "Derived constructors may only return object or undefined"
    );
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}

function _isNativeReflectConstruct() {
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;
  try {
    Boolean.prototype.valueOf.call(
      Reflect.construct(Boolean, [], function () {})
    );
    return true;
  } catch (e) {
    return false;
  }
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf.bind()
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

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);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}

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

    this.name = name;
  }

  _createClass(Obj, [
    {
      key: "eating",
      value: function eating() {
        console.log("eating");
      },
    },
  ]);

  return Obj;
})();

var Stu = /*#__PURE__*/ (function (_Obj) {
  _inherits(Stu, _Obj);

  var _super = _createSuper(Stu);

  function Stu(name, age) {
    var _this;

    _classCallCheck(this, Stu);

    _this = _super.call(this, name);
    _this.age = age;
    return _this;
  }

  _createClass(Stu, [
    {
      key: "running",
      value: function running() {
        console.log("running");
      },
    },
  ]);

  return Stu;
})(Obj);
Last Updated:
Contributors: hqchqc