javascript
JavaScript为什么使用原型模式而不是类模式
導言: 作為JavaScript初學者的本菜雞而言,剛一開始接觸這門語言我就被他的原型模式給嚇到了。并且在相當長的一段時間之內,我都完全不能理解或者不能接受這個模式。直到最近經過多方調查和思考才有所明悟。本篇文章就來記錄一下我對JavaScript為什么使用原型模式而不是用類模式這個問題的一點看法。
為什么是原型模式面向對象
是啊,為什么是原型模式呢?類模式那么的優雅,那么容易理解,當今世界扛把子級別的語言無論是c++,Java還是Python等,全部都是基于類的面向對象。基于類的面向對象就類似于鑄模和鑄件的關系。我們精心維護好這樣的鑄模,然后生成一堆的鑄件,這些鑄件就可以愉快的運行在各行各業,起到相應的功能。當鑄件出了一些問題之后沒有關系,我重新在用鑄模搞出一個鑄件就可以了。也有可能鑄模跟不上時代的發展了,也沒有關系,我重新調整一下鑄模的結構,這樣以后就能生產出緊跟行業最新需求的鑄件了。基于類的面向對象是多么的好啊,有了它我們就可以建設美麗新世界了!
甚至我一度以為,面向對象就是指的基于類的面向對象。直到我遇到了邪惡的JavaScript,一下子讓我三觀盡碎!
不過我還是存有一點僥幸心理,或許可能是那個JavaScript設計者設計這門語言時間過于倉促,沒有考慮到基于類的面向對象這種設計是多么優雅,直到我后來讀到下面這段內容:
在Brendan Eich為JavaScript設計面向對象系統時,借鑒了Self和Smalltalk這兩門基于原型的語言。之所以選擇基于原型的面向對象,不是因為時間匆忙,它設計起來相對簡單,而是因為從一開始Brendan Eich就沒有打算在JavaScript中加入類的概念。
蒼天啊!JavaScript居然沒有類的概念。到了ES6出了class關鍵字之后,也不過是基于原型的一種語法糖。可是你都沒有類了,為什么還要搞new關鍵字,簡直是脫褲子放屁。后來我又讀到一段話:
然而很不幸,因為一些公司政治原因,JavaScript 推出之時受管理層之命被要求模仿 Java,所以,JavaScript 創始人 Brendan Eich 在“原型運行時”的基礎上引入了 new、 this 等語言特性,使之“看起來更像 Java”。
好吧!不管怎么說,就是基于原型了。但是為什么要選原型呢?下面談談我的理解。
我想設計模式的差異可能是因為前端和后端面臨的情況不太一樣。對于在服務端運行的程序而言,更應該追求穩定性。而在客戶端運行的程序,經常要面臨和用戶交互、渲染等任務,動態性的成分更多一些。因此服務端的程序使用基于類的面向對象,走鑄模、鑄件路線。我們做修改基本只改模具,不要鑄件發揮自己的主觀能動性。比如你在寫Python或者Java程序的時候,你生成實例化對象之后,很少會要求對象變來變去吧。
而JavaScript希望對象盡可能發揮自己的主觀能動性,干脆直接把模具扔了。玩起了原型模式和原型繼承。沒有模具那該如何搞對象呢?這個我們后面會講到,首先來看一下JavaScript對象的主觀能動性吧。
let o = {a: 1 }o.b = 2console.log(o)因為JavaScript沒有類的束縛,所以對象可以盡情的添加屬性(在Java語言中,我們將類中的函數稱為方法,但是JavaScript只有屬性沒有方法這種叫法,函數也是一種屬性),對于上述代碼,寫成添加函數這樣的形式也沒有問題:
o.b = function() {console.log('haha') }你可以看看o的輸出結果。如果是用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())可以看一下輸出結果。這是Javascript基于原型的靈活性。此外,可能是為了進一步增加靈活性吧,JavaScript還增加了訪問器屬性。
let o = {get a() {return 1} }console.log(o.a)訪問器屬性跟數據屬性不同,每次訪問屬性都會執行 getter 或者 setter 函數。這樣我們每次訪問a屬性時都會有對應的輸出。(有沒有一種proxy的感覺?)
原型模式特點
正是基于原型的靈活性,所以JavaScript選擇了原型模式而沒有使用類對象模式。接下來我們來看一下原型模式的特點。
前面我們提到,基于類的面向對象可以比作為鑄模和鑄件的關系,我們更關注與模具而不是鑄件。我想了半天,沒有想到一個十分形象和恰當的比喻,有一個不太恰當的。可以將原型這種模式比作吸血鬼發展新的吸血鬼。為什么這樣比喻呢?上面我們舉例子也提到了,原型模式希望我們更加關注對象,而不是模具。那么每一個吸血鬼都有自己的充分主觀能動性。比如電影《暮光之城》中的吸血鬼,還可以轟轟烈烈的談戀愛,撒狗糧。但是對于工廠生產的鑄件而言,比較類似于僵尸,沒有辦法很好發揮對象的主觀能動性。(比如類似于僵尸沒有理智,而吸血鬼有比較強的理智)
如何創建一個新的對象呢?答案是通過對象克隆的方式。而不是通過類實例化對象的方式。(比如類似于每一個有充分主觀能動性的吸血鬼,咬人之后就會發展出一個新的吸血鬼),需要注意的是,我們不要被new關鍵字所迷惑了,以為是通過類實例化對象的方式來創建新的對象。比如下面代碼:
let obj1 = new Object() // 或者是寫成這樣 let obj2 = {}引擎內部都會從Object.prototype上面克隆出來一個對象,我們最終得到的就是這樣克隆出來的一個對象。還是舉吸血鬼的例子,就是讓Object這個吸血鬼,咬了一個人,然后這個人就變成了有Object屬性的吸血鬼了。這個新的吸血鬼可能也會去談戀愛,總之是具有很高的主觀能動性的。
關于new關鍵字我再展開說一下,當一個對象被new關鍵字修飾的時候,會首先調用這個對象的構造函數,也就是constructor。我們要是普通對象,是不是就不能克隆出對象呢?答案不是這樣,還可以使用Object.create(),每一個吸血鬼都可以發揮自身特點,克隆出一個新的吸血鬼。
let cat = {say() {console.log('miao-miao')} }let anotherCat = Object.create(cat)需要注意的是,你不能寫成這樣
let anotherCat = cat這句話的意思是把anotherCat指向cat的內存地址。而不是復制。
參考資料
[1] 《JavaScript設計模式與開發實戰》
總結
以上是生活随笔為你收集整理的JavaScript为什么使用原型模式而不是类模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从Proxy到Vue3数据绑定
- 下一篇: JavaScript装饰器模式