es6 class 和extends源码分析
前言
- 本文对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。protoProps和staticProps中的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()当前调用栈的this,call对应上文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属性。
参考资料
转载自:https://juejin.cn/post/7077579810599337992