都说 ES6 的 Class 是 ES5 的语法糖,那么 ES6 的 Class 是如何实现的呢?其实现继承的原理又是什么呢?无妨咱们通过 Babel 转译代码的形式,看看其中有什么门道。

这篇文章会从最简略的代码动手,一步步分析相干的原理以及每个函数的作用。代码的转译间接在 Babel 官网进行即可。

ES6 的 Class 是如何实现的

先从最简略的一个 Parent 类看起:

class Parent{    constructor(){        this.a = 1           this.getA = function(){}   }}

转译之后的后果是:

function _classCallCheck(instance, Constructor) {    if (!(instance instanceof Constructor)) {        throw new TypeError("Cannot call a class as a function");    }}var Parent = function Parent() {    "use strict";    _classCallCheck(this, Parent);    this.a = 1;    this.getA = function () {};};

能够看到,这里的类本质上就是 ES5 中的构造函数,除了增加实例属性和实例办法之外,它还调用了一个 _classCallCheck 函数。

_classCallCheck 函数

这个函数会承受一个实例和构造函数作为参数,外部的 instance instanceof Constructor 用于判断这个类是不是通过 new 调用的,如果不是就抛出一个谬误。

接下来咱们尝试给这个类增加原型办法和静态方法:

class Parent{   constructor(){     this.a = 1     this.getA = function(){}   }   getB(){}   getC(){}   static getD(){}   static getE(){}}

转译后失去:

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;}var Parent = /*#__PURE__*/ (function () {  "use strict";  function Parent() {    _classCallCheck(this, Parent);    this.a = 1;    this.getA = function () {};  }  _createClass(    Parent,    [      {        key: "getB",        value: function getB() {}      },      {        key: "getC",        value: function getC() {}      }    ],    [      {        key: "getD",        value: function getD() {}      },      {        key: "getE",        value: function getE() {}      }    ]  );  return Parent;})();

emmm 看起来如同有点简单,不过没关系,咱们一个一个函数理分明就行了。

能够看到,此时的 Parent 变成了一个 IIFE,IIFE 执行之后依然是返回 Parent 类,但外部还封装了一个 _createClass 函数的调用。

_createClass 函数

_createClass 函数做了什么事呢?首先,它能够承受三个参数:

  • 第一个参数: 类(这里是 Parent 类)
  • 第二个参数:寄存对象的数组,每个对象都是对于类的原型办法的个性形容对象(这里是 getBgetC
  • 第三个参数:寄存对象的数组,每个对象都是对于类的静态方法的个性形容对象(这里是 getDgetE

接着,它会顺次查看是否有传第二个和第三个参数,如果有,就调用 _defineProperties 函数,别离为类的原型定义原型办法,为类自身定义静态方法。

_defineProperties 函数

_defineProperties 函数做了什么事呢?它承受类(或者类的原型)和一个寄存对象的数组作为参数,之后遍历数组中的每个对象,定义每个办法的个性,并将它们逐个增加到类(或者类的原型)下面。这里波及到的个性包含:

  • enumberable:该属性(办法)是否可枚举。如果办法自身曾经定义了该个性,则采纳该个性;如果没有定义,则定义该办法为不可枚举
  • configurable:该属性(办法)是否能够配置
  • writable:如果该属性是数据属性而不是拜访器属性,那么会有一个 value,此时设置该属性为可写

ES6 的继承是如何实现的

好了,根本搞清楚一个 class 的原理之后,当初咱们来看一下 ES6 是如何实现继承的。

将上面的代码进行转译:

class Parent{   constructor(){     this.a = 1      this.getA = function(){}    }   getB(){}     getC(){}   static getD(){}   static getE(){}}class Son extends Parent{    constructor(){        super()            }}

就失去了:

"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);}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 {    Boolean.prototype.valueOf.call(      Reflect.construct(Boolean, [], 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 _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;}var Parent = /*#__PURE__*/ (function () {  function Parent() {    _classCallCheck(this, Parent);    this.a = 1;    this.getA = function () {};  }  _createClass(    Parent,    [      {        key: "getB",        value: function getB() {}      },      {        key: "getC",        value: function getC() {}      }    ],    [      {        key: "getD",        value: function getD() {}      },      {        key: "getE",        value: function getE() {}      }    ]  );  return Parent;})();var Son = /*#__PURE__*/ (function (_Parent) {  _inherits(Son, _Parent);  var _super = _createSuper(Son);  function Son() {    _classCallCheck(this, Son);    return _super.call(this);  }  return Son;})(Parent);

emmm 如同越来越简单了,没事,咱们先稍稍简化一下(后面解释过的函数这里就间接略过了),再一个一个缓缓剖析:

"use strict";function _typeof(obj) { ... }function _inherits(subClass, superClass) { ... }function _setPrototypeOf(o, p) { ... }function _createSuper(Derived) { ... }function _possibleConstructorReturn(self, call) { ... }function _assertThisInitialized(self) { ... }function _isNativeReflectConstruct() { ... }function _getPrototypeOf(o) { ... }function _classCallCheck() { ... }function _defineProperties() { ... }function _createClass() { ... }var Parent = /*#__PURE__*/ (function () {  function Parent() { ... }  _createClass(...);  return Parent;})();var Son = /*#__PURE__*/ (function (_Parent) {  _inherits(Son, _Parent);  var _super = _createSuper(Son);  function Son() {    _classCallCheck(this, Son);    return _super.call(this);  }  return Son;})(Parent);

这里多出了很多新的函数,有的函数不是咱们探讨的重点,而且也齐全能够独自拎进去剖析,所以这里先简略把它们的作用介绍了,之后如果遗记了函数的作用,翻到这里来看即可。

_typeof(obj)
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);}

这是 Babel 引入的一个工具函数,次要是为了对 Symbol 进行正确的解决。它首先会查看以后环境是否反对原生的 Symbol,如果反对就间接返回 typeof obj 表达式的计算结果;如果不反对,再查看 obj 是不是通过 polyfill 实现的 Symbol 的一个实例,如果是就返回它的类型(也就是返回 "symbol"),如果不是,就返回 typeof obj 的计算结果。在这里,这个函数假设了咱们以后的环境是原生反对 Symbol 或者通过 polyfill 实现了反对的。

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

首先查看以后环境是否反对间接调用 Object.setPrototypeOf() 办法,如果不反对,就通过 __proto__ 手动给实例建设原型关系( __proto__ 是一个裸露的外部属性,个别不提倡间接进行操作)。

_possibleConstructorReturn(self,call)
function _possibleConstructorReturn(self, call) {  if (call && (_typeof(call) === "object" || typeof call === "function")) {    return call;  }  return _assertThisInitialized(self);}

如果你看过 new 或者 [[Construct]] 的外部实现,就会晓得,给构造函数指定了一个非空对象或者函数作为返回值之后,调用函数之后返回的将不是实例,而是这个对象或者函数。这里就是通过 _possibleConstructorReturn 这个函数来实现这件事的 —— 认真看它的名字,意思不就是“构造函数可能返回的值”吗?

这个函数承受两个参数,self 代表构造函数的实例,call 代表构造函数的返回值。外部的判断也很简略,call && (_typeof(call) === "object" || typeof call === "function") 是查看 call 的类型,当它是一个对象(留神这里是应用 typeof 进行查看,须要排除可能为 null 的状况)或者函数的时候,间接将其作为返回值;否则就返回 _assertThisInitialized(self)。等等,怎么又来了一个新函数呢?不要急,咱们接着就来看这个函数是干什么用的。

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

看这个函数的名字 —— “断言 this 曾经初始化”,也就是说,在调用这个办法的时候,咱们冀望的后果是 this 曾经失去初始化了。这里如果查看发现 thisundefined,就会抛出一个谬误,提醒咱们因为没有调用 super(),所以无奈失去 this;否则就返回 this 。为什么要应用 void 0 而不是 undefined 呢?因为非严格模式下 undefined 可能会被重写,这里应用 void 0 更加保险。

_isNativeReflectConstruct()
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;  }}

这个办法用于检测以后环境是否反对原生的 Reflect。为什么要做这个查看呢?前面咱们再来解释。

好了,咱们曾经剖析了这几个函数的作用,当初间接翻到最上面的代码,从 Son 子类看起:

var Son = /*#__PURE__*/ (function (_Parent) {  _inherits(Son, _Parent);  var _super = _createSuper(Son);  function Son() {    _classCallCheck(this, Son);    return _super.call(this);  }  return Son;})(Parent);

这里的 Son 同样是一个 IIFE,并且实际上也是返回一个 Son 子类构造函数,不同的是,它外部还封装了其它办法的调用。咱们逐个看一下这些办法的作用。

_inherits(Son,_Parent)
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);}

_inherit 是实现继承的其中一个外围办法,能够说它的实质就是 ES5 中的寄生组合式继承。这个办法承受一个父类和子类作为参数,首先会查看父类是不是函数或者 null,如果不是,则抛出谬误(为什么父类能够是 null?从 extends 看 JS 继承这篇文章进行了解释,这里我就不反复了)。

接着,调用 Object.create 设置父类的原型为子类原型的 __proto__。这里咱们会看到还传入了第二个参数,这个参数是子类原型的属性的个性形容对象(descriptor),咱们对 constructor 属性进行了设置,将它设置为可写、可配置,同时利用 value 修复了因重写子类原型而失落的 constructor 指向。为什么不设置 enumerable: false 呢?因为默认就是不可枚举的,不设置也行。

最初,咱们设置子类的 __proto__ 指向父类,这是 ES5 中没有的,目标是让子类继承父类的静态方法(能够间接通过类调用的办法)。

能够看到,通过调用 _inherit 函数,咱们曾经胜利让子类继承了父类的原型办法和静态方法。不过,实例上的属性怎么继承呢?这就要持续往下看了,接下来咱们调用 _createSuper() 函数并传入派生类(子类),这不是重点,重点是它创立并返回的另一个函数 _super

_super.call(this)
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);  };}

这里的 _createSuperInternal 就是 _super,调用的时候咱们绑定了其外部的 this 为子类实例。

它首先会依据之前的 _isNativeReflectConstruct 查看以后环境是否反对 Reflect,如果反对,则执行 result = Reflect.construct(Super, arguments, NewTarget),否则执行 result = Super.apply(this, arguments)

解释一下这里为什么要优先应用 Reflect。当执行 Reflect.construct(Super, arguments, NewTarget)的时候,最终会返回一个基于 Super 父类构造函数创立的实例,相当于执行了 new Super(...arguments),然而,这个实例的 __proto__constructorNewTarget,因而在某种程度上,你也能够说这就是一个子类实例,不过它领有父类实例的所有属性。

可能你会说,这和上面的 Super.apply (借用构造函数继承)不是没区别吗?非也。咱们应用 Super.apply 的时候,其实 new.target 属性是会失落的,就像上面这样:

function Super(){    console.log(new.target)}new Super()       // SuperSuper.apply({})   // undefined

然而如果应用 Reflect.consturct 来创建对象,则 new.target 不会失落:

function Super1(){    console.log('Super1')    console.log(new.target)}function Super2(){    console.log('Super2')    console.log(new.target)}const obj1 = Reflect.construct(Super1,{})              // 'Super1'//  Super1const obj2 = Reflect.construct(Super1,{},Super2)// 'Super1'//  Super2

能够看到,即使没有通过 new 去调用 Super1new.target 也依然指向 Super1;而在传了第三个参数之后,new.target 也没有失落,只是指向了 Super2(后面咱们说过了,某种程度上,能够说 obj1 就是 Super2 的实例)。

所以,这里优先应用 Reflect,是为了保障 new.target 不会失落。

之后,result 可能有三种取值:

  • 一个继承了父类实例所有属性的子类实例
  • 父类构造函数的调用后果,可能是父类构造函数中自定义返回的一个非空对象
  • 父类构造函数的调用后果,可能是默认返回的 undefined

如何解决这些不同的状况呢?这里调用了后面讲过的 _possibleConstructorReturn(this,result)函数,如果判断 result 是一个非空对象,也就是第一种和第二种取值状况,那么就间接返回 result;否则就是第三种状况了,此时就对当初传进去的子类实例(曾经通过 Super.apply 对它进行了加强),也就是 this,进行断言,而后返回进来。

当初,让咱们再回到 Son 构造函数。能够看到,调用它之后返回的正是 _super.call(this),也就是返回 result 或者通过加强的this。这里的 result 咱们晓得也有两种取值,如果是一个继承了父类实例所有属性的子类实例,那么实际上等价于通过加强的 this;如果是父类构造函数中自定义返回的一个非空对象,则意味着调用 Son构造函数之后返回的对象实际上并没有继承父类中申明的实例属性。相似上面这样:

function Parent(){    this.a = 1    return {b:1}}function Son(){    return Parent.call(this)}Son.prototype.__proto__ = Parent.prortotypeconst obj = new Son()console.log(obj)     // {b:1}      // 这里 `Son` 同样也是返回父类 `Parent` 的调用后果(一个对象),它并没有继承在父类上申明的实例属性 `a`。

到这里,咱们的剖析根本就完结了。心愿你浏览完本文之后有所播种,若发现文章有谬误,也欢送评论区斧正。