關于我們
書單推薦
新書推薦
|
JavaScript設計模式與開發(fā)實踐
《JavaScript設計模式與開發(fā)實踐》在尊重《設計模式》原意的同時,針對JavaScript語言特性全面介紹了更適合JavaScript程序員的了16個常用的設計模式,講解了JavaScript面向對象和函數式編程方面的基礎知識,介紹了面向對象的設計原則及其在設計模式中的體現,還分享了面向對象編程技巧和日常開發(fā)中的代碼重構!禞avaScript設計模式與開發(fā)實踐》將教會你如何把經典的設計模式應用到JavaScript語言中,編寫出優(yōu)美高效、結構化和可維護的代碼。
適讀人群 :適合初中級Web前端開發(fā)人員閱讀。
騰訊前端Alloy Team團隊出品,資深前端工程師曾探力作 全面涵蓋專門針對JavaScript的16個設計模式 深入剖析面向對象設計原則、編程技巧及代碼重構 設計模式是軟件設計中經過了大量實際項目驗證的可復用的優(yōu)秀解決方案,它有助于程序員寫出可復用和可維護性高的程序。許多優(yōu)秀的JavaScript開源框架都運用了不少設計模式,越來越多的程序員從設計模式中獲益,也許是改善了自己編寫的某個軟件,也許是更好地理解了面向對象的編程思想。無論如何,系統(tǒng)地學習設計模式都會令你受益匪淺。
《設計模式》一書自1995年成書一來,一直是程序員談論的“高端”話題之一。許多程序員從設計模式中學到了設計軟件的靈感,或者找到了問題的解決方案。在社區(qū)中,既有人對模式無比崇拜,也有人對模式充滿誤解。有些程序員把設計模式視為圣經,唯模式至上;有些人卻認為設計模式只在C++或者Java中有用武之地,JavaScript這種動態(tài)語言根本就沒有設計模式一說。
那么,在進入設計模式的學習之前,我們最好還是從模式的起源說起,分別聽聽這些不同的聲音。 設計模式并非是軟件開發(fā)的專業(yè)術語。實際上,“模式”最早誕生于建筑學。20世紀70年代,哈佛大學建筑學博士Christopher Alexander和他的研究團隊花了約20年的時間,研究了為解決同一個問題而設計出的不同建筑結構,從中發(fā)現了那些高質量設計中的相似性,并且用“模式”來指代這種相似性。 受Christopher Alexander工作的啟發(fā),Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides四人(人稱Gang Of Four ,GoF)把這種“模式”觀點應用于面向對象的軟件設計中,并且總結了23種常見的軟件開發(fā)設計模式,錄入《設計模式:可復用面向對象軟件的基礎》一書。 設計模式的定義是:在面向對象軟件設計過程中針對特定問題的簡潔而優(yōu)雅的解決方案。 通俗一點說,設計模式是在某種場合下對某個問題的一種解決方案。如果再通俗一點說,設計模式就是給面向對象軟件開發(fā)中的一些好的設計取個名字。 GoF成員之一 John Vlissides在他的另一本關于設計模式的著作《設計模式沉思錄》中寫過這樣一段話: 設想有一個電子愛好者,雖然他沒有經過正規(guī)的培訓,但是卻日積月累地設計并制造出許多有用的電子設備:業(yè)余無線電、蓋革計數器、報警器等。有一天這個愛好者決定重新回到學校去攻讀電子學學位,來讓自己的才能得到真實的認可。隨著課程的展開,這個愛好者突然發(fā)現課程內容都似曾相識。似曾相識的并不是術語或者表述的方式,而是背后的概念。這個愛好者不斷學到一些名稱和原理,雖然這些名稱和原理原來他不知道,但事實上他多年來一直都在使用。整個過程只不過是一個接一個的頓悟。 軟件開發(fā)中的設計也是如此。這些“好的設計”并不是GoF發(fā)明的,而是早已存在于軟件開發(fā)中。一個稍有經驗的程序員也許在不知不覺中數次使用過這些設計模式。GoF最大的功績是把這些“好的設計”從浩瀚的面向對象世界中挑選出來,并且給予它們一個好聽又好記的名字。 那么,給模式一個名字有什么意義呢?上述故事中的電子愛好者在未進入學校之前,一點都不知道這些關于電器的概念有一些特定的名稱,但這不妨礙他制造出一些電子設備。 實際上給“模式”取名的意義非常重要。人類可以走到生物鏈頂端的前兩個原因分別是會“使用名字”和“使用工具”。在軟件設計中,一個好的設計方案有了名字之后,才能被更好地傳播,人們才有更多的機會去分享和學習它們。 也許這個小故事可以說明名字對于模式的重要性:假設你是一名足球教練,正在球場邊指揮一場足球賽。通過一段時間的觀察后,發(fā)現對方的后衛(wèi)技術精湛,身體強壯,但邊后衛(wèi)速度較慢,中后衛(wèi)身高和頭球都非常一般。于是你在場邊大聲指揮隊員:“用速度突破對方邊后衛(wèi)之后,往球門方向踢出高球,中路接應隊員搶點頭球攻門! 在機會稍縱即逝的足球場上,教練這樣費盡口舌地指揮隊員比賽無疑是荒謬的。實際上這種戰(zhàn)術有一個名字叫作“下底傳中”。正因為戰(zhàn)術有了對應的名字,在球場上教練可以很方便地和球員交流!跋碌讉髦小边@種戰(zhàn)術即是足球場上的一種“模式”。 在軟件設計中亦是如此。我們都知道設計經驗非常重要。也許我們都有過這種感覺:這個問題發(fā)生的場景似曾相識,以前我遇到并解決過這個問題,但是我不知道怎么跟別人去描述它。我們非常希望給這個問題出現的場景和解決方案取一個統(tǒng)一的名字,當別人聽到這個名字的時候,便知道我想表達什么。比如一個JavaScript新手今天學會了編寫each函數,each函數用來迭代一個數組。他很難想到這個each函數其實就是迭代器模式。于是他向別人描述這個函數結構和意圖的時候會遇到困難,而一旦大家對迭代器模式這個名字達成了共識,剩下的交流便是自然而然的事情。 學習模式的作用 小說家很少從頭開始設計劇情,足球教練也很少從頭開始發(fā)明戰(zhàn)術,他們總是沿襲一些已經存在的模式。當足球教練看到對方邊后衛(wèi)速度慢,中后衛(wèi)身高矮時,自然會想到“下底傳中”這種模式。 同樣,在軟件設計中,模式是一些經過了大量實際項目驗證的優(yōu)秀解決方案。熟悉這些模式的程序員,對某些模式的理解也許形成了條件反射。當合適的場景出現時,他們可以很快地找到某種模式作為解決方案。 比如,當他們看到系統(tǒng)中存在一些大量的相似對象,這些對象給系統(tǒng)的內存帶來了較大的負擔。如果他們熟悉享元模式,那么第一時間就可以想到用享元模式來優(yōu)化這個系統(tǒng)。再比如,系統(tǒng)中某個接口的結構已經不能符合目前的需求,但他們又不想去改動這個被灰塵遮住的老接口,一個熟悉模式的程序員將很快地找到適配器模式來解決這個問題。 如果我們還沒有學習全部的模式,當遇到一個問題時,我們冥冥之中覺得這個問題出現的幾率很高,說不定別人也遇到過同樣的問題,并且已經把它整理成了模式,提供了一種通用的解決方案。這時候去翻翻《設計模式》這本書也許就會有意外的收獲。 模式在不同語言之間的區(qū)別 《設計模式》一書的副標題是“可復用面向對象軟件的基礎”。《設計模式》這本書完全是從面向對象設計的角度出發(fā)的,通過對封裝、繼承、多態(tài)、組合等技術的反復使用,提煉出一些可重復使用的面向對象設計技巧。所以有一種說法是設計模式僅僅是就面向對象的語言而言的。 《設計模式》最初講的確實是靜態(tài)類型語言中的設計模式,原書大部分代碼由C++寫成,但設計模式實際上是解決某些問題的一種思想,與具體使用的語言無關。模式社區(qū)和語言一直都在發(fā)展,如今,除了主流的面向對象語言,函數式語言的發(fā)展也非常迅猛。在函數式或者其他編程范型的語言中,設計模式依然存在。 人類飛上天空需要借助飛機等工具,而鳥兒天生就有翅膀。在Dota游戲里,牛頭人的人生目標是買一把跳刀(跳刀可以使用跳躍技能),而敵法師天生就有跳躍技能。因為語言的不同,一些設計模式在另外一些語言中的實現也許跟我們在《設計模式》一書中看到的大相徑庭,這一點也不令人意外。 Google的研究總監(jiān)Peter Norvig早在1996年一篇名為“動態(tài)語言設計模式”的演講中,就指出了GoF所提出的23種設計模式,其中有16種在Lisp語言中已經是天然的實現。比如,Command模式在Java中需要一個命令類,一個接收者類,一個調用者類。Command模式把運算塊封裝在命令對象的方法內,成為該對象的行為,并把命令對象四處傳遞。但在Lisp或者JavaScript這些把函數當作一等對象的語言中,函數便能封裝運算塊,并且函數可以被當成對象一樣四處傳遞,這樣一來,命令模式在Lisp或者JavaScript中就成為了一種隱形的模式。 在Java這種靜態(tài)編譯型語言中,無法動態(tài)地給已存在的對象添加職責,所以一般通過包裝類的方式來實現裝飾者模式。但在JavaScript這種動態(tài)解釋型語言中,給對象動態(tài)添加職責是再簡單不過的事情。這就造成了JavaScript語言的裝飾者模式不再關注于給對象動態(tài)添加職責,而是關注于給函數動態(tài)添加職責。 設計模式的適用性 設計模式被一些人認為只是夸夸其談的東西,這些人認為設計模式并沒有多大用途。畢竟我們用普通的方法就能解決的問題,使用設計模式可能會增加復雜度,或帶來一些額外的代碼。如果對一些設計模式使用不當,事情還可能變得更糟。 從某些角度來看,設計模式確實有可能帶來代碼量的增加,或許也會把系統(tǒng)的邏輯搞得更復雜。但軟件開發(fā)的成本并非全部在開發(fā)階段,設計模式的作用是讓人們寫出可復用和可維護性高的程序。假設有一個空房間,我們要日復一日地往里面放一些東西。最簡單的辦法當然是把這些東西直接扔進去,但是時間久了,就會發(fā)現很難從這個房子里找到自己想要的東西,要調整某幾樣東西的位置也不容易。所以在房間里做一些柜子也許是個更好的選擇,雖然柜子會增加我們的成本,但它可以在維護階段為我們帶來好處。使用這些柜子存放東西的規(guī)則,或許就是一種模式。 所有設計模式的實現都遵循一條原則,即“找出程序中變化的地方,并將變化封裝起來”。一個程序的設計總是可以分為可變的部分和不變的部分。當我們找出可變的部分,并且把這些部分封裝起來,那么剩下的就是不變和穩(wěn)定的部分。這些不變和穩(wěn)定的部分是非常容易復用的。這也是設計模式為什么描寫的是可復用面向對象軟件基礎的原因。 設計模式被人誤解的一個重要原因是人們對它的誤用和濫用,比如將一些模式用在了錯誤的場景中,或者說在不該使用模式的地方刻意使用模式。特別是初學者在剛學會使用一個模式時,恨不得把所有的代碼都用這個模式來實現。錘子理論在這里體現得很明顯:當我們有了一把錘子,看什么都是釘子。拿足球比賽的例子來說,我們的目標只是進球,“下底傳中”這種“模式”僅僅是達到進球目標的一種手段。當我們面臨密集防守時,下底傳中或許是一種好的選擇;但如果我們的球員獲得了一個直接面對對方守門員的單刀機會,那么是否還要把球先傳向邊路隊友,再由邊路隊友來一個邊路傳中呢?答案是顯而易見的,模式應該用在正確的地方。而哪些才算正確的地方,只有在我們深刻理解了模式的意圖之后,再結合項目的實際場景才會知道。 分辨模式的關鍵是意圖而不是結構 在設計模式的學習中,有人經常發(fā)出這樣的疑問:代理模式和裝飾者模式,策略模式和狀態(tài)模式,策略模式和智能命令模式,這些模式的類圖看起來幾乎一模一樣,它們到底有什么區(qū)別? 實際上這種情況是普遍存在的,許多模式的類圖看起來都差不多,模式只有放在具體的環(huán)境下才有意義。比如我們的手機,把它當電話的時候,它就是電話;把它當鬧鐘的時候,它就是鬧鐘;用它玩游戲的時候,它就是游戲機。我看到有人手中拿著iPhone18,但那實際上可能只是一個吹風機。有很多模式的類圖和結構確實很相似,但這不太重要,辨別模式的關鍵是這個模式出現的場景,以及為我們解決了什么問題。 對JavaScript設計模式的誤解 雖然JavaScript是一門完全面向對象的語言,但在很長一段時間內,JavaScript在人們的印象中只是用來驗證表單,或者完成一些簡單動畫特效的腳本語言。在JavaScript語言上運用設計模式難免顯得小題大做。但目前JavaScript已成為最流行的語言之一,在許多大型Web項目中,JavaScript代碼的數量已經非常多了。我們絕對有必要把一些優(yōu)秀的設計模式借鑒到JavaScript這門語言中。許多優(yōu)秀的JavaScript開源框架也運用了不少設計模式。 J avaScript設計模式的社區(qū)目前還幾乎是一片荒漠。網絡上有一些討論JavaScript設計模式的資料和文章,但這些資料和文章大多都存在兩個問題。 第一個問題是習慣把靜態(tài)類型語言的設計模式照搬到JavaScript中,比如有人為了模擬JavaScript版本的工廠方法(Factory Method)模式,而生硬地把創(chuàng)建對象的步驟延遲到子類中。實際上,在Java等靜態(tài)類型語言中,讓子類來“決定”創(chuàng)建何種對象的原因是為了讓程序迎合依賴倒置原則(DIP)。在這些語言中創(chuàng)建對象時,先解開對象類型之間的耦合關系非常重要,這樣才有機會在將來讓對象表現出多態(tài)性。 而在JavaScript這種類型模糊的語言中,對象多態(tài)性是天生的,一個變量既可以指向一個類,又可以隨時指向另外一個類。JavaScript不存在類型耦合的問題,自然也沒有必要刻意去把對象“延遲”到子類創(chuàng)建,也就是說,JavaScript實際上是不需要工廠方法模式的。模式的存在首先是能為我們解決什么問題,這種牽強的模擬只會讓人覺得設計模式既難懂又沒什么用處。 另一個問題是習慣根據模式的名字去臆測該模式的一切。比如命令模式本意是把請求封裝到對象中,利用命令模式可以解開請求發(fā)送者和請求接受者之間的耦合關系。但命令模式經常被人誤解為只是一個名為execute的普通方法調用。這個方法除了叫作execute之外,其實并沒有看出其他用處。所以許多人會誤會命令模式的意圖,以為它其實沒什么用處,從而聯想到其他設計模式也沒有用處。 這些誤解都影響了設計模式在JavaScript語言中的發(fā)展。 模式的發(fā)展 前面說過,模式的社區(qū)一直在發(fā)展。GoF在1995年提出了23種設計模式。但模式不僅僅局限于這23種。在近20年的時間里,也許有更多的模式已經被人發(fā)現并總結了出來。比如一些JavaScript圖書中會提到模塊模式、沙箱模式等。這些“模式”能否被世人公認并流傳下來,還有待時間驗證。不過某種解決方案要成為一種模式,還是有幾個原則要遵守的。這幾個原則即是“再現”“教學”和“能夠以一個名字來描述這種模式”。 不管怎樣,在一些模式被公認并流行起來之前,需要慎重地冠之以某種模式的名稱。否則模式也許很容易泛濫,導致人人都在發(fā)明模式,這反而增加了交流的難度。說不準哪天我們就能聽到有人說全局變量模式、加模式、減模式等。 在《設計模式》出版后的近20年里,也出現了另外一批講述設計模式的優(yōu)秀讀物。其中許多都獲得過Jolt大獎。數不清的程序員從設計模式中獲益,也許是改善了自己編寫的某個軟件,也許是從設計模式的學習中更好地理解了面向對象編程思想。無論如何,相信對我們這些大多數的普通程序員來說,系統(tǒng)地學習設計模式并沒有壞處,相反,你會在模式的學習過程中受益匪淺。
曾探,2007年畢業(yè)于吉林大學軟件學院。就職于國內知名前端團隊騰訊AlloyTeam,高級工程師。曾參與WebQQ、QQ群、Q+開發(fā)者網站、微云、QQ興趣部落等大型前端項目的開發(fā)。有過Java、Python和JavaScript的開發(fā)經驗,業(yè)余作品有HTML5版街頭霸王等。平時喜歡電影和音樂,業(yè)余時間也是一名健身教練。
第一部分 基礎知識
第1章 面向對象的JavaScript 1.1 動態(tài)類型語言和鴨子類型 1.2 多態(tài) 1.3 封裝 1.4 原型模式和基于原型繼承的JavaScript對象系統(tǒng) 第2章 this、call和apply 2.1 this 2.2 call和apply 第3章 閉包和高階函數 3.1 閉包 3.2 高階函數 3.3 小結 第二部分 設計模式 第4章 單例模式 4.1 實現單例模式 4.2 透明的單例模式 4.3 用代理實現單例模式 4.4 JavaScript中的單例模式 4.5 惰性單例 4.6 通用的惰性單例 4.7 小結 第5章 策略模式 5.1 使用策略模式計算獎金 5.2 JavaScript 版本的策略模式 5.3 多態(tài)在策略模式中的體現 5.4 使用策略模式實現緩動動畫 5.5 更廣義的"算法" 5.6 表單校驗 5.7 策略模式的優(yōu)缺點 5.8 一等函數對象與策略模式 5.9 小結 第6章 代理模式 6.1 第一個例子--小明追MM的故事 6.2 保護代理和虛擬代理 6.3 虛擬代理實現圖片預加載 6.4 代理的意義 6.5 代理和本體接口的一致性 6.6 虛擬代理合并HTTP 請求 6.7 虛擬代理在惰性加載中的應用 6.8 緩存代理 6.9 用高階函數動態(tài)創(chuàng)建代理 6.10 其他代理模式 6.11 小結 第7章 迭代器模式 7.1 jQuery 中的迭代器 7.2 實現自己的迭代器 7.3 內部迭代器和外部迭代器 7.4 迭代類數組對象和字面量對象 7.5 倒序迭代器 7.6 中止迭代器 7.7 迭代器模式的應用舉例 7.8 小結 第8章 發(fā)布-訂閱模式 8.1 現實中的發(fā)布-訂閱模式 8.2 發(fā)布-訂閱模式的作用 8.3 DOM 事件 8.4 自定義事件 8.5 發(fā)布-訂閱模式的通用實現 8.6 取消訂閱的事件 8.7 真實的例子--網站登錄 8.8 全局的發(fā)布-訂閱對象 8.9 模塊間通信 8.10 必須先訂閱再發(fā)布嗎 8.11 全局事件的命名沖突 8.12 JavaScript實現發(fā)布-訂閱模式的便利性 8.13 小結 第9章 命令模式 9.1 命令模式的用途 9.2 命令模式的例子--菜單程序 9.3 JavaScript中的命令模式 9.4 撤銷命令 9.5 撤消和重做 9.6 命令隊列 9.7 宏命令 9.8 智能命令與傻瓜命令 9.9 小結 第10章 組合模式 10.1 回顧宏命令 10.2 組合模式的用途 10.3 請求在樹中傳遞的過程 10.4 更強大的宏命令 10.5 抽象類在組合模式中的作用 10.6 透明性帶來的安全問題 10.7 組合模式的例子--掃描文件夾 10.8 一些值得注意的地方 10.9 引用父對象 10.10 何時使用組合模式 10.11 小結 第11章 模板方法模式 11.1 模板方法模式的定義和組成 11.2 第一個例子--Coffee or Tea 11.3 抽象類 11.4 模板方法模式的使用場景 11.5 鉤子方法 11.6 好萊塢原則 11.7 真的需要"繼承"嗎 11.8 小結 第12章 享元模式 12.1 初識享元模式 12.2 內部狀態(tài)與外部狀態(tài) 12.3 享元模式的通用結構 12.4 文件上傳的例子 12.5 享元模式的適用性 12.6 再談內部狀態(tài)和外部狀態(tài) 12.7 對象池 12.8 小結 第13章 職責鏈模式 13.1 現實中的職責鏈模式 13.2 實際開發(fā)中的職責鏈模式 13.3 用職責鏈模式重構代碼 13.4 靈活可拆分的職責鏈節(jié)點 13.5 異步的職責鏈 13.6 職責鏈模式的優(yōu)缺點 13.7 用AOP 實現職責鏈 13.8 用職責鏈模式獲取文件上傳對象 13.9 小結 第14章 中介者模式 14.1 現實中的中介者 14.2 中介者模式的例子--泡泡堂游戲 14.3 中介者模式的例子--購買商品 14.4 小結 第15章 裝飾者模式 15.1 模擬傳統(tǒng)面向對象語言的裝飾者模式 15.2 裝飾者也是包裝器 15.3 回到JavaScript 的裝飾者 15.4 裝飾函數 15.5 用AOP 裝飾函數 15.6 AOP 的應用實例 15.7 裝飾者模式和代理模式 15.8 小結 第16章 狀態(tài)模式 16.1 初識狀態(tài)模式 16.2 狀態(tài)模式的定義 16.3 狀態(tài)模式的通用結構 16.4 缺少抽象類的變通方式 16.5 另一個狀態(tài)模式示例--文件上傳 16.6 狀態(tài)模式的優(yōu)缺點 16.7 狀態(tài)模式中的性能優(yōu)化點 16.8 狀態(tài)模式和策略模式的關系 16.9 JavaScript版本的狀態(tài)機 16.10 表驅動的有限狀態(tài)機 16.11 實際項目中的其他狀態(tài)機 16.12 小結 第17章 適配器模式 17.1 現實中的適配器 17.2 適配器模式的應用 17.3 小結 第三部分 設計原則和編程技巧 第18章 單一職責原則 18.1 設計模式中的SRP原則 18.2 何時應該分離職責 18.3 違反SRP原則 18.4 SRP 原則的優(yōu)缺點 第19章 最少知識原則 19.1 減少對象之間的聯系 19.2 設計模式中的LKP原則 19.3 封裝在LKP 原則中的體現 第20章 開放-封閉原則 20.1 擴展window.onload函數 20.2 開放和封閉 20.3 用對象的多態(tài)性消除條件分支 20.4 找出變化的地方 20.5 設計模式中的開放-封閉原則 20.6 開放-封閉原則的相對性 20.7 接受第一次愚弄 第21章 接口和面向接口編程 21.1 回到Java的抽象類 21.2 interface 21.3 JavaScript 語言是否需要抽象類和interface 21.4 用鴨子類型進行接口檢查 21.5 用TypeScript 編寫基于interface的命令模式 第22章 代碼重構 22.1 提煉函數 22.2 合并重復的條件片段 22.3 把條件分支語句提煉成函數 22.4 合理使用循環(huán) 22.5 提前讓函數退出代替嵌套條件分支 22.6 傳遞對象參數代替過長的參數列表 22.7 盡量減少參數數量 22.8 少用三目運算符 22.9 合理使用鏈式調用 22.10 分解大型類 22.11 用return退出多重循環(huán) 參考文獻
你還可能感興趣
我要評論
|