在ES6版本以前,JavaScript語言並無傳統面嚮對象語言的class寫法,ES6發佈以後,Babel迅速跟進,廣大開發者也很快喜歡上ES6帶來的新的編程體驗。
固然,在這門「混亂」而又精妙的語言中,許多天天出現咱們視野中的東西卻經常被咱們忽略。
對於ES6語法,考慮到瀏覽器的兼容性問題,咱們仍是要把代碼轉換爲ES5版本運行。然而,以前的ES版本爲何能模仿ES6的諸多特性,好比class與繼承,super,static?JavaScript又作了哪些改變以應對這些新角色?本文將對class實例構造,class繼承關係,super關鍵字,static關鍵字的運行機制進行探索。
水平有限,文中如有引發困惑或錯誤之處,還望指出。chrome
基本而言,ES6 class形式以下:編程
class Whatever{ }
固然,還能夠有constructor方法。瀏覽器
class Whatever{ constructor(){ this.name = 'hahaha'; } }
請看ES5對應版本:babel
function Whatever{ this.name = 'hahaha'; }
可知,constructor至關於之前在構造函數裏的行爲。而對於ES5構造函數而言,在被new調用的時候,大致上進行了下面四步:app
因此,構造函數的實例會繼承掛載在prototype上的方法,在ES6 calss中,咱們這樣寫會把方法掛載在class的prototype:異步
class Whatever{ //... methodA(){ //... } }
對應ES5寫法:函數
Whatever.prototype = function methodA(){ //... }
在基於原型的語言,有如下四個特色:this
看到這,你們應該明白了,爲何掛載在Constructor.prototype的方法會被實例「繼承」!
在ES6 class中,繼承關係仍是由[[prototype]]連維持,即:編碼
Child.prototype.__proto__ === Parent.prototype; Child.__proto__ === Parent; childObject.__proto === Child.prototype;
ES6的箭頭函數,一出身便深受衆人喜好,由於它解決了使人頭疼的函數執行時動態this指向的「問題」(爲何加引號?由於有時候咱們有時確實須要動態this帶來的巨大便利)。箭頭函數中this綁定在詞法做用域,即它定義的地方:prototype
//ES6: const funcArrow = () => { //your code } //ES5: var _this = this; var funcArrow = function(){ this = _this; //your code }
有的童鞋可能會想到了,既然js中繼承和this的關係這麼大,在calss中採用詞法綁定this的箭頭函數,會有怎麼樣呢?
咱們來瞧瞧。
class WhateverArrow{ // methodArrow = () => { //... } }
這種寫法會與上文中寫法有何區別?
class WhateverNormal{ // methodNormal() { //... } }
咱們在chrome環境下運行一下,看看這兩種構造函數的prototype有何區別:
WhateverArrow.prototype打印結果: constructor: class Whatever1 __proto__: Object WhateverNormal.prototype打印結果: constructor: class Whatever2 methodNormal: ƒ methodNormal() __proto__: Object
結合上文中關於原型的論述,仔細品味這二者的差異,最好手動嘗試一下。
咱們稱func(){}的形式爲「方法」,而methodArrow = () =>:any爲屬性!方法會被掛載在prototype,在屬性不會。箭頭函數methodArrow屬性會在構造函數裏賦值給this:
this.methodArrow = function methodArrow(){ this = _this; //any code }
在實例調用methodArrow時,調用的是本身的methodArrow,而非委託calss WhateverArrow.prototype上的方法,而這個箭頭函數中this的指向,Babel或許能給咱們一些啓示:
var WhateverArrow = function WhateverArrow() { var _this = this; _classCallCheck(this, WhateverArrow); _defineProperty(this, "methodArrow", function () { consoe.log(_this); }); };
當咱們談論繼承時,每每指兩種:
上文中咱們探討了第一種,如今,請把注意力轉向第二種。
考慮下方代碼:
class Parent { constructor(){ this.tag = 'A'; this.name = 'parent name' } methodA(){ console.log('methodA in Parent') } methodB(){ console.log(this.name); } } class Child extends Parent{ constructor(){ super(); //調用super()以後才用引用this this.name = 'child name' } methodA(){ super.methodA(); console.log('methodA in Child') } } const c1 = new Child(); c1.methodA();//methodA in Parent // methodA in Child
咱們經過extends鏈接了兩個class,標明他們是「父子關係」的類,子類中方法會屏蔽掉父類中同名方法,與Java中多態特性不一樣,這裏的方法參數數量並不影響「是否同一種方法」的斷定。
在Child的constructor中,必須在調用super()以後才能調用this,不然將會因this爲undefined而報錯。其中原因,簡單來講就是執行new操做時,Child的_this來自於調用Parent的constructor,若不調用super(),_this將爲undefined。對這個問題感興趣的同窗能夠自行操做試試,並結合Babel的轉換結果,進行思考。
super可讓咱們在子類中借用父類的屬性和方法。
methodA(){ super.methodA(); console.log('methodA in Child') }
super關鍵詞真是一個增進父子情的天才創意!
值得注意的是,子類中methodA調用super.methodA()時候,super.methodA中的this綁定到了子類實例。
用的舒服以後,咱們有必要想想,Child.prototype.methodA中的super是如何找到Parent.prototype.methodA的?
咱們知道:
Child.prototype.__proto__ === Parent.prototype; cs.__proto__ === Child.prototype; c1.methodA();
當c1.methodA()執行時,methodA中this指向c1,難道經過多少人愛就有多少人恨的this?
仔細想一想,若是是這樣(經過this找),考慮以下代碼:
//如下代碼刪除了當前話題無關行 class GrandFather{ methodA(){ console.log('methodA in GrandFather') } } class Parent extends GrandFather{ methodA(){ super.methodA(); console.log('methodA in Parent') } } class Child extends Parent{ methodA(){ super.methodA(); console.log('methodA in Child') } }
想一想咱們如今是執行引擎,咱們經過this找到了c1,而後經過原型找到了Child.prototype.methodA;
在Child.prototype.methodA中咱們碰見了super.methodA();
如今咱們要去找super,即Parent。
咱們經過this.__proto__.__proto__methodA找到了Parent.prototype.methodA;
對於Parent.prototype.methodA來講,也要像對待c1同樣走這個方式找,即在Parent..prototype.methodA中經過this找其原型。
這時候問題來了,運行到Parent.prototype.methodA時,該方法中的this指向的仍是c1。
這豈不是死循環了?
顯然,想經過this找super,只會鬼打牆。
爲了應對super,js引擎乾脆就讓方法(注意,是方法,不是屬性)在建立時硬綁定上[[HomeObject]]屬性,指向它所屬的對象!
顯然,Child中methodA的[[HomeObject]]綁定了Child.prototype,Parent中methodA的[[HomeObject]]綁定了Parent.prototype。
這時候,根據[[HomeObject]],能夠準確無誤地找到super!
而在Babel轉爲ES5時,是經過硬編碼的形式,解決了對super的引用,思路也同樣,硬綁定當前方法所屬對象(對象或者函數):
//babel轉碼ES5節選 _createClass(Parent, [{ key: "methodA", value: function methodA() { //此處就是對super.methodA()所作的轉換,一樣是硬綁定思路 _get(_getPrototypeOf(Parent.prototype), "methodA", this).call(this); console.log('methodA in Parent'); } }]);
注意屬性與方法的差異:
var obj1 = { __proto__:SomePrototype, methodQ(){ //methodQ綁定了[[HomeObject]]->obj1,調用super super.someMethod(); } } var obj2 = { __proto__:SomePrototype, methodQ:function(){ //methodQ不綁定任何[[HomeObject]] super.someMethod();//Syntax Eroor!語法錯誤,super不容許在對象的非方法中調用 } }
結合前文中關於class內部箭頭函數的談論,有個問題不得不引發咱們思考:class中的箭頭函數裏的super指向哪裏?
考慮以下代碼:
class Parent{ methodA(){ console.log('methodA in Parent') } } class Child extends Parent{ methodA = () => { super.methodA(); console.log('methodA in Child') } } const c1 = new Child(); c1.methodA();
輸出爲:
methodA in Parent methodA in Child
彷佛沒什麼意外。咱們須要更新異步,把Parent的methodA方法改成箭頭函數:
class Parent{ methodA = () => { console.log('methodA in Parent') } } class Child extends Parent{ methodA = () => { super.methodA(); console.log('methodA in Child') } } const c1 = new Child(); c1.methodA();
很抱歉,人見人恨得異常發生了:
Uncaught TypeError: (intermediate value).methodA is not a function at Child.methodA
如何把Child中的methodA改成普通方法函數呢?
class Parent{ methodA = () => { console.log('methodA in Parent') } } class Child extends Parent{ methodA () { super.methodA(); console.log('methodA in Child') } } const c1 = new Child(); c1.methodA();
輸出:
methodA in Parent //並無打印methodA in Child
以上幾種結果產生的緣由請結合前幾章節細緻品味,你會有所收穫的。
簡單來講,static關鍵詞標誌了一個掛載在class自己的屬性或方法,咱們能夠經過ClassName.staticMethod訪問到。
class Child{ static name = '7788'; static methodA () { console.log('static methodA in Child') } } Child.name;//7788; Child.methodA();//static methodA in Child
由於Child自己的[[prototype]]指向了Parent,即Child.__proto__===Parent 因此,static能夠被子類繼承:
class Parent{ static methodA () { console.log('static methodA in Parent') } } class Child extends Parent{ } Child.methodA();//static methodA in Parent
class Parent{ static methodA () { console.log('static methodA in Parent') } } class Child extends Parent{ static methodA () { super.methodA() console.log('static methodA in Child') } } Child.methodA(); //輸出: //static methodA in Parent // static methodA in Child
JS是門神奇的語言,神奇到不少人每每會用JS可是不會JS(...hh)。做爲一門熱門且不斷改進中的語言,因爲跟隨時代和歷史遺留等方面的因素,它有不少使人迷惑的地方。在咱們天天面對的一些特性中,咱們很容易忽視其中機理。就算哪天以爲本身明白了,過一段時間可能又遇到別的問題,忽然以爲本身懂得仍是太少(仍是太年輕)。而後刨根問底的搞明白,過一段時間可能又。。。或者研究JS的歷程就是這樣螺旋式的進步吧。感謝Babel,她真的對咱們理解JS一些特性的運行機理很是有用,由於Babel對JS吃的真的很透徹(...)。她對ES6的「翻譯」,能夠幫助咱們對ES6新特性以及往前版本的JS的理解。行文匆忙,不免有錯漏之處,歡迎指出。祝你們身體健康,BUG愈來愈少。