繼承行爲在 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...
相關文章
相關標籤/搜索