摘要:請看對應版本干了什么可知,相當于以前在構造函數里的行為。這種寫法會與上文中寫法有何區別我們在環境下運行一下,看看這兩種構造函數的有何區別打印結果打印結果結合上文中關于原型的論述,仔細品味這兩者的差別,最好手動嘗試一下。
ES6 class
在ES6版本之前,JavaScript語言并沒有傳統面向對象語言的class寫法,ES6發布之后,Babel迅速跟進,廣大開發者也很快喜歡上ES6帶來的新的編程體驗。
當然,在這門“混亂”而又精妙的語言中,許多每天出現我們視野中的東西卻常常被我們忽略。
對于ES6語法,考慮到瀏覽器的兼容性問題,我們還是要把代碼轉換為ES5版本運行。然而,之前的ES版本為什么能模仿ES6的諸多特性,比如class與繼承,super,static?JavaScript又做了哪些改變以應對這些新角色?本文將對class實例構造,class繼承關系,super關鍵字,static關鍵字的運行機制進行探索。
水平有限,文中若有引起困惑或錯誤之處,還望指出。
基本而言,ES6 class形式如下:
class Whatever{ }
當然,還可以有constructor方法。
class Whatever{ constructor(){ this.name = "hahaha"; } }
請看ES5對應版本:
function Whatever{ this.name = "hahaha"; }new干了什么
可知,constructor相當于以前在構造函數里的行為。而對于ES5構造函數而言,在被new調用的時候,大體上進行了下面四步:
新建對象var _this = {};
this的[[prototype]]指向構造函數的prototype,即_this.__proto_ = Constructor.prototype
改變Constructor的this到_this并執行Constructor,即Constructor.apply(_this,agrs);得到構造好的_this對象
判斷Constructor的返回值,若返回值不為引用類型,則返回_this,否則返回改引用對象
所以,構造函數的實例會繼承掛載在prototype上的方法,在ES6 calss中,我們這樣寫會把方法掛載在class的prototype:
class Whatever{ //... methodA(){ //... } }
對應ES5寫法:
Whatever.prototype = function methodA(){ //... }class繼承關系 原型語言基本特點
在基于原型的語言,有以下四個特點:
一切皆為對象(js中除了對象還有基本類型,函數式第一等對象)
對象皆是從其他對象復制而來(在JS對象世界中,萬物始于Object.prototype這顆蛋)
對象會記住它的原型(在JS中對象的__proto__屬性指向它的原型)
調用對象本身沒有的屬性/方法時,對象會嘗試委托它的原型
看到這,大家應該明白了,為什么掛載在Constructor.prototype的方法會被實例“繼承”!
在ES6 class中,繼承關系還是由[[prototype]]連維持,即:
Child.prototype.__proto__ === Parent.prototype; Child.__proto__ === Parent; childObject.__proto === Child.prototype;當箭頭函數與class碰撞
ES6的箭頭函數,一出身便深受眾人喜愛,因為它解決了令人頭疼的函數執行時動態this指向的“問題”(為什么加引號?因為有時候我們有時確實需要動態this帶來的巨大便利)。箭頭函數中this綁定在詞法作用域,即它定義的地方:
//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); }); };遇見extends,super與[[HomeObject]] 讓我們extends一下
當我們談論繼承時,往往指兩種:
對象實例繼承自一個類(構造函數)
子類繼承父類
上文中我們探討了第一種,現在,請把注意力轉向第二種。
考慮下方代碼:
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不允許在對象的非方法中調用 } }箭頭函數再襲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 static的表現簡單來說,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 Childstatic如何傳給子類
因為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 Parentstatic方法中訪問super
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越來越少。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106902.html
摘要:這是因為子類沒有自己的對象,而是繼承父類的對象,然后對其進行加工。 溫馨提示:作者的爬坑記錄,對你等大神完全沒有價值,別在我這浪費生命溫馨提示-續:你們要非得看,我也攔不住,但是至少得準備個支持ES6的Chrome瀏覽器吧?溫馨提示-再續:ES6簡直了,放著不用簡直令人發指! 書接上回,即便是程序員,也還是能夠通過自己的努力辛辛苦苦找到合適對象的,見前文《javascript對象不完全...
摘要:下面是用實現轉成抽象語法樹如下還支持繼承以下是轉換結果最終的結果還是代碼,其中包含庫中的一些函數。可以使用新的易于使用的類定義,但是它仍然會創建構造函數和分配原型。 這是專門探索 JavaScript 及其所構建的組件的系列文章的第 15 篇。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 如果你錯過了前面的章節,可以在這里找到它們: JavaScript 是...
摘要:而和的存在就是為了建立這種子類與父類間的聯系。創建一個基本對象建立新對象與原型我把它理解為類之間的連接執行構造函數小結可以理解為類,也就是存儲一類事物的基本信息。原型原型鏈和繼承之間的關系。 原型 原型的背景 首先,你應該知道javascript是一門面向對象語言。 是對象,就具有繼承性。 繼承性,就是子類自動共享父類的數據結構和方法機制。 而prototype 和 __proto__...
摘要:一步,一步前進一步深入淺出之。是構造函數,可在里面初始化我們想初始化的東西。類靜態方法大多數情況下,類是有靜態方法的。中添加類方法十分容易類方法和靜態方法是同一個東西在的語法中,我們可以使用關鍵字修飾方法,進而得到靜態方法。 一步,一步前進の一步 ES6深入淺出之Classes。翻譯的同時亂加個人見解,強烈推薦閱讀原作者的文章,言簡意賅。es6-classes-in-depth 類語...
摘要:但在可以用和的地方使用它們很有好處的。它會盡可能的約束變量的作用域,有助于減少令人迷惑的命名沖突。在回調函數外面,也就是中,它指向了對象。這就意味著當引擎查找的值時,可以找到值,但卻和回調函數之外的不是同一個值。 使用 ES6 寫更好的 JavaScript part I:廣受歡迎新特性 介紹 在ES2015規范敲定并且Node.js增添了大量的函數式子集的背景下,我們終于可以拍著胸脯...
閱讀 3558·2021-11-22 15:11
閱讀 4634·2021-11-18 13:15
閱讀 2702·2019-08-29 14:08
閱讀 3576·2019-08-26 13:49
閱讀 3090·2019-08-26 12:17
閱讀 3287·2019-08-26 11:54
閱讀 3111·2019-08-26 10:58
閱讀 2030·2019-08-26 10:21