继承行为在 ES5 与 ES6 中的区别

原文地址: https://monster1935.com/2019/12/23/the-difference-of-extends-behavior-between-ES5-and-ES6/
笔者注:一句话引起的基础知识回炉,基础不扎实,还要什么自行车

最近在看 React 方面的一些文章时,看到了这样一个问题,「为何每一个 class 中都要写 super, super 是作什么的?」, 刚看到这个问题时,直接就想到了继承行为在 javascript 中的表现。后面做者的一句话「super 不能够省略,省略的话会报错」。当时脑海中蹦出来一个念头,这个同窗是否是写错了,super 不就是用来完成调用父类构造函数,将父类的实例属性挂在到 this 上吗?为何不写还会报错?javascript

后来本身亲自写了一个 Demo 尝试了一下,还真是会报错,究竟是哪里出了问题,找到了阮老师的教程又打开仔细看了一遍,发现里面还真是有这样一句话:java

子类必须在 constructor 方法中调用 super 方法,不然新建实例时会报错。这是由于子类本身的 this 对象,必须先经过父类的构造函数完成塑造,获得与父类一样的实例属性和方法,而后再对其进行加工,加上子类本身的实例属性和方法。若是不调用 super 方法,子类就得不到 this 对象。

原来如此,ES6 中 this 对象的构造方式发生了变化。react

ES5 中的继承

// Shape - 父类(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?',
  rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
  rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

如上所示: 展现了一个 ES5 中实现单继承的例子,在《Javascript 高级程序设计》一书中,给这种继承方式定义为「寄生组合式继承」。无论什么形式,什么命名,在 ES5 中实现继承始终就是要坚持一个原则:将实例属性放在构造函数中挂在this上,将一些方法属性挂在原型对象上,子类可共享。 上面这种继承方式的关键在于两点:es6

  1. 子类构造函数经过 apply 或者 call 的方式运行父类的构造函数,此举将父类的实例属性挂在子类的 this 对象上
  2. 以父类的原型对象为基础,与子类的原型对象之间创建原型链关系,使用了 Object.create,本质在于 Child.prototype.__proto === Parent.prototype;

ES6 中的继承

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); 
  }
}

ES6 中的继承使用到了 extends 关键字,function 也变成了 class 关键字。class 的本质仍是一个语法糖,这个你们都会脱口而出,可是在继承机制这里究竟是如何作到的,咱们看一下 babel 在此处是如何帮咱们转译的,面试

var ColorPoint =
/*#__PURE__*/
function (_Point) {
  _inherits(ColorPoint, _Point);

  function ColorPoint(x, y, color) {
    var _this;

    _classCallCheck(this, ColorPoint);

    _this = _possibleConstructorReturn(this, _getPrototypeOf(ColorPoint).call(this, x, y)); // 调用父类的constructor(x, y)

    _this.color = color;
    return _this;
  }

  _createClass(ColorPoint, [{
    key: "toString",
    value: function toString() {
      return this.color + ' ' + _get(_getPrototypeOf(ColorPoint.prototype), "toString", this).call(this);
    }
  }]);

  return ColorPoint;
}(Point);

如上是通过babel转译后的代码,有几个关键点:express

1、 _inherits()babel

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对象的校验,必须是function 或者null,不然报错。其次完成如下事情:app

ColorPoint.__proto__ === Point;
ColorPoint.prototype.__proto__ === Point.prototype;

2、 ColorPoint 构造函数中 _classCallCheck(), _possibleConstructorReturn()函数

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

主要是用来检测构造函数不能直接调用,必须是经过new的方式来调用。ui

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
      return call;
  }
  return _assertThisInitialized(self);
}

调用父类的构造函数,初始化一些实例属性,并将this返回。使用该返回的this赋值给子类的this对象,子类经过这一步返回的this对象,再该基础之上在添加一些实例属性。

这就是最大的不一样之处。若是不经历这一步,子类没有this对象,一旦操做一个不存在的this对象就会报错。

3、 _createClass()

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

最后一步完成原型属性与静态属性的挂载,若是是原型属性,挂在在Constructor上的prototype上,若是是静态属性或者静态方法,则挂在Constuctor 上。

总结

基础知识要打牢,不是为了面试,前期打不劳,后面不少事情就会变的模棱两可,别人问到的时候,就会是「可能」、「也许」。不积跬步何以致千里 ,加油。

参考连接

  1. http://es6.ruanyifeng.com/#do...
  2. https://developer.mozilla.org...
  3. https://babeljs.io/repl/#?bab...
相关文章
相关标签/搜索