阅读 1242

探索下es6的class以及继承的原理

前言

在javascript中,生成实例对象的方法是创建一个函数,再用new的方式实例化这个函数的对象。这种方案和传统语言区别很大,容易让人感到困惑。es6提供了一种更接近于传统语言的写法:class。不过,class可以看作是语法糖,它的绝大部分功能,ES5 都可以做到。下面,我们就来探究下class在es5下是如何实现的。

es6的写法

class A {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(this.name);
  }
}

class B extends A {
  constructor() {
    super();
  }
}
复制代码

这里我们定义了两个类,A和B,B继承于A。

接下来,我们在babel转义下,得到如下代码

var A = /*#__PURE__*/ (function () {
  function A(name) {
    _classCallCheck(this, A);
    this.name = name;
  }
  _createClass(A, [
    {
      key: 'say',
      value: function say() {
        console.log(this.name);
      },
    },
  ]);

  return A;
})();

var B = /*#__PURE__*/ (function (_A) {
  _inherits(B, _A);
  var _super = _createSuper(B);
  function B() {
    _classCallCheck(this, B);
    return _super.call(this);
  }
  return B;
})(A);
复制代码

可以看到,class实际还是由function来做的实现

类的实现原理

我们通过 A,来看下class的实现原理

首先,为变量A赋值了一个自执行函数,在这个函数中返回了内部定义的function A,所以变量A就是一个function

接着,函数内部除了为实例赋值外,还调用了_classCallCheck(this,A)

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) { // 等同于 !(this instanceof A)
    throw new TypeError('Cannot call a class as a function');
  }
}
复制代码

即如果你不是用new关键字来调用函数的话,this instanceof A就会是false,取反结果为true,抛出异常。这就是为什么class不能直接作为一个函数来执行。

_createClass

接下来,在函数创建完成后,又调用了_createClass函数,这个函数传递了两个值,第一个为函数A,第二个是一个数组,里面包含着我们在class A中定义的函数say

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

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);
  }
}
复制代码
 if (protoProps) _defineProperties(Constructor.prototype, protoProps);
复制代码

通过_createClass我们会执行这里的_defineProperties,为它传递两个参数,其中,Constructor就是函数A,所以第一个参数等于A.prototype,第二个参数就是存着say函数的数组

这里就是遍历存储着函数的数组,然后将对应的函数用 Object.defineProperty 存入 targetA.prototype

最后,自执行函数返回A,变量A就被赋值为function A,同时,function A的prototype上有say函数

继承的原理

var B = /*#__PURE__*/ (function (_A) {
  _inherits(B, _A);
  var _super = _createSuper(B);
  function B() {
    _classCallCheck(this, B);
    return _super.call(this);
  }
  return B;
})(A);
复制代码

可以看到,B和A一样,也是一个自执行函数,不过B有一个参数,就是函数A

B的自执行函数首先会执行_inherits(B, _A)

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);
}
复制代码

这里,首先让 B.prototype._proto_ = A.prototype,并且constructor指向B。接着,执行_setPrototypeOf,让B._proto = A

所以,_inherits就是为了构造B与A之间的原型链

super

接下来,就是创建super

var _super = _createSuper(B);

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 _isNativeReflectConstruct() {
  if (typeof Reflect === 'undefined' || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === 'function') return true;
  try {
    Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
    return true;
  } catch (e) {
    return false;
  }
}

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;
}
复制代码

Reflect 是es6新的操作对象的api,前面是为了判断Reflect是否可以,可以用就使用Reflect。我们先不关注Reflect,直接看Reflect不可用的逻辑

 var Super = _getPrototypeOf(Derived) // 获取Derived的原型,即B的原型A
 result = Super.apply(this, arguments); // 执行A,传递的参数是this实例以及arguments
复制代码

可以看到,_createSuper会返回一个函数,这个函数会执行当前函数的原型,即会执行A。也就是借用构造函数的继承方式。这也就是为什么子类必须在constructor方法中调用super方法,因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法。

最后会通过_possibleConstructorReturn判断父函数的执行是否会返回一个对象,如果是个对象的话,最终返回父函数返回的对象。

总结

这些基本上就是es6的继承原理。

第一次写文章,写的不好,请大家多多包涵,如果文章中出现问题,欢迎评论,我会及时改正。

文章分类
前端
文章标签