前言前端
很久不見了,你不懂JS系列又跟你們見面了,今天這篇是this與對象原型的最後一篇,繼續由前端早讀課專欄做者@JoeHetfield帶來的翻譯分享。設計模式
正文從這開始~框架
若是說本書後半部分(第四至六章)有什麼關鍵信息,那就是類是一種代碼的可選設計模式(不是必要的),並且用像JavaScript這樣的[[Prototype]]語言來實現它老是很尷尬。ide
雖然這種尷尬很大一部分關於語法,但 不只 限於此。第四和第五章審視了至關多的難看語法,從使代碼雜亂的.prototype引用的繁冗,到 顯式假想多態:當你在鏈條的不一樣層級上給方法相同的命名以試圖實現從低層方法到高層方法的多態引用。.constructor被錯誤地解釋爲「被XX構建」,這成爲了一個不可靠的定義,也成爲了另外一個難看的語法。函數
但關於類的設計的問題要深入多了。第四章指出在傳統的面向類語言中,類實際上發生了從父類向子類,由子類向實例的 拷貝 動做,而在[[Prototype]]中,動做 不是 一個拷貝,而是相反——一個委託連接。工具
OLOO風格和行爲委託接受了[[Prototype]],而不是將它隱藏起來,當比較它們的簡單性時,類在JS中的問題就凸顯出來。性能
classthis
咱們 沒必要 再次爭論這些問題。我在這裏簡單地重提這些問題僅僅是爲了使它們在你的頭腦裏保持新鮮,以使咱們將注意力轉向ES6的class機制。咱們將在這裏展現它如何工做,而且看看class是否實質上解決了任何這些「類」的問題。編碼
讓咱們重溫第六章的Widget/Button例子:spa
除了語法上 看起來 更好,ES6還解決了什麼?
再也不有(某種意義上的,繼續往下看!)指向.prototype的引用來弄亂代碼。
Button被聲明爲直接「繼承自」(也就是extends)Widget,而不是須要用Object.create(..)來替換.prototype連接的對象,或者用__proto__和Object.setPrototypeOf(..)來設置它。
super(..)如今給了咱們很是有用的 相對多態 的能力,因此在鏈條上某一個層級上的任何方法,能夠引用鏈條上相對上一層的同名方法。第四章中有一個關於構造器的奇怪現象:構造器不屬於它們的類,並且所以與類沒有聯繫。super(..)含有一個對此問題的解決方法 —— super()會在構造器內部想正如你指望的那樣工做。
class字面語法對指定屬性沒有什麼啓發(僅對方法有)。這看起來限制了某些東西,可是絕大多數狀況下指望一個屬性(狀態)存在於鏈條末端的「實例」之外的地方,這一般是一個錯誤和使人詫異(由於這個狀態被隱含地在全部「實例」中「分享」)的。因此,也能夠說class語法防止你出現錯誤。
extends甚至容許你用很是天然的方式擴展內建的對象(子)類型,好比Array或者RegExp。在沒有class .. extends的狀況下這樣作一直以來是一個極端複雜而使人沮喪的任務,只有最熟練的框架做者曾經正確地解決過這個問題。如今,它是小菜一碟!
憑心而論,對大多數明顯的(語法上的)問題,和經典的原型風格代碼令人詫異的地方,這些確實是實質上的解決方案。
class的坑
然而,它不全是優勢。在JS中將「類」做爲一種設計模式,仍然有一些深入和很是使人煩惱的問題。
首先,class語法可能會說服你JS在ES6中存在一個新的「類」機制。但不是這樣。class很大程度上僅僅是一個既存的[[Prototype]](委託)機制的語法糖!
這意味着class實際上不是像傳統面向類語言那樣,在聲明時靜態地拷貝定義。若是你在「父類」上更改/替換了一個方法(有意或無心地),子「類」和/或實例將會受到「影響」,由於它們在聲明時沒有獲得一份拷貝,它們依然都使用那個基於[[Prototype]]的實時委託模型。
這種行爲只有在 你已經知道了 關於委託的性質,而不是期待從「真的類」中 拷貝 時,纔看起來合理。那麼你要問本身的問題是,爲何你爲了根本上就和類不一樣的東西選擇class語法?
ES6的class語法不是使觀察和理解傳統的類和委託對象間的不一樣 變得更困難 了嗎?
class語法 沒有 提供聲明類的屬性成員的方法(僅對方法有)。因此若是你須要跟蹤對象間分享的狀態,那麼你最終會回到醜陋的.prototype語法,像這樣:
這裏最大的問題是,因爲它將.prototype做爲實現細節暴露(泄露!)出來,而背叛了class語法的初衷。
並且,咱們還依然面臨着那個使人詫異的陷阱:this.count++將會隱含地在c1和c2兩個對象上建立一個分離的遮蔽屬性.count,而不是更新共享的狀態。class沒有在這個問題上給咱們什麼安慰,除了(大概是)經過缺乏語法支持來暗示你 根本 就不該該這麼作。
另外,無心地遮蔽依然是個災難:
還有一些關於super如何工做的微妙問題。你可能會假設super將會以一種相似與this獲得綁定的方式(間第二章)來被綁定,也就是super老是會綁定到當前方法在[[Prototype]]鏈中的位置的更高一層。
然而,由於性能問題(this綁定已經很耗費性能了),super不是動態綁定的。它在聲明時,被有些「靜態地」綁定。不是什麼大事兒,對吧?
恩……多是,可能不是。若是你像大多數JS開發者那樣,開始把函數賦值給不一樣的(來自於class定義的)對象,以各類不一樣的方式,你可能不會意識到在全部這些狀況下,底層的super機制會不得不每次都從新綁定。
並且根據你每次賦值採起的語法方式不一樣,頗有可能在某些狀況下super不能被正確地綁定(至少不會像你指望的那樣),因此你可能(在寫做這裏時,TC39正在討論這個問題)會不得不用toMethod(..)來手動綁定super(有點兒像你不得不用bind(..)綁定this —— 見第二章)。
你曾經能夠給不一樣的對象賦予方法,來經過 隱含綁定 規則(見第二章),自動地利用this的動態性。但對於使用super的方法,一樣的事情極可能不會發生。
考慮這裏super應當怎樣動做(對D和E):
若是你(十分合理地!)認爲super將會在調用時自動綁定,你可能會指望super()將會自動地認識到E委託至D,因此使用super()的E.foo()應當調用D.foo()。
不是這樣。 因爲實用主義的性能緣由,super不像this那樣 延遲綁定(也就是動態綁定)。相反它從調用時[[HomeObject]].[[Prototype]]派生出來,而[[HomeObject]]實在聲明時靜態綁定的。
在這個特定的例子中,super()依然解析爲P.foo(),由於方法的[[HomeObject]]仍然是C並且C.[[Prototype]]是P。
可能 會有方法手動地解決這樣的陷阱。在這個場景中使用toMethod(..)來綁定/重綁定方法的[[HomeObject]](設置這個對象的[[Prototype]]一塊兒!)彷佛會管用:
注意: toMethod()克隆這個方法,而後將它的第一個參數做爲homeObject(這就是爲何咱們傳入E),第二個參數(可選)用來設置新方法的name(保持「foo」不變)。
除了這種場景之外,是否還有其餘的極端狀況會使開發者們陷入陷阱還有待觀察。不管如何,你將不得不費心保持清醒:在哪裏引擎自動爲你肯定super,和在哪裏你不得不手動處理它。噢!
靜態優於動態?
可是關於ES6的最大問題是,全部這些種種陷阱意味着class有點兒將你帶入一種語法,它看起來暗示着(像傳統的類那樣)一旦你聲明一個class,它是一個東西的靜態定義(未來會實例化)。使你徹底忘記了這個事實:C是一個對象,一個你能夠直接互動的具體的東西。
在傳統面向類的語言中,你從不會在晚些時候調整類的定義,因此類設計模式不提供這樣的能力。可是JS的 一個最強大的部分 就是它 是 動態的,並且任何對象的定義都是(除非你將它設定爲不可變)不固定的可變的 東西。
class看起來在暗示你不該該作這樣的事情,經過強制你使用.prototype語法才能作到,或強制你考慮super的陷阱,等等。並且它對這種動態機制可能帶來的一切陷阱 幾乎不 提供任何支持。
換句話說,class好像在告訴你:「動態太壞了,因此這可能不是一個好主意。這裏有看似靜態語法,把你的東西靜態編碼。」
關於JavaScript的評論是多麼悲傷啊:動態太難了,讓咱們僞裝成(但實際上不是!)靜態吧。
這些就是爲何ES6的class假裝成一個語法頭痛症的解決方案,可是它實際上把水攪得更渾,並且更不容易對JS造成清晰簡明的認識。
注意: 若是你使用.bind(..)工具製做一個硬綁定函數(見第二章),那麼這個函數是不能像普通函數那樣用ES6的extend擴展的。
複習
class在僞裝修復JS中的類/繼承設計模式的問題上作的很好。但他實際上作的卻正相反:它隱藏了許多問題,並且引入了其餘微妙並且危險的東西。
class爲折磨了JavaScript語言將近20年的「類」的困擾作出了新的貢獻。在某些方面,它問的問題比它解決的多,並且在[[Prototype]]機制的優雅和簡單之上,它總體上感受像是一個很是不天然的匹配。
底線:若是ES6 class使穩健地利用[[Prototype]]變得困難,並且隱藏了JS對象機制最重要的性質 —— 對象間的實時委託連接 —— 咱們不該該認爲class產生的麻煩比它解決的更多,而且將它貶低爲一種反模式嗎?
我真的不能幫你回答這個問題。但我但願這本書已經在你從未經歷過的深度上徹底地探索了這個問題,並且已經給出了 你本身回答這個問題 所需的信息。
最後,雖然是this與對象原型的最後一篇,可能你須要如下這些文章能更好的鏈接:
或者,回覆[你不懂js]查看@JoeHetfield專欄文章。