聊一聊 JavaScript 中的 Super() 是什么?
當(dāng)你在js代碼中看到有調(diào)用super()時(shí),會(huì)不會(huì)好奇這個(gè)super()到底有什么作用?在子類中,您可以使用super()調(diào)用其父級的構(gòu)造函數(shù),并使用super.<方法名>來訪問其父級的方法。
本文將假定您至少對構(gòu)造函數(shù)以及子類和父類的概念有所了解。如果你不了解這些,則可能需要從Mozilla的Object-oriented JavaScript for beginners開始學(xué)習(xí)。
super并不是只javascript語言才有--許多其它編程語言,如java, python都有一個(gè)super()關(guān)鍵字來提供對父類的引用。與Java和Python不同,JavaScript并不是圍繞類繼承模型構(gòu)建的。相反,它擴(kuò)展了JavaScript的原型繼承模型,以提供與類繼承一致的行為。
讓我們進(jìn)一步了解它,并查看一些代碼示例。
首先,這里引用的一段話Mozilla’s web docs for classes:
一個(gè)簡單的子類和父類的例子將有助于說明這句話的真正含義:
我的例子有兩個(gè)類:Fish和 Trout。所有的魚都有棲息地和長度的信息,所以這些屬性屬于魚類。鱒魚也有一個(gè)多樣性的屬性,所以它基于fish又?jǐn)U展了屬性variety。下面是魚和鱒魚的構(gòu)造函數(shù):
魚類的構(gòu)造函數(shù)定義棲息地和長度,鱒魚的構(gòu)造函數(shù)定義了種類。我必須在鱒魚的構(gòu)造函數(shù)中調(diào)用super(),否則在嘗試設(shè)置this.variety時(shí)會(huì)出現(xiàn)錯(cuò)誤。那是因?yàn)樵邝V魚類的第一行中,我告訴JavaScript鱒魚是使用extends關(guān)鍵字的“魚”。
這意味著鱒魚的上下文包括fish類中定義的屬性和方法,以及鱒魚為其自身定義的任何屬性和方法。調(diào)用super()本質(zhì)上使JavaScript知道魚是什么,以便可以為鱒魚創(chuàng)建this上下文,其中包括魚中的所有內(nèi)容以及我們將為鱒魚定義的所有內(nèi)容。fish類不需要super(),因?yàn)樗摹案讣墶敝皇荍avaScript對象。Fish已處于原型繼承鏈的頂部,因此無需調(diào)用super()。
我在trout的構(gòu)造函數(shù)中調(diào)用super(habitat, length),使這三個(gè)屬性在這個(gè)上下文中立即可用。實(shí)際上還有另一種方法可以從trout的構(gòu)造函數(shù)中得到相同的行為。我必須調(diào)用super()來避免引用錯(cuò)誤,但我不必使用fish的構(gòu)造函數(shù)所期望的參數(shù)正確調(diào)用它。
這是因?yàn)槲也恍枰褂胹uper()來給fish創(chuàng)建的字段賦值,我只需要確保這些字段存在于這個(gè)上下文上。這是JavaScript與真正的類繼承模型(例如Java)之間的重要區(qū)別,根據(jù)我的實(shí)現(xiàn)方式,以下代碼可能是非法的:
這種替代的trout構(gòu)造函數(shù)使您更難分辨哪些屬性屬于fish和哪些屬性屬于trout,但其結(jié)果與前面的示例相同。唯一的區(qū)別是,在此情況下,不帶參數(shù)調(diào)用super()會(huì)在當(dāng)前此this的上下文上創(chuàng)建屬性habitat和length,而無需為其分配任何內(nèi)容。
如果我在第三行之后調(diào)用console.log(this),它將顯示{habitat:undefined,length:undefined}。第四行和第五行分配值。
我也可以在trout的構(gòu)造函數(shù)之外使用super(),以引用父類上的方法。在這里,我定義了renderProperties方法,該方法會(huì)將類的所有屬性顯示在我傳遞給它的HTML元素中。
super()在這里很有用,因?yàn)槲蚁M业膖rout類實(shí)現(xiàn)一個(gè)類似的方法,該方法可以完成相同的工作,并且還要多做一些事情—它在更新HTML之前為該元素執(zhí)定了一個(gè)類名。我可以通過在相關(guān)類函數(shù)內(nèi)調(diào)用super.renderProperties()來重用fish類中的邏輯。
你在定義時(shí)方法命名很重要。我把我在trout類中的方法叫做renderPropertiesWithSuper(),因?yàn)槲胰匀幌M梢赃x擇調(diào)用trout.renderProperties(),因?yàn)樗窃趂ish類上定義的。
如果我只是將函數(shù)命名為trout類中的renderProperties,那將是完全有效的;但是,我將不再能夠從trout的實(shí)例中直接訪問這兩個(gè)函數(shù)--調(diào)用trout.renderProperties將調(diào)用定義在trout上的函數(shù)。
這不一定是一個(gè)有用的實(shí)現(xiàn)方式--可以說,像這樣調(diào)用super的函數(shù)覆蓋其父函數(shù)的名稱是一個(gè)更好的模式--但它確實(shí)說明了JavaScript允許你的類是多么靈活。
其實(shí)也可以完全可以不使用前面代碼示例中非常有用的super()或extends關(guān)鍵字來實(shí)現(xiàn)這個(gè)例子,只是不太方便。這就是Mozilla所說的 "語法糖 "的意思。事實(shí)上,如果我把我之前的代碼插入像Babel這樣的移植器中,以確保我的類能與舊版本的JavaScript一起工作,它將生成一些更接近下面的代碼。
這里的代碼大部分是一樣的,但你會(huì)注意到,如果沒有extends和super(),我必須將fish和trout定義為函數(shù)并直接訪問它們的原型。我還必須在第15、16和17行對原型做一些額外的改動(dòng),并確保trout可以在其構(gòu)造函數(shù)中傳遞正確的this上下文(混合繼承)。
如果你有興趣深入了解這里發(fā)生的事情,Eric Green有一篇優(yōu)秀的帖子,里面有很多代碼片段,介紹了如何使用和不使用ES2015構(gòu)建類以及其繼承的關(guān)系。
JavaScript中的類是共享功能的強(qiáng)大方法。例如,React中的類組件依賴于它們。但是,如果您習(xí)慣使用另一種使用類繼承模型的語言進(jìn)行面向?qū)ο蟮木幊蹋敲碕avaScript的行為有時(shí)可能會(huì)令人驚訝。所以學(xué)習(xí)原型繼承的基礎(chǔ)知識(shí)可以幫助闡明如何使用JavaScript中的類。