掌握JavaScript:類和原型繼承之間有什麼區別?
“掌握JavaScript面試”是一系列帖子,旨在幫助候選人準備他們在面試高階JavaScript職位時可能遇到的常見問題。這些是我經常在面試中使用的問題。想從頭開始?請參閱 ofollow,noindex" target="_blank">這裡
注意:本文使用ES6示例。如果您尚未學習ES6,請參閱 “如何學習ES6” 。
物件經常在JavaScript中使用,如何有效地使用它們將會為你 的工作效率有極大的幫組。事實上,糟糕的OO(面向物件)設計可能會導致專案失敗。
與大多數其他語言不同,JavaScript的物件系統基於 原型,而不是類 。不幸的是,大多數JavaScript開發人員都不瞭解JavaScript的物件系統,或者如何充分利用它。有一些開發者確實理解它,但希望它表現得更像基於類的系統。結果是JavaScript的物件系統具有令人困惑的分裂個性,這意味著JavaScript開發人員需要了解 原型和類 。
類和原型繼承有什麼區別?
這可能是一個棘手的問題,您可能需要通過後續問答來尋找答案,因此要特別注意學習差異,以及如何應用知識來編寫更好的程式碼。
類繼承: 類就像一個藍圖 - 要建立的物件的描述。 類繼承自類並建立子類關係:等級分類法。
例項通常使用帶有 new
關鍵字的建構函式進行例項化。類繼承也可以使用ES6中的 class
關鍵字。您可能從Java等語言中瞭解它們的類,JavaScript中的“class”與java中的“class”並不相同。而是使用建構函式。以下是ES6 class
關鍵字去建構函式:
class Foo { } typeof Foo // 'function'
在JavaScript中,類繼承是在原型繼承之上實現的,但是 並不意味著它做同樣的事情:
JavaScript的類繼承使用原型鏈將子 Constructor.prototype
連線到父 Constructor.prototype
以進行委派。通常,也會呼叫 super()
建構函式。這些步驟形成 單祖先父/子層次結構 和 建立OO設計中可用的最緊密耦合.
“類繼承自類, 建立子類關係 :等級分類法。”
原型繼承: 原型是工作物件例項. 物件直接從其他物件繼承。
例項可以由許多不同的源物件組成,允許容易的選擇性繼承和扁平的 [[Prototype]]
委託層次結構。換句話說, 類分類法不是原型OO 的自動副作用: 這是一個關鍵的區別.
例項通常通過 factory functions ,物件文字或 Object.create()
例項化。
“原型是一個工作物件例項. 物件直接從其他物件繼承。”
為什麼這很重要?
繼承基本上是一種程式碼重用機制
:一種讓不同型別的物件共享程式碼的方法。你共享程式碼的方式很重要,因為如果你弄錯了, 它會產生很多問題, 具體來說:
類繼承建立父/子物件分類法的副作用。
這些分類法幾乎不可能適用於所有新的用例,並且基類的廣泛使用導致了脆弱的基類問題,**這使得當你弄錯它們時很難修復它們。事實上,類繼承在OO設計中引起許多眾所周知的問題:
-
緊耦合問題(類繼承是oo設計中可用的最緊密耦合),這導致下一個......
-
脆弱的基類問題
-
不靈活的層次結構問題(最終,所有不斷髮展的層次結構對於新用途都是錯誤的)
-
必要性重複問題(由於不靈活的層次結構,新的用例通常是通過複製而不是調整現有程式碼來實現的)
-
大猩猩/香蕉問題(你想要的是香蕉,但你得到的是拿著香蕉和整個叢林的大猩猩)
我在演講中更深入地討論了一些問題,“經典繼承已經過時:如何在Prototypal OO中思考”:[演講]( https://youtu.be/lKCCZTUx0sI)
所有這些問題的解決方案是支援物件組合而不是類繼承。
“贊成物件組合而不是類繼承。” JNIUXY4NSH" rel="nofollow,noindex" target="_blank">“設計模式:可重用面向物件軟體的元素”
總結: 連結視訊
所有繼承都不好嗎?
當人們說“贊成組成而不是繼承”,這是“贊成組合超過 類 繼承”的縮寫“。這是OO設計中的常識,因為 類繼承有許多缺陷 並導致許多問題。人們常常在談論類繼承時忽略 類 ,這聽起來像 all inheritance 很糟糕 - 但事實並非如此。
實際上有幾種不同的繼承,其中大多數都非常適合從多個元件物件組合複合物件。
三種不同的原型繼承
在我們深入研究其他型別的繼承之前,讓我們仔細看看 類繼承 的含義:
你可以 在Codepen上試驗這個例子 .
BassAmp
繼承自 GuitarAmp
,而'ChannelStrip 繼承自'BassAmp
和 GuitarAmp
。這是OO設計出錯的一個例子。ChannelStrip實際上不是一種GuitarAmp,實際上根本不需要BassAmp。一個更好的選擇是建立一個ChannelStrip和GuitarAmp繼承的新基類,但即便如此也有侷限性。
最終,新的共享基類策略也會崩潰。
還有更好的方法。您可以使用物件組合繼承您真正需要的東西:
如果你仔細觀察,你可能會發現我們更具體地說明哪些物件獲得哪些屬性,因為有了合成。它不是真正的類繼承選項。當你從一個類繼承時,你得到了所有東西,即使你不想要它也是如此._
在這一點上,你可能會想到自己,“那很好,但原型在哪裡?”
要理解這一點,您必須瞭解有三種不同的原型OO。
連續繼承:通過複製源物件屬性將要素直接從一個物件繼承到另一個物件的過程。在JavaScript中,源原型通常被稱為 mixins。 從ES6開始,這個特性在JavaScript中有一個名為 Object.assign()
的便利工具。在ES6之前,這通常使用Underscore / Lodash的 .extend()
jQuery的 $ .extend()
等等......上面的組合示例使用了連線繼承。
原型委託:在JavaScript中,物件可能具有 委託 原型的連結。如果在物件上找不到屬性,則查詢 被委託 給 委託原型, 可能有一個連結到它自己的委託原型,依此類推,直到你到達 Object.prototype
,這是根代表。當你附加到 Constructor.prototype
並用 new
例項化時,這是連線起來的原型。您也可以使用 Object.create()
來實現此目的,甚至將此技術與串聯混合,以便將多個原型展平為單個委託,或者在建立後擴充套件物件例項。
功能繼承:在JavaScript中,任何函式都可以建立一個物件。當該函式不是建構函式(或 class
)時,它被稱為 工廠函式 。功能繼承的工作原理是從工廠生成物件,並通過直接為其分配屬性來擴充套件生成的物件(使用連線繼承)。Douglas Crockford創造了這個術語,但功能繼承在JavaScript中已經普遍使用了很長時間。
正如您可能已經開始意識到的那樣, 連線繼承是在JavaScript中實現物件組合的祕訣 ,這使得原型委派和功能繼承更加有趣。
當大多數人想到JavaScript中的原型OO時,_他們會想到原型委託。現在你應該看到他們錯過了很多東西。委託原型不是類繼承的絕佳替代 - 物件組合是 。
為什麼組合物對脆弱的基類問題免疫
要理解脆弱的基類問題及其不適用於組合的原因,首先您必須瞭解它是如何發生的:
-
A
是基類 -
B
繼承自A
-
C
繼承自B
-
D
繼承自B
C
呼叫 super
,它在 B
中執行程式碼。 B
呼叫 super
,它在 A
中執行程式碼。
A
和 B
包含 C
和 D
所需的無關特徵。 D
是一個新的用例,在 A
的初始程式碼中需要 slightly different 行為而不是 C
需要。所以新手開發人員去調整 A'的初始化程式碼。__ ** \ _ C \ _中斷因為它取決於現有行為**,並且
D`開始工作。
我們這裡有的是在“A”和“B”之間散佈的特徵,即“C”和“D”需要以各種方式使用。 C
和 D
不使用'A 和
B 的每個特徵......他們只想繼承一些已經在'A
和'B`中定義的東西。但是通過繼承和呼叫“超級”, 你不會選擇你繼承的東西 。你繼承了一切:
“......面嚮物件語言的問題在於,他們已經擁有了所有這些隱含的環境。 你想要一個香蕉,但你得到的是拿著香蕉 和整個叢林的大猩猩。“〜喬阿姆斯特朗 - ”工作中的編碼員“
使用Composition想象一下,你有功能而不是類:
feat1, feat2, feat3, feat4
C
需要 feat1
和 feat3
, D
需要 feat1
, feat2
, feat4
:
const C = compose(feat1, feat3);const D = compose(feat1, feat2, feat4);
現在,想象一下你發現'D 需要與'feat1
略有不同的 行為。它實際上並不需要改變 feat1
,相反, 你可以製作 feat1
**的定製版本並使用它。您仍然可以繼承 feat2
和 feat4
中的現有行為而不做任何更改:
const D = compose(custom1, feat2, feat4);
而且__ \ _ C \ _仍然不受影響 。
這種類繼承不可能的原因是因為 當你使用類繼承時,你會購買整個現有的類分類。
如果您想稍微適應一個新的用例,您要麼最終複製現有分類法的部分(必要性重複問題),要麼重構依賴於現有分類法的所有內容以使分類法適應新用法案例由於 脆弱的基類問題 。
組合物對兩者都免疫。
你認為你知道原型,但......
如果你被教導構建類或建構函式並繼承那些,你所教的是 不是原型繼承 。你被教導如何 使用原型 模仿類繼承**。請參閱 “關於JavaScript中繼承的常見誤解” .
在JavaScript中,類繼承依賴於很久以前內置於語言中的非常豐富,靈活的原型繼承功能,但是當你使用類繼承 - 甚至是在原型之上構建的ES6 + class
繼承時,你不是使用原型OO的全部功能和靈活性。事實上,你正在把自己畫成角落,並且 選擇了所有的類繼承問題 。
在JavaScript中使用類繼承就像將新的特斯拉Model S推向經銷商並將其換成生鏽的1973 Ford Pinto。