likes
comments
collection
share

es6 class 和extends源码分析

作者站长头像
站长
· 阅读数 18

前言

  • 本文对class与extends核心源码方法进行分析,并指出es6 class extends与es5 寄生组合继承的不同点。
  • 本文源码来源于Babel
  • 本文为原创,未经同意请勿转载。

class 源码分析

class Person{
    constructor(name,age){
        this.name = name
        this.age = age
    }
    eating(){
        console.log(this.name + "eating~")
    }
}

将以上代码放到Babel,得到:

var Person = /*#__PURE__*/ (function () {
  function Person(name, age) {
    /**
     * 判断是否是new调用,若不是,throw new TypeError('...')
     */
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }
  
  _createClass(Person, [
    {
      key: "eating",
      value: function eating() {
        console.log(this.name + "eating~");
      }
    }
  ]);

  return Person;
})();
  • /*#__PURE__*/是纯函数标识。
  • 立即执行函数执行完后,var Person指向了内部声明的Person。
  • 有两个核心方法,分别为_classCallCheck和_createClass。

_classCallCheck(instance, Constructor)

这个方法判断是否是new调用,若不是,throw new TypeError('...')抛出语法错误

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
  • 传入的两个实参为,当前调用栈的this和构造函数(这里是Person)。
  • 当执行new Person()时,当前调用栈的this.__proto__指向Person.prototype,所以此时的this instanceof Person为true。
  • 当执行Person(),当前调用栈的this指向window,所以此时的this instanceof Person为false。

_createClass(Constructor, protoProps, staticProps)

这个方法为构造函数和其原型绑定属性,并将prototype的writable设为false。

/**
 * 
 * @param {Function} Constructor 构造函数
 * @param {Descriptor[]} protoProps 构造函数原型上的描述符数组
 * @param {Descriptor[]} staticProps 构造函数上的描述符数组
 * @returns 
 */
function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}
  • 这个方法接受Constructor 构造函数(Person),protoProps 构造函数原型上的描述符数组,staticProps 构造函数上的描述符数组。
  • _defineProperties(Constructor.prototype, protoProps);:为原型绑定protoProps
  • _defineProperties(Constructor, staticProps);:为构造函数绑定staticProps
  • protoPropsstaticProps中的Prop是带有key的属性描述符或存取描述符。

_defineProperties(target, props)

这个方法是Object.defineProperty的封装,迭代props并写入到target。

/**
 * 
 * @param {Function} target 目标函数
 * @param {Descriptor[]} props 描述符数组
 */
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);
  }
}

es6 class 与new的区别

_classCallCheck对类的调用方式进行了判断,如果不为new调用,会提示TypeError。

class extends 源码分析

class Student extends Person{
    constructor(name,age,sno){
        super(name,age);
        this.sno = sno;
    }
    running(){
        console.log(this.name + "stu runnig~")
    }
}

将以上代码放到Babel,得到:

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

  var _super = _createSuper(Student);

  function Student(name, age, sno) {
    var _this;

    _classCallCheck(this, Student);

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

  _createClass(Student, [{
    key: "running",
    value: function running() {
      console.log(this.name + "stu runnig~");
    }
  }]);

  return Student;
}(Person);
  • 这是个立即执行函数,内部的Student就是外部的Student。
  • _classCallCheck和_createClass前面已经分析过,接下来分析_inherits和_createSuper两个核心方法。

_inherits(subClass, superClass)

  • subClass.prototype[[prototype]]指向superClass.prototype
  • subClass[[prototype]]指向了superClass
  • subClass.prototype.constructor指向subClass
/**
 * @param {Fucntion} subClass 子类构造函数
 * @param {Fucntion} superClass 父类构造函数
 */
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 });

  //Object.setPrototypeOf
  if (superClass) _setPrototypeOf(subClass, superClass);
}
  • 这个方法接受subClass 子类构造函数(Student),superClass 父类构造函数(Person)。
  • if (typeof superClass !== "function" && superClass !== null)判断父类类型是否为function或null,若不是则抛出错误。
  • subClass.prototype = Object.create(superClass.prototype)与寄生式继承一样,创建一个[[prototype]]指向superClass.prototype的对象并赋值给subClass.prototype
  • Object.create第二个参数将subClass.prototype.constructor指向subClass
  • 这里出现的方法_setPrototypeOf其实是Object.setPrototypeOf的兼容性封装,将subClass[[prototype]]指向了superClass。目的是class的static方法的原型继承。

_setPrototypeOf(o, p)

Object.setPrototypeOf的兼容性封装

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

_createSuper(Derived)

返回一个借用父类构造绑定了属性的对象,使用Reflect.constructor支持es6 class的new.target属性。

/**
 * @param {Function} Derived 子类构造函数
 */
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);
  };
}
  • 这个方法接收Derived子类构造函数,返回的方法_createSuperInternal就是外部的_super
  • _isNativeReflectConstruct()返回是否支持内置方法Reflect.construct的布尔值。
  • _getPrototypeOf(o)Object.getPrototypeOf的兼容性封装,因为前面分析了_inherits通过_setPrototype设置子类的[[Prototype]]指向父类,所以这里_getPrototypeOf(Derived)获取到子类构造函数(Student)的[[Prototype]]指向Person。
  • _createSuperInternal()_super.call(this,name,age)调用时,call传入的this就是new Student执行时当前调用栈的this,注意这里使用了_getPrototypeOf(this).constructor而不使用Derived,原因是前面设置了子类构造函数的constructor属性可以修改writable: true
  • result = Reflect.construct(Super, arguments, NewTarget)借用了Super父类的构造函数并将属性绑定到result。result = Super.apply(this, arguments)借用了Super父类的构造函数并将属性绑定到this(new Student()当前调用栈的this)。
  • 接下来分析_possibleConstructorReturn方法。

_isNativeReflectConstruct()

判断是否支持内置方法Reflect.construct

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

_getPrototypeOf(o)

Object.getPrototypeOf的兼容性封装。

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

_possibleConstructorReturn(self, call)

这个方法从self和call选择被父类构造函数绑定属性的那个值。

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);
}
  • 这里的_typeof其实是针对Symbol类型对typeof做了额外处理,_typeof(call)可以理解为typeof call
  • 联系前面的内容,self值对应new Student()当前调用栈的thiscall对应上文result
  • call为object或function类型,那么call会作为_super.call(this,...args)最终返回值。
  • _assertThisInitialized(self)是判断self值是否为undefined,若不是,则返回self

_assertThisInitialized(self)

判断self值是否为undefined,若不是,则返回self,若是则,抛出super()未调用错误。

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

总结

es6 class其实本质还是es5的寄生组合继承,区别主要在于Object.setPrototypeOf,new.target等兼容性的处理及声明方式,函数调用方式,class extends super()等语法的不同。

es6 class 的特点

  • _classCallCheck对类的调用方式进行了判断,如果不为new调用,会提示TypeError。
  • class声明经过babel编译后是函数表达式,与function函数声明的预解析规则不同。

es6 class extends 与 es5寄生组合继承的区别

  • subClass的[[prototype]]指向superClass,以实现static方法继承。
  • 使用_this代替this,当未调用_this=_super.call(this,...args)时,_this为undefined,最终通过_assertThisInitialized(self)抛出super()未调用错误。
  • 对借用构造函数模式进行了额外的处理,使用Reflect.constructor支持es6 class的new.target属性。

为什么使用_this代替this?

  • 利于对super()是否已经调用的判断,若使用this,this为引用类型,也就无法通过_assertThisInitialized(self) 抛出错误。
  • Function.prototype.apply(call)在非new调用时不支持es6的new.target属性。

参考资料