最近一直在看原型繼承相關的東西,翻到這麼一篇文章: 從ES6中的extends講js原型鏈與繼承javascript
文中有一個點讓我很感興趣,箭頭函數在繼承過程當中沒法經過super關鍵字獲取,這是爲何呢?html
The super keyword is used to access and call functions on an object's parent - in MDN
大概有這麼幾個關鍵點:java
個人見解是不須要。
網上有些文章(好比這篇)寫道:es6
由於若不執行super,則this沒法初始化。
個人我的理解是,this是指代執行上下文環境的,不存在沒法初始化的狀況。更準確的說法是這樣:若是不使用super方法,那麼父類中的屬性值沒法進行初始化,若是這個時候子類經過this字段來訪問了父類中的屬性值,那麼只能獲得一個undefined。至於爲何這麼寫編譯的時候會報錯?個人理解是,這應該是一種語法錯誤,並且是一種規範要求,ES6語法的規範要求,這種要求並非說會影響到代碼的實際執行。舉個栗子:typescript
// typescript中一段簡單的繼承代碼實現 class Parent { name = 'parent'; func = function() { console.log('func in parent called.'); } } class Child extends Parent { age = 3; func = function() { console.log('age is: ', this.age); // 使用了this,不會報錯 } }
這段代碼很是簡單,在子類中使用了this關鍵字,編譯時不會報錯,也能夠正常執行。而後咱們進行一點修改,在子類中引入constructor方法segmentfault
class Child extends Parent { age = 3; // error TS2377: Constructors for derived classes must contain a 'super' call. constructor() { } func = function() { console.log('age is: ', this.age); } }
能夠看到,編譯階段已經開始報錯了。在typescript的語法中,子類的constructor方法中不但須要調用super方法,並且必須在第一行代碼就調用super,不然都是會報錯的。看下面這段代碼:app
class Child extends Parent { age = 3; constructor() { console.log('First line in constructor without super method'); super(); // error TS2376: A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties. } func = function() { console.log('age is: ', this.age); } }
來,咱們接着改函數
class Parent { name = 'parent'; func = function() { console.log('func in parent called.'); } } class Child extends Parent { age = 3; constructor() { console.log('Show property of parent, name is: ', this.name); // error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. console.log('Show property of child, age is: ', this.age); // error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class. super(); // error TS2376: A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties. console.log('Show property of parent, name is: ', this.name); console.log('Show property of child, age is: ', this.age); } func = function() { console.log('age is: ', this.age); } }
能夠看到,編譯期已經開始報各類錯誤了,不過這不重要,咱們這裏利用typescript的編譯器(tsc)來進行編譯,並查看編譯後的代碼內容:this
var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var Parent = (function () { function Parent() { this.name = 'parent'; this.func = function () { console.log('func in parent called.'); }; } return Parent; }()); var Child = (function (_super) { __extends(Child, _super); function Child() { var _this = this; _this.age = 3; _this.func = function () { console.log('age is: ', this.age); }; console.log('Show property of parent, name is: ', _this.name); // 輸出undefined,由於此時子類的實例上尚未繼承到父類的屬性值 console.log('Show property of child, age is: ', _this.age); // 輸出3,子類實例本身的屬性值能夠訪問 _this = _super.call(this) || this; // 構造函數式的繼承實現,這一步就是講父類的屬性值設置到子類實例上 console.log('Show property of parent, name is: ', _this.name); // 輸出parent,此時子類的實例上通過上一步的繼承,獲得了父類的屬性值 console.log('Show property of child, age is: ', _this.age); // 輸出3,子類實例本身的屬性值能夠訪問 return _this; } return Child; }(Parent)); //# sourceMappingURL=demo.js.map
由此能夠知道,在ES6中使用extends進行繼承操做的過程當中,prototype
咱們直接來看代碼吧,關鍵點都註釋了的
class Parent { public name = 'parent'; public static staticName = 'staticParent'; public static staticFunc() { console.log('staticFunc called in parent.'); } public arrowFunc = () => { console.log('arrowFunc called in parent.'); } public normalFunc() { console.log('normalFunc called in parent.') } } class Child extends Parent { public static staticFunc() { super.staticFunc(); console.log('staticFunc called in Child.'); } arrowFunc = () => { super.arrowFunc(); console.log('arrowFunc called in Child.'); } normalFunc() { super.normalFunc(); console.log('normalFunc called in Child.') } getName() { console.log('parent name is: ', super.name); console.log('parent staticName is: ', super.staticName); console.log('child name is: ', this.name); } } /** 編譯後的代碼 **/ var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var Parent = (function () { function Parent() { this.name = 'parent'; this.arrowFunc = function () { console.log('arrowFunc called in parent.'); }; } // 編譯後的靜態方法能夠存在於Parent類的內部 Parent.staticFunc = function () { console.log('staticFunc called in parent.'); }; Parent.prototype.normalFunc = function () { console.log('normalFunc called in parent.'); }; return Parent; }()); Parent.staticName = 'staticParent'; // 編譯後的靜態屬性依然存在於Parent類外 var Child = (function (_super) { __extends(Child, _super); function Child() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.arrowFunc = function () { // 子類實例調用arrowFunc的時候會報錯,由於_super.prototype上是不存在arrowFunc方法的 _super.prototype.arrowFunc.call(_this); // Uncaught TypeError: Cannot read property 'call' of undefined console.log('arrowFunc called in Child.'); }; return _this; } Child.staticFunc = function () { _super.staticFunc.call(this); // super能夠正常訪問父類的靜態方法 console.log('staticFunc called in Child.'); }; Child.prototype.normalFunc = function () { _super.prototype.normalFunc.call(this); console.log('normalFunc called in Child.'); }; Child.prototype.getName = function () { console.log('parent name is: ', _super.prototype.name); // 輸出undefined, 父類原型(_super.prototype)上不存在name屬性 console.log('parent staticName is: ', _super.prototype.staticName); // 輸出undefined,super沒法正常訪問父類的靜態屬性 console.log('child name is: ', this.name); // 輸出parent,這是子類實例上的屬性,繼承自父類 }; return Child; }(Parent)); //# sourceMappingURL=demo.js.map
這裏再順嘴提一句,關於靜態屬性和靜態方法的區別。爲何在子類中經過super關鍵字來獲取父類的靜態方法通過編譯後是_super.staticFunc,而獲取靜態屬性依然是_super.prototype.staticName,從原型上獲取致使獲取失敗呢?這個問題目前我尚未找到答案,但願有知道的小夥伴能夠不吝指教。
不過我卻是搜到一些其餘相關內容。
Class 的靜態屬性和實例屬性
由於 ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。
雖然這種規定從ES7開始獲得了修正,咱們目前已經能夠將靜態屬性寫在Class的內部,可是通過編譯以後能夠發現,靜態屬性依然存在於類的實現的外部。
var Parent = (function () { function Parent() { this.name = 'parent'; this.arrowFunc = function () { console.log('arrowFunc called in parent.'); }; } // 編譯後的靜態方法能夠存在於Parent類的內部 Parent.staticFunc = function () { console.log('staticFunc called in parent.'); }; Parent.prototype.normalFunc = function () { console.log('normalFunc called in parent.'); }; return Parent; }()); Parent.staticName = 'staticParent'; // 編譯後的靜態屬性依然存在於Parent類外
問:箭頭函數在繼承過程當中沒法經過super關鍵字獲取,這是爲何呢?
答:由於子類中使用super.prop和super[expr]的方式獲取的是父類原型(prototype)上的方法,靜態方法除外。
從ES6中的extends講js原型鏈與繼承
React ES6 class constructor super()
Class 的靜態屬性和實例屬性