ES6_Extends如何對ES5的繼承進行「糖化」

公所周知,JS在常規開發語言中。位於技術鄙視鏈頂端。說JS很差嘛,不是。Node的出現。預示着JS大有統一先後端的趨勢。(這只是小弟的一個拙見,勿噴)或者卑微的說一句,JS能在後端也能夠展現一下拳腳了。javascript

其中有一點很讓其餘OOP語言詬病的就是:JS基於Prototype的繼承。對於一個接觸過C++JAVA,並在實際項目中使用過這些語言的卑微的我來講,第一次接觸prototype的時候,那是尼克楊臉上都是問號(估計只有懂點NBA的人才會知道這個梗吧)。前端

可是自從ES6頒佈以來,局面有一些好轉,ES6也有了class/extends等相關語法實現。JS在衆多OOP語言裏,瞬間站了起來,有沒有。java

其實,做爲一個接觸過其餘OOP語言的前端開發者,很喜歡用class來定義對象的結構,並用extends來實現繼承。 可是幾乎全部關於ES6特性介紹的文檔中,你確定會看到express

ES6 的class能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到後端

其實在開發中直接用class定義對象結構,並用extends來實現繼承。很常見,很方便。瀏覽器

既然人家都說了,這是一個。可是做爲一個合格的開發者。要有打破砂鍋問到底的心思。因此,我用我拙劣的代碼來將這層的成分細細分析一下。app

下面一些方法,都是基於class脫糖以後的分析。關於class是如何被脫糖的過程===>請先查看ES6-Class如何優雅的進行Prototype「糖化」 內容很精彩,可是不要忘記回來看這個。這個劇情也挺赤雞的。函數

ES5基於prototype的繼承

在進行繼承的講解的時候,仍是須要明確一點。就是ES5的類 = 構造函數+prototpye。因此基於這點就存在兩個繼承方向。post

  1. 繼承構造函數中的實例屬性和方法
  2. 繼承原型對象上的屬性和方法

關於ES5的繼承有不少實現方式:例如原型鏈繼承(原型繼承)構造函數繼承(實例屬性)組合式繼承原型式繼承寄生式繼承寄生組合式繼承。 其中咱們按組合式繼承(原型鏈+構造函數繼承)來簡單說明一下實現原理。ui

原型鏈繼承

繼承原型對象上的屬性和方法

將一個類型的實例,賦值給另外一個構造函數的原型

子類是繼承父類的原型方式

function SuperObj(){
    this.objName = 'super';
}

SuperObj.prototype.getName = function(){
    return this.objName;
}

function SubObj(){
    this.objName = 'sub';
}
//將父類的實例,賦值給目標類的prototype
SubObj.prototype = new SuperObj();

var instance = new SubObj();

//繼承父類的原型方法
instance.getName() //super

instance  instanceof SubObj //true
instance instanceof SuperOjb //true

複製代碼

構造函數繼承

繼承構造函數中的實例屬性和方法

首先說明一點,構造函數繼承,只是對目標構造函數中屬性的繼承,而不是真正基於prototype的繼承。子類的實例若是用instanceof來進行判斷的話,其實返回的是false

子類繼承父類的屬性和方法

function SuperObj(){
    this.nameArr = ['北宸','南蓁'];
}
//在子類的構造函數調用父類構造函數
function SubObj(){
    SuperOjb.call(this)
}

var instance1 = new SubObj();
instance1.nameArr.push('你好'); //['北宸','南蓁','你好']

var instance2  = new SubObj();

instance2.nameArr //['北宸','南蓁']

instance1 instanceof SuperObj //false 


複製代碼

組合繼承

組合式繼承其實就是構造函數繼承+原型鏈繼承 。也就是使用原型鏈實現對原型方法的繼承,借用構造函數來實現對實例屬性的繼承

因爲ES5實現一個完整的類,就須要構造函數+prototype

function SuperObj(allname){
    this.nameArr = ['北宸','南蓁'];
    this.allName =allname;
}

SuperObj.prototype.getAllName = function(){
    return this.allName;
}

//構造函數繼承,繼承目標構造函數的屬性和方法
function SubObj(allname,age){  
    SuperObj.call(this,allname);
    this.age = age;
}

//原型鏈繼承 
SubObj.prototype = new SuperObj('北宸');

var instance1 = new SubObj('instance1',1);

var instance2 = new SubOjb('instance2',2);

instance1 instanceof SuperObj //true
instance2 instanceof SuperObj //true

instance1.getAllName() //instance1
instance2.getAllName()//instance2

複製代碼

ES6利用extends的"糖化"繼承

最簡單的繼承

Talk is cheap,show you the code

class A {
    
}

class B extends A{
    
}

複製代碼

這算是最簡單的一個繼承。或者從嚴格意義上,B是對A複製了一份。由於A沒有任何的實例屬性和方法,也沒有原型屬性。

可是咱們能夠看看脫糖以後的代碼。

 "use strict";

function _typeof(obj) {
  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 _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 _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

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

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

var A = function A() {
  _classCallCheck(this, A);
};

var B =
  /*#__PURE__*/
  (function(_A) {
    _inherits(B, _A);

    function B() {
      _classCallCheck(this, B);

      return _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).apply(this, arguments)
      );
    }

    return B;
  })(A);

複製代碼

咱們來分析一波。

var A = function A() {
  _classCallCheck(this, A);
};
複製代碼

這個就不用在多說啥了。就是簡單定義了一個構造函數。

其實最關鍵的仍是下面的代碼: 首先映入眼簾的是一個IIFE,接收剛被定義的構造函數A

這裏直接給你們把對應的註釋給加上了。

(function(_A) {
    //繼承原型對象上的屬性和方法
    _inherits(B, _A);
    
    function B() {
      _classCallCheck(this, B);
      //繼承構造函數中的實例屬性和方法
      return _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).apply(this, arguments)
      );
    }

    return B;
  })(A);

複製代碼

經過代碼咱們看到_inherits(B, _A)用於繼承原型對象上的屬性和方法,而_possibleConstructorReturn則是繼承構造函數中的實例屬性和方法。因此很符合咱們上面講的。

而後咱們繼續分析其中的原委:

繼承原型對象上的屬性和方法(_inherits)

Talk is cheap,show you the code:

//_inherits(B, _A)
function _inherits(subClass, superClass) {
    //對superClass進行類型判斷
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  //子類的prototype繼承父類的prototype
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  });
  //子類是父類構建出的函數對象,須要指定對象的__proto__
  if (superClass) _setPrototypeOf(subClass, superClass);
}
複製代碼

Note: 有一點須要注意的就是:
大多數瀏覽器的 ES5 實現之中,每個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class 做爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,所以同時存在兩條繼承鏈。
(1)子類的__proto__屬性,表示構造函數的繼承,老是指向父類。
(2)子類prototype屬性的__proto__屬性,表示方法的繼承,老是指向父類的prototype屬性。

繼承構造函數中的實例屬性和方法(_possibleConstructorReturn)

/* _possibleConstructorReturn( this,//指向子類構造函數 //_getPrototypeOf(B)用於獲取指定對象的父類 _getPrototypeOf(B).apply(this, arguments) ); */
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;
}

複製代碼

在進行_possibleConstructorReturn調用的時候,其實處理classconstructor的實例屬性和方法的繼承。當父類存在constructor就須要_getPrototypeOf(B).apply(this, arguments)將父類的屬性和方法複製到新的構造函數中。實現繼承。

Note:存在_getPrototypeOf(B).apply(this, arguments)是父類存在constructor。若是不存在,直接就是_possibleConstructorReturn(this)

要點彙總

其實ES6extends實現繼續仍是基於ES5的組合繼承(構造函數繼承+原型鏈繼承)

  1. _inherits(B, _A)實現原型鏈的繼承
  2. 在子類的構造函數中的_possibleConstructorReturn實現構造函數的繼承

複雜的類

上面講的那個例子就是一個空類AB繼承。其實就是B將A複製了一遍,這是利用的extends的語法。來看基本的語法實現。

ES6示例

如今咱們來構建一個比較常規的類:擁有實例方法的繼承

Talk is cheap ,show you the code:

class A {
  static name = 'superClass'
  constructor(x,y){
    this.x =x;
    this.y =y;
  }
}

class B extends A{
  	
	constructor(x,y,z){
    	super(x,y);
		this.y = y;
    }
}

複製代碼

ES5脫糖

"use strict";

function _typeof(obj) {
  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 _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 _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

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

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

var A = function A(x, y) {
  _classCallCheck(this, A);

  this.x = x;
  this.y = y;
};

_defineProperty(A, "name", "superClass");

var B =
  /*#__PURE__*/
  (function(_A) {
    _inherits(B, _A);

    function B(x, y, z) {
      var _this;

      _classCallCheck(this, B);

      _this = _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).call(this, x, y)
      );
      _this.y = y;
      return _this;
    }

    return B;
  })(A);


複製代碼

看到代碼以後,其實大體的實現流程和B繼承一個空類A是同樣的。都是_inherits()實現原型繼承,_possibleConstructorReturn()實現構造函數繼承。

可是這裏多了一個內部_this

這裏須要額外的提出:ES5 的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修改this。

也就是說,若是在子類的構造函數中想調用this必須先調用super()

相關文章
相關標籤/搜索