从babel编译结果来学习ES6-class

1,441 阅读15分钟

1、准备工作-->搭建babel转码环境

本节不作为重点(如果觉得本节阅读有问题的同学,请查阅此处),长话短说。

npm init

全部回车,将会生成一个package.json文件,编辑内容,如下:

{
  "name": "class-explain",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "@babel/cli": "^7.12.10",
    "@babel/core": "^7.12.10",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "@babel/preset-env": "^7.12.11"
  },
  "scripts": {
    "build": "./node_modules/.bin/babel src --out-dir lib",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

新建.babelrc文件,内容如下:

{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

至此,我们的babel编译环境已经准备就绪。

2、编写用于测试的class文件

为了揭开class的神秘面纱,我们将尽可能的涵盖class中的语法(此时我们暂且不考虑继承,关键语法有属性(方法)静态属性(方法)私有属性(方法)构造函数),我们编写一个如下的class:

class Point {
  //静态属性
  static getName() {
    return "Rectangle";
  }

  /**
   * 私有属性
   */
  #background = 'red';

  /**
   * 普通成员属性
   */
  width = 100;

  height = 100;

  /**
   * 构造函数
   * @param {Number} x
   * @param {Number} y
   */
  constructor(x, y) {
    this.x = x;
    this.y = y;
    console.log(this.#background);
  }

  /**
   * 普通成员函数
   */
  toString() {
    return "(" + this.x + ", " + this.y + ")";
  }
}

使用

npm run build

如果命令行成功执行的话,我们将会得到如下内容:

"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);
  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 _background = new WeakMap();

var Point = /*#__PURE__*/ (function () {
  _createClass(Point, null, [
    {
      key: "getName",
      value: function getName() {
        return "Rectangle";
      },
    },
  ]);

  function Point(x, y) {
    _classCallCheck(this, Point);

    _background.set(this, {
      writable: true,
      value: 'red',
    });

    _defineProperty(this, "width", 100);

    _defineProperty(this, "height", 100);

    this.x = x;
    this.y = y;
  }

  _createClass(Point, [
    {
      key: "toString",
      value: function toString() {
        return "(" + this.x + ", " + this.y + ")";
      },
    },
  ]);

  return Point;
})();

我们对代码进行逐一分析:

2.1 限定类的调用形式

因为class需要使用new调用,因此,此处代码为了防止我们直接调用class(在使用new调用函数的时候,this指向构造器,这是强绑定this的一种方式,也是优先级最高的一种强绑定形式(参考自《你不知道的JavaScript(上)》))

//函数定义
function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
// 函数调用
function Point(x, y) {
    _classCallCheck(this, Point);
	//此处省略无关代码
}

2.2 生成类的属性和方法,静态属性和方法

接着为class生成属性、方法,静态属性、方法,

//定义一系列属性的方法
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;
}
//为class添加静态属性、方法和属性、方法
var Point = /*#__PURE__*/ (function () {
  //添加静态属性或者方法
  _createClass(Point, null, [
    {
      key: "getName",
      //静态属性
      value: function getName() {
        return "Rectangle";
      },
    },
  ]);
  
  function Point(x, y) {
    _classCallCheck(this, Point);

    _background.set(this, {
      writable: true,
      value: 'red',
    });

	//为类的实例生成属性
    _defineProperty(this, "width", 100);
	//为类的实例生成属性
    _defineProperty(this, "height", 100);

    this.x = x;
    this.y = y;
  }

  /**
   * 添加方法
   */
  _createClass(Point, [
    {
      key: "toString",
      value: function toString() {
        return "(" + this.x + ", " + this.y + ")";
      },
    },
  ]);

  return Point;
})();

这儿出现了一个非常关键的语法:Object.defineProperty(target:Object, prop: string, descriptor:PropertyDescriptor),请大家划重点。这个方法,相信使用Vue的朋友们非常属性,Vue便是利用这个方法递归的设置双向绑定,这个方法接收3个参数,第一个参数是要设置属性的源对象,第二个参数是这个属性的键值,第三个属性是一个比较关键的属性,叫PropertyDescriptor,这个属性有6个属性(这6个属性都非常重要,尤其是与装饰器的结合,将会出现化腐朽为神奇的作用,笔者此处只做语法介绍),分别是

1、enumerable,属性是否可以被枚举,如果设置为true,for-in或Object.keys()将会遍历到该属性;

2、configurable,属性是否可以使用delete操作符将其删除,如果设置为true,将可以执行delete删除;

3、value,属性的值;

4、writable,属性的值是否是可写,如果是false的话,属性的值将会是不可改写的;

5、get,取值器函数,如果定义了取值函数的话,当我们获取属性值的时候,将会自动调用该函数;

6、set,赋值器函数,如果定义了赋值函数的话,当我们设置属性值的时候,将会自动调用该函数;

Vue的双向绑定便是动态的劫持了data函数返回的内容,建立侦听,从而实现数据变化,视图即可更新的效果。 Object.defineProperty还有个兄弟,叫Object.defineProperties,此处不再赘述。 从babel转码的结果来看,当如果class的属性或者方法重复定义,后面的定义将会覆盖前面。 并且从这一部分的代码,我们可以看出,class的静态属性或者方法可以直接通过类名调用,而class的方法,是定义在构造器的原形上,而class的属性定义在类的实例上的;

2.3 私有属性

刚才我们暂且没有讨论私有属性,我们单独来看babel的转码结果来学习私有属性

function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = privateMap.get(receiver);
  if (!descriptor) {
    throw new TypeError("attempted to get private field on non-instance");
  }
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}

var _background = new WeakMap();

var Point = /*#__PURE__*/ (function () {
  
  /**
   * 构造函数
   * @param {Number} x
   * @param {Number} y
   */
  function Point(x, y) {
    _classCallCheck(this, Point);
    _background.set(this, {
      writable: true,
      value: 200,
    });
	console.log(_classPrivateFieldGet(this, _background));
  }

  return Point;
})();

由于私有属性只能在类的内部访问,我们可以看到,babel在这儿使用的是是WeakMap,这也是ES6中引入的新的方法,可以通过WeakMap建立对象到值的映射(WeakMap的键只能是对象)。在这儿通过this设置了值,因此我们也只能通过this才能访问到这个值,这便印证了私有属性只能在类的内部访问的语法规则。不得不称赞这个设计思路,妙啊,🤓,同时也学到了WeakMap的一个使用场景(笔者在之前在掘金论坛有过和开发者对于私有属性设计的讨论)。

2.4 构造函数

看完了上面比较复杂的代码,此处就比较简单了,跟我们写的ES5也没有任何区别。

function Point(x, y) {
	//无关代码已忽略
    _classCallCheck(this, Point);
    this.x = x;
    this.y = y;
  }

我们可以看到,在构造器内部定义属性或者方法则是直接定义在类的实例上的。

2.5 小结

1、 静态属性或方法直接通过类调用(静态属性或方法定义在构造器上);

2、 属性或访问直接通过类的实例调用(方法定义在构造器的原形上,属性定义在类的实例上);

3、 构造器内部,通过this设置的属性或者方法直接定义在类的实例上;

4、 私有属性只能类内部访问(WeakMap设置this的引用,只能通过这个作用域下的this访问, 外界无法访问);

3、class的继承

如果说我为什么要用class,那便是class的继承语法,使得JS在的继承语法优雅程度上面有了质的飞越。 我们继续编写用于测试的class文件:

import Point from './index.js';
class Rectangle extends Point {
  /**
   * 私有属性
   */
  #alpha = 0;

  width = 100;

  static height = 100;

  constructor() {
    super();
  }

  toString() {
    console.log("Hello World");
    super.toString();
  }

  static draw() {}

  dispose() {}
}

运行命令,得到babel转码之后的代码,

"use strict";

// 辅助函数 惰性加载
function _typeof(obj) {
  "@babel/helpers - typeof";
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj &&
        typeof Symbol === "function" &&
        obj.constructor === Symbol &&
        obj !== Symbol.prototype
        ? "symbol"
        : typeof obj;
    };
  }
  return _typeof(obj);
}

var _index = _interopRequireDefault(require("./index"));

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

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

function _get(target, property, receiver) {
  if (typeof Reflect !== "undefined" && Reflect.get) {
    _get = Reflect.get;
  } else {
    _get = function _get(target, property, receiver) {
      var base = _superPropBase(target, property);
      if (!base) return;
      var desc = Object.getOwnPropertyDescriptor(base, property);
      if (desc.get) {
        return desc.get.call(receiver);
      }
      return desc.value;
    };
  }
  return _get(target, property, receiver || target);
}

function _superPropBase(object, property) {
  while (!Object.prototype.hasOwnProperty.call(object, property)) {
    object = _getPrototypeOf(object);
    if (object === null) break;
  }
  return object;
}

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 },
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf ||
    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;
  }
  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 {
    Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
    return true;
  } catch (e) {
    return false;
  }
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}
//此处不再赘述
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 _alpha = new WeakMap();

var Rectangle = /*#__PURE__*/ (function (_Point) {
  _inherits(Rectangle, _Point);

  var _super = _createSuper(Rectangle);

  /**
   * 私有属性
   */
  function Rectangle() {
    var _this;

    _classCallCheck(this, Rectangle);

    _this = _super.call(this);

    _alpha.set(_assertThisInitialized(_this), {
      writable: true,
      value: 0,
    });

    _defineProperty(_assertThisInitialized(_this), "width", 100);

    return _this;
  }

  _createClass(
    Rectangle,
    [
      {
        key: "toString",
        value: function toString() {
          console.log("Hello World");

          _get(_getPrototypeOf(Rectangle.prototype), "toString", this).call(this);
        },
      },
      {
        key: "dispose",
        value: function dispose() {},
      },
    ],
    [
      {
        key: "draw",
        value: function draw() {},
      },
    ]
  );

  return Rectangle;
})(_index["default"]);

_defineProperty(Rectangle, "height", 100);

除去我们之前已经讨论的代码,我们对上述代码进行分析:

3.1 惰性加载

此处,有一些辅助函数用到了一个比较重要的编码技巧,叫作“惰性加载”。 例如:

function _get(target, property, receiver) {
  if (typeof Reflect !== "undefined" && Reflect.get) {
    _get = Reflect.get;
  } else {
    _get = function _get(target, property, receiver) {
      var base = _superPropBase(target, property);
      if (!base) return;
      var desc = Object.getOwnPropertyDescriptor(base, property);
      if (desc.get) {
        return desc.get.call(receiver);
      }
      return desc.value;
    };
  }
  return _get(target, property, receiver || target);
}

惰性加载的目的是减少不必要的if-else分支语句,从而提升程序的执行效率。我们直接拿《JavaScript高级程序设计》这本书上的示例举例子,假设我们现在需要封装一个Ajax的函数(说用axios的朋友直接给我联系方式,我帮你挂华西最好的精神病门诊医生🤣),由于我们需要支持IE6,因此我们会进行兼容性判断,但是目前市面上绝大部分浏览器已经不再是IE6了,而这些浏览器还需要去走这个兼容性if-else分支判断吗,答案肯定是不需要的。因此,我们直接用当前宿主环境可支持的分支路径复写函数,以后我们的函数将不再进行兼容性的判断,这样,函数已经执行过一次之后,我们的性能将会大大提升,而我们仅仅只损失的是在函数第一次执行时候的这么一丁点儿性能。

这儿我们先从构造器分析,逐步的分析babel所定义的辅助函数。

3.2 关联原型链

首先

_inherits(Rectangle, _Point);

Rectangle是子类,Point是父类。

//核心继承函数
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 },
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

//设置原形的方法
function _setPrototypeOf(o, p) {
	//如果不存在Object.setPrototypeOf则手动修改原型链
  _setPrototypeOf =
    Object.setPrototypeOf ||
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

首先,我们先介绍一下Object.create方法,笔者借花献佛,直接贴出MDN的解释

Object.create(proto,[propertiesObject])
proto
新创建对象的原型对象。

propertiesObject
可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。
如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。

所以核心继承方法里面的这段代码

subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true },
});

意思大概就是: 通过Object.create得到一个对象,这个对象的__proto__指向,superClassd的原形对象,但这个对象的构造器指向的是subClass本身,同时,让subClass的原形对象指向这个对象(这儿比较绕,如果读者不太清楚,可以打开调试工具尝试一下)。 我们用代码来阐述这段文字所描述的意思 temp = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true }, }) subClass.prototype = temp; 由于temp.__proto__ === superClass.prototype(但temp的constructor是subClass) 由此关系,得出:subClass.prototype.__proto__ === superClass.prototype;

从_inherits方法中,我们得出2个非常重要的结论,

1、SubClass.prototype.__proto__ === SuperClass.prototype;

2、SubClass.__proto__ === SuperClass;

这正是阮一峰老师著《ES6入门》所述的class的原形指向关系。(笔者在此赘述一下我个人记忆这个原形链的指向关系,由于静态属性可以继承,静态属性直接通过类名调用,因此直接得出结论2;由于构造器以外的非私有非静态方法或属性可继承,而这些方法或属性是定义在原形上的,由此可得结论1;)

3.3 对构造器及构造器super的处理

接着:

var _super = _createSuper(Rectangle);

首先,在class中有一个重要的关键字:super,通过super可以调用父类的属性或方法(这是我们在面向对象的前端开发过程中实现多态的一个重要手段)。同时,在如果子类编写了constructor方法,必须在其开头调用super()(如果不写,则程序自动生成默认的),其目的是为了将构造器里面定义的的属性或者方法继承到子类; 我们先看构造器里面调用super的实现:

//辅助函数
function _typeof(obj) {
  "@babel/helpers - typeof";
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj &&
        typeof Symbol === "function" &&
        obj.constructor === Symbol &&
        obj !== Symbol.prototype
        ? "symbol"
        : typeof obj;
    };
  }
  return _typeof(obj);
}

//辅助函数
function _isNativeReflectConstruct() {
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;
  try {
    Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
    return true;
  } catch (e) {
    return false;
  }
}

//辅助函数
function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

//处理构造函数如果有return的情况
// 这儿 有个有趣的现象
// 当我们使用new调用函数的时候,如果构造函数有返回值,若返回的是基础类型,则自动忽略,并返回new的对象,如果函数返回的是引用类型,则返回这个引用类型
function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }
  return _assertThisInitialized(self);
}

//断言是否在构造器开头调用了super();
function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return self;
}

//定义super()
function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived),
      result;
      //执行构造器,目的是为了将构造器里面定义的的属性或者方法继承到子类,
      //如果存在反射的API,则用反射的API,否则,调用父类的构造函数
    if (hasNativeReflectConstruct) {
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this, arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}

var Rectangle = /*#__PURE__*/ (function (_Point) {
  _inherits(Rectangle, _Point);

  var _super = _createSuper(Rectangle);

  /**
   * 私有属性
   */
  function Rectangle() {
    var _this;

    _classCallCheck(this, Rectangle);

    _this = _super.call(this);
	//如果还没有调用super的话,错误断言;
    _alpha.set(_assertThisInitialized(_this), {
      writable: true,
      value: 0,
    });
	//如果还没有调用super的话,错误断言;
    _defineProperty(_assertThisInitialized(_this), "width", 100);

    return _this;
  }
  
  return Rectangle;
})(_index["default"]);

3.3 对使用super调用的属性或方法处理

接着,我们看一看,当我们调用super的属性或者函数的时候,babel所做的处理:

//babel转码前:
 super.toString();
//babel转码后,从当前类的原形对象上尝试获取该方法
_get(_getPrototypeOf(Rectangle.prototype), "toString", this).call(this);

//获取对象属性的辅助方法
function _get(target, property, receiver) {
  // 如果浏览器支持反射,则使用反射API	
  if (typeof Reflect !== "undefined" && Reflect.get) {
    _get = Reflect.get;
  } else {
  	// 否则,尝试从原形链向上查找对象
    _get = function _get(target, property, receiver) {
      var base = _superPropBase(target, property);
      // 如果找到原形链顶端都还没有找到,则终止查找
      if (!base) return;
      //如果找得到,获取属性的描述符
      var desc = Object.getOwnPropertyDescriptor(base, property);
      //如果PropertyDescriptor定义了getter,则调用getter方法
      if (desc.get) {
        return desc.get.call(receiver);
      }
      //否则,返回真正的属性值
      return desc.value;
    };
  }
  return _get(target, property, receiver || target);
}

//在对象及对象的原形链查找对应的属性,如果找的到,返回这个对象,如果找到了原形链的顶端,还找不到,则返回null
function _superPropBase(object, property) {
  while (!Object.prototype.hasOwnProperty.call(object, property)) {
    object = _getPrototypeOf(object);
    if (object === null) break;
  }
  return object;
}

4、总结

4.1 执行流程

我们现在来回顾一下class的执行流程:

1、首先判断是否有继承,如果有的话,先确定原形的指向关系;

2、然后紧接着,调用构造函数,如果没有构造函数,先生成默认构造,否则直接调用构造函数,在构造函数里判断是否有返回,如果返回的是基础类型,无视,并且返回这个对象,如果返回的是对象,则返回这个对象,否则,返回构造的对象;

3、随后,再初始化自己构造内部的属性(包括外部的属性),方法还有类的私有属性。

4、最后再初始化自己的方法,静态属性或方法。

4.2 结论

1、class的本质仍然是ES5的构造函数;

2、class的静态属性或方法定义在类上;

3、class的方法定义在类的原形上,属性定义在类的实例上;

4、在class的构造器内部,通过this设置的属性或者方法直接定义在类的实例上;

5、class的私有属性只能类内部访问,外界无法访问;

6、若对于两个类,SuperClass和SubClass,且SubClass继承自SuperClass,则有如下关系: SubClass.prototype.__proto__ === SuperClass.prototype, SubClass.__proto__ === SuperClass;

7、若对于两个类,SuperClass和SubClass,且SubClass继承自SuperClass,若不写contructor,则JS解析器自动生成,如若编写,则必须在constructor的开头调用父类的构造器super();

由于笔者水平有限,写作过程中难免出现错误,若有纰漏,请各位读者指正,你们的意见将会帮助我更好的进步。本文乃笔者原创,若转载,请联系作者本人,邮箱404189928@qq.com🥰