關於ES6中子類繼承的實現原理逐行解析

JS中萬物皆對象

所以到了es6對一個對象生成器(構造函數)也定義了新的寫法——類(class)使之寫法和麪向對象的寫法更像

現如今我們定義一個一個構造函數就是定義一個類,寫法如下:

class Parent {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    saySomething() {
        console.log('Hi, I am Parent');
    }
}

這是一個最簡單的類,定義了實例屬性name和age和一個原型方法saySomething

 

我們知道es6的語法需要編譯成es5的語法後才能在瀏覽器端運行,babel這個工具就可以把我們的es6的代碼編譯成es5,

我們將這段代碼複製到babel編譯器看能得到什麼:

"use strict";

function _instanceof(left, right) {
  if (                              // 判斷傳進來的構造函數right不爲null並且當前Symbol類型有定義(即可用),並且構造函數的[Symbol.hasInstance]方法能訪問
    right != null &&    
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left); // 能則把實例傳進構造函數的[Symbol.hasInstance]去看是不是由該構造函數生成的
  } else {
    return left instanceof right; // 若沒有則直接調用instanceof來判斷
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) { // 判斷調用當前是否是用new操作符調用了函數
    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; // 有value值則是可寫的
    Object.defineProperty(target, descriptor.key, descriptor); //再調用內部方法defineProperty進行定義
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps); // 添加原型方法
  if (staticProps) _defineProperties(Constructor, staticProps); // 添加靜態方法
  return Constructor;
}

// 在es5寫法中就是用變量聲明的方式定義了一個Parent的自執行函數,得到一個Parent的構造函數
var Parent = /*#__PURE__*/ (function() {
  function Parent(name, age) {
    _classCallCheck(this, Parent); // 在這裏爲了確認Parent是通過new方法調用的

    this.name = name; // 添加name和age兩個實例方法
    this.age = age;
  }

// 這一步主要就是爲這個構造函數添加原型方法和靜態方法(如果有就作爲第三個參數傳遞進去,與第二個參數一樣是一個數組)
  _createClass(Parent, [
    {
      key: "saySomething",
      value: function saySomething() {
        console.log("Hi, I am Parent");
      }
    }
  ]);

  return Parent;
})();

這裏定義Parent構造函數時主要就是調用了兩個方法_classCallCheck和_createClass.

總結:_classCallCheck是用來確認Parent這個構造函數是不是由new調用的。_createClass.是用來掛載原型方法/靜態方法的

 

現在我們理解了在es6中定義一個類實質是發生了什麼。

下一步就是看子類繼承父類(即使用extends關鍵字時)es5中又是怎麼實現的呢

// 現在我們再來定義一個子類,繼承與上面的父類
class Child extends Parent {
    constructor(name,age){
        super(name,age);  // es6中規定子類繼承父類必須在constructor函數裏實現super方法,因爲子類沒有this,必須繼承自父類的this,所以也就是說只能在super後面使用this關鍵字
    }
    speak(){
        console.log("I am Child");
    }
}

我們把class Child和class Parent都複製到babel編譯器中,得到如下es5代碼:

"use strict";

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

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function"); // 判斷傳進來的父類是不是null或函數,如果不是則拋出錯誤
  }
// 這一步就是重點,用Object.create方法,使子類的prototype的__proto__指向父類的prototype,即共享其原型上的方法/屬性
// superClass && superClass.prototype短路操作符保證存在superClass存在的情況下取superClass.prototype
// 第二個參數爲了正確設置subClass.prototype的constructor指向
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  });
   // 最後如果superClass存在,則調用_setPrototypeOf
  if (superClass) _setPrototypeOf(subClass, superClass);
}

// 這個方法主要就是使用Object.setPrototypeOf方法使子類subClass.__proto__指向父類,實現靜態方法/屬性的繼承
function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf || // 如果有Object.setPrototypeOf就調這個方法,沒有就自己實現o.__proto__ = p
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function() {
    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;
  }
  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 {
    Date.prototype.toString.call(Reflect.construct(Date, [], 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 _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, 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);
  return Constructor;
}


// 構造函數定義在這裏,變量聲明式定義一個自執行函數,返回Parent構造函數,與上面無異
var Parent = /*#__PURE__*/ (function() {
  function Parent(name, age) {
    _classCallCheck(this, Parent);

    this.name = name;
    this.age = age;
  }

  _createClass(Parent, [
    {
      key: "saySomething",
      value: function saySomething() {
        console.log("Hi, I am Parent");
      }
    }
  ]);

  return Parent;
})();


// 主要就是定義子類的時候,我們在使用關鍵字extends來繼承一個類的時候,實際上是把這個類當作參數傳入了這個自執行函數中,現在傳入的是Parent,當然你也可以class Child extends null{}來繼承null,此時就會把null傳入這個自執行函數中
var Child = /*#__PURE__*/ (function(_Parent) {  //_Parent就是自執行函數拿到的父類的形參
  _inherits(Child, _Parent);  // 第一步就是調用_inherits方法,把子構造函數(也就是下面的function Child()函數聲明會有變量提升所以可以先調用再定義)和父構造函數(也就是上面的var Parent這個變量)

  var _super = _createSuper(Child); // 這個方法以後再研究。。。hhhhhh(尷尬又不失禮貌的微笑)

  function Child(name, age) {
    _classCallCheck(this, Child); // _classCallCheck方法同上不在贅述

    return _super.call(this, name, age);
  }

  _createClass(Child, [// _createClass方法同上不在贅述
    {
      key: "speak",
      value: function speak() {
        console.log("I am Child");
      }
    }
  ]);

  return Child;
})(Parent);

通過理解上面的代碼和旁邊的註釋。

總結:我們知道了子類繼承父類使調用了一個最主要的方法_inherits(Child, _Parent);

在其內部實現了兩個繼承:

  1. 實現了Child.prototype.__proto__ = Parent.prototype的繼承

  2. 實現了Child.__proto__ = Parent的繼承

前者作爲構造函數調用時,子類繼承父類的原型屬性/方法。

即:

let b = new Child();
b.saySomething(); // Hi, I am Parent

後者作爲對象調用時,使子類繼承父類的靜態屬性/方法。

即:

Child.index; // 1

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章