這一章,我將結合babel的轉化,來講解super的用法
super關鍵字,有兩種用法,要麼作爲函數調用,要麼作爲對象使用(深入理解ES6中super關鍵字(super作爲對象使用))。
- 作爲函數使用時,指向父類的構造函數;
- 作爲對象使用時,在靜態方法中,指向父類,在普通方法中,指向的是父類的原型對象;
super作爲函數使用
當super作爲函數使用時,只能在子類的構造函數中使用,否則,會拋出一個錯誤。同時,在子類的構造函數中,必須要super(...args),並且super(...args),必須放在在使用this之前。如果類中沒有寫constructor函數,則會默認加上constructor,如果當前類是子類的話,constructor中的super也會默認加上。
注:子類必須在constructor
方法中調用super
方法,否則新建實例時會報錯。這是因爲子類自己的this
對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然後再對其進行加工,加上子類自己的實例屬性和方法。如果不調用super
方法,子類就得不到this
對象。
下面我將分析類class通過babel轉化後的代碼,來解釋下爲什麼子類中constructor方法必須要調用super方法:
比如:
class A {}
class B extends A {
constructor(x, y) {
super();
this.x = x;
this.y = y;
}
}
babel轉化後:
var A = function A() {
_classCallCheck(this, A);
};
var B = /*#__PURE__*/function (_A) {
_inherits(B, _A);
var _super = _createSuper(B);
function B(x, y) {
var _this;
_classCallCheck(this, B);
_this = _super.call(this);
_this.x = x;
_this.y = y;
return _this;
}
return B;
}(A);
可以看出類class其實質還是構造函數,在每個構造函數A、B中的對實例對象添加實例方法和實例屬性時,都必須進行_classCallCheck(this, 類)。
_classCallCheck(this, 類)目的是:判斷執行該類的時候,是通過new 還是通過類名直接執行,比如類A,是new A()還是直接A()。
我們可以看到類B通過babel轉化後的代碼其實是一個立即執行函數function()(),將類A作爲參數,傳入立即執行函數中,在立即執行函數中主要有三行代碼重要,一行:_inherits(B, A),另一行是var _super = _createSuper(B),另一行是:_this = _super.call(this)。
_inherits(B, A)作用:創建兩條原型鏈,一條是以實例對象爲開始,另一條是以子類B開始。比如:我們通過let b = new B()new一個實例對象(如果想了解原型鏈,請進入JavaScript--原型鏈)
以實例對象開始的一條原型鏈是(目的:原型方法、原型屬性的繼承):
b.__proto__ === B.prototype
B.prototype.__proto__ === A.prototype
A.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
另一條是以子類B開始的原型鏈(目的:類的靜態方法上的繼承):
B.__proto__ === A
A.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
_createSuper是一個高階組件,第一次執行會返回一個函數,作爲_super,爲啥要將子類作爲參數傳入呢?因爲我們要通過傳入的子類,查找子類的原型__proto__(即繼承的父類,通過B.__proto__ === A這個理解)。
_createSuper第二次執行,_super.call(this),目的是:通過call方法改變父類構造函數中的this對象爲子類中this,執行父類的構造函數,完成子類中this對象的塑造,得到與父類一樣的實例對象和方法,並且返回一個對象(這個對象有可能是傳入的子類構造函數中的this對象,有可能是父類中構造函數中的返回的對象),之後將這個對象賦值給_this。
在子類B構造函數中,首先會定義一個_this,_this相當於構造函數中的this指針(即實例對象),之後將_super.call(this)返回的結果賦值給_this(得到父類的實例屬性和方法),之後在添加子類中實例方法和屬性,最後返回_this,作爲new的實例對象。
總的來說,其實super(...args),相當於A.apply(this, args) (A是父類,B是子類),不過與babel轉化的代碼功能唯一區別是:A.apply(this, ...args)的返回值是undefined,而_super.apply(this, args)是有返回值的,如果執行父類中構造函數(A.apply(this, args))有返回值,那_super.apply(this, args)就返回這個,如果A.apply(this, args)沒有返回值,那就返回這個this。這也就是爲什麼我們要在constructor中進行this調用之前要進行super()。
function _createSuper(Derived) {
return function () {
var Super = _getPrototypeOf(Derived), result;
if (_isNativeReflectConstruct()) {
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;
}
下一章,介紹super作爲對象使用時情況(深入理解ES6中super關鍵字(super作爲對象使用))