深入理解ES6中super關鍵字(super作爲對象使用)

super關鍵字,有兩種用法,一種是:super作爲函數使用,具體介紹請進入深入理解ES5中super關鍵字(super作爲函用數使),另外一種是:super作爲對象使用。

    要注意:在使用super的時候,必須要顯示的指定是作爲函數使用,還是作爲對象使用,否則會報錯,因爲,如果不能顯示的指定,就無法準確的確定super到底要怎麼使用。

本文將介紹super作爲對象使用。

super作爲對象使用時,要麼對super進行類似get,要麼是類似set功能,即要麼賦值,要麼取值,基本上就是這兩種。我們通過對這兩種分別舉例,並通過babel轉化後的代碼進行分析這兩種情況。

一、super類似取值時使用情況

當super類似取值時,比如說super.x、super.toString()等等,一般都是先去查找父類原型對象上有沒有這個方法或者屬性,如果沒有,再去往更高級繼承類的原型對象上去查找。接着我們舉個實例分析:

實例:

class A {
    constructor() {
        this.x = 'A';
    }

    getX() {
        console.log(this.x);
    }
}

class B extends A {
    constructor() {
        super();
        this.x = 'B';
        console.log(super.x);
    }
  
    getX() {
        super.getX();
    }
	
    static getY() {
        super.getY();
    }
}

babel轉化後:

var A = /*#__PURE__*/function () {
  function A() {
    _classCallCheck(this, A);

    this.x = 'A';
  }

  _createClass(A, [{
    key: "getX",
    value: function getX() {
      console.log(this.x);
    }
  }]);

  return A;
}();

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

  var _super = _createSuper(B);

  function B() {
    var _this;

    _classCallCheck(this, B);

    _this = _super.call(this);
    _this.x = 'B';
    console.log(_get(_getPrototypeOf(B.prototype), "x", _assertThisInitialized(_this)));
    return _this;
  }

  _createClass(B, [{
    key: "getX",
    value: function getX() {
      _get(_getPrototypeOf(B.prototype), "getX", this).call(this);
    }
  }], [{
    key: "getY",
    value: function getY() {
      _get(_getPrototypeOf(B), "getY", this).call(this);
    }
  }]);

  return B;
}(A);

通過對babel轉化後的代碼分析,明顯看出super.x、super.getX(),靜態方法中的super.getY()轉化成_get(_getPrototypeOf(B.prototype), "x", _assertThisInitialized(_this))、_get(_getPrototypeOf(B.prototype), "getX", this).call(this), _get(_getPrototypeOf(B), "getY", this).call(this),都涉及到_get方法,現在呢?我們就分析_get(),_get()代碼如;

function _get(target, property, receiver) { 
	if (typeof Reflect !== "undefined" && Reflect.get) { 
		_get = Reflect.get; 
	} else { 
		_get = function _get(target, property, receiver) { 
			var base = _superPropBase(target, property); 
			if (!base) return; 
			var desc = Object.getOwnPropertyDescriptor(base, property); 
			if (desc.get) { 
				return desc.get.call(receiver); 
			} 
			return desc.value; 
		}; 
	} 
	return _get(target, property, receiver || target); 
}

分析三個super的使用,_get方法中,第一個參數_getPrototypeOf(target)指的是什麼呢?對於target,如果你在當前子類的普通方法中使用super,那target指的是當前子類的prototype(即B.prototype),如果你在子類靜態方法中使用super,那target指的是當前子類(即B),第二個參數指的是你通過super調用的方法名或者屬性名(即x, getX, getY),第三個參數是當前this(如果是在子類普通方法中,this指向實例對象,如果在靜態方法中,this指向子類),如果你是在constructor中使用super作爲對象的話,第三個參數會爲_assertThisInitialized(_this),爲啥在子類constructor中要寫成_assertThisInitialized(_this)呢,因爲要判斷super(super作爲對象時使用)使用是否是在super()之後,因爲super()之後,纔有子類的this對象(如果想了解這個,請進入深入理解ES5中super關鍵字(super作爲函用數使))。

function _assertThisInitialized(self) { 
	if (self === void 0) { 
		throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 
	} 
	return self; 
}

_getPrototypeOf(target)返回的是target的原型__proto__,比如在當前子類的普通方法中調用super,_getPrototypeOf(target)返回的是子類的prototype的__proto__(即B.prototype.__proto__),如果在子類的靜態方法中調用super,它返回的是子類的__proto__(即B.__proto__)。


_superPropBase(target, property)作用是檢查target的原型鏈上是不是有property屬性或者方法(上面實例中,檢查A.prototype、A的原型鏈上是否有property屬性或者方法),如果沒有檢查到,則_get()返回undefined,如果檢查到了,就執行Object.getOwnPropertyDescriptor(base, property)來獲取property的描述對象,如果property描述對象上有get方法,就執行get方法,將get方法中的this指向receiver,如果沒有就返回desc.value,desc.value可以是一個屬性,也可以是一個方法。

所以總結如下:

當你使用super作爲對象使用時,如果在普通方法中,你先去查找當前子類的父類的prototype的原型鏈(包括父類上的原型對象及其以上原型鏈)上是否有這個屬性或者方法,會有兩種可能:

  • 如果沒有查找到,則super[屬性名]爲undefined,super[方法名]此時會報一個錯誤,super[方法名]不是一個方法;
  • 如果查找到了,首先還要看一下當前屬性是不是有get方法,如果有,則執行get方法,get方法內的this指向子類中的this(實例對象),如果沒有get方法,要是super[屬性名]的話,返回查找到的屬性值,要是super[方法名],執行查找的方法,並且內方法部的this,是子類中的this(實例化)

當你使用super作爲對象使用時,如果在子類靜態方法中,你先去查找當前子類的父類的原型鏈(包括父類的原型及其以上原型鏈)上是否有這個屬性或者方法,會有兩種可能:

  • 如果沒有查找到,則super[屬性名]爲undefined,super[方法名]此時會報一個錯誤,super[方法名]不是一個方法;
  • 如果查找到了,首先還要看一下當前屬性是不是有get方法,如果有,則執行get方法,get方法內的this指向子類中的this(子類),如果沒有get方法,要是super[屬性名]的話,返回查找到的屬性值,要是super[方法名],執行查找的方法,並且方法內部的this,是子類中的this(子類)

哈哈,現在呢,我們就去做幾個例子來驗證下。

class A {
    constructor() {
        this.x = 'A';
    }

    getX() {
        console.log(this.x);
    }
	
    static getY() {
        console.log(this.x);
    }

    get t() {
        return '普通方法 get t()';
    }
    
    set t(t) {
        console.log('普通方法 set t() ', t);
    }

    static get t() {
        return '靜態 get t()';
    }

    static set t(t) {
        console.log('靜態方法 set t() ', t);
    }
}

A.prototype.x = '父類A原型上定義的屬性';
A.x = '父類A上定義的靜態屬性';

class B extends A {
    constructor() {
        super();
        this.x = 'B';
        console.log(super.x); //第一個super運用
        console.log(super.y); //第二個super運用
    }
  
    getX() {
        super.getX(); //第三個super運用
    }
  
    getZ() {
        super.getZ(); //第四個super運用 
	}
	
    static getY() {
        super.getY(); //第五個super運用
        console.log(super.x); //第六個super運用
        console.log(super.y); //第七個super運用
    }
  
    static getW() {
        super.getW(); //第八個super運用
    }

    getT() {
        console.log(super.t); //第九個super運用
    }

    static getT() {
        console.log(super.t); //第10個super運用
    }
}
B.x = '子類B上的靜態屬性';

實例代碼如上所示,結果如下:

1、當進行let b = new B();時,會執行第一個、第二個console.log,根據我們上面的分析,這兩個super.x、super.y是在普通方法中,因此,我們需要去檢查A.prototype的原型鏈上是否有這兩個x、y,分析A.prototype原型鏈(如果想了解原型鏈,請進入JavaScript--原型鏈):

    A.prototype.__proto__ === Object.prototype

    Object.prototype.__proto__ === null

檢查A.prototype的整個原型鏈發現,只有A.prototype.x,沒有找到y,同時對於x,y,又沒有get方法,因此,console.log(super.x)打印爲:"父類A原型上定義的屬性",而console.log(super.y)打印undefined。

2、當進行b.getX()時,執行super.getX(),因爲b.getX是一個普通方法,那麼我們還是要去檢查A.prototype的原型鏈上去查看是否有getX,由上面原型鏈的分析,可得在父類的原型中有一個getX,因此super.getX執行的是A.prototype.getX,但是要注意的是,A.prototype.getX方法裏面的this對象是子類中實例對象(this),因此A.prototype.getX中的console.log(this.x)打印的是子類B的實例對象上實例屬性x,即"B"。

3、當執行b.getZ()時,執行super.getZ(),發現整個A.prototype原型鏈上沒有getZ,因此此處會報一個錯誤提示,提示(intermediate value).getZ is not a function。

4、執行B.getY()時,getY時子類B的靜態方法,那麼內部代碼中super.getY()、super.x、super.y,這些屬性和方法,我們需要去父類A的原型鏈上去查找,父類A的原型鏈(如果想了解原型鏈,請進入JavaScript--原型鏈)如下:

    A.__proto__ === Function.prototype

   Function.prototype.__proto__ === Object.prototype

   Object.prototype.__proto__ === null

super.getY其實執行的應該是父類A的靜態方法getY,同時要注意,A.getY方法中的this指向的是子類B,而不是子類B的實例對象,因此執行B.getY(),依次打印出:"子類B上的靜態方法" “父類A上定義的靜態屬性” undefined。

5、執行B.getW()時,由於在父類A上的原型鏈上不存在getW,因此此處會報一個錯誤,提示(intermediate value).getW is not a function。

6、執行b.getT()時,super.t返回什麼呢?首先,super.t是處於普通方法中,所以我們應該去檢查父類的原型的原型鏈上是否有t,經檢查,父類的原型的原型鏈上有t屬性,之後,t屬性的描述對象上有get方法,因此,將會執行get方法,所以打印出:"普通方法 get t()" 。

7、執行B.getT()時,super.t返回什麼呢?首先,super.t是處於子類的靜態方法中,所以我們應該去檢查父類的原型鏈上是否有t,經檢查,父類的原型鏈上有t屬性,之後,t屬性的描述對象上有get方法,因此,將會執行get方法,所以依次打印出:"靜態 get t()" 。

二、super類似設置值時使用情況

我們可能會在方法中,使用super.x = 11等類似設置值的情況,這種情況內部是如何操作的呢?

下面是來自阮一峯關於ES6中的一個實例,通過分析這個,來詳細說明內部操作。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();

babel轉化後:

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

  this.x = 1;
};

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

  var _super = _createSuper(B);

  function B() {
    var _this;

    _classCallCheck(this, B);

    _this = _super.call(this);
    _this.x = 2;

    _set(_getPrototypeOf(B.prototype), "x", 3, _assertThisInitialized(_this), true);

    console.log(_get(_getPrototypeOf(B.prototype), "x", _assertThisInitialized(_this))); // undefined

    console.log(_this.x); // 3

    return _this;
  }

  return B;
}(A);

super.x = 3,轉化後爲_set(_getPrototypeOf(B.prototype), "x", 3, _assertThisInitialized(_this), true),通過上面_get的分析,很容易理解這個,_assertThisInitialized(_this)是判斷子類constructor中在使用super.x = 3之前是否已經super()·。_getPrototypeOf(B.prototype)目的是查找B.prototype.__proto(其實是它的父類的原型對象)。下面分析下_set方法:

function _set(target, property, value, receiver, isStrict) { 
	var s = set(target, property, value, receiver || target); 
	if (!s && isStrict) { 
		throw new Error('failed to set property'); 
	} 
	return value; 
}

function set(target, property, value, receiver) { 
	if (typeof Reflect !== "undefined" && Reflect.set) { 
		set = Reflect.set; 
	} else { 
		set = function set(target, property, value, receiver) { 
			var base = _superPropBase(target, property); 
			var desc; 
			if (base) { 
				desc = Object.getOwnPropertyDescriptor(base, property); 
				if (desc.set) { 
					desc.set.call(receiver, value); 
					return true; 
				} else if (!desc.writable) { 
					return false; 
				} 
			}

			desc = Object.getOwnPropertyDescriptor(receiver, property); 
			if (desc) { 
				if (!desc.writable) { 
					return false; 
				} 
				desc.value = value; 
				Object.defineProperty(receiver, property, desc); 
			} else { 
				_defineProperty(receiver, property, value); 

			} 
			return true; 
		}; 
	} 
	return set(target, property, value, receiver);

}

代碼中,主要還是var s = set(target, property, value, receiver || target); target表示父類的原型對象(A.prototype),property表示要設置的屬性(比如super.x = 3,property表示'x'),value表示屬性的設置值,receiver表示當前this(如果在普通方法中,表示實例對象,如果在靜態方法中,receiver表示當前類),set方法內部實現:

1、var base = _superPropBase(target, property); 表示在target的原型鏈上是否存在property屬性,並返回存在property屬性的原型對象,否則返回null,比如上面的實例中,原型鏈是:A.prototype.__proto__ === Object.prototype  Object.prototype.__proto__ === null,如果想了解原型鏈,請進入JavaScript--原型鏈

2、如果找到了存在property屬性的原型對象,則返回property的描述對象,如果property存在存值函數set,就執行set方法,並將set方法中的this指向實例對象或者子類(如果super在普通方法中使用,this指向子類的實例對象,如果在靜態方法中使用,this指向子類),如果沒有set方法,就去查看property是否可以修改,如果不能修改,就結束,返回false,會拋出一個錯誤:failed to set property

3、如果在target的原型鏈上,存在的property,即沒有set方法,也沒有設置writable爲false,則就去執行let desc = Object.getOwnPropertyDescriptor(receiver, property),返回receiver(如果super在普通方法中使用,receiver指向子類的實例對象,如果在靜態方法中使用,receiver指向子類)上的property的描述對象,如果desc不爲null,如果desc的writable爲false,不做任何操作,否則,將value賦值給desc.value,將本身的property配置原封不動設置。

4、如果desc爲null,則receiver上設置property屬性值爲value,並且property的配置設置都爲true(enumerable: true, configurable: true, writable: true)。

總結如下:

如果你在子類中碰到類似super.x = 123賦值的情況,請按照以下步驟處理:

  1. 如果super.x 是在普通方法中調用的話,先去父類的原型對象的原型鏈(父類的原型對象的原型鏈指的是:比如A代表父類,那這個鏈就是A.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null,A.prototype、Object.prototype)上去查找這個屬性,如果存在的話,如果他有set方法,那就去執行set方法(set方法裏面的this指向的是子類的實例對象this),否則,如果它的writable爲false,則結束,會拋出一個錯誤:failed to set property。要是前面兩個條件都不滿足,則去子類的實例對象(實例對象和實例方法)查找是否存在property屬性,如果存在且wiritable爲false,不做任何操作,返回failed to set property,其他情況就是去設置或者更改value
  2. 如果super.x 是在子類靜態方法中調用的話,先去父類的原型鏈(父類的原型鏈指的是:比如A代表父類,那這個鏈就是A.__proto__ === Function.prototype  Function.prototype.__proto__ ===  Object.prototype Object.prototype.__proto__ === null,A、Function.prototype、Object.prototype)上去查找這個屬性,如果存在的話,如果他有set方法,那就去執行set方法(set方法裏面的this指向的是子類),否則,如果它的writable爲false,則結束,會拋出一個錯誤:failed to set property。要是前面兩個條件都不滿足,則去子類靜態屬性上查找是否存在property屬性,如果存在且wiritable爲false,不做任何操作,返回failed to set property,其他情況就是去設置或者更改value。

其實上面可以寫的更簡潔點,只不過爲了更好的理解罷了。

現在再去看剛開始的那個例子,是不是很簡單,套這個不走就可以了,super.x = 3處於子類的constructor(普通方法)中,在父類的原型對象的原型鏈(父類的原型對象的原型鏈指的是:比如A代表父類,那這個鏈就是A.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null,A.prototype、Object.prototype)上沒有x這個屬性,則去子類的實例對象(實例屬性和實例方法,不要和子類的原型對象上的屬性和方法弄混)查找這個x屬性,這個子類實例屬性x的writable又不爲false,所有說super.x = 3,其實質是this.x = 3(this指的是實例對象),所有console.log(this.x)打印的是3。

下面呢,我們再舉個例子

class A {
  constructor() {
    this.x = 1;
  }
  set t(t) {
  	console.log('父類A的x屬性的set方法 ', t);
  }
  

}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    this.t = 2;
    super.t = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 2
  }
}

let b = new B();

代碼上圖所示,執行之後,打印出:'父類A的x屬性的set方法 3' undefined 2

注:由於對象總是繼承其他對象的,所以可以在任意一個對象中,使用super關鍵字

注:super關鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。目前,只有對象方法的簡寫法可以讓 JavaScript 引擎確認,定義的是對象的方法

 

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