摘要:首先,需要來理清一些基礎(chǔ)的計(jì)算機(jī)編程概念編程哲學(xué)與設(shè)計(jì)模式計(jì)算機(jī)編程理念源自于對(duì)現(xiàn)實(shí)抽象的哲學(xué)思考,面向?qū)ο缶幊淌瞧湟环N思維方式,與它并駕齊驅(qū)的是另外兩種思路過程式和函數(shù)式編程。
JavaScript 中的原型機(jī)制一直以來都被眾多開發(fā)者(包括本人)低估甚至忽視了,這是因?yàn)榻^大多數(shù)人沒有想要深刻理解這個(gè)機(jī)制的內(nèi)涵,以及越來越多的開發(fā)者缺乏計(jì)算機(jī)編程相關(guān)的基礎(chǔ)知識(shí)。對(duì)于這樣的開發(fā)者來說 JavaScript 的原型機(jī)制是一個(gè)尚待發(fā)掘的大寶藏,深入了解下去會(huì)讓大家在編程這條路上走得更長遠(yuǎn),當(dāng)然你不能妄想任何一種機(jī)制、模式或范式是完美無缺的。
首先,需要來理清一些基礎(chǔ)的計(jì)算機(jī)編程概念:
編程哲學(xué)與設(shè)計(jì)模式:Programming Philosophy and Design Pattern計(jì)算機(jī)編程理念源自于對(duì)現(xiàn)實(shí)抽象的哲學(xué)思考,面向?qū)ο缶幊蹋∣OP)是其一種思維方式,與它并駕齊驅(qū)的是另外兩種思路:過程式和函數(shù)式編程。這三種方式對(duì)應(yīng)于解決計(jì)算機(jī)架構(gòu)問題的三種不同思路。它們也分別代表了不同的編程哲學(xué)。
具體實(shí)現(xiàn)編程架構(gòu)的代碼方案可以稱為設(shè)計(jì)模式。設(shè)計(jì)模式是解決具體問題的一種最佳實(shí)踐,可以用在設(shè)計(jì)語言本身,也可以用在具體業(yè)務(wù)場(chǎng)景中。
三種思路在語言本身的設(shè)計(jì)和應(yīng)用業(yè)務(wù)中是可能混用的,靈活的語言正如 JavaScript ,內(nèi)部雖然是基于面向?qū)ο缶幊潭鴮?shí)現(xiàn),但在開發(fā)過程中也可以運(yùn)用過程式編程或函數(shù)式編程的思路進(jìn)行具體業(yè)務(wù)的設(shè)計(jì)。正因?yàn)檫@容易造成開發(fā)者的混亂,所以特別指出,下面一段討論的是針對(duì)語言內(nèi)部的實(shí)現(xiàn)方式而不是應(yīng)用業(yè)務(wù)。
面向?qū)ο缶幊陶Z言的核心是對(duì)象,針對(duì)如何設(shè)計(jì)出一套語言的對(duì)象模型編程大師們又提出了三種不同的模式:類、原型、元類(元類是基于類模型產(chǎn)生的新模型)。三種模型造就了許多不同的編程語言,JavaScript 恰好是原型模式的典型代表,正如 JAVA 是基于類模式的典范,請(qǐng)謹(jǐn)記這一語言本身在設(shè)計(jì)模式上的區(qū)別。
很多語言由于自身的實(shí)現(xiàn)而限制了在其中可能應(yīng)用到業(yè)務(wù)中的設(shè)計(jì)模式。但對(duì)于 JavaScript 這樣的語言來說,選擇是開放性的,因?yàn)槲覀兘?jīng)常在應(yīng)用業(yè)務(wù)上聽到大家討論類繼承或原型繼承這樣的實(shí)現(xiàn)方案,這便是它非常靈活的一個(gè)表現(xiàn)。但對(duì)于類模式和原型模式,有一些本質(zhì)上的概念區(qū)別和使用混淆是很多人沒有注意到的,下面對(duì)這兩種設(shè)計(jì)模式做一個(gè)詳細(xì)的討論。
作為一種設(shè)計(jì)模式的類:"Class" Design Pattern基于類的應(yīng)用或業(yè)務(wù)架構(gòu)實(shí)現(xiàn)可以稱為類設(shè)計(jì)模式,我們?cè)跇I(yè)務(wù)開發(fā)中不可避免地會(huì)使用到繼承的概念便是出自于類的范疇。類不專屬于 JavaScript 語言范疇,JavaScript 中實(shí)質(zhì)上也沒有實(shí)現(xiàn)真正的基于類設(shè)計(jì)模式的接口。JavaScript 中一切關(guān)于“類”的說法實(shí)際上都是一種有名無實(shí)的冒充和混淆。
我們通常以為在 JavaScript 中“類”是必選的,使用它來實(shí)現(xiàn)業(yè)務(wù)架構(gòu)不僅天經(jīng)地義而且是唯一的——這是對(duì) JavaScript 的最大誤解。JavaScript 雖然是面向?qū)ο蟮木幊陶Z言,但以類作為對(duì)象模型來實(shí)現(xiàn)業(yè)務(wù)需求的方式只能說是一種設(shè)計(jì)模式:面向?qū)ο蠼^不等同于類。
類是一份產(chǎn)品制造說明書,指導(dǎo)生產(chǎn)機(jī)器生產(chǎn)符合其定義參數(shù)、具有相應(yīng)功能的產(chǎn)品。它的用途在于規(guī)定而不在于實(shí)際使用,使用的是通過類制造出來的產(chǎn)品,在 JavaScript 中即對(duì)象。我們基于復(fù)用、繼承等工業(yè)化生產(chǎn)需求而使用類這套設(shè)計(jì)模式:規(guī)定 -> 制造 -> 使用。但我們千萬不能忘記,在工業(yè)化時(shí)代出現(xiàn)之前,通過手工的方式一樣可以制造產(chǎn)品,如果你需要批量生產(chǎn)模樣一樣的東西才需要這份產(chǎn)品制造說明說。就手段來說要澄清的一個(gè)誤區(qū)是,類并不是實(shí)現(xiàn)功能復(fù)用、廣義上的繼承等業(yè)務(wù)目標(biāo)的唯一模式。
類:What"s Class類,是面向?qū)ο缶幊讨幸环N通用對(duì)象模型,它是基于一種對(duì)現(xiàn)實(shí)中事物進(jìn)行分類的抽象,天生帶有類別層級(jí)的觀念,如生物是一級(jí)類、動(dòng)物是一個(gè)具有所有生物特性而派生出自己獨(dú)有特性的二級(jí)類,依照這樣的邏輯還可以繼續(xù)推及到其下更多細(xì)別的子類,這是一種將所有對(duì)象進(jìn)行樹狀類別組織關(guān)聯(lián)的思維方式:
通過這張圖可以得出一個(gè)顯而易見卻容易被忽視的事實(shí):永遠(yuǎn)沒有一只具體的哺乳動(dòng)物(比如說一只獅子)等同于哺乳動(dòng)物這個(gè)類別,就像你不等于人類一樣。類是一個(gè)并不具有實(shí)體的概念,是人為的發(fā)明,為了將具有類似特性的事物分門別類以適應(yīng)人腦簡化處理信息的方式,盡管自然并不是出于這樣的目的而生成各種事物的。
JavaScript 中類的概念也是人為的設(shè)計(jì),為的是更靠近本身以類模式設(shè)計(jì)而成的語言,盡管它本身是以原型模式設(shè)計(jì)而成的。因此我們有了 new 一個(gè)對(duì)象這種操作,為的是更符合采用類這一設(shè)計(jì)模式來實(shí)踐面向?qū)ο缶幊?。所以在此處埋下了第一個(gè)令人迷惑的種子:JavaScript 原生基于原型關(guān)聯(lián)起來的對(duì)象與基于類創(chuàng)建的與類關(guān)聯(lián)起來的對(duì)象兩種概念的混淆。對(duì)于發(fā)現(xiàn)了這一對(duì)使人迷惑的概念的開發(fā)者來說,便有了第一個(gè)疑問:
為什么基于原型模式設(shè)計(jì)而成的 JavaScript 不繼續(xù)在業(yè)務(wù)場(chǎng)景中使用原型設(shè)計(jì)模式,而是轉(zhuǎn)而求向類設(shè)計(jì)模式?
之前有過說明,實(shí)踐面向?qū)ο缶幊痰姆绞接腥N的,并且沒有任何一種是完美無缺的。所以請(qǐng)把類模式是最好的這種想法拋到九霄云外吧。暫且將這個(gè)問題移到潛意識(shí)中去,繼續(xù)了解一下類范疇的的其他相關(guān)概念。
實(shí)例:What"s Instance實(shí)例的概念基于類之上。正如自然界中單一的個(gè)體即是它所屬類別中的一個(gè)實(shí)例,面向?qū)ο笳Z言中的一個(gè)對(duì)象就是它所屬類中的一個(gè)實(shí)例。語言通過類的規(guī)定,生成了具有內(nèi)存實(shí)體的對(duì)象。在這樣的語言中,實(shí)例和對(duì)象的指代物是一致的,我們通常在類設(shè)計(jì)模式中采用實(shí)例來描述一個(gè)內(nèi)存實(shí)體,而在編程實(shí)踐中使用對(duì)象來描述一個(gè)內(nèi)存實(shí)體,其實(shí)是在不同層面上的語言轉(zhuǎn)換。理解這種詞語的轉(zhuǎn)換,對(duì)于我們?cè)陂喿x各種技術(shù)書籍時(shí)了解作者所選擇的表述視角是有幫助的。
創(chuàng)建實(shí)例操作的結(jié)果是將類的屬性和方法分別復(fù)制到不同的實(shí)例對(duì)象中,它們持有各自獨(dú)立的版本,這也意味著每一個(gè)由同一個(gè)類創(chuàng)建出的實(shí)例都是各自獨(dú)立互不影響的個(gè)體。
而在 JavaScript 中,事情就變得沒那么簡單了。不管在它的設(shè)計(jì)者設(shè)計(jì)出模擬類模式的原生 API 之前還是之后(當(dāng)然官方一直有關(guān)于類的語法糖的支持),JavaScript 的世界實(shí)際上都是由且只由對(duì)象組成。當(dāng)你創(chuàng)建了一個(gè)構(gòu)造器函數(shù)或使用 ES6 的類定義語法時(shí),其實(shí)質(zhì)根本沒有真的定義了類,它是由對(duì)象偽裝而成的。
在這一事實(shí)的基礎(chǔ)上,就能發(fā)現(xiàn)既然“類”也是對(duì)象,那么我們本以為應(yīng)用類模式建立的類與實(shí)例之間的純粹關(guān)系就被基于對(duì)象的模擬打破了。使用上面那個(gè)大自然的歸類例子再來解釋下這是什么意思:當(dāng)哺乳動(dòng)物這一類別是一只獅子時(shí),它既是具體又是抽象的,作為一個(gè)類這只獅子囊括了所有的哺乳動(dòng)物,它是凌駕于其他具體生物之上的;作為一個(gè)具體生物它又是被包含進(jìn)它本身的...這似乎變成了一個(gè)邏輯問題。
人類在采用類這一概念時(shí)就已經(jīng)將這個(gè)概念進(jìn)行了抽象,它不指代任何具體的個(gè)體,即便它是一份具有實(shí)體的藍(lán)圖,也是與遵循它創(chuàng)造出來的物品不相同的東西。而在 JavaScript 里所發(fā)生的正是與之相矛盾的,它對(duì)于類模式的模擬實(shí)現(xiàn)其實(shí)是對(duì)類模式的顛覆。
繼承:What"s Inheritance繼承是類范疇里的重要概念,也是我們之所以要使用類的重要理由。繼承的目的是為了實(shí)現(xiàn)屬性或功能復(fù)用,順便減少編寫代碼的機(jī)械操作。類模式的繼承操作使子類擁有已經(jīng)在父類里定義的屬性或方法,繼承而來的屬性或方法是子類所有的獨(dú)立版本,子類可以在此基礎(chǔ)上繼續(xù)修改已繼承的屬性或方法,并且擴(kuò)展屬于自己的屬性或方法。
繼承即是基于現(xiàn)實(shí)中類別的多級(jí)抽象。前面圖示中所列出的樹狀結(jié)構(gòu)就是對(duì)繼承很好的說明。在自然過程中,我們從祖先那里繼承而來的基因是屬于復(fù)制而來的獨(dú)立版本,現(xiàn)實(shí)中當(dāng)然不存在繼承而來的一模一樣的基因,但即便是一模一樣的基因序列,也是各自獨(dú)立的版本,你身體中的基因再也不是祖先身體中的那個(gè)基因了。
尤其強(qiáng)調(diào)獨(dú)立這個(gè)詞,是因?yàn)轭惸J饺鐚?shí)地實(shí)現(xiàn)了對(duì)自然界這一復(fù)制過程的模擬,而在 JavaScript 這一基于原型模式設(shè)計(jì)的語言中,我們又一次被它的表面類模式糊弄了。
在真正的類模式中,不管是父類還是子類都是獨(dú)立封裝好的一份規(guī)格,如果一個(gè)子類沒有繼承到父類的某一屬性或方法它自身也沒有進(jìn)行擴(kuò)展時(shí),它的實(shí)例是不可能使用這個(gè)屬性或方法的。很明顯 JavaScript 中的繼承“完美解決了這個(gè)問題”,即便一個(gè)“類”自己沒有繼承也沒有擴(kuò)展某個(gè)屬性或方法,它創(chuàng)造出的實(shí)例還可以從祖先那里借用。
結(jié)合實(shí)例一節(jié)所述,于是第二個(gè)問題呼之欲出:除了寫法相似之外,JavaScript 中幾乎所有與類相關(guān)的概念和行為都同慣常的類模式不那么相符,這真的可以被稱為是類模式的實(shí)現(xiàn)么?
基于以上兩個(gè)問題對(duì)自己進(jìn)行了靈魂拷問,終于決定要來仔細(xì)瞧瞧 JavaScript 中一直被當(dāng)做類的影子的那個(gè)親骨肉——原型。
作為一種機(jī)制的原型:"Prototype" Mechanism在詞匯語義上,原型的概念就與類所區(qū)別:原型是一個(gè)最初的對(duì)象。類的邏輯在于將已存在事物劃分層次,達(dá)到概括事物或分類的目的;原型的邏輯中沒有抽象的層級(jí),它是根據(jù)已存在事物尋找能代表它最初的最本源的那一個(gè),層層溯源,途徑的都是具象的??峙略偷母拍顚?duì)于熟稔哲學(xué)的人來說比類更為親切。它在編程上的思想是:新的物體藉由復(fù)制原型產(chǎn)生。
JavaScript 的原型機(jī)制就遵循了一定程度原型哲學(xué)的思路。而原型機(jī)制是 JavaScript 所特有的。原型機(jī)制的實(shí)現(xiàn)是,對(duì)象有一個(gè)內(nèi)部屬性指向另一個(gè)對(duì)象,將二者聯(lián)結(jié)起來的屬性的變量名就是我們熟悉的 __proto__,它暴露了內(nèi)部實(shí)現(xiàn)的原型,被指向的對(duì)象被稱為前者的原型,通常用 obj.__proto__ 來指代 obj 這個(gè)對(duì)象的原型。除此之外別忘記,這只是那個(gè)真實(shí)的原型對(duì)象的別稱。例如 origin 是另一個(gè)對(duì)象,以下這條語句就建立了這兩個(gè)對(duì)象的原型關(guān)聯(lián)關(guān)系:
let obj = {} let origin = {} obj.__proto__ = origin
你可以使用 origin 引用它指向的那個(gè)對(duì)象,其實(shí)質(zhì)是一個(gè)內(nèi)存地址,也可以使用 obj.__proto__ 來引用同樣的內(nèi)存地址。作為一個(gè)多帶帶個(gè)體的對(duì)象和一個(gè)作為別的對(duì)象的原型的對(duì)象是合而為一的。(實(shí)際開發(fā)中不要直接使用 __proto__ ,此處只是為了簡便。應(yīng)該用 Object.getPrototypeOf() 方法獲取原型對(duì)象)
原型機(jī)制用一句話概括就是:將單個(gè)對(duì)象建立起原型關(guān)聯(lián)關(guān)系的過程。
原型:What"s Prototype原型的語義概念上面已經(jīng)介紹了,現(xiàn)在專門講講 JavaScript 中的原型。在 JavaScript 中,一切都是對(duì)象,那么這個(gè)世界總要有一個(gè)本源性的對(duì)象,就像上圖中的原核生物一樣,從它一生二而生成萬物。的確,這樣的一個(gè)被稱為最初的原型的對(duì)象是存在的,它就是 Object.prototype,原因是它再也無法向上追溯到任何對(duì)象了:
Object.prototype.__proto__ === null
這里我們要知道 null 代表的是“沒有”的意思。因此 JavaScript 的世界是從 Object.prototype 開始的。使用過 JavaScript 的開發(fā)者必定對(duì)這個(gè)對(duì)象印象深刻,但可能很多人從來沒有從這個(gè)視角看待它。
從它衍生出的一個(gè)重要的對(duì)象是一個(gè)函數(shù) Object,它被稱為構(gòu)造函數(shù),盡管由 Object 構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象的原型都是指向 Object.prototype 的,但它自己的原型對(duì)象卻并不是 Object.prototype,而是 Function.prototype, Function.prototype 的原型才指向的是 Object.prototype,從這里我們可以隱隱窺見原型繼承的精髓。
再次強(qiáng)調(diào)一下,Object 是一個(gè)名字叫做“對(duì)象”的函數(shù),Object.prototype 是一個(gè)叫做“對(duì)象構(gòu)造器原型”的對(duì)象,與其他的原生構(gòu)造器原型對(duì)象一樣,這些對(duì)象都是沒有自己獨(dú)立名稱的對(duì)象。在學(xué)習(xí) JavaScript 時(shí),必須好好區(qū)分這些基礎(chǔ)概念。
原型鏈:Prototype Chain原型鏈?zhǔn)窃屠^承得以實(shí)現(xiàn)的基礎(chǔ),但其實(shí)在原型中使用“繼承”這個(gè)詞是不那么準(zhǔn)確的。原型鏈?zhǔn)莾?nèi)部機(jī)制通過私有的“原型”屬性實(shí)現(xiàn)對(duì)象之間的關(guān)聯(lián)而形成的一條鏈?zhǔn)綄傩圆檎乙?guī)則。它是單向度的,只能向上回溯,作為原型的對(duì)象無法查找它的繼承者們的任何屬性和方法。
原型鏈機(jī)制為 JavaScript 提供了實(shí)現(xiàn)強(qiáng)大功能的基礎(chǔ),但可以想象,每次查找都是要花費(fèi)額外開銷的,鏈條越長,開銷越大。它具有一個(gè)奇特的特點(diǎn),即便某個(gè)對(duì)象上并未定義變量它也不會(huì)導(dǎo)致程序報(bào)錯(cuò),而是得到 undefined,這正是原型鏈機(jī)制自動(dòng)查找屬性的一個(gè)后果。在沒有必要的情況下,應(yīng)該避免編寫造成無謂的原型鏈查找的代碼。
我們時(shí)常需要通過判斷一個(gè)對(duì)象的屬性存在與否實(shí)現(xiàn)一些分支判斷,現(xiàn)在假設(shè)一條原型鏈?zhǔn)沁@樣的,
obj5 -> obj4 -> obj3 -> obj2 -> obj1
它們都不具有一個(gè)叫做 prop 的屬性,接著實(shí)現(xiàn)了如下簡化了過程的判斷場(chǎng)景:
let condition = action() ... if (condition) obj5.prop = true ... if (obj5.prop) { ... }
沒有任何問題的代碼對(duì)不對(duì)?當(dāng)然,在條件為true時(shí)一切都很完美,但是如果 condition 為 false 呢,最后那條判斷語句就要查找5次最后才能回到判斷,如果鏈條更長呢?
// 解決方案1:不需要中間變量時(shí) obj5.prop = action() // 解決方案2:需要中間變量時(shí)(可能二次改變) obj5.prop = condition // 當(dāng)然還有更多變種...
或許有人覺得不太可能出現(xiàn)這樣的錯(cuò)誤,但當(dāng)代碼復(fù)雜到一定程度、中間過程非常繁瑣,工期非常緊迫時(shí),一切都是有可能的,大問題都是因?yàn)槟切┬〔襟E中一個(gè)又一個(gè)的將就累積出來的。更何況作為一個(gè)有追求的開發(fā)者,即便瀏覽器為我們的代碼實(shí)現(xiàn)了最大程度的性能優(yōu)化,不應(yīng)該多一些對(duì)自我的要求么。
原型的作用:Why Prototype既然類設(shè)計(jì)模式已經(jīng)如此流行并深入一代又一代開發(fā)者的腦海,那么為什么還會(huì)有原型設(shè)計(jì)模式的立足之地呢?毫無疑問是因?yàn)?JavaScript 的存在。作為網(wǎng)頁開發(fā)腳本的 JavaScript 一直唯我獨(dú)尊地統(tǒng)御著這片疆域,至少目前開來還沒有哪一種新的腳本語言能夠取代它的位置。但試想一下假如有一天一種以類模式設(shè)計(jì)而成的語言可以徹底取代它,原型機(jī)制將要消亡的那天大概就要來臨了,沒有哪一種語言能夠像 JavaScript 這樣能夠徹底地實(shí)踐原型機(jī)制了。
除了上面這個(gè)從語言層面來說的使用原型模式的前提,在 JavaScript 編程中使用原型模式而不是類模式實(shí)現(xiàn)業(yè)務(wù)功能也有一個(gè)讓人較為信服的原因。眾所周知使用類和原型的目的都是為了實(shí)現(xiàn)繼承,或者從更本質(zhì)上來說是功能復(fù)用。
而在 JavaScript 中選擇原型模式的理由就在《You Don"t Know JS》這本書的章節(jié)中。作者敘述地那么明了,也不需要做額外的解析了。在此我只引用兩張圖作為最直觀的證據(jù):
使用類模式實(shí)現(xiàn)繼承的邏輯圖 使用原型模式實(shí)現(xiàn)繼承的邏輯圖很多最為有效的問題處理方式通常都是最簡潔的方式,那些需要通過制造一個(gè)問題而去解決另一個(gè)問題的方法只會(huì)讓人頭腦暈眩,通常如果我們不能三言兩語就點(diǎn)出問題的核心,只能反思自己可能對(duì)問題理解得不夠透徹。如果能用一個(gè)非常簡單有效的方法實(shí)現(xiàn)同樣的結(jié)果,我實(shí)在是找不出什么原因非要去采用一個(gè)更加復(fù)雜的方法。
如上鋪墊了一大堆概念,到底能從中得出什么結(jié)論?——你為什么想在 JavaScript 的業(yè)務(wù)開發(fā)中使用類模式而不是原型模式?
原型模式作為 JavaScript 原生的設(shè)計(jì)模式卻沒有得到開發(fā)者足夠的理解,這與官方挖空心思強(qiáng)行模擬類模式的引導(dǎo)不無關(guān)系。
一位國外開發(fā)者 Eric Elliott 作了一個(gè)尖銳的比喻:
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年的福特平托。
這種比喻何以見得恐怕通過上面那兩張圖的比較已經(jīng)有了一個(gè)大致的理解,即便是不打算放棄類模式的開發(fā)方式,深入理解這種爭(zhēng)議的緣由更助于提高我們的開發(fā)能力。我們需要時(shí)不時(shí)停下來多問問幾個(gè)為什么。
模式之爭(zhēng):The War of Pattern一直以來在 JavaScript 中使用類繼承還是原型繼承似乎不是什么值得爭(zhēng)論的事情。但目前越來越多的國外開發(fā)者開始意識(shí)到原型模式在 JavaScript 中的自然性與邏輯簡潔性。類模式與原型模式開始升級(jí)為不同陣營實(shí)現(xiàn)功能復(fù)用的爭(zhēng)論點(diǎn)。
原型與類:Prototype vs. Class如果我說在 JavaScript 中使用類模式實(shí)現(xiàn)繼承是不符合目前人類大腦思維模式的復(fù)雜度的,我相信深入理解其中緣由的大多數(shù)人是會(huì)認(rèn)可的,證據(jù)還是上面那張圖,有多少人能夠清晰地把上面的邏輯復(fù)演出來呢?恐怕大多數(shù)人都會(huì)在來來往往的直線曲線中迷失了方向,畢竟這樣的方式要求你不僅要對(duì)類、子類和實(shí)例的關(guān)系把握精準(zhǔn),還要時(shí)刻銘記著它們暗中的原型關(guān)聯(lián)關(guān)系,對(duì)于初學(xué)者來說這種雙重性關(guān)系一定是會(huì)在未來學(xué)習(xí)的道路上橫梗多年的坎。所以才需要在此尤為強(qiáng)調(diào)類與原型的種種區(qū)別。
但如果只是將注意力集中在對(duì)象之間的原型關(guān)聯(lián)關(guān)系上,事情就簡單多了。要清楚的是只要 JavaScript 語言本身的實(shí)現(xiàn)不改變,對(duì)象的原型關(guān)聯(lián)關(guān)系是我們無法擺脫的。
不過原型與類的爭(zhēng)論已經(jīng)屬于“舊時(shí)代”的爭(zhēng)論,在隨后開發(fā)者們對(duì)原型模式更加深入的理解基礎(chǔ)上,形成了更深刻的認(rèn)識(shí)和結(jié)論,“現(xiàn)代爭(zhēng)論”不再是原型與類的沖突,而是原型更新、更本質(zhì)的行為委托。
原型與委托:Prototype vs. Delegation前面有提到過在原型里說“繼承”是不準(zhǔn)確的,原因是名副其實(shí)的類繼承的行為本質(zhì)上是復(fù)制,而 JavaScript 里無論是用何種方式實(shí)現(xiàn)“繼承”,它的本質(zhì)行為都不是復(fù)制。
這里要澄清一個(gè)可能的誤會(huì),JavaScript 當(dāng)然是支持復(fù)制的,然而成熟的開發(fā)者都知道復(fù)制與引用原型上的方法可是完全不一樣的內(nèi)存消耗,也正是由于 JavaScript 的原型機(jī)制才得以通過不增加副本的方式實(shí)現(xiàn)“繼承”,所以就此排除了這種使用復(fù)制實(shí)現(xiàn)“繼承”的方式。
那么在 JavaScript 里“繼承”的本質(zhì)又是什么呢?許多開發(fā)者共同倡導(dǎo)了一種新的概念——委托。這種機(jī)制可以這樣簡單地理解:所謂的“繼承”其實(shí)是對(duì)象委托其原型們代勞辦事,繼承者借助原型上的方法實(shí)現(xiàn)功能。這個(gè)新的說法確實(shí)是比較生動(dòng)地描述了原型繼承機(jī)制的本質(zhì)的。
以后或許開發(fā)者們會(huì)達(dá)成共識(shí),把使用原型模式實(shí)現(xiàn)繼承的方式稱為原型委托,如此更符合它的實(shí)際情況。但究竟想使用哪種模式進(jìn)行開發(fā)最終還是在于個(gè)人的選擇,官方對(duì)類模式的不懈支持當(dāng)然無法讓眾多開發(fā)者立即摒棄類語法糖,要從類轉(zhuǎn)換到純粹的原型上,是需要耗費(fèi)思路轉(zhuǎn)換和習(xí)慣改變的成本的,希望對(duì)這個(gè)核心知識(shí)點(diǎn)的剖析能夠使學(xué)習(xí)者們更好地理解 JavaScript 的本質(zhì)語言特性,啟發(fā)來者們更多的深入思考。
參考文獻(xiàn):Reference
You Don"t Know JS: this & object prototypes
Chapter 4: Mixing (Up) "Class" Objects
Chapter 5: Prototypes
Chapter 6: Behavior Delegation
MDN web docs: 繼承與原型鏈
The JavaScript language:Prototypal inheritance
JavaScript Inheritance and the Prototype Chain
Master the JavaScript Interview: What’s the Difference Between Class & Prototypal Inheritance?
Common Misconceptions About Inheritance in JavaScript
面向?qū)ο缶幊蹋∣bject Oriented Programming,OOP,面向?qū)ο蟪绦蛟O(shè)計(jì))
Understanding JavaScript: Prototype and Inheritance
JavaScript Prototypal Inheritance
The JavaScript Prototypal Inheritance Pattern
Prototypal Inheritance in JavaScript
面向過程,面向?qū)ο?,函?shù)式對(duì)同一個(gè)問題的思考方式
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/105442.html
摘要:作用域鏈的作用就是做標(biāo)示符解析。事件循環(huán)還有個(gè)明顯的特點(diǎn)單線程。早期都是用作開發(fā),單線程可以比較好當(dāng)規(guī)避同步問題,降低了開發(fā)門檻。單線程需要解決的是效率問題,里的解決思想是異步非阻塞。 0、前言 本人在大學(xué)時(shí)非常癡迷java,認(rèn)為java就是世界上最好的語言,偶爾在項(xiàng)目中會(huì)用到一些javascript,但基本沒放在眼里。較全面的接觸javascript是在實(shí)習(xí)的時(shí)候,通過這次的了解發(fā)現(xiàn)...
摘要:前言原型這個(gè)概念在這門語言中是一個(gè)核心關(guān)鍵的知識(shí)點(diǎn),但是你是否真的已經(jīng)完全理解透徹了呢可能我個(gè)人的理解能力較差,因此經(jīng)過多次翻閱書籍和實(shí)踐我才真正了解原型,所以記錄下來以加深理解,也以便日后深入探討。 前言 原型這個(gè)概念在JavaScript這門語言中是一個(gè)核心關(guān)鍵的知識(shí)點(diǎn),但是你是否真的已經(jīng)完全理解透徹了呢?可能我個(gè)人的理解能力較差,因此經(jīng)過多次翻閱書籍和實(shí)踐我才真正了解原型,所以記...
摘要:同理,原型鏈也是實(shí)現(xiàn)繼承的主要方式的只是語法糖。原型對(duì)象也可能擁有原型,并從中繼承方法和屬性,一層一層以此類推。利用構(gòu)造函數(shù)小明張三張三小明缺點(diǎn)每次實(shí)例化都需要復(fù)制一遍函數(shù)到實(shí)例里面。寄生構(gòu)造函數(shù)模式只有被類出來的才能用。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 最近又攀登了一下JS三座大山中的第二...
摘要:目錄導(dǎo)語理解對(duì)象和面向?qū)ο蟮某绦蛟O(shè)計(jì)創(chuàng)建對(duì)象的方式的繼承機(jī)制原型對(duì)象原型鏈與原型對(duì)象相關(guān)的方法小結(jié)導(dǎo)語前面的系列文章,基本把的核心知識(shí)點(diǎn)的基本語法標(biāo)準(zhǔn)庫等章節(jié)講解完本章開始進(jìn)入核心知識(shí)點(diǎn)的高級(jí)部分面向?qū)ο蟮某绦蛟O(shè)計(jì),這一部分的內(nèi)容將會(huì)對(duì)對(duì)象 目錄 導(dǎo)語 1.理解對(duì)象和面向?qū)ο蟮某绦蛟O(shè)計(jì) 2.創(chuàng)建對(duì)象的方式 3.JavaScript的繼承機(jī)制 3.1 原型對(duì)象 3.2 原型鏈 3.3 與...
摘要:可能有信息敏感的同學(xué)已經(jīng)了解到庫爆出嚴(yán)重安全漏洞,波及萬項(xiàng)目。以此為例,可見這次漏洞算是比較嚴(yán)重了。此外,凍結(jié)一個(gè)對(duì)象后該對(duì)象的原型也不能被修改。使用數(shù)據(jù)結(jié)構(gòu),不會(huì)存在原型污染狀況。 可能有信息敏感的同學(xué)已經(jīng)了解到:Lodash 庫爆出嚴(yán)重安全漏洞,波及 400萬+ 項(xiàng)目。這個(gè)漏洞使得 lodash 連夜發(fā)版以解決潛在問題,并強(qiáng)烈建議開發(fā)者升級(jí)版本。 我們?cè)诿χ礋狒[或者升級(jí)版本的同時(shí)...
閱讀 1068·2023-04-26 02:49
閱讀 1239·2021-11-25 09:43
閱讀 2625·2021-11-18 10:02
閱讀 2985·2021-10-18 13:32
閱讀 1334·2019-08-30 13:54
閱讀 2142·2019-08-30 12:58
閱讀 3074·2019-08-29 14:06
閱讀 2217·2019-08-28 18:10