ES6 Class源码分析,带你一行行看懂Class背后的逻辑

1,250 阅读8分钟

上一篇关于React的博客中留了一个关于Es6 class的坑,关于class与function之间的关系,我们这篇博客来讨论一下。

我们带着两个问题来看这篇博客:

  • class是如何用function实现的
  • new class和new function有什么区别

class如何用function实现

原型链

我们都知道,class其实是function的一个语法糖,是基于原型链的的,所以想要看懂这篇博客,建议大家先去看看我的前面一篇讲原型链的博客:用公式讲清楚原型链

利用babel将class转化为es5代码

我们可以写一段class的代码,然后利用babel在线工具将其转化为es5的代码,然后一步步分析。

首先我们先写一段,class的代码

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

    static type = 'being'

  sayName (){
    return this.name
    }

  static intro(){
    console.log("")
    }
}

class Men extends Person{
    constructor(name, age){
        super(name, age)
      this.gender = 'male'
    }
}

转化后的函数太长了,我就不完整贴在这里了,想看完整版的可以去上面的连接自己看,我们现在开始一步步分析这个转化后的函数。

辅助函数

_inherits

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

看这个函数的名字是用来做继承用的,也就是extends的时候会调用这个函数。

这个函数首先上来是判断父类是否符合条件,如果不符合,直接抛出异常。

紧接着是调用Object.create方法,这个方法的作用是:

**Object.create()**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

也就是说:

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

这段代码就完成了基于原型链的继承,这段代码的结果就是,通过superClass.prototype创建了一个新的对象,新对象的__proto__指向superClass.prototype,然后将这个对象赋值给subClass.prototype。

也就是说,subClass.prototype.__proto__ = superClass.prototype

这样一来,subClass的原型链上就有了superclass的原型。

然后就是最后一步的_setPrototypeOf,我们继续看看这个函数是做什么的

_setPrototypeOf

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

这个函数的作用也是构造原型链,其核心目的就是这句

o.__proto__ = p

结合上一个辅助函数

_setPrototypeOf(subClass, superClass)

那结果就是subClass.__proto__ = superClass

_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是什么,Reflect.construct是什么

首先Reflect其实也是ES6的新语法,具体可以做什么可以去看MDN,不是我们目前的重点,我们暂时只关注其中一点:

Reflect.construct(target, argumentsList[, newTarget\])

对构造函数进行 new 操作,相当于执行 new target(...args)

那这个函数的作用就比较明显了,就是看看当前的执行环境下能不能用Reflect.construct去创建新的对象。

_getPrototypeOf

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

这个函数就是获取传入参数的原型链上的下一个原型,也可以理解为它直接继承的对象。

_typeof

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

这段代码首先通过if-else创建了函数变量_typeof,由于是es5代码,所以没有const和let,而这种直接变量赋值的方式其实与var相同,所以会存在变量提升

结果就是在函数作用域中声明了一个函数,赋值给_typeof,然后调用这个函数返回结果。

置于为什么会有这个判断,我应该又要在这里留一个坑了,我们暂时不管它,就当它是为了浏览器的兼容性好了。

_possibleConstructorReturn

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

首先我们先要知道void 0 === undefined 是 true.

再回来看这段代码,就是说如果call是对象或者函数,直接返回call。

如果不是,那就看看self是不是undefined,是的话就抛出异常,不是则返回self

_createSuper

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

好了,看了上面那么多辅助函数,最终都是在这里使用的。

我们好好梳理一下这个函数是做什么的:

  • 首先判断当前执行环境能不能调用Reflect.construct,如果可以hasNativeReflectConstruct就是true。
  • 然后这个函数执行的结果,其实返回了另外一个函数_createSuperInternal,这个函数中用到了外层的参数,所以这是个闭包。
  • 那我们看一下,这个闭包中做了什么:
    • 获取Derived的原型作为Super
    • 如果当前环境可以调用Reflect.construct(hasNativeReflectConstruct是true)
    • var NewTarget = _getPrototypeOf(this).constructor;获取当前函数执行者的原型链上的上一级的构造函数,并赋值给NewTarget。
    • result = Reflect.construct(Super, arguments, NewTarget);调用 new Super,参数是arguments,创建的对象的构造函数是NewTarget
    • 如果hasNativeReflectConstruct是false,直接在this上调用apply,这种继承方式其实就是借用构造函数继承这种继承方式,还有许多其他的继承方式,有兴趣可以去看我的另外一篇博客:kingworker.cn/javascript-…
    • 最终调用_possibleConstructorReturn(this, result),结果就是,如果result如果是对象或者函数,则返回result,否则如果this不是undefined,则返回this,都不符合则抛出错误。

_classCallCheck

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

这个函数很简单,就是用来检测不能像普通函数那样调用class,比如class Persion你不能直接Persion()

_defineProperties

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

这两个函数一起的作用其实就是在target上不断定义新的属性

_createClass

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

结合上面的_defineProperties函数,这个函数三个参数,第一个参数是构造函数,第二个参数是所有普通属性的数组,第三个是所有静态属性的数组,所以这个函数的作用其实就是:

  • 将class中的普通属性定义在构造函数的原型上,这样当我们new一个实例的时候,就可以在实例的原型链上找到这些普通的属性

    看过我前面关于原型链博客的应该对我的公式有印象:

    a = new A() => a.__proto__ = A.prototype

  • 将class中的静态属性定义在构造函数上,这样我们就可以直接在构造函数上点出静态方法。

动态的生成代码

好了,有了上面的这些辅助函数,我就可以看看我们一开始定义的class是怎么利用这些辅助函数来实现的了

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

    this.name = name;
    this.age = age;
  }

  _createClass(
    Person,
    [
      {
        key: "sayName",
        value: function sayName() {
          return this.name;
        }
      }
    ],
    [
      {
        key: "intro",
        value: function intro() {
          console.log("");
        }
      }
    ]
  );

  return Person;
})();

_defineProperty(Person, "type", "being");

var Men = /*#__PURE__*/ (function (_Person) {
  _inherits(Men, _Person);

  var _super = _createSuper(Men);

  function Men(name, age) {
    var _this;

    _classCallCheck(this, Men);

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

  return Men;
})(Person);
  • 首先Persion定义为一个立即执行函数的返回结果,这个返回结果还是一个函数,所以我们可以得知,最后Persion其实还是一个函数,我们仍然称这个函数为Constructor Persion。

  • 这个Constructor Persion函数做了什么?

    • 首先,调用_classCallCheck(this, Person);,这个是我们刚才分析的辅助函数,保证我们的这个Constructor Persion函数不是直接调用的,而是放在new后面当做构造函数。
    • 然后在实例上添加两个属性,name和age
  • 调用_createClass,我们刚才分析过了, 这个函数三个参数分别是构造函数,普通属性的数组,静态属性的数组,所以这一步之后,Persion.prototype上有了一个新的属性,叫做sayName,Persion上有了一个新的静态属性,叫做intro。

  • 最后返回Constructor Persion

  • 因为type是Class Persion的静态属性,所以,在Constructor Persion上定义type,这样就可以直接在构造函数上找到。

  • Men同样也定义为一个立即执行函数的返回结果,这个返回结果也是一个函数。我们称这个函数为Constructor Men。

  • 首先调用_inherits(Men, _Person),通过刚才我们分析的_inherits作用可知,其作用就是

    subClass.prototype.__proto__ = superClass.prototype

    subClass.__proto__ = superClass

    放在这里,也就是说

    Men.prototype.__proto__ = _Person.prototype

    Men.__proto__ = _Person

    这样就完成了原型链的继承

    这里要注意的一点是,这个Men是下面声明的function Men,而不是外层的Men,因为函数声明会提升

  • var _super = _createSuper(Men)

    我们刚才分析过这个_createSuper函数,它会返回另外一个函数,这个函数的作用是获取Men.__proto__作为Super,然后以函数调用者为上下文去调用Super,而上面的_inherits(Men, _Person)已经使得Men.__proto__ = _Person。

    所以这一步的结果是返回了一个函数,会在以调用者为上下文去调用Persion函数

  • 其次定义Constructor Men(虽然代码顺序不是这样的,但是函数声明会变量提升),那这个Constructor Men做了什么呢?

    • 同样检查下是不是直接调用的Men。
    • 调用_super,也就是Men的一个实例上调用Persion,这就是典型的借用构造函数继承。
    • 在Men实例上挂载gender属性。
  • 返回Constructor Men

我们最后来分析下结果:

  • 首先我们得到了两个函数Persion和Men,这两个函数都只能通过new去调用,否则会抛出异常。
  • 其次这两个构造函数本身上都定义了声明的静态属性,构造函数的原型上定义了普通属性。
  • Men.prototype.__proto__ = _Person.prototype
  • Men.__proto__ = _Person
  • Men的构造函数中会通过在Men实例上调用Persion构造函数的方法去进行继承。

new function和new class

看到这里并且看明白的小伙伴应该明白了,并没有什么区别,只是多了一些内置的检查,帮你做了原型链的继承而已。

class本质返回的还是一个函数。