理解JavaScript的核心知識點:原型

JavaScript 中的原型機制一直以來都被衆多開發者(包括本人)低估甚至忽視了,這是由於絕大多數人沒有想要深入理解這個機制的內涵,以及愈來愈多的開發者缺少計算機編程相關的基礎知識。對於這樣的開發者來講 JavaScript 的原型機制是一個尚待發掘的大寶藏,深刻了解下去會讓你們在編程這條路上走得更長遠,固然你不能妄想任何一種機制、模式或範式是天衣無縫的。javascript

首先,須要來理清一些基礎的計算機編程概念:html

編程哲學與設計模式:Programming Philosophy and Design Pattern

計算機編程理念源自於對現實抽象的哲學思考,面向對象編程(OOP)是其一種思惟方式,與它並駕齊驅的是另外兩種思路:過程式和函數式編程。這三種方式對應於解決計算機架構問題的三種不一樣思路。它們也分別表明了不一樣的編程哲學。java

具體實現編程架構的代碼方案能夠稱爲設計模式。設計模式是解決具體問題的一種最佳實踐,能夠用在設計語言自己,也能夠用在具體業務場景中。git

三種思路在語言自己的設計和應用業務中是可能混用的,靈活的語言正如 JavaScript ,內部雖然是基於面向對象編程而實現,但在開發過程當中也能夠運用過程式編程或函數式編程的思路進行具體業務的設計。正由於這容易形成開發者的混亂,因此特別指出,下面一段討論的是針對語言內部的實現方式而不是應用業務。github

面向對象編程語言的核心是對象,針對如何設計出一套語言的對象模型編程大師們又提出了三種不一樣的模式:類、原型、元類(元類是基於類模型產生的新模型)。三種模型造就了許多不一樣的編程語言,JavaScript 剛好是原型模式的典型表明,正如 JAVA 是基於類模式的典範,請謹記這一語言自己在設計模式上的區別。web

不少語言因爲自身的實現而限制了在其中可能應用到業務中的設計模式。但對於 JavaScript 這樣的語言來講,選擇是開放性的,由於咱們常常在應用業務上聽到你們討論類繼承或原型繼承這樣的實現方案,這即是它很是靈活的一個表現。但對於類模式和原型模式,有一些本質上的概念區別和使用混淆是不少人沒有注意到的,下面對這兩種設計模式作一個詳細的討論。編程

做爲一種設計模式的類:"Class" Design Pattern

基於類的應用或業務架構實現能夠稱爲類設計模式,咱們在業務開發中不可避免地會使用到繼承的概念即是出自於的範疇。類不專屬於 JavaScript 語言範疇,JavaScript 中實質上也沒有實現真正的基於類設計模式的接口。JavaScript 中一切關於「類」的說法實際上都是一種有名無實的冒充和混淆。設計模式

咱們一般覺得在 JavaScript 中「類」是必選的,使用它來實現業務架構不只天經地義並且是惟一的——這是對 JavaScript 的最大誤解。JavaScript 雖然是面向對象的編程語言,但以類做爲對象模型來實現業務需求的方式只能說是一種設計模式:面向對象毫不等同於類瀏覽器

類是一份產品製造說明書,指導生產機器生產符合其定義參數、具備相應功能的產品。它的用途在於規定而不在於實際使用,使用的是經過類製造出來的產品,在 JavaScript 中即對象。咱們基於複用、繼承等工業化生產需求而使用類這套設計模式:規定 -> 製造 -> 使用。但咱們千萬不能忘記,在工業化時代出現以前,經過手工的方式同樣能夠製造產品,若是你須要批量生產模樣同樣的東西才須要這份產品製造說明說。就手段來講要澄清的一個誤區是,類並非實現功能複用、廣義上的繼承等業務目標的惟一模式性能優化

類:What's Class

,是面向對象編程中一種通用對象模型,它是基於一種對現實中事物進行分類的抽象,天生帶有類別層級的觀念,如生物是一級類、動物是一個具備全部生物特性而派生出本身獨有特性的二級類,依照這樣的邏輯還能夠繼續推及到其下更多細別的子類,這是一種將全部對象進行樹狀類別組織關聯的思惟方式:

類-分類

經過這張圖能夠得出一個顯而易見卻容易被忽視的事實:永遠沒有一隻具體的哺乳動物(好比說一隻獅子)等同於哺乳動物這個類別,就像你不等於人類同樣。類是一個並不具備實體的概念,是人爲的發明,爲了將具備相似特性的事物分門別類以適應人腦簡化處理信息的方式,儘管天然並非出於這樣的目的而生成各類事物的。

JavaScript 中類的概念也是人爲的設計,爲的是更靠近自己以類模式設計而成的語言,儘管它自己是以原型模式設計而成的。所以咱們有了 new 一個對象這種操做,爲的是更符合採用類這一設計模式來實踐面向對象編程。因此在此處埋下了第一個使人迷惑的種子:JavaScript 原生基於原型關聯起來的對象與基於類建立的與類關聯起來的對象兩種概念的混淆。對於發現了這一對令人迷惑的概念的開發者來講,便有了第一個疑問:

爲何基於原型模式設計而成的 JavaScript 不繼續在業務場景中使用原型設計模式,而是轉而求向類設計模式?

以前有過說明,實踐面向對象編程的方式有三種的,而且沒有任何一種是天衣無縫的。因此請把類模式是最好的這種想法拋到九霄雲外吧。暫且將這個問題移到潛意識中去,繼續瞭解一下類範疇的的其餘相關概念。

實例:What's Instance

實例的概念基於類之上。正如天然界中單一的個體便是它所屬類別中的一個實例,面嚮對象語言中的一個對象就是它所屬類中的一個實例。語言經過類的規定,生成了具備內存實體的對象。在這樣的語言中,實例和對象的指代物是一致的,咱們一般在類設計模式中採用實例來描述一個內存實體,而在編程實踐中使用對象來描述一個內存實體,實際上是在不一樣層面上的語言轉換。理解這種詞語的轉換,對於咱們在閱讀各類技術書籍時瞭解做者所選擇的表述視角是有幫助的。

建立實例操做的結果是將類的屬性和方法分別複製到不一樣的實例對象中,它們持有各自獨立的版本,這也意味着每個由同一個類建立出的實例都是各自獨立互不影響的個體。

而在 JavaScript 中,事情就變得沒那麼簡單了。無論在它的設計者設計出模擬類模式的原生 API 以前仍是以後(固然官方一直有關於類的語法糖的支持),JavaScript 的世界實際上都是由且只由對象組成。當你建立了一個構造器函數或使用 ES6 的類定義語法時,其實質根本沒有真的定義了類,它是由對象假裝而成的。

在這一事實的基礎上,就能發現既然「類」也是對象,那麼咱們本覺得應用類模式創建的類與實例之間的純粹關係就被基於對象的模擬打破了。使用上面那個大天然的歸類例子再來解釋下這是什麼意思:當哺乳動物這一類別是一隻獅子時,它既是具體又是抽象的,做爲一個類這隻獅子囊括了全部的哺乳動物,它是凌駕於其餘具體生物之上的;做爲一個具體生物它又是被包含進它自己的...這彷佛變成了一個邏輯問題。

人類在採用類這一律念時就已經將這個概念進行了抽象,它不指代任何具體的個體,即使它是一份具備實體的藍圖,也是與遵循它創造出來的物品不相同的東西。而在 JavaScript 裏所發生的正是與之相矛盾的,它對於類模式的模擬實現實際上是對類模式的顛覆。

繼承:What's Inheritance

繼承是類範疇裏的重要概念,也是咱們之因此要使用類的重要理由。繼承的目的是爲了實現屬性或功能複用,順便減小編寫代碼的機械操做。類模式的繼承操做使子類擁有已經在父類裏定義的屬性或方法,繼承而來的屬性或方法是子類全部的獨立版本,子類能夠在此基礎上繼續修改已繼承的屬性或方法,而且擴展屬於本身的屬性或方法。

繼承便是基於現實中類別的多級抽象。前面圖示中所列出的樹狀結構就是對繼承很好的說明。在天然過程當中,咱們從祖先那裏繼承而來的基因是屬於複製而來的獨立版本,現實中固然不存在繼承而來的如出一轍的基因,但即使是如出一轍的基因序列,也是各自獨立的版本,你身體中的基因不再是祖先身體中的那個基因了。

尤爲強調獨立這個詞,是由於類模式如實地實現了對天然界這一複製過程的模擬,而在 JavaScript 這一基於原型模式設計的語言中,咱們又一次被它的表面類模式糊弄了。

在真正的類模式中,無論是父類仍是子類都是獨立封裝好的一份規格,若是一個子類沒有繼承到父類的某一屬性或方法它自身也沒有進行擴展時,它的實例是不可能使用這個屬性或方法的。很明顯 JavaScript 中的繼承「完美解決了這個問題」,即使一個「類」本身沒有繼承也沒有擴展某個屬性或方法,它創造出的實例還能夠從祖先那裏借用

結合實例一節所述,因而第二個問題呼之欲出:除了寫法類似以外,JavaScript 中幾乎全部與類相關的概念和行爲都同慣常的類模式不那麼相符,這真的能夠被稱爲是類模式的實現麼?

基於以上兩個問題對本身進行了靈魂拷問,終於決定要來仔細瞧瞧 JavaScript 中一直被當作類的影子的那個親骨肉——原型。

做爲一種機制的原型:"Prototype" Mechanism

在詞彙語義上,原型的概念就與類所區別:原型是一個最初的對象。類的邏輯在於將已存在事物劃分層次,達到歸納事物或分類的目的;原型的邏輯中沒有抽象的層級,它是根據已存在事物尋找能表明它最初的最本源的那一個,層層溯源,途徑的都是具象的。恐怕原型的概念對於熟稔哲學的人來講比類更爲親切。它在編程上的思想是:新的物體藉由複製原型產生

原型和類

JavaScript 的原型機制就遵循了必定程度原型哲學的思路。而原型機制是 JavaScript 所特有的。原型機制的實現是,對象有一個內部屬性指向另外一個對象,將兩者聯結起來的屬性的變量名就是咱們熟悉的 __proto__,它暴露了內部實現的原型,被指向的對象被稱爲前者的原型,一般用 obj.__proto__ 來指代 obj 這個對象的原型。除此以外別忘記,這只是那個真實的原型對象的別稱。例如 origin 是另外一個對象,如下這條語句就創建了這兩個對象的原型關聯關係:

let obj = {}
let origin = {}
obj.__proto__ = origin

你可使用 origin 引用它指向的那個對象,其實質是一個內存地址,也可使用 obj.__proto__ 來引用一樣的內存地址。做爲一個單獨個體的對象和一個做爲別的對象的原型的對象是合而爲一的。(實際開發中不要直接使用 __proto__ ,此處只是爲了簡便。應該用 Object.getPrototypeOf() 方法獲取原型對象)

原型機制用一句話歸納就是:將單個對象創建起原型關聯關係的過程。

原型:What's Prototype

原型的語義概念上面已經介紹了,如今專門講講 JavaScript 中的原型。在 JavaScript 中,一切都是對象,那麼這個世界總要有一個本源性的對象,就像上圖中的原核生物同樣,從它一輩子二而生成萬物。的確,這樣的一個被稱爲最初的原型的對象是存在的,它就是 Object.prototype,緣由是它再也沒法向上追溯到任何對象了:

Object.prototype.__proto__ === null

這裏咱們要知道 null 表明的是「沒有」的意思。所以 JavaScript 的世界是從 Object.prototype 開始的。使用過 JavaScript 的開發者一定對這個對象印象深入,但可能不少人歷來沒有從這個視角看待它。

從它衍生出的一個重要的對象是一個函數 Object,它被稱爲構造函數,儘管由 Object 構造函數建立出來的對象的原型都是指向 Object.prototype 的,但它本身的原型對象卻並非 Object.prototype,而是 Function.prototypeFunction.prototype 的原型才指向的是 Object.prototype,從這裏咱們能夠隱隱窺見原型繼承的精髓。

再次強調一下,Object 是一個名字叫作「對象」的函數,Object.prototype 是一個叫作「對象構造器原型」的對象,與其餘的原生構造器原型對象同樣,這些對象都是沒有本身獨立名稱的對象。在學習 JavaScript 時,必須好好區分這些基礎概念。

原型鏈:Prototype Chain

原型鏈是原型繼承得以實現的基礎,但其實在原型中使用「繼承」這個詞是不那麼準確的。原型鏈是內部機制經過私有的「原型」屬性實現對象之間的關聯而造成的一條鏈式屬性查找規則。它是單向度的,只能向上回溯,做爲原型的對象沒法查找它的繼承者們的任何屬性和方法。

原型鏈機制爲 JavaScript 提供了實現強大功能的基礎,但能夠想象,每次查找都是要花費額外開銷的,鏈條越長,開銷越大。它具備一個奇特的特色,即使某個對象上並未定義變量它也不會致使程序報錯,而是獲得 undefined,這正是原型鏈機制自動查找屬性的一個後果。在沒有必要的狀況下,應該避免編寫形成無謂的原型鏈查找的代碼。

咱們時常須要經過判斷一個對象的屬性存在與否實現一些分支判斷,如今假設一條原型鏈是這樣的,

obj5 -> obj4 -> obj3 -> obj2 -> obj1

它們都不具備一個叫作 prop 的屬性,接着實現了以下簡化了過程的判斷場景:

let condition = action()
...
if (condition)  obj5.prop = true
...
if (obj5.prop) { ... }

沒有任何問題的代碼對不對?固然,在條件爲true時一切都很完美,可是若是 conditionfalse 呢,最後那條判斷語句就要查找5次最後才能回到判斷,若是鏈條更長呢?

// 解決方案1:不須要中間變量時
obj5.prop = action()

// 解決方案2:須要中間變量時(可能二次改變)
obj5.prop = condition

// 固然還有更多變種...

或許有人以爲不太可能出現這樣的錯誤,但當代碼複雜到必定程度、中間過程很是繁瑣,工期很是緊迫時,一切都是有可能的,大問題都是由於那些小步驟中一個又一個的將就累積出來的。更況且做爲一個有追求的開發者,即使瀏覽器爲咱們的代碼實現了最大程度的性能優化,不該該多一些對自個人要求麼。

原型的做用:Why Prototype

既然類設計模式已經如此流行並深刻一代又一代開發者的腦海,那麼爲何還會有原型設計模式的立錐之地呢?毫無疑問是由於 JavaScript 的存在。做爲網頁開發腳本的 JavaScript 一直惟我獨尊地統御着這片疆域,至少目前開來尚未哪種新的腳本語言可以取代它的位置。但試想一下假若有一天一種以類模式設計而成的語言能夠完全取代它,原型機制將要消亡的那天大概就要來臨了,沒有哪種語言可以像 JavaScript 這樣可以完全地實踐原型機制了。

除了上面這個從語言層面來講的使用原型模式的前提,在 JavaScript 編程中使用原型模式而不是類模式實現業務功能也有一個讓人較爲信服的緣由。衆所周知使用類和原型的目的都是爲了實現繼承,或者從更本質上來講是功能複用。

而在 JavaScript 中選擇原型模式的理由就在《You Don't Know JS》這本書的章節中。做者敘述地那麼明瞭,也不須要作額外的解析了。在此我只引用兩張圖做爲最直觀的證據:

使用類模式實現繼承的邏輯圖

類繼承邏輯圖

使用原型模式實現繼承的邏輯圖

原型繼承邏輯圖

不少最爲有效的問題處理方式一般都是最簡潔的方式,那些須要經過製造一個問題而去解決另外一個問題的方法只會讓人頭腦暈眩,一般若是咱們不能三言兩語就點出問題的核心,只能反思本身可能對問題理解得不夠透徹。若是能用一個很是簡單有效的方法實現一樣的結果,我實在是找不出什麼緣由非要去採用一個更加複雜的方法。

如上鋪墊了一大堆概念,到底能從中得出什麼結論?——你爲何想在 JavaScript 的業務開發中使用類模式而不是原型模式?

原型模式做爲 JavaScript 原生的設計模式卻沒有獲得開發者足夠的理解,這與官方挖空心思強行模擬類模式的引導不無關係。

一位國外開發者 Eric Elliott 做了一個尖銳的比喻:

Using class inheritance in JavaScript is like driving your new Tesla Model S to the dealer and trading it in for a rusted out 1973 Ford Pinto.

翻譯:在 JavaScript 中使用類繼承就像把你嶄新的特斯拉Model S開到交易商那換了一輛生鏽的1973年的福特平託。

這種比喻何以見得恐怕經過上面那兩張圖的比較已經有了一個大體的理解,即使是不打算放棄類模式的開發方式,深刻理解這種爭議的原因更助於提升咱們的開發能力。咱們須要時不時停下來多問問幾個爲何。

模式之爭:The War of Pattern

一直以來在 JavaScript 中使用類繼承仍是原型繼承彷佛不是什麼值得爭論的事情。但目前愈來愈多的國外開發者開始意識到原型模式在 JavaScript 中的天然性與邏輯簡潔性。類模式與原型模式開始升級爲不一樣陣營實現功能複用的爭論點。

原型與類:Prototype vs. Class

若是我說在 JavaScript 中使用類模式實現繼承是不符合目前人類大腦思惟模式的複雜度的,我相信深刻理解其中原因的大多數人是會承認的,證據仍是上面那張圖,有多少人可以清晰地把上面的邏輯復演出來呢?恐怕大多數人都會在來來每每的直線曲線中迷失了方向,畢竟這樣的方式要求你不只要對類、子類和實例的關係把握精準,還要時刻銘記着它們暗中的原型關聯關係,對於初學者來講這種雙重性關係必定是會在將來學習的道路上橫梗多年的坎。因此才須要在此尤其強調類與原型的種種區別。

但若是隻是將注意力集中在對象之間的原型關聯關係上,事情就簡單多了。要清楚的是隻要 JavaScript 語言自己的實現不改變,對象的原型關聯關係是咱們沒法擺脫的。

不過原型與類的爭論已經屬於「舊時代」的爭論,在隨後開發者們對原型模式更加深刻的理解基礎上,造成了更深入的認識和結論,「現代爭論」再也不是原型與類的衝突,而是原型更新、更本質的行爲委託

原型與委託:Prototype vs. Delegation

前面有提到過在原型裏說「繼承」是不許確的,緣由是名副其實的類繼承的行爲本質上是複製,而 JavaScript 裏不管是用何種方式實現「繼承」,它的本質行爲都不是複製。

這裏要澄清一個可能的誤會,JavaScript 固然是支持複製的,然而成熟的開發者都知道複製與引用原型上的方法但是徹底不同的內存消耗,也正是因爲 JavaScript 的原型機制才得以經過不增長副本的方式實現「繼承」,因此就此排除了這種使用複製實現「繼承」的方式。

那麼在 JavaScript 裏「繼承」的本質又是什麼呢?許多開發者共同倡導了一種新的概念——委託。這種機制能夠這樣簡單地理解:所謂的「繼承」實際上是對象委託其原型們代勞辦事,繼承者藉助原型上的方法實現功能。這個新的說法確實是比較生動地描述了原型繼承機制的本質的。

之後或許開發者們會達成共識,把使用原型模式實現繼承的方式稱爲原型委託,如此更符合它的實際狀況。但究竟想使用哪一種模式進行開發最終仍是在於我的的選擇,官方對類模式的不懈支持固然沒法讓衆多開發者當即摒棄類語法糖,要從類轉換到純粹的原型上,是須要耗費思路轉換和習慣改變的成本的,但願對這個核心知識點的剖析可以使學習者們更好地理解 JavaScript 的本質語言特性,啓發來者們更多的深刻思考。

參考文獻:Reference

相關文章
相關標籤/搜索