从 Babel 转译过程浅谈 ES6 实现继承的原理

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

这篇文章会从最简单的代码入手,一步步剖析相关的原理以及每一个函数的做用。代码的转译直接在 Babel 官网进行便可。数组

ES6 的 Class 是如何实现的

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

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

转译以后的结果是:app

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 调用的,若是不是就抛出一个错误。工具

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

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

转译后获得:this

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 看起来好像有点复杂,不过不要紧,咱们一个一个函数理清楚就好了。prototype

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

_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()       // Super
Super.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'
//  Super1
const 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.prortotype
const obj = new Son()
console.log(obj)     
// {b:1}      
// 这里 `Son` 一样也是返回父类 `Parent` 的调用结果(一个对象),它并无继承在父类上声明的实例属性 `a`。

到这里,咱们的分析基本就结束了。但愿你阅读完本文以后有所收获,若发现文章有错误,也欢迎评论区指正。

相关文章
相关标签/搜索