yliu

时来天地皆同力,运去英雄不自由

Babel to Class之继承(2)


前言

这是《Babel to Class》系列的第二篇

这个系列主要讲 Class 的普通写法、继承、私有属性和原生构造函数继承 babel 是如何实现这一过程的。

注:文章顺序存在关联,请按照顺序阅读。

前置知识点

super

super 通常用于 Class 继承中调用父类构造函数,以及在方法中调用父类方法。

class Rectangle {
  logNbSides() {
    return 'I have 4 sides';
  }
}

class Square extends Rectangle {
  logDescription() {
    return super.logNbSides() + ' which are all equal';
  }
}

更多的内容请参考 super

Object.create

Object.create(proto,[propertiesObject])

Object.create 用于创建一个新的对象,并将新的对象原型设置在 proto 上,它还支持第二个参数,第二个参数为对象每个对象的值为属性描述符。

var o = Object.create(Object.prototype, {
  // foo会成为所创建对象的数据属性
  foo: {
    writable: true,
    configurable: true,
    value: 'hello',
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function () {
      return 10;
    },
    set: function (value) {
      console.log('Setting `o.bar` to', value);
    },
  },
});

更多内容请参考 Object.create

Reflect.construct

Reflect.construct(target, argumentsList[, newTarget])
  • target:被运行的目标构造函数
  • argumentsList:类数组,目标构造函数调用时的参数。
  • newTarget:作为新创建对象的原型对象的 constructor 属性, 参考 new.target 操作符,默认值为 target。

Reflect.construct 的作用相当于不用 new 操作符调用构造函数,创建对象。

class Foo {}
var obj = new Foo(...args);
var obj = Reflect.construct(Foo, args);

注意点:

  1. 使用 Reflect.construct 创建对象,new.target 会自动指向 newTarget,
  2. 如果指定 newTarget,new.target 会指向 newTarget 且构造函数内部的 this 为 newTarget

更多内容请参考 Reflect.construct

Class

  • Class extends 继承的必须是 null 或者 function(class 也是 function),否则抛出错误;
  • Class 子类 extends 父类的时候必须在 constructor 显式调用 super(),否则抛出错误;

示例代码

class Parent {
  constructor() {
    this.age = 17;
  }

  getAge() {
    return this.age;
  }

  static getName() {
    return this.name;
  }
}

class Child extends Parent {
  constructor() {
    super();
    this.name = 'child';
  }

  static name = 'child';

  getAll() {
    return {
      name: this.name,
      age: this.age,
    };
  }
}

const child = new Child();
console.log(child.getAll());
console.log(Child.getName());

以下讲解基于上面的内容而来。

babel

'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 _defineProperty(obj, key, value) {
  // ...省略实现
}

function _classCallCheck(instance, Constructor) {
  // ...省略实现
}

function _defineProperties(target, props) {
  // ...省略实现
}

function _createClass(Constructor, protoProps, staticProps) {
  // ...省略实现
}

var Parent = /*#__PURE__*/ (function () {
  function Parent() {
    _classCallCheck(this, Parent);

    this.age = 17;
  }

  _createClass(
    Parent,
    [
      {
        key: 'getAge',
        value: function getAge() {
          return this.age;
        },
      },
    ],
    [
      {
        key: 'getName',
        value: function getName() {
          return this.name;
        },
      },
    ],
  );

  return Parent;
})();

var Child = /*#__PURE__*/ (function (_Parent) {
  _inherits(Child, _Parent);

  var _super = _createSuper(Child);

  function Child() {
    var _this;

    _classCallCheck(this, Child);

    _this = _super.call(this);
    _this.name = 'child';
    return _this;
  }

  _createClass(Child, [
    {
      key: 'getAll',
      value: function getAll() {
        return {
          name: this.name,
          age: this.age,
        };
      },
    },
  ]);

  return Child;
})(Parent);

_defineProperty(Child, 'name', 'child');

var child = new Child();
console.log(child.getAll());
console.log(Child.getName());

下面函数在 Babel to Class 做了什么(1) 讲解过了

  • _defineProperty
  • _classCallCheck
  • _defineProperties
  • _createClass

所以不再赘述,重点先看新出现的函数。

_typeof

function _typeof(obj) {
  '@babel/helpers - typeof';
  return (
    (_typeof =
      'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
        ? function (obj) {
            return typeof obj;
          }
        : function (obj) {
            return obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype
              ? 'symbol'
              : typeof obj;
          }),
    _typeof(obj)
  );
}

这个函数的作用为类型判断。

  • 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator 这段化意思是如果支持原生的 Symbol 就直接执行 typeof 即可

  • obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype 这段代码主要是用于判断低版本不兼容 Symbol 的浏览器是不是使用了 polyfill 库,如果是的化就返回 symbol

  • 其他情况返回 typeof 结果

_inherits

function _inherits(subClass, superClass) {
  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function');
  }
  Object.defineProperty(subClass, 'prototype', {
    value: Object.create(superClass && superClass.prototype, {
      constructor: { value: subClass, writable: true, configurable: true },
    }),
    writable: false,
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}
  • 这段代码首先判断继承的 superClass 是否符合要求
  • 其次对 subClass.prototype 进行设置确保 prototype 属性非遍历
  • 设置原型,将 subClass 的原型设置为 superClass

_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__是浏览器实现的一个私有属性,在其他环境下不是必须实现。

不过仔细观察,你会发现它还使用了一个函数懒加载,确保判断只执行一次,后续直接不会改变了。

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

这段代码的作用是返回 super

  • _isNativeReflectConstruct 这个函数我们后面讲解,它的作用就是判断当前环境支不支持 Reflect.construct
  • _possibleConstructorReturn 这个函数后面讲解,作用为判断 Class 符不符合要求

_possibleConstructorReturn

function _possibleConstructorReturn(self, call) {
  debugger;
  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);
}

这个函数用于判断 Class 和 extends 的 Class 符不符合要求

  • call && (_typeof(call) === 'object' || typeof call === 'function')

这个相当于判断 isObject,构造函数被调用是可以返回一个对象的

function Foo() {
  return { name: 1 };
}
new Foo();
// {name: 1}

如果不是对象也没有默认返回 undefined,则直接抛出错误

  • _assertThisInitialized,这个函数用于判断子类有没有调用 super,如果没有调用抛出错误

_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.construct,它的判断思路为前置判断,如果不符合早早返回

  • if (typeof Reflect === 'undefined' || !Reflect.construct) return false;,不支持原生返回 false
  • if (Reflect.construct.sham) return false;polyfill 的则也返回 false
  • if (typeof Proxy === 'function') return true;,返回 true 的原因是因为,Proxy 的兼容性比 Reflect 差,如果 Proxy 支持 Reflect 一定也支持
  • Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));,如果都符合直接运行,前面说过 Reflect.construct 支持第三个参数,如果运行没报错返回 true

_getPrototypeOf

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

这段代码为获取原型,跟 _setPrototypeOf 函数类似,如果当前环境支持优先使用 Object.getPrototypeOf 上的,否则回滚。

运行流程

讲解到这里,继承基本讲完了,现在把完整代码贴以下,之后讲一下运行的流程

'use strict';

function _typeof(obj) {
  '@babel/helpers - typeof';
  return (
    (_typeof =
      'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
        ? function (obj) {
            return typeof obj;
          }
        : function (obj) {
            return obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype
              ? 'symbol'
              : typeof obj;
          }),
    _typeof(obj)
  );
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function');
  }
  Object.defineProperty(subClass, 'prototype', {
    value: Object.create(superClass && superClass.prototype, {
      constructor: { value: subClass, writable: true, configurable: true },
    }),
    writable: false,
  });
  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;
  } else if (call !== void 0) {
    throw new TypeError('Derived constructors may only return object or undefined');
  }
  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 _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;
}

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);
  Object.defineProperty(Constructor, 'prototype', { writable: false });
  return Constructor;
}

var Parent = /*#__PURE__*/ (function () {
  function Parent() {
    _classCallCheck(this, Parent);

    this.age = 17;
  }

  _createClass(
    Parent,
    [
      {
        key: 'getAge',
        value: function getAge() {
          return this.age;
        },
      },
    ],
    [
      {
        key: 'getName',
        value: function getName() {
          return this.name;
        },
      },
    ],
  );

  return Parent;
})();

var Child = /*#__PURE__*/ (function (_Parent) {
  _inherits(Child, _Parent);

  var _super = _createSuper(Child);

  function Child() {
    var _this;

    _classCallCheck(this, Child);

    _this = _super.call(this);
    _this.name = 'child';
    return _this;
  }

  _createClass(Child, [
    {
      key: 'getAll',
      value: function getAll() {
        return {
          name: this.name,
          age: this.age,
        };
      },
    },
  ]);

  return Child;
})(Parent);

_defineProperty(Child, 'name', 'child');

var child = new Child();
console.log(child.getAll());
console.log(Child.getName());

代码从 var Parent = /*#__PURE__*/ (function () { 这段开始

  • 调用 _createClass,将 prototype 属性static 属性 设置到 Parent 函数上
  • 之后代码往下执行,到var Child = /*#__PURE__*/ (function (_Parent) {
  • 调用 _inherits,判断 Parent 符合要求,设置 Child.prototype 属性,设置 Child 原型
  • 调用 _createSuper,获取 super
  • 调用 _createClass,设置 prototype 属性static 属性 设置到 Child 函数上
  • 调用 _defineProperty,覆盖 Child 上的属性,确保是可以遍历修改的
  • new Child
  • Child 函数内部执行 _classCallCheck,确保一定为 new 调用
  • 在 Child 函数内部执行 _super.call(this) 操作,调用父类构造函数
  • Parent 内部,调用 _classCallCheck,确保 new 调用
  • 返回 Parent 的 this 值
  • Child 内部修改 Parent 返回的 this 值,进行重写
  • 执行后续代码...

跟 es5 继承的差异

从上面代码不难看到跟 es5 写的继承有很大差异

  1. prototype 不可以遍历;
  2. 不通过 new 调用显式报错;
  3. Class 的继承不仅是将子类的 prototype 指向父类的 prototype,同时也将子类的原型指向了父类;
  4. 得益于子类原型指向父类,静态方法和属性也可以调用;

最后

如果有什么不正确或者书写错误欢迎指出。