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賦值的情況,請按照以下步驟處理:
- 如果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
- 如果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 引擎確認,定義的是對象的方法