javascript
JavaScript为什么使用原型模式而不是类模式
導(dǎo)言: 作為JavaScript初學(xué)者的本菜雞而言,剛一開始接觸這門語言我就被他的原型模式給嚇到了。并且在相當(dāng)長(zhǎng)的一段時(shí)間之內(nèi),我都完全不能理解或者不能接受這個(gè)模式。直到最近經(jīng)過多方調(diào)查和思考才有所明悟。本篇文章就來記錄一下我對(duì)JavaScript為什么使用原型模式而不是用類模式這個(gè)問題的一點(diǎn)看法。
為什么是原型模式面向?qū)ο?/h3>
是啊,為什么是原型模式呢?類模式那么的優(yōu)雅,那么容易理解,當(dāng)今世界扛把子級(jí)別的語言無論是c++,Java還是Python等,全部都是基于類的面向?qū)ο蟆;陬惖拿嫦驅(qū)ο缶皖愃朴阼T模和鑄件的關(guān)系。我們精心維護(hù)好這樣的鑄模,然后生成一堆的鑄件,這些鑄件就可以愉快的運(yùn)行在各行各業(yè),起到相應(yīng)的功能。當(dāng)鑄件出了一些問題之后沒有關(guān)系,我重新在用鑄模搞出一個(gè)鑄件就可以了。也有可能鑄模跟不上時(shí)代的發(fā)展了,也沒有關(guān)系,我重新調(diào)整一下鑄模的結(jié)構(gòu),這樣以后就能生產(chǎn)出緊跟行業(yè)最新需求的鑄件了。基于類的面向?qū)ο笫嵌嗝吹暮冒?#xff0c;有了它我們就可以建設(shè)美麗新世界了!
甚至我一度以為,面向?qū)ο缶褪侵傅幕陬惖拿嫦驅(qū)ο蟆V钡轿矣龅搅诵皭旱腏avaScript,一下子讓我三觀盡碎!
不過我還是存有一點(diǎn)僥幸心理,或許可能是那個(gè)JavaScript設(shè)計(jì)者設(shè)計(jì)這門語言時(shí)間過于倉促,沒有考慮到基于類的面向?qū)ο筮@種設(shè)計(jì)是多么優(yōu)雅,直到我后來讀到下面這段內(nèi)容:
在Brendan Eich為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)時(shí),借鑒了Self和Smalltalk這兩門基于原型的語言。之所以選擇基于原型的面向?qū)ο?#xff0c;不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_始Brendan Eich就沒有打算在JavaScript中加入類的概念。
蒼天啊!JavaScript居然沒有類的概念。到了ES6出了class關(guān)鍵字之后,也不過是基于原型的一種語法糖。可是你都沒有類了,為什么還要搞new關(guān)鍵字,簡(jiǎn)直是脫褲子放屁。后來我又讀到一段話:
然而很不幸,因?yàn)橐恍┕菊卧?#xff0c;JavaScript 推出之時(shí)受管理層之命被要求模仿 Java,所以,JavaScript 創(chuàng)始人 Brendan Eich 在“原型運(yùn)行時(shí)”的基礎(chǔ)上引入了 new、 this 等語言特性,使之“看起來更像 Java”。
好吧!不管怎么說,就是基于原型了。但是為什么要選原型呢?下面談?wù)勎业睦斫狻?/p>
我想設(shè)計(jì)模式的差異可能是因?yàn)榍岸撕秃蠖嗣媾R的情況不太一樣。對(duì)于在服務(wù)端運(yùn)行的程序而言,更應(yīng)該追求穩(wěn)定性。而在客戶端運(yùn)行的程序,經(jīng)常要面臨和用戶交互、渲染等任務(wù),動(dòng)態(tài)性的成分更多一些。因此服務(wù)端的程序使用基于類的面向?qū)ο?#xff0c;走鑄模、鑄件路線。我們做修改基本只改模具,不要鑄件發(fā)揮自己的主觀能動(dòng)性。比如你在寫Python或者Java程序的時(shí)候,你生成實(shí)例化對(duì)象之后,很少會(huì)要求對(duì)象變來變?nèi)グ伞?/p>
而JavaScript希望對(duì)象盡可能發(fā)揮自己的主觀能動(dòng)性,干脆直接把模具扔了。玩起了原型模式和原型繼承。沒有模具那該如何搞對(duì)象呢?這個(gè)我們后面會(huì)講到,首先來看一下JavaScript對(duì)象的主觀能動(dòng)性吧。
let o = {a: 1 }o.b = 2console.log(o)因?yàn)镴avaScript沒有類的束縛,所以對(duì)象可以盡情的添加屬性(在Java語言中,我們將類中的函數(shù)稱為方法,但是JavaScript只有屬性沒有方法這種叫法,函數(shù)也是一種屬性),對(duì)于上述代碼,寫成添加函數(shù)這樣的形式也沒有問題:
o.b = function() {console.log('haha') }你可以看看o的輸出結(jié)果。如果是用class約束一下呢?或者class真的約束的了嗎?
class Animal {constructor(name) {this.name = name}getName() {return this.name} }let dog = new Animal('dog') console.log(dog.getName())dog.bark = function() {console.log('wangwang') } console.log(dog.bark())可以看一下輸出結(jié)果。這是Javascript基于原型的靈活性。此外,可能是為了進(jìn)一步增加靈活性吧,JavaScript還增加了訪問器屬性。
let o = {get a() {return 1} }console.log(o.a)訪問器屬性跟數(shù)據(jù)屬性不同,每次訪問屬性都會(huì)執(zhí)行 getter 或者 setter 函數(shù)。這樣我們每次訪問a屬性時(shí)都會(huì)有對(duì)應(yīng)的輸出。(有沒有一種proxy的感覺?)
原型模式特點(diǎn)
正是基于原型的靈活性,所以JavaScript選擇了原型模式而沒有使用類對(duì)象模式。接下來我們來看一下原型模式的特點(diǎn)。
前面我們提到,基于類的面向?qū)ο罂梢员茸鳛殍T模和鑄件的關(guān)系,我們更關(guān)注與模具而不是鑄件。我想了半天,沒有想到一個(gè)十分形象和恰當(dāng)?shù)谋扔?#xff0c;有一個(gè)不太恰當(dāng)?shù)摹?梢詫⒃瓦@種模式比作吸血鬼發(fā)展新的吸血鬼。為什么這樣比喻呢?上面我們舉例子也提到了,原型模式希望我們更加關(guān)注對(duì)象,而不是模具。那么每一個(gè)吸血鬼都有自己的充分主觀能動(dòng)性。比如電影《暮光之城》中的吸血鬼,還可以轟轟烈烈的談戀愛,撒狗糧。但是對(duì)于工廠生產(chǎn)的鑄件而言,比較類似于僵尸,沒有辦法很好發(fā)揮對(duì)象的主觀能動(dòng)性。(比如類似于僵尸沒有理智,而吸血鬼有比較強(qiáng)的理智)
如何創(chuàng)建一個(gè)新的對(duì)象呢?答案是通過對(duì)象克隆的方式。而不是通過類實(shí)例化對(duì)象的方式。(比如類似于每一個(gè)有充分主觀能動(dòng)性的吸血鬼,咬人之后就會(huì)發(fā)展出一個(gè)新的吸血鬼),需要注意的是,我們不要被new關(guān)鍵字所迷惑了,以為是通過類實(shí)例化對(duì)象的方式來創(chuàng)建新的對(duì)象。比如下面代碼:
let obj1 = new Object() // 或者是寫成這樣 let obj2 = {}引擎內(nèi)部都會(huì)從Object.prototype上面克隆出來一個(gè)對(duì)象,我們最終得到的就是這樣克隆出來的一個(gè)對(duì)象。還是舉吸血鬼的例子,就是讓Object這個(gè)吸血鬼,咬了一個(gè)人,然后這個(gè)人就變成了有Object屬性的吸血鬼了。這個(gè)新的吸血鬼可能也會(huì)去談戀愛,總之是具有很高的主觀能動(dòng)性的。
關(guān)于new關(guān)鍵字我再展開說一下,當(dāng)一個(gè)對(duì)象被new關(guān)鍵字修飾的時(shí)候,會(huì)首先調(diào)用這個(gè)對(duì)象的構(gòu)造函數(shù),也就是constructor。我們要是普通對(duì)象,是不是就不能克隆出對(duì)象呢?答案不是這樣,還可以使用Object.create(),每一個(gè)吸血鬼都可以發(fā)揮自身特點(diǎn),克隆出一個(gè)新的吸血鬼。
let cat = {say() {console.log('miao-miao')} }let anotherCat = Object.create(cat)需要注意的是,你不能寫成這樣
let anotherCat = cat這句話的意思是把a(bǔ)notherCat指向cat的內(nèi)存地址。而不是復(fù)制。
參考資料
[1] 《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)戰(zhàn)》
總結(jié)
以上是生活随笔為你收集整理的JavaScript为什么使用原型模式而不是类模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从Proxy到Vue3数据绑定
- 下一篇: JavaScript装饰器模式