學習JavaScript設計模式是下發布知識共享署名-非商業性使用-禁止演繹3.0 unported許可證。它可用于購買通過O'Reilly的媒體,但仍然可為免費在線和物理(或電子書)購買為希望支持該項目的讀者。
學習JavaScript的設計模式書的封面
這個有幫助嗎? 我們希望你寫評論。
前言
設計模式是經常發生的軟件設計問題的可重用的解決方案。他們是既令人興奮又一個引人入勝的話題,在任何編程語言探索。
這其中的一個原因是,他們幫助我們建立在我們面前來了眾多開發商的經驗相結合,確保我們構建我們的代碼以優化的方式,滿足我們正在試圖解決問題的需要。
設計模式也為我們提供了一個共同的詞匯來形容的解決方案。這可以比描述的語法和語義,當我們試圖傳達構建在代碼形式對他人的溶液的方式顯著簡單。
在這本書中,我們將探討同時應用古典與現代的設計模式,以JavaScript編程語言。
目標聽眾
這本書是針對希望提高自己的設計模式的知識,以及他們如何可以應用到JavaScript編程語言的專業開發人員。
一些涉及的概念(閉包,原型繼承)將承擔基本的先驗知識和理解的程度。如果你發現自己需要進一步了解這些主題,提供了方便建議標題的列表。
如果您想了解如何編寫漂亮,結構和組織代碼,我相信這是你的書。
致謝
我將永遠為有才華的技術評論家誰幫助審查和提高這本書,包括來自社會大眾表示感謝。他們帶來了該項目的知識和熱情是簡直太神奇了。官方的技術評審微博和博客也是雙方想法和靈感常規來源,我衷心建議檢查出來。
尼古拉斯Zakas(http://nczonline.net,@slicknet)
漢森的Andrée(http://andreehansson.se,@peolanha)
盧克·史密斯(http://lucassmith.name,@ls_n)
埃里克Ferraiuolo(http://ericf.me/,@ericf)
彼得·米肖(http://michaux.ca,@petermichaux)
亞歷克斯·塞克斯頓(http://alexsexton.com,@slexaxton)
我還要感謝麗貝卡·墨菲(http://rmurphey.com,@rmurphey)提供靈感來寫這本書,更重要的是,繼續使它既可以在GitHub上,并通過奧賴利。
最后,我要感謝我出色的妻子埃莉,為所有她的支持,而我是這個刊物放在一起。
積分
雖然有些本書介紹的模式根據個人經驗的順利實施,其中許多人先前已經通過JavaScript社區標識。這個工作是這樣生產的一些開發商的綜合經驗。類似斯托揚斯特凡的邏輯的辦法來防止敘事中斷與學分(在JavaScript的模式),我所列舉的學分,并建議閱讀覆蓋在參考部分的任何內容。
如果有任何文章或鏈接已在引用列表中遺漏了,請接受我誠摯的歉意。如果你與我聯系,我一定會對其進行更新,包括你在名單上。
讀
雖然這本書是針對初學者和中級開發人員,則假定JavaScript的基本面有一個基本了解。如果您想了解更多關于語言,我很樂意為您推薦以下商品:
JavaScript的:權威指南由大衛·弗拉納根
雄辯的JavaScript由Marijn Haverbeke
JavaScript的模式由斯托揚斯特凡
編寫可維護的JavaScript由Nicholas Zakas
JavaScript的:好零件由Douglas Crockford的
目錄
介紹
什么是模式?
“模式”-ity測試,始祖模式與三國規則
設計模式的結構
寫作設計模式
反模式
分類設計模式
匯總表設計模式分類的
JavaScript的設計模式
構造模式
模塊模式
揭示模塊模式
Singleton模式
觀察者模式
調解模式
原型模式
命令模式
門面模式
工廠模式
混入模式
裝飾圖案
享元模式
JavaScript的MV *模式
MVC模式
MVP模式
MVVM模式
現代模塊化的JavaScript設計模式
AMD
CommonJS的
ES和諧
設計模式的jQuery
組合模式
適配器模式
門面模式
觀察者模式
迭代器模式
延遲初始化模式
代理模式
Builder模式
jQuery插件設計模式
JavaScript的命名空間模式
結論
參考
簡介
一個編寫維護的代碼的最重要的方面是能夠注意到在該代碼的重復出現的主題,并對其進行優化。這是一個領域的設計模式的知識可以證明非常寶貴的。
在這本書的第一部分,我們將探討這真的可以適用于任何編程語言設計模式的歷史和重要性。如果你已經賣了或熟悉這段歷史,隨意跳到章“ 什么是模式? ” 繼續讀書。
設計模式可以追溯到名為建筑師的早期作品克里斯托弗·亞歷山大。他會經常寫出版了他在解決設計問題的經驗,以及它們如何與建筑和城鎮。有一天,它發生在亞歷山大的再次使用時間和時間,一定的設計結構導致所需的最佳的效果。
在與Sara石川和穆雷西爾弗斯坦合作,亞歷山大生產的模式語言,這將有助于賦予希望設計和建造在任何規模的任何人。這是在題為“模式語言”一文,后來發布了作為一個完整的精裝發布回在1977年的書。
大約30年前,軟件工程師的開始將亞歷山大寫了關于進入設計模式,這是新手開發商希望提高自己的編程技巧指導的第一個文件的原則。需要注意的是背后的設計模式的概念其實一直圍繞在編程行業自成立以來,盡管在一個不太正式的形式是非常重要的。
一個軟件工程的設計模式出版了第一本,可以說是最具代表性的作品正式的一本書于1995年被稱為設計模式:元素可復用面向對象軟件的。這是寫的埃里希·伽馬,理查德頭盔,拉爾夫·約翰遜和約翰Vlissides -這被稱為四人幫(或GoF的簡稱)一組。
GoF的的出版被認為是相當的工具來進一步推設計模式的概念,在我們的領域,因為它描述了一些開發技術和陷阱,以及提供23核心的面向對象的設計模式,當今世界各地的頻繁使用。我們將在部分“設計模式的分類”更詳細地覆蓋這些圖案。
在這本書中,我們將看看一些流行的JavaScript的設計模式,并探討為什么某些模式可能更適合你的項目比其他人。請記住,模式可以應用于不只是香草的JavaScript(即標準JavaScript代碼),而且還抽象庫,如jQuery的或道場為好。在我們開始之前,讓我們來看看在軟件設計中的“模式”的確切定義。
什么是模式?
一種模式是可以適用于經常發生在軟件設計問題的可重復使用的解決方案 - 在我們的案例 - 在編寫JavaScript的Web應用程序。看著模式的另一種方式是為我們如何解決問題的模板 - 那些可以在很多不同的情況下使用。
那么,為什么是重要的理解模式和熟悉他們?設計模式有三個主要優勢:
模式是行之有效的解決方案:他們使用反映的經驗和見解,幫助定義它們帶到模式開發成熟的技術提供軟件開發固態方法來解決問題。
模式可以方便地重復使用:一個模式通常反映了一個開箱即用的解決方案,可以調整,以適應自己的需求。這一特性使得它們相當強勁。
模式可以表現:當我們看一個模式通常有一組結構和詞匯所提出的解決方案,可以幫助快遞相當大的解決方案,非常典雅。
模式是不是一個確切的解決方案。大家記住一個模式的作用僅僅是為我們提供一個解決方案,方案是非常重要的。模式不能解決所有的設計問題,也不會取代優秀的軟件設計師,然而,他們不支持他們。接下來,我們來看看其他一些優勢模式所提供的。
重用模式有助于防止可能會導致在應用程序開發過程中的重大問題,小問題。這意味著,當代碼是建立在成熟的模式,我們能負擔得起花費更少的時間擔心我們的代碼和更多的時間專注于結構我們的整體解決方案的質量。這是因為模式可以鼓勵我們以更結構化和有組織的方式避免了需要重構其在未來清潔目的的代碼。
圖案可提供廣義解這是在一個方式記載,并不要求它們被連接到一個特定的問題。這廣義方法意味著不管應用程序(以及在許多情況下,程序設計語言),我們用,設計模式工作可應用于提高我們的代碼的結構。
某些模式可以通過避免重復實際上減少了代碼的整體文件大小的足跡。通過鼓勵開發人員在他們的領域的解決方案更密切地關注其中即時減少重復可以制成,例如減少的功能進行支持類似的進程數單個廣義的功能,我們的代碼庫的整體尺寸可以減小。這也被稱為使代碼更干。
模式添加到開發人員的詞匯,這使得通信速度更快。
頻繁被通過利用使用這些模式的集體的經驗的其他開發人員使用可以隨時間改進型態有助于回到設計圖案的社區。在一些情況下,這導致創建的全新的設計模式,而在其他情況可以導致提供關于具體怎么圖案可以用最好的改進的準則。這可以確保基于模式的解決方案,繼續變得更加健壯比特設解決方案可能。
我們已經日常使用模式
要了解有用的模式怎么可以,讓我們回顧了jQuery庫解決了我們一個非常簡單的元素選擇問題。
試想一下,我們在那里有一個頁面上發現類“foo”的每個DOM元素,我們希望遞增計數器的腳本。什么是查詢該集合元素的最有效方法是什么?嗯,有幾個不同的方法解決此問題可以解決:
選擇中的所有頁面的元素,然后存儲到它們的引用。接下來,該過濾器收集和使用正則表達式(或其他方式)只存儲那些與類“富”。
使用現代的原生瀏覽器的功能,如querySelectorAll()選擇所有與類“foo”的元素。
使用本機的功能,如getElementsByClassName()以同樣找回所需的集合。
那么,這些選項中是最快的?它實際上是由8-10倍的一個因素選項3. 替代品。然而,在現實世界的應用程序,將3不會在Internet Explorer 9以下版本的工作,因此使用方法1.其中兩個2和3不支持的必要。
使用jQuery的開發人員不必擔心這個問題但是,由于它使用的幸運抽象出來給我們正面的圖案。正如我們將在后面詳細審查,這種模式提供了一套簡單的抽象接口(如$el.css(),$el.animate())的代碼幾個比較復雜的底層機構。正如我們所看到的,這意味著不必關心執行層面的細節較少的時間。
在幕后,圖書館只需選擇采用,取決于什么我們當前的瀏覽器支持的元素,我們只是消耗抽象層選擇最優化的方法。
我們很可能是全部也熟悉jQuery的$("selector")。這是顯著更易于使用的頁面上選擇的HTML元素與具有手動選擇getElementById(),getElementsByClassName(),getElementByTagName()等。
雖然我們知道,querySelectorAll()試圖解決這個問題,比較了使用jQuery的門面接口與選擇最優化的路徑選擇自己的努力。有沒有比賽!使用模式的抽象可以提供真實世界的價值。
我們將在這個多的設計模式后來在書中尋找。
“模式”-ity測試,始祖模式與三國規則
請記住,不是每一個算法,最佳實踐或解決方案代表了什么可能被視為一個完整的圖案。這里可能有幾個關鍵的成分,缺少和花紋社會普遍警惕一些自稱為一個,除非它已經嚴重審核。因此即使被呈現給我們哪些顯示滿足標準的模式,它不應該被視為一個,直到它已被他人接受審查和檢驗的適當的周期。
回首工作由Alexander一次,他聲稱模式都應該是一個過程,一個“東西”。這個定義,因為他遵循說,這是應建立“一事一議”的過程是有意鈍。這是有原因的圖案一般集中于尋址視覺識別結構即,我們應當能夠目視描繪(或繪制),表示放置圖案付諸實踐的結果中的結構的圖像。
在學習設計模式,它不是不規則碰到術語“原型模式”。這是什么?好,這尚未已知要通過“模式”-ity測試圖案通常被稱為原圖案。原模式可能導致已建立一個特定的解決方案,是值得與社區分享的人的工作,但可能尚未有已審核大量的機會,因為它非常年輕的年齡。
另外,共享模式的個人(S)可能沒有時間或通過“模式”-ity過程持續的興趣,并可能釋放他們的原型模式的一個簡短描述來代替。簡要描述或這種類型的圖案的片段被稱為patlets。
參與記錄完全合格的模式工作可以說是相當艱巨的。在一些設計模式的領域最早的工作回眸,一個模式可以被認為是“好”,如果它執行以下操作:
解決一個具體問題:模式不應該只是捕捉原則或策略。他們需要捕捉解決方案。這是一個良好的圖案最重要的成分之一。
這個問題的解決方案不能明顯:我們可以發現,解決問題的技術通常試圖從公知的第一原理得出。最好的設計模式通常的問題提供解決方案,間接地-這被認為是一個必要的方式來設計相關的最具挑戰性的問題。
所描述的概念必須已經證明:設計模式需要證明,他們所描述的,沒有這方面的證據設計不能被認真考慮的功能。如果一個模式在本質上是高度投機性,只有勇敢可以嘗試使用它。
它必須說明的關系:在某些情況下,可能出現的一個模式描述一種類型的模塊。盡管實現可能會出現這樣的格局的正式說明必須描述更深的系統結構和機制,解釋其代碼的關系。
我們會誤以為這不符合準則原型模式是不值得從然而,學習,這是與事實不符。許多原模式實際上是相當不錯的。我不是說所有的原模式是值得看的,但也有在野外不少有用的,可以幫助我們與未來的項目。使用最好的判斷與考慮到上述列表中,您會在您的選擇過程罰款。
一對才有效模式的附加 ??要求是,它們顯示一些經常性的現象。這往往是東西,可以至少在三個關鍵領域,被稱為合格三個規則。要使用這個規則表明復發,必須證明:
目的健身 -如何圖案認為是成功的?
實用性 -為什么格局認為是成功的?
適用性 -是設計不愧是一個模式,因為它具有更廣泛的適用性?如果是這樣,這需要進行說明。在審查或定義一個模式,它保持上述的一點是重要的。
設計模式的結構
你可能會好奇如何的模式作者可能接近新格局的概括結構,實施和目的。圖案最初呈現的形式規則的規定之間的關系:
一個背景
的系統力量的產生在這方面和
一個配置,使這些力量來解決自己的上下文
考慮到這一點,現在讓我們來看看一個設計模式構成要素的總結。設計模式應該有:
圖案名稱和描述
語境的輪廓 -在這種模式有效地應對用戶需求的上下文。
問題陳述 -問題的聲明得到解決,所以我們可以理解模式的意圖。
解決方案 -如何在用戶的問題在步驟和看法可以理解的名單正在解決的描述。
設計 -在特定的圖案的設計和說明,用戶在與它相互作用的行為
實施 -指導,以模式將如何實施
插圖 -類模式中的可視化表示形式(如示意圖)
例子 -該模式中一個最小的形式實現
聯合必要條件 -可能需要什么其他的模式被描述為支持使用模式?
關系 -這是否類似的模式是什么模式?它緊密地模仿任何其他方面?
已知的使用 -在正在使用的模式野性?如果是這樣,在哪里以及如何?
討論 -團隊或作者的想法上的圖案的令人激動的好處
設計模式是一個相當強大的方法來創建或維護解決方案時,讓所有的開發人員在一個組織或團隊在同一頁上。如果考慮到你自己的圖案時,請記住,雖然他們可能在規劃一個沉重的初始成本和寫了階段,從投資的返回值可以是相當值得的。總是在新的模式工作然而,你可能會發現它更有利于對現有成熟的模式重新相比起上面使用或創建之前摸底調研。
寫作設計模式
這本書雖然是針對這些新的設計模式,一個設計模式是怎么寫的一個基本的了解可以提供一些有用的好處。首先,我們可以為為什么需要一個模式背后的原因有更深刻的贊賞。我們還可以學習如何判斷審查其用于我們自己的需要,當一個模式(或原圖案)是達到標準。
編寫好的模式是一項艱巨的任務。模式不僅需要(理想)提供面向最終用戶的參考材料的數量可觀,但他們還需要能夠保衛他們為什么是必要的。
看了上一節什么的模式是,我們可能會認為,這本身就足以幫助我們確定我們在野外看到的模式。這其實并不完全正確。如果一段代碼,我們正在尋找以下是一組的模式,或只是偶然碰巧出現像它它并不總是很清楚。
當我們正在尋找的代碼體,我們認為可能會使用一個模式,我們應該考慮寫下一些代碼方面,我們相信一個特定的現有格局下跌倒或設置模式。
在模式分析的很多情況下,我們可以發現,我們只是在看后面可能發生的與意外模式的規則重疊好的原則和設計實踐的代碼。記住-在沒有交流,也沒有定義的規則出現的解決方案是不是模式。
如果有意冒險下來寫你自己的設計模式,我建議從其他人誰已經通過的過程中學習的路徑,并把事情做好。花時間吸收來自許多不同的設計模式描述的信息,并采取什么實際意義。
探索結構和語義 - 這可以通過檢查交互和你有興趣,所以你可以找出協助在有用的配置組織這些模式共同原則的模式方面進行。
一旦我們暴露自己,豐富的圖案上的文獻信息,我們不妨使用開始編寫我們的模式存在的格式,并看看我們是否能夠改善它,或者在那里整合我們的想法腦力激蕩的新思路。
開發人員認為這樣做是近年來的一個例子是基督徒海爾曼,誰把現有的模塊模式并對其作出了一些根本有用的變化創造了顯露的模塊模式(這就是后來在這本書涵蓋的模式之一)。
以下是提示如果有意創建一個新的設計模式,我建議:
如何實際是模式?:確保模式描述了行之有效的解決方案,以重復出現的問題,而不是它沒有資格只是投機性的解決方案。
保持最佳做法:設計決策,我們做應該基于我們從最佳實踐的理解推導而來的原則。
我們的設計模式應該是透明的,用戶:設計模式應該是完全透明的任何類型的用戶體驗。它們主要有利用他們所服務的開發者,并且不應迫使在于不會沒有使用的圖案的招致用戶體驗改變行為。
請記住,原創是不是在設計模式鍵入:當編寫一個模式,我們并不需要成為解決方案的最初發現者被記錄也不必擔心我們的設計與其他模式的次要部分重疊。如果方法是強大到足以具有廣闊的應用非常有用,它被認為是一個有效的模式的機會。
模式需要一個強大的一套例子:一個好的模式描述需要跟著一個同樣強大的一套例子,表明我們的模式的成功應用。要顯示的廣泛使用,表現出良好的設計原則的例子是理想的。
模式寫作是創造一個設計是通用的,具體的和高于一切的,有用的一個謹慎的平衡。盡量保證如果寫入的模式,你支付應用最廣泛的領域,你應該罰款。我希望這個簡短的介紹寫作模式給了你一些見解,這將有助于你的學習過程,這本書的下一個部分。
反模式
如果我們認為這是一個模式代表一種最佳實踐,反模式表示已吸取了教訓。術語反模式是在1995年的十一月C ++報告當年創造的安德魯·柯尼希,由GoF的書啟發設計模式。在柯尼希的報告中,也有被提出反模式的兩個概念。反模式:
描述一個壞的解決方案,這就造成了惡劣的情況下發生的特殊問題
描述如何走出所述情形以及如何從那里到一個很好的解決方案
關于這個話題,亞歷山大寫到取得了良好的設計結構和良好的環境之間的良好平衡的困難:
“這說明是有關設計過程; 。發明它顯示一個新的物理順序,組織形式的物理的東西,響應功能的過程......每一個設計問題,開始努力在兩個實體之間達到健身:有問題的形式和內容。的形式是解決問題的辦法; 上下文定義了該問題。“
雖然需要注意的設計模式是相當重要的,它可以是要了解的反模式同樣重要。讓我們限定這背后的原因。當創建一個應用程序,一個項目的生命周期與建設始于然而,一旦你已經得到了最初的版本完成,它需要維護。最終的解決方案的質量要么是好還是壞,取決于技巧和時間球隊紛紛投入到它的水平。在這里,好和壞的方面考慮-如果應用在錯誤的情況下一個“完美”的設計可以用一個反模式出線。
更大的挑戰出現的應用程序已達到生產和準備進入維護模式后。開發人員這樣誰沒有對應用程序的工作之前可能會推出一個系統上工作不好設計成事故的項目。如果說壞的做法為反模式創建的,它允許開發者事先認識到這些,使他們能夠避免可能發生的常見錯誤的方法-這是平行于設計模式為我們提供了一種方法來識別常見的方式技術,都是有用的。
總之,反模式是一個不好的設計,是值得記錄的。在JavaScript中的反模式的例子如下:
通過在全球范圍內定義大量的變量污染全局命名空間
傳遞字符串,而不是功能要么的setTimeout或setInterval的,因為這會觸發使用eval()內部。
修改Object類原型(這是一個特別壞的反模式)
在內嵌的形式使用JavaScript,因為這是不靈活
使用文件撰寫的,其中原生DOM等替代了document.createElement是比較合適的。文件撰寫已被嚴重濫用多年來有不少缺點,包括,如果之后的頁面已被加載它實際上可以覆蓋我們的頁面,同時使用document.createElement沒有它的執行。我們可以看到在這里為這個動作一個活生生的例子。它也沒有使用XHTML這是另一個原因選擇了多個DOM友好的方法,如使用document.createElement是有利的工作。
反模式知識是成功的關鍵。一旦我們能夠認識到這種反模式,我們能夠重構我們的代碼來否定他們,使我們的解決方案的整體素質提高了瞬間。
分類設計模式
從著名的設計書的詞匯,領域驅動條款,正確地指出:
“設計模式名稱,摘要,并確定一個共同的設計結構使其成為創建??可重用的面向對象的設計非常有用的關鍵方面。設計模式確定了參與類和它們的實例,他們的角色和協作和責任的分配。
每一個設計圖案集中在一個特定的面向對象的設計問題或問題。它描述了當它適用,無論它是否可以在考慮其它設計約束施加,其后果及其使用的權衡。因為我們必須最終實現我們的設計中,設計模式還提供了樣本...代碼來說明一個實現。
雖然設計模式描述的面向對象的設計中,它們是基于已在主流的面向對象編程語言中實現的實際解決辦法......“
設計模式可以細分成若干不同的類別。在本節中,我們將回顧這三個類別,并簡要提到屬于這些類別更詳細地探索具體的之前模式的幾個例子。
造物設計模式
造物設計模式著重處理在哪里適合我們的工作情況的方式創建對象的對象創建機制。要創建對象,否則可能導致額外的復雜性,項目的基本方法,而這些模式的目標是解決這個問題控制創建過程。
一些屬于這一類的模式是:構造函數,工廠,摘要,原型,辛格爾頓和生成器。
結構設計模式
結構模式所關注的對象組成,通常找出簡單的方法來實現不同對象之間的關系。它們有助于確保當一個系統的變化之一的一部分,該系統的整個結構不需要做。他們還協助重鑄也不適合特定用途為那些做了系統的組成部分。
這屬于此類模式包括:裝飾,幕墻,飛錘,適配器和代理。
行為設計模式
行為模式注重提高或精簡的系統中不同對象之間的通信。
有些行為模式包括:迭代器,中保,觀察員和游客。
設計模式分類
在我學習設計模式的早期經驗,我個人以下表中找到一個什么樣的一些模式所提供的一個非常有用的提醒 - 它涵蓋GoF的中提到的23個設計模式。原始表于2004年總結了伊利斯尼爾森回來,我已經修改了它在必要時以適應書中的這一部分我們的討論。
我建議使用此表作為參考,但千萬記住,有一些是這里沒有提到的其他模式,但將在本書后面討論。
在類的簡要說明
請記住,會出現在該表的模式引用“類”的概念。JavaScript是一類少的語言,但類可以使用函數來模擬。
為實現這一點的最常用的方法是通過定義一個JavaScript功能,我們然后使用創建一個對象new的關鍵字。this可用于幫助定義新的屬性和方法為對象如下:
~~~
// A car "class"
function Car( model ) {
this.model = model;
this.color = "silver";
this.year = "2012";
this.getInfo = function () {
return this.model + " " + this.year;
};
}
~~~
然后,我們可以用我們上面這樣定義的汽車構造實例化對象:
~~~
var myCar = new Car("ford");
myCar.year = "2010";
console.log( myCar.getInfo() );
~~~
欲了解更多的方法來定義“類”使用JavaScript,請斯托揚斯特凡的有用的崗位上他們。
現在讓我們繼續審查表。
造物 基于創建對象的概念。
類
工廠方法 這使得根據接口的數據或事件的多個派生類的一個實例。
對象
抽象工廠 創建類的幾個家庭的實例,但沒有詳細說明具體的類。
生成器 從它的表示中隔離對象的構造,總是創建相同類型的對象。
原型 用于復制或克隆完全初始化實例。
獨生子 一類以只與全球接入點的一個實例。
結構 基于建立的對象的塊的概念。
類
適配器 不同級別的比賽,因此接口類都可以盡管不兼容的接口一起工作。
對象
適配器 不同級別的比賽,因此接口類都可以盡管不兼容的接口一起工作。
橋梁 從它的實現中分離對象的接口,所以可以獨立地變化。
綜合 簡單和復合物的結構,這使得總的目標比其部分剛總和。
裝飾 動態交替處理添加到對象。
正面 一個單獨的類,隱藏了一個完整的子系統的復雜性。
飛錘 用于在別處包含的信息的高效共享細粒度實例。
代理 代表真正的對象的占位符對象。
行為的 基于對象的游戲和一起工作的方式。
類
翻譯員 一種方法,包括在應用程序中相匹配的預期語言的語法的語言元素。
模板
方法 創建一個算法的殼的方法,那么推遲到子類的具體步驟。
對象
鏈
責任 傳遞對象鏈之間的請求,以找到能夠處理請求的對象的方式。
命令 封裝命令請求為對象,以使,記錄和/或請求的隊列,并提供錯誤處理未處理請求。
迭代器 順序訪問一個集合中的元素不知道集合的內部運作。
中間人 定義類之間簡化的通信,以防止一組類從明確提到彼此。
紀念 拍攝對象的內部狀態,這樣才能在稍后恢復它。
觀察 通知更改了許多類以保證類之間一致性的一種方式。
狀態 當改變對象的行為及其狀態的變化。
戰略 封裝類分開的實現里面選擇的算法。
游客 添加一個新的操作類,而不改變類。
JavaScript的設計模式
在本節中,我們將探討一些既古典與現代的設計模式的JavaScript實現。
開發人員通常不知道是否有理想的,他們應該用他們的工作流模式的模式或集。沒有一個真正的單一答案; 我們的工作對每個腳本和Web應用程序很可能有自己的個性化需求,我們需要思考的問題,我們覺得一個模式可以提供真正的價值的實現。
例如,一些項目可能由Observer模式帶來的效益脫鉤受益(這減少了應用程序的相關部分如何彼此),而其他的可能只是太小,去耦引起人們的關注的。
這就是說,當我們有設計模式和他們是最適合的具體問題,牢牢把握,它變得更容易將其集成到我們的應用程序架構。
我們將在本節中探索的模式是:
構造模式
模塊模式
揭示模塊模式
Singleton模式
觀察者模式
調解模式
原型模式
命令模式
門面模式
工廠模式
混入模式
裝飾圖案
享元模式
構造函數模式
在傳統的面向對象的編程語言,構造函數是用來初始化一旦內存被分配給它一個新創建的對象的特殊方法。在JavaScript中,因為幾乎一切都是對象,我們最常感興趣的對象構造函數。
對象構造用于創建特定類型的對象 - 無論是準備使用的對象,并接受其構造可以使用第一次創建對象時設置成員屬性和方法的值的參數。
對象的創建
在JavaScript來創建新對象的三種常用的方法如下:
?
1
2
3
4
五
6
7
8
9
// Each of the following options will create a new empty object:
var newObject = {};
// or
var newObject = Object.create( Object.prototype );
// or
var newObject = new Object();
凡在最后一個例子“對象”的構造函數創建一個特定值的對象包裝,或??沒有傳遞值,它會創建一個空的對象,并將其返回。
有然后在其中鍵和值然后可以被分配給一個目的四種方式:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// ECMAScript 3 compatible approaches
// 1. Dot syntax
// Set properties
newObject.someKey = "Hello World";
// Get properties
var value = newObject.someKey;
// 2. Square bracket syntax
// Set properties
newObject["someKey"] = "Hello World";
// Get properties
var value = newObject["someKey"];
// ECMAScript 5 only compatible approaches
// For more information see: http://kangax.github.com/es5-compat-table/
// 3. Object.defineProperty
// Set properties
Object.defineProperty( newObject, "someKey", {
value: "for more control of the property's behavior",
writable: true,
enumerable: true,
configurable: true
});
// If the above feels a little difficult to read, a short-hand could
// be written as follows:
var defineProp = function ( obj, key, value ){
var config = {
value: value,
writable: true,
enumerable: true,
configurable: true
};
Object.defineProperty( obj, key, config );
};
// To use, we then create a new empty "person" object
var person = Object.create( Object.prototype );
// Populate the object with properties
defineProp( person, "car", "Delorean" );
defineProp( person, "dateOfBirth", "1981" );
defineProp( person, "hasBeard", false );
console.log(person);
// Outputs: Object {car: "Delorean", dateOfBirth: "1981", hasBeard: false}
// 4. Object.defineProperties
// Set properties
Object.defineProperties( newObject, {
"someKey": {
value: "Hello World",
writable: true
},
"anotherKey": {
value: "Foo bar",
writable: false
}
});
// Getting properties for 3. and 4. can be done using any of the
// options in 1. and 2.
正如我們將在本書稍后看到,這些方法甚至可以用于繼承,如下所示:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
// Usage:
// Create a race car driver that inherits from the person object
var driver = Object.create( person );
// Set some properties for the driver
defineProp(driver, "topSpeed", "100mph");
// Get an inherited property (1981)
console.log( driver.dateOfBirth );
// Get the property we set (100mph)
console.log( driver.topSpeed );
基本構造
正如我們前面看到的,JavaScript不支持類的概念,但它不支持使用對象特殊的構造函數。通過簡單的前綴與關鍵字“新”的構造函數的調用,我們可以告訴我們的JavaScript愿功能表現得像一個構造函數和實例與函數定義成員的新對象。
內部構造函數中,關鍵字此引用的正在創建的新對象。重訪對象的創建,基本構造可能如下所示:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Car( model, year, miles ) {
this.model = model;
this.year = year;
this.miles = miles;
this.toString = function () {
return this.model + " has done " + this.miles + " miles";
};
}
// Usage:
// We can create new instances of the car
var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );
// and then open our browser console to view the
// output of the toString() method being called on
// these objects
console.log( civic.toString() );
console.log( mondeo.toString() );
以上是構造模式的一個簡單的版本,但它確實從一些問題的困擾。之一是,它使繼承困難,另一個是如函數toString()被重新定義為每個使用汽車構造創建的新對象。這不是非常理想的功能應理想的所有車輛類型的實例之間共享。
值得慶幸的是,因為有一些既ES3和ES5兼容的替代構造的對象,是微不足道的解決此限制。
構造方法原型
功能,如在JavaScript中幾乎所有的對象,包含一個“原型”的對象。當我們調用JavaScript構造函數來創建一個對象,構造函數的原型的所有屬性,然后提供給新的對象。以這種方式,多個車載對象可以創建了訪問同一原型。因此,我們可以擴展原始舉例如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Car( model, year, miles ) {
this.model = model;
this.year = year;
this.miles = miles;
}
// Note here that we are using Object.prototype.newMethod rather than
// Object.prototype so as to avoid redefining the prototype object
Car.prototype.toString = function () {
return this.model + " has done " + this.miles + " miles";
};
// Usage:
var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );
console.log( civic.toString() );
console.log( mondeo.toString() );
以上,的toString的單個實例()現在將所有對車對象之間共享。
模塊模式
模塊
模塊是任何強大的應用架構中不可或缺的一塊,通常在保持代碼的單位為一個項目都清楚分開的和有組織的幫助。
在JavaScript中,有實現模塊的幾個選項。這些包括:
模塊模式
對象的文字符號
AMD模塊
CommonJS的模塊
ECMAScript的和諧模塊
我們將在后面探討后三種這些選項在本書中的部分現代模塊化JavaScript的設計模式。
模塊模式是基于在對象的文字部分,因此它是有道理的,首先刷新我們對他們的了解。
對象文本
在對象的文字符號,一個對象被描述為一組(包含在大括號逗號分隔的名稱/值對{})。物體內部名稱可能是字符串或標識符后跟一個冒號。不應該有在對象的最終名稱/值對后使用,因為這可能會導致錯誤的逗號。
?
1
2
3
4
五
6
7
8
var myObjectLiteral = {
variableKey: variableValue,
functionKey: function () {
// ...
}
};
對象文字不需要實例使用new操作者,但不應該在開始發言被用作開口{可以被解釋為一個塊的開始。一個對象外,新成員可以使用分配如下被加入到其myModule.property = "someValue";
下面我們可以看到使用對象的文字符號定義的模塊的更完整的例子:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var myModule = {
myProperty: "someValue",
// object literals can contain properties and methods.
// e.g we can define a further object for module configuration:
myConfig: {
useCaching: true,
language: "en"
},
// a very basic method
saySomething: function () {
console.log( "Where in the world is Paul Irish today?" );
},
// output a value based on the current configuration
reportMyConfig: function () {
console.log( "Caching is: " + ( this.myConfig.useCaching ? "enabled" : "disabled") );
},
// override the current configuration
updateMyConfig: function( newConfig ) {
if ( typeof newConfig === "object" ) {
this.myConfig = newConfig;
console.log( this.myConfig.language );
}
}
};
// Outputs: Where in the world is Paul Irish today?
myModule.saySomething();
// Outputs: Caching is: enabled
myModule.reportMyConfig();
// Outputs: fr
myModule.updateMyConfig({
language: "fr",
useCaching: false
});
// Outputs: Caching is: disabled
myModule.reportMyConfig();
使用對象文本可以協助封裝和組織代碼和麗貝卡·墨菲以前曾寫過關于此主題的深度應該要讀入對象文本進一步。
這就是說,如果我們選擇了這種技術,我們可以同樣在模塊圖案感興趣。它仍然使用對象文本,但只有從一個范圍函數的返回值。
模塊模式
模塊模式最初被定義為一種方法來提供傳統軟件工程類私人和公共封裝。
在JavaScript中,模塊模式被用于進一步模擬的類的概念在這樣一種方式,我們能夠包括單個對象內部公共/私有方法和變量,從而屏蔽特定部件從全局范圍。這是什么導致在我們的函數名在頁面上的其他腳本定義等功能相沖突的可能性降低。
隱私
模塊模式使用閉包封裝“隱私”,國家和組織。它提供了包裝的公共和私有方法和變量的組合,保護片泄漏到全球范圍時,不慎與其他開發界面碰撞的方式。有了這個模式,只返回一個公共的API,保持封閉的私人內的一切。
這為我們提供了屏蔽邏輯做繁重的任務,而只露出我們希望我們的應用程序中使用的其他部分的接口干凈的解決方案。圖案是相當類似的緊調用功能表達(IIFE -見命名空間型態更多關于此的部分),除了一個對象被返回,而不是一個函數。
應該指出的是,不是一個真正的真實明確的“隱私”里的JavaScript意義,因為不像一些傳統的語言,它沒有訪問修飾符。變量不能在技術上被聲明為公眾人士,亦私,所以我們使用的功能范圍來模擬這一概念。在模塊模式,聲明的變量或方法僅是模塊本身由于閉包內可用。然而,變量或返回對象中定義的方法提供給大家。
歷史
從歷史的角度來看,模塊模式最初是由許多人包括發達國家理查德康福德于2003年,后來被道格拉斯在克羅克福德他的講座推廣。瑣事的另一件是,如果你曾經與雅虎的YUI庫出場,它的某些功能可能會出現相當的熟悉和這樣做的原因是,他們創建組件時模塊模式是YUI強大的影響力。
例子
讓我們首先創建一個模塊,它是自包含在看模塊模式的實現。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var testModule = (function () {
var counter = 0;
return {
incrementCounter: function () {
return counter++;
},
resetCounter: function () {
console.log( "counter value prior to reset: " + counter );
counter = 0;
}
};
})();
// Usage:
// Increment our counter
testModule.incrementCounter();
// Check the counter value and reset
// Outputs: counter value prior to reset: 1
testModule.resetCounter();
這里,碼的其他部分無法直接閱讀我們的值incrementCounter()或resetCounter()。它的存在,這樣能夠訪問其范圍的唯一的代碼是我們兩個功能僅限于模塊的關閉內-計數器變量實際上是從我們的全球范圍,以便它的行為就像一個私有變量將完全屏蔽。我們的方法是有效的命名空間所以我們代碼的測試部分,我們需要與模塊的名稱(如“testModule”),前綴任何電話。
當與模塊模式工作時,我們可能會發現它有用的定義,我們使用入門與它一個簡單的模板。這里有一個覆蓋命名空間,公共和私有變量:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
var myNamespace = (function () {
var myPrivateVar, myPrivateMethod;
// A private counter variable
myPrivateVar = 0;
// A private function which logs any arguments
myPrivateMethod = function( foo ) {
console.log( foo );
};
return {
// A public variable
myPublicVar: "foo",
// A public function utilizing privates
myPublicFunction: function( bar ) {
// Increment our private counter
myPrivateVar++;
// Call our private method using bar
myPrivateMethod( bar );
}
};
})();
看著另外一個例子,下面我們可以看到使用這種模式實現了一個購物籃。該模塊本身完全是自包含在一個叫做全局變量basketModule。該basket模塊中的陣列將保密,我們的應用程序,所以其他部分都無法直接讀取它。它僅與模塊的閉合存在,并且因此能夠訪問它的唯一的方法是那些能夠訪問它的范圍(即addItem(),getItemCount()等)。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var basketModule = (function () {
// privates
var basket = [];
function doSomethingPrivate() {
//...
}
function doSomethingElsePrivate() {
//...
}
// Return an object exposed to the public
return {
// Add items to our basket
addItem: function( values ) {
basket.push(values);
},
// Get the count of items in the basket
getItemCount: function () {
return basket.length;
},
// Public alias to a private function
doSomething: doSomethingPrivate,
// Get the total value of items in the basket
getTotal: function () {
var q = this.getItemCount(),
p = 0;
while (q--) {
p += basket[q].price;
}
return p;
}
};
})();
在模塊內,你可能已經注意到,我們返回object。這被自動分配給basketModule這樣我們可以按照如下與其交互:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// basketModule returns an object with a public API we can use
basketModule.addItem({
item: "bread",
price: 0.5
});
basketModule.addItem({
item: "butter",
price: 0.3
});
// Outputs: 2
console.log( basketModule.getItemCount() );
// Outputs: 0.8
console.log( basketModule.getTotal() );
// However, the following will not work:
// Outputs: undefined
// This is because the basket itself is not exposed as a part of our
// public API
console.log( basketModule.basket );
// This also won't work as it only exists within the scope of our
// basketModule closure, but not in the returned public object
console.log( basket );
以上的方法是有效的命名空間內basketModule。
請注意如何在上述筐模塊中的作用域功能是??圍繞我們的所有功能,我們然后調用并立即存儲的返回值包裹。這有許多優點,包括:
自由有私人活動和只能由我們的模塊消耗私有成員。因為它們沒有暴露在頁面(只有我們出口的API)的其余部分,他們認為真正私有的。
鑒于函數通常聲明,并命名,它可以更容易顯示調用堆棧在調試器中,當我們正在試圖發現什么功能(S)引發了異常。
作為TJ克勞德在過去指出的那樣,它也使我們能夠返回根據環境不同的功能。在過去,我已經看到了開發人員使用此,以便提供特定于IE瀏覽器的模塊中的代碼路徑執行UA測試,但我們可以很容易地選擇功能檢測,這些天來實現類似的目標。
模塊模式變化
進口混入
該模式的這種變化說明了如何全局(如jQuery的,下劃線)可以作為參數傳遞給我們的模塊的匿名函數傳遞。這有效地使我們能夠導入他們和當地的別名他們如人所愿。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Global module
var myModule = (function ( jQ, _ ) {
function privateMethod1(){
jQ(".container").html("test");
}
function privateMethod2(){
console.log( _.min([10, 5, 100, 2, 1000]) );
}
return{
publicMethod: function(){
privateMethod1();
}
};
// Pull in jQuery and Underscore
})( jQuery, _ );
myModule.publicMethod();
出口
接下來的這個變化使我們無需耗費它們聲明全局,并可能同樣支持在最后一個例子看出全球進口的概念。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Global module
var myModule = (function () {
// Module object
var module = {},
privateVariable = "Hello World";
function privateMethod() {
// ...
}
module.publicProperty = "Foobar";
module.publicMethod = function () {
console.log( privateVariable );
};
return module;
})();
工具包和框架特定模塊的模式實現
道場
Dojo提供與被調用對象的工作的一個便捷方法dojo.setObject()。這需要作為第一個參數一個圓點分隔字符串,如myObj.parent.child它指的是一個名為“子”對象“父”內里“MyObj中”定義的屬性。使用setObject()允許我們設置兒童的價值,傳遞的路徑的其余部分創建任何中間的對象,如果他們不存在。
例如,如果我們想聲明basket.core作為的一個目的store名稱空間,這是可以實現用傳統的方式如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
var store = window.store || {};
if ( !store["basket"] ) {
store.basket = {};
}
if ( !store.basket["core"] ) {
store.basket.core = {};
}
store.basket.core = {
// ...rest of our logic
};
或者用道場1.7(AMD兼容的版本)及以上如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require(["dojo/_base/customStore"], function( store ){
// using dojo.setObject()
store.setObject( "basket.core", (function() {
var basket = [];
function privateMethod() {
console.log(basket);
}
return {
publicMethod: function(){
privateMethod();
}
};
})());
});
有關更多信息dojo.setObject(),請參閱官方文檔。
ExtJS的
對于使用煎茶的ExtJS的那些,一個例子演示如何正確使用模塊模式與框架可以在下面找到。
在這里,我們看到的如何定義,然后可以用含有一個私人和公共API模塊填充命名空間的一例。隨著一些語義差異外,這是相當接近模塊模式是如何在香草JavaScript實現的:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// create namespace
Ext.namespace("myNameSpace");
// create application
myNameSpace.app = function () {
// do NOT access DOM from here; elements don't exist yet
// private variables
var btn1,
privVar1 = 11;
// private functions
var btn1Handler = function ( button, event ) {
console.log( "privVar1=" + privVar1 );
console.log( "this.btn1Text=" + this.btn1Text );
};
// public space
return {
// public properties, e.g. strings to translate
btn1Text: "Button 1",
// public methods
init: function () {
if ( Ext.Ext2 ) {
btn1 = new Ext.Button({
renderTo: "btn1-ct",
text: this.btn1Text,
handler: btn1Handler
});
} else {
btn1 = new Ext.Button( "btn1-ct", {
text: this.btn1Text,
handler: btn1Handler
});
}
}
};
}();
YUI
同樣,我們也可以用YUI3構建應用程序時實現的模塊的模式。下面的例子主要是基于由埃里克·米拉利亞原YUI模塊模式實現,但同樣,不能從香草JavaScript版本大不相同:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
Y.namespace( "store.basket" ) ;
Y.store.basket = (function () {
var myPrivateVar, myPrivateMethod;
// private variables:
myPrivateVar = "I can be accessed only within Y.store.basket.";
// private method:
myPrivateMethod = function () {
Y.log( "I can be accessed only from within YAHOO.store.basket" );
}
return {
myPublicProperty: "I'm a public property.",
myPublicMethod: function () {
Y.log( "I'm a public method." );
// Within basket, I can access "private" vars and methods:
Y.log( myPrivateVar );
Y.log( myPrivateMethod() );
// The native scope of myPublicMethod is store so we can
// access public members using "this":
Y.log( this.myPublicProperty );
}
};
})();
jQuery的
有多種方式,其中jQuery代碼特異性到插件可以模塊圖案內包裹。本櫻桃先前建議,其中一個功能封裝在那里是一個數字模塊之間的共同性的事件周圍使用模塊定義的實現。
在下面的例子中,library函數定義它宣布一個新的圖書館,并自動綁定了init函數來document.ready創建新庫(即模塊)時。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function library( module ) {
$( function() {
if ( module.init ) {
module.init();
}
});
return module;
}
var myLibrary = library(function () {
return {
init: function () {
// module implementation
}
};
}());
優點
我們已經看到,為什么構造模式可能是有用的,但為什么是模塊模式一個好的選擇嗎?對于初學者來說,它是開發者不是真正的封裝的想法一個面向對象的背景的,從JavaScript的角度來看,至少變得更干凈。
其次,它支持私有數據 - 因此,在模塊的模式,我們的代碼公共部位都能夠觸摸私處,但外界無法接觸到類的私有部分(不笑的哦,感謝大衛Engfer!為笑談)。
缺點
模塊模式的缺點是,當我們訪問不同的公共和私營部門的成員,當我們希望改變的知名度,我們實際上不得不更改使用了成員的每個地方。
我們也不能訪問在被添加到對象在以后的方法私有成員。這就是說,在許多情況下,模塊格局依然相當有用的,正確使用時,當然要改善我們的應用程序結構的潛力。
其他缺點包括:無法創建私有成員和額外的復雜性錯誤時,需要熱修復補丁程序自動單元測試。這是根本不可能修補士兵。相反,人們必須重寫它與車互動士兵所有公共方法。開發商不能輕易擴展要么私處,所以它是值得記住的士兵并不像他們最初可能會顯示為靈活。
有關模塊的格局進一步閱讀,請參閱本Cherry的優秀深入的文章就可以了。
透露出模塊模式
現在,我們多了幾分熟悉的模塊模式,讓我們來看看一個稍微改進版 - 基督教海爾曼的顯露的模塊模式。
透出模塊模式來作為左右海爾曼感到沮喪的事實,他不得不重復的主要對象的名字的時候,我們想從另一個或訪問公共變量調用一個公共方法。他還討厭模塊模式的要求,具有切換到對象的文字符號,因為他希望公開的事情。
他的努力的結果是一個更新的模式,我們會簡單地定義了我們所有的函數和變量在私人范圍和返回的指針匿名對象,我們希望揭示公共私營功能。
如何使用的顯露的模塊模式的例子可以發現如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
var myRevealingModule = (function () {
var privateVar = "Ben Cherry",
publicVar = "Hey there!";
function privateFunction() {
console.log( "Name:" + privateVar );
}
function publicSetName( strName ) {
privateVar = strName;
}
function publicGetName() {
privateFunction();
}
// Reveal public pointers to
// private functions and properties
return {
setName: publicSetName,
greeting: publicVar,
getName: publicGetName
};
})();
myRevealingModule.setName( "Paul Kinlan" );
圖案也可用于揭示私有函數和屬性,更具體的命名方案,如果我們希望:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
var myRevealingModule = (function () {
var privateCounter = 0;
function privateFunction() {
privateCounter++;
}
function publicFunction() {
publicIncrement();
}
function publicIncrement() {
privateFunction();
}
function publicGetCount(){
return privateCounter;
}
// Reveal public pointers to
// private functions and properties
return {
start: publicFunction,
increment: publicIncrement,
count: publicGetCount
};
})();
myRevealingModule.start();
優點
該模式允許我們的腳本的語法更加一致。這也使得它在其中我們的函數和變量可以公開訪問的模塊,它簡化了可讀性的端部更清晰。
缺點
這種模式的一個缺點是,如果一個私有函數是指一個公共功能,即公共功能不能如果貼劑是必要的覆蓋。這是因為私有函數將繼續參考私有實現和圖案并不適用于公共成員,只有功能。
公共對象成員其中提到私有變量也受到上述無補丁規則的注釋。
由于這個結果,與顯露的模塊模式創建模塊可以比那些與原來的模??塊圖案創建更脆弱,所以護理應使用期間服用。
Singleton模式
因此公知的單例模式,因為它限制一個類的實例化到單個對象。經典的單例模式可以通過創建,如果不存在,創建類的新實例的方法的類來實現。在已有的一個實例的情況下,它只是簡單地返回到該對象的引用。
單身從靜態不同的類(或對象),因為我們可以延遲它們的初始化,通常由于需要一些信息期間初始化時間,可能無法使用。它們不提供對代碼的方式,是不知道先前提及他們很容易地檢索它們。這是因為它既不對象或這是由一個單身返回“下課”,這是一個結構。想想如何合攏變量實際上不是倒閉-功能范圍,提供閉包是關閉。
在JavaScript中,單身作為一種共享資源空間從全局命名空間隔離的實現代碼,以提供功能的訪問的單點。
我們可以實現一個Singleton如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
var mySingleton = (function () {
// Instance stores a reference to the Singleton
var instance;
function init() {
// Singleton
// Private methods and variables
function privateMethod(){
console.log( "I am private" );
}
var privateVariable = "Im also private";
var privateRandomNumber = Math.random();
return {
// Public methods and variables
publicMethod: function () {
console.log( "The public can see me!" );
},
publicProperty: "I am also public",
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
// Get the Singleton instance if one exists
// or create one if it doesn't
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
})();
var myBadSingleton = (function () {
// Instance stores a reference to the Singleton
var instance;
function init() {
// Singleton
var privateRandomNumber = Math.random();
return {
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
// Always create a new Singleton instance
getInstance: function () {
instance = init();
return instance;
}
};
})();
// Usage:
var singleA = mySingleton.getInstance();
var singleB = mySingleton.getInstance();
console.log( singleA.getRandomNumber() === singleB.getRandomNumber() ); // true
var badSingleA = myBadSingleton.getInstance();
var badSingleB = myBadSingleton.getInstance();
console.log( badSingleA.getRandomNumber() !== badSingleB.getRandomNumber() ); // true
// Note: as we are working with random numbers, there is a
// mathematical possibility both numbers will be the same,
// however unlikely. The above example should otherwise still
// be valid.
是什么讓辛格爾頓是實例(通常通過全球接入MySingleton.getInstance()),因為我們不(在靜態語言至少)調用new MySingleton()直接。但是,這是有可能在JavaScript中。
在GoF的書,適用性 Singleton模式描述如下:
必須有一個類中的恰好一個實例,并且它必須是客戶端可以訪問從一個公知的接入點。
當鞋底實例應該是由子類擴展和客戶端應能夠使用擴展實例,而無需修改其代碼。
這些點的第二指的是,我們可能需要的代碼,如一個案例:
?
1
2
3
4
五
6
7
8
9
10
mySingleton.getInstance = function(){
if ( this._instance == null ) {
if ( isFoo() ) {
this._instance = new FooSingleton();
} else {
this._instance = new BasicSingleton();
}
}
return this._instance;
};
在這里,getInstance變得有點像一個工廠方法,我們并不需要更新我們的代碼訪問它的每一點。FooSingleton上面會的一個子類BasicSingleton,并實現相同的接口。
為什么推遲執行考慮了辛格爾頓重要?
在C ++中它的作用是從動態初始化秩序的不可預測性隔離,控制權返回給程序員。
需要注意的一個類(對象)的靜態實例和辛格爾頓之間的區別是很重要的:而一個單身可以作為一個靜態實例來實現,也可以懶洋洋地構建,而不需要資源,也沒有內存中,直到這實際上是必要的。
如果我們有一個可以直接初始化的靜態對象,我們必須確保代碼總是以相同的順序執行(例如,在情況objCar需要objWheel其初始化期間),當有大量的源文件的本不能擴展。
無論單身和靜態對象是有用的,但他們不應該被過度使用 - 在此我們不應該過度使用其他模式一樣。
在實踐中,當需要一個對象跨越系統以協調其它Singleton模式是有用的。這里是一個例子,在這種情況下所使用的圖案:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var SingletonTester = (function () {
// options: an object containing configuration options for the singleton
// e.g var options = { name: "test", pointX: 5};
function Singleton( options ) {
// set options to the options supplied
// or an empty object if none are provided
options = options || {};
// set some properties for our singleton
this.name = "SingletonTester";
this.pointX = options.pointX || 6;
this.pointY = options.pointY || 10;
}
// our instance holder
var instance;
// an emulation of static variables and methods
var _static = {
name: "SingletonTester",
// Method for getting an instance. It returns
// a singleton instance of a singleton object
getInstance: function( options ) {
if( instance === undefined ) {
instance = new Singleton( options );
}
return instance;
}
};
return _static;
})();
var singletonTest = SingletonTester.getInstance({
pointX: 5
});
// Log the output of pointX just to verify it is correct
// Outputs: 5
console.log( singletonTest.pointX );
雖然辛格爾頓具有有效的用途,往往當我們發現自己需要它在JavaScript中它是我們可能需要重新評估我們的設計標志。
他們常常是,在一個系統中任一緊耦合模塊或邏輯被越過代碼庫的多個部分過于擴散的指示。單身人士可以更難以測試,由于問題,從隱藏的依賴,在掘根的依賴性等創建多個實例,困難難度。
米勒·梅德羅斯以前曾建議這對辛格爾頓和它的各種問題的極好的文章,詳細閱讀和評論,以這個文章,討論單身如何提高緊密耦合。我很高興第二這些建議為兩件提高對這種模式很多重要的穴位,同時也是可圈可點。
觀察者模式
觀察員是一種設計模式,其中一個對象(稱為一級學科)維持取決于它(觀察者)對象的列表,自動通知他們的任何變化的狀態。
當主體需要通知有關一些有趣的事情發生觀察員,它廣播到觀察者(可能包括有關通知的特定主題的數據)的通知。
當我們不再為特定的觀察者希望被通知通過它們與登記對象的變化,個體可以從觀察名單中刪除。
它通常指回是語言無關隨著時間的推移得到他們的用途和優點的更廣泛意義上的設計模式出版的定義是有用的。在GoF的書中提供的觀察者模式的定義,設計模式:可復用面向對象軟件的基礎是:
“一個或多個觀察員對某一主題感興趣的狀態,并通過將自己注冊自己的學科的興趣。當東西在我們的主題改變觀察者可能感興趣的,則發送通知消息,它??調用更新方法在每個觀察者。當觀察者在主體的狀態不再感興趣,他們可以簡單地跳出來。“
現在,我們可以擴大我們學到的東西來實現與下列組件觀察者模式:
主題:保持觀察者的列表,方便添加或刪除觀察者
觀察:提供了一個更新界面,需要通知的國家的國民的對象變化
ConcreteSubject:廣播通知狀態變化的觀測,存儲ConcreteObservers的狀態
ConcreteObserver:存儲對ConcreteSubject參考,實現對觀察員,以確保狀態更新界面與主題的一致
首先,讓我們依賴觀察員的對象可能有列表模型:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
function ObserverList(){
this.observerList = [];
}
ObserverList.prototype.add = function( obj ){
return this.observerList.push( obj );
};
ObserverList.prototype.count = function(){
return this.observerList.length;
};
ObserverList.prototype.get = function( index ){
if( index > -1 && index < this.observerList.length ){
return this.observerList[ index ];
}
};
ObserverList.prototype.indexOf = function( obj, startIndex ){
var i = startIndex;
while( i < this.observerList.length ){
if( this.observerList[i] === obj ){
return i;
}
i++;
}
return -1;
};
ObserverList.prototype.removeAt = function( index ){
this.observerList.splice( index, 1 );
};
接下來,讓我們模型中的主題,并添加,刪除或通知觀察員觀察名單上的能力。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
function Subject(){
this.observers = new ObserverList();
}
Subject.prototype.addObserver = function( observer ){
this.observers.add( observer );
};
Subject.prototype.removeObserver = function( observer ){
this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};
Subject.prototype.notify = function( context ){
var observerCount = this.observers.count();
for(var i=0; i < observerCount; i++){
this.observers.get(i).update( context );
}
};
然后,我們定義了一個框架來創建新的觀察員。在update這里的功能將在后面自定義行為覆蓋。
?
1
2
3
4
五
6
// The Observer
function Observer(){
this.update = function(){
// ...
};
}
在使用上述觀察部件示例應用程序,我們現在定義:
一種把新觀察到的復選框的頁面按鈕
控制復選框,將作為一個主體,他們通知應檢查其他復選框
被添加了一個新的復選框容器
然后,我們定義ConcreteSubject和ConcreteObserver處理為增加新的觀察員頁面和實施更新接口。請參閱以下行內注釋關于這些部件在我們的例子的情況下做的。
HTML:
?
1
2
3
<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>
示例腳本:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Extend an object with an extension
function extend( obj, extension ){
for ( var key in extension ){
obj[key] = extension[key];
}
}
// References to our DOM elements
var controlCheckbox = document.getElementById( "mainCheckbox" ),
addBtn = document.getElementById( "addNewObserver" ),
container = document.getElementById( "observersContainer" );
// Concrete Subject
// Extend the controlling checkbox with the Subject class
extend( controlCheckbox, new Subject() );
// Clicking the checkbox will trigger notifications to its observers
controlCheckbox.onclick = function(){
controlCheckbox.notify( controlCheckbox.checked );
};
addBtn.onclick = addNewObserver;
// Concrete Observer
function addNewObserver(){
// Create a new checkbox to be added
var check = document.createElement( "input" );
check.type = "checkbox";
// Extend the checkbox with the Observer class
extend( check, new Observer() );
// Override with custom update behaviour
check.update = function( value ){
this.checked = value;
};
// Add the new observer to our list of observers
// for our main subject
controlCheckbox.addObserver( check );
// Append the item to the container
container.appendChild( check );
}
在這個例子中,我們研究了如何實現和利用觀察者模式,涵蓋主題,觀察者,ConcreteSubject和ConcreteObserver的概念。
觀察者和發布/訂閱模式之間的差異
盡管Observer模式是需要注意的是有用的,往往在JavaScript中的世界,我們會發現它通常使用被稱為發布/訂閱模式的變化來實現。雖然很相似,有這些模式值得關注的差異。
觀察者模式要求觀察者(或對象)希望接收的話題通知必須訂閱這個興趣觸發事件(主體)的對象。
然而,發布/訂閱模式使用它希望接收通知(用戶)的對象和觸發事件(發布者)對象之間坐著一個話題/事件信道。該事件系統可以讓代碼來定義它可以傳遞包含由用戶根據需要自定義的值參數應用特定的事件。這里的想法是,以避免用戶和發行人之間的依賴關系。
這不同于觀察者模式,因為它允許實施適當的事件處理程序進行注冊并獲得主題的通知由出版商播放任何用戶。
這里是一個可能如何使用發布/如果設置有功能的實現供電訂閱一個例子publish(),subscribe()和unsubscribe()幕后:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
// A very simple new mail handler
// A count of the number of messages received
var mailCounter = 0;
// Initialize subscribers that will listen out for a topic
// with the name "inbox/newMessage".
// Render a preview of new messages
var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) {
// Log the topic for debugging purposes
console.log( "A new message was received: ", topic );
// Use the data that was passed from our subject
// to display a message preview to the user
$( ".messageSender" ).html( data.sender );
$( ".messagePreview" ).html( data.body );
});
// Here's another subscriber using the same data to perform
// a different task.
// Update the counter displaying the number of new
// messages received via the publisher
var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) {
$('.newMessageCounter').html( ++mailCounter );
});
publish( "inbox/newMessage", [{
sender: "hello@google.com",
body: "Hey there! How are you doing today?"
}]);
// We could then at a later point unsubscribe our subscribers
// from receiving any new topic notifications as follows:
// unsubscribe( subscriber1 );
// unsubscribe( subscriber2 );
這里的總體思路是促進松耦合。而不是單個對象上調用其他對象的方法,直接,它們代替訂閱特定任務或另一個對象的活動,并當它發生時得到通知。
優點
觀察者和發布/訂閱模式鼓勵我們要認真思考我們的應用程序的不同部分之間的關??系。他們還幫助我們識別含有可以改為用套主體和觀察員取代直接關系的圖層。這有效地可以用來分解應用到更小,更松散耦合塊以提高代碼管理和潛力進行再利用。
背后使用Observer模式進一步動機是我們需要保持相關對象之間的一致性未做緊耦合類。例如,當一個對象需要能夠不進行關于這些對象的假設來通知其他對象。
動態關系可以用兩種模式,當觀察者和主體之間存在。這提供了靈活性,可能不那么容易實現,當我們的應用程序的不同部分被緊密耦合很大。
雖然它可能不總是對每一問題的最佳解決方案,這些模式仍然是最好的工具之一用于設計去耦系統和應被視為在任何JavaScript顯影劑的效用帶的一個重要工具。
缺點
因此,一些與這些模式的問題實際上是從他們的主要收益干。在發布/訂閱,通過來自用戶的去耦出版商,它有時難??以獲得保證我們的應用程序的特定部分被充當我們可以預期。
例如,發布商可能使一個假設,即一個或多個用戶正在收聽他們。說,我們正在使用這樣的假設對于一些應用程序記錄或輸出錯誤。如果用戶在執行日志崩潰(或者因為某些原因無法正常使用),出版商不會看到這一種方式由于系統的解耦性質。
的圖案的另一拉回是訂戶都相當無知彼此的存在,并且盲到開關發布的成本。由于訂戶和發行商之間的動態關系,更新相關性可能難以追蹤。
發布/訂閱實現
發布/訂閱的配合相當不錯,在JavaScript中的生態系統,主要是因為為核心,實現ECMAScript的是事件驅動的。作為使用DOM事件為腳本的主要交互的API這是在瀏覽器環境中尤其如此。
這就是說,無論是ECMAScript中也不DOM提供核心對象或方法的實現代碼中創建自定義事件系統(帶或許DOM3自定義事件,這勢必給DOM,因此一般不是有用的除外)。
幸運的是,流行的JavaScript庫,如道場,jQuery的(自定義事件)和YUI已經有可以幫助輕松實現發布/訂閱系統,用很少的努力工具。下面我們可以看到這樣一些例子:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
// Publish
// jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]);
$( el ).trigger( "/login", [{username:"test", userData:"test"}] );
// Dojo: dojo.publish("channel", [arg1, arg2, arg3] );
dojo.publish( "/login", [{username:"test", userData:"test"}] );
// YUI: el.publish("channel", [arg1, arg2, arg3]);
el.publish( "/login", {username:"test", userData:"test"} );
// Subscribe
// jQuery: $(obj).on( "channel", [data], fn );
$( el ).on( "/login", function( event ){...} );
// Dojo: dojo.subscribe( "channel", fn);
var handle = dojo.subscribe( "/login", function(data){..} );
// YUI: el.on("channel", handler);
el.on( "/login", function( data ){...} );
// Unsubscribe
// jQuery: $(obj).off( "channel" );
$( el ).off( "/login" );
// Dojo: dojo.unsubscribe( handle );
dojo.unsubscribe( handle );
// YUI: el.detach("channel");
el.detach( "/login" );
對于希望使用香草的JavaScript(或其他庫)發布/訂閱模式的AmplifyJS包括清潔,圖書館無關的實現,它可以與任何庫或工具包中。Radio.js(http://radio.uxder.com/),PubSubJS(https://github.com/mroderick/PubSubJS)或彼得·希金斯純JS PubSub的(https://github.com/phiggins42/bloody- jQuery的,插件/ BLOB / 55e41df9bf08f42378bb08b93efcb28555b61aeb / pubsub.js)也值得一試類似的替代品。
jQuery的開發者尤其是有不少其他的選項,可以選擇使用許多發達的實施范圍從彼得·希金斯的jQuery插件奔Alman的(優化)的Pub / Sub jQuery的GitHub上依據之一。鏈接只是其中的幾個可以在下面找到。
本Alman的發布/訂閱要點https://gist.github.com/661855(推薦)
里克沃爾德倫的jQuery的核心風格采取上述https://gist.github.com/705311
彼得·希金斯“插件http://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js。
在AmplifyJS AppendTo的發布/訂閱http://amplifyjs.com
本Truyman的要點https://gist.github.com/826794
因此,我們能夠得到多少的JavaScript觀察者模式的實現可能工作香草的欣賞,讓我們通過發布/訂閱了一項名為下我在GitHub上發布的極簡版散步pubsubz。這表明訂閱的核心概念,發布以及退訂的概念。
我選擇了在此基礎上的代碼我們的例子中,因為它緊密地粘在方法簽名和實施辦法我都期望在經典的觀察者模式的一個JavaScript版本,看看。
發布/訂閱的實現
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
var pubsub = {};
(function(myObject) {
// Storage for topics that can be broadcast
// or listened to
var topics = {};
// An topic identifier
var subUid = -1;
// Publish or broadcast events of interest
// with a specific topic name and arguments
// such as the data to pass along
myObject.publish = function( topic, args ) {
if ( !topics[topic] ) {
return false;
}
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func( topic, args );
}
return this;
};
// Subscribe to events of interest
// with a specific topic name and a
// callback function, to be executed
// when the topic/event is observed
myObject.subscribe = function( topic, func ) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = ( ++subUid ).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
// Unsubscribe from a specific
// topic, based on a tokenized reference
// to the subscription
myObject.unsubscribe = function( token ) {
for ( var m in topics ) {
if ( topics[m] ) {
for ( var i = 0, j = topics[m].length; i < j; i++ ) {
if ( topics[m][i].token === token ) {
topics[m].splice( i, 1 );
return token;
}
}
}
}
return this;
};
}( pubsub ));
例如:根據我們的實施
現在我們可以使用的實施發布和訂閱感興趣的事件如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
// Another simple message handler
// A simple message logger that logs any topics and data received through our
// subscriber
var messageLogger = function ( topics, data ) {
console.log( "Logging: " + topics + ": " + data );
};
// Subscribers listen for topics they have subscribed to and
// invoke a callback function (e.g messageLogger) once a new
// notification is broadcast on that topic
var subscription = pubsub.subscribe( "inbox/newMessage", messageLogger );
// Publishers are in charge of publishing topics or notifications of
// interest to the application. e.g:
pubsub.publish( "inbox/newMessage", "hello world!" );
// or
pubsub.publish( "inbox/newMessage", ["test", "a", "b", "c"] );
// or
pubsub.publish( "inbox/newMessage", {
sender: "hello@google.com",
body: "Hey again!"
});
// We can also unsubscribe if we no longer wish for our subscribers
// to be notified
pubsub.unsubscribe( subscription );
// Once unsubscribed, this for example won't result in our
// messageLogger being executed as the subscriber is
// no longer listening
pubsub.publish( "inbox/newMessage", "Hello! are you still there?" );
例如:用戶界面通知
接下來,讓我們想象,我們有負責顯示實時股票信息的Web應用程序。
應用程序可能具有用于顯示股票統計信息和用于顯示更新的最后一個點的計數器的網格。當數據模型更改,應用程序將需要更新網格和計數器。在這種情況下,我們的主題(其將出版主題/通知)是數據模型和我們的訂戶是網格和計數器。
當我們的用戶收到該模式本身已經改變的通知,就可以相應地更新自己。
在我們的實現,我們的用戶會聽取專題“newDataAvailable”,以找出是否新的股票信息是可用的。如果一個新的通知發布到這個話題,就會引發gridUpdate一個新行添加到包含這些信息我們的網格。它也將更新上次更新計數器日志加入最后一次數據
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Return the current local time to be used in our UI later
getCurrentTime = function (){
var date = new Date(),
m = date.getMonth() + 1,
d = date.getDate(),
y = date.getFullYear(),
t = date.toLocaleTimeString().toLowerCase();
return (m + "/" + d + "/" + y + " " + t);
};
// Add a new row of data to our fictional grid component
function addGridRow( data ) {
// ui.grid.addRow( data );
console.log( "updated grid component with:" + data );
}
// Update our fictional grid to show the time it was last
// updated
function updateCounter( data ) {
// ui.grid.updateLastChanged( getCurrentTime() );
console.log( "data last updated at: " + getCurrentTime() + " with " + data);
}
// Update the grid using the data passed to our subscribers
gridUpdate = function( topic, data ){
if ( data !== undefined ) {
addGridRow( data );
updateCounter( data );
}
};
// Create a subscription to the newDataAvailable topic
var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate );
// The following represents updates to our data layer. This could be
// powered by ajax requests which broadcast that new data is available
// to the rest of the application.
// Publish changes to the gridUpdated topic representing new entries
pubsub.publish( "newDataAvailable", {
summary: "Apple made $5 billion",
identifier: "APPL",
stockPrice: 570.91
});
pubsub.publish( "newDataAvailable", {
summary: "Microsoft made $20 million",
identifier: "MSFT",
stockPrice: 30.85
});
例如:采用解耦本Alman的酒吧應用程序/ sub實現
在下面的電影評級的例子中,我們將使用本Alman的jQuery的執行發布/訂閱證明我們如何可以斷開用戶界面的。請注意如何提交評分只有發布了新的用戶和評級數據可用事實的效果。
它留給用戶對這些主題然后委托與該數據會發生什么。在我們的例子中,我們正在推動新的數據到現有數組,然后使用下劃線庫的渲染他們.template()的方法為模板。
HTML /模板
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<script id="userTemplate" type="text/html">
<li><%= name="" %=""></li>
</script>
<script id="ratingsTemplate" type="text/html">
<li><strong><%= %=""></strong> was rated <%= rating="" %="">/5</li>
</script>
<div id="container">
<div class="sampleForm">
<p>
<label for="twitter_handle">Twitter handle:</label>
<input type="text" id="twitter_handle" />
</p>
<p>
<label for="movie_seen">Name a movie you've seen this year:</label>
<input type="text" id="movie_seen" />
</p>
<p>
<label for="movie_rating">Rate the movie you saw:</label>
<select id="movie_rating">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5" selected>5</option>
</select>
</p>
<p>
<button id="add">Submit rating</button>
</p>
</div>
<div class="summaryTable">
<div id="users"><h3>Recent users</h3></div>
<div id="ratings"><h3>Recent movies rated</h3></div>
</div>
</div>
<!--%=--><!--%=--><!--%=-->
JavaScript的
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
;(function( $ ) {
// Pre-compile templates and "cache" them using closure
var
userTemplate = _.template($( "#userTemplate" ).html()),
ratingsTemplate = _.template($( "#ratingsTemplate" ).html());
// Subscribe to the new user topic, which adds a user
// to a list of users who have submitted reviews
$.subscribe( "/new/user", function( e, data ){
if( data ){
$('#users').append( userTemplate( data ));
}
});
// Subscribe to the new rating topic. This is composed of a title and
// rating. New ratings are appended to a running list of added user
// ratings.
$.subscribe( "/new/rating", function( e, data ){
if( data ){
$( "#ratings" ).append( ratingsTemplate( data ) );
}
});
// Handler for adding a new user
$("#add").on("click", function( e ) {
e.preventDefault();
var strUser = $("#twitter_handle").val(),
strMovie = $("#movie_seen").val(),
strRating = $("#movie_rating").val();
// Inform the application a new user is available
$.publish( "/new/user", { name: strUser } );
// Inform the app a new rating is available
$.publish( "/new/rating", { title: strMovie, rating: strRating} );
});
})( jQuery );
例如:解耦基于Ajax的應用程序的jQuery
在我們的最后一個例子,我們將采取在如何早期使用的Pub / Sub在發展過程中可以在以后為我們節省一些潛在的痛苦的去耦重構我們的代碼實際的樣子。
經常在Ajax的應用程序重,一旦我們收到我們要實現的不僅僅是一個唯一的作用更多的是請求的響應。人們可以簡單地增加他們的所有請求后的邏輯變成了??成功回調,但也有缺點,這種方法。
高度耦合應用有時增加重用功能所需的努力由于增加間功能/碼依賴性。這意味著,盡管保持我們在回調硬編碼請求后的邏輯,如果我們只是試圖抓住設置一次的結果可能是罰款,它時,我們要作進一步的Ajax調用同一個數據源并不適當(和不同的最終行為)無需重寫代碼多次的部分。而不是通過每個調用數據源同一層回去以后他們推廣,我們可以使用的pub / sub從一開始就和節省時間。
利用觀察員,我們還可以對不同的事件到任何粒度級別,我們感到滿意輕易單獨的應用程序范圍內的通知 - 一些東西,也可以用其它方式來優雅少做。
請注意如何我們下面的示例中,當用戶表示他們想使搜索查詢一個主題的通知是由當請求返回與實際數據可用于消費的另一個而成。它留給用戶能夠再決定如何使用這些事件的知識(或返回的數據)。這樣做的好處是,如果我們想,我們可以有利用不同的方式返回,但至于阿賈克斯層而言,它并不關心數據10個不同的用戶。它的唯一責任是請求并返回數據,然后把它傳遞給誰想來使用它。這樣的事務分離可以使我們的代碼少許清潔劑的整體設計。
HTML /模板:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form id="flickrSearch">
<input type="text" name="tag" id="query"/>
<input type="submit" name="submit" value="submit"/>
</form>
<div id="lastQuery"></div>
<ol id="searchResults"></ol>
<script id="resultTemplate" type="text/html">
<% _.each(items,="" function(="" item="" ){="" %="">
<li><img src="<%= item.media.m="" %="">"/></li>
<% });%="">
</script>
<!--%--><!--%=--><!--%-->
JavaScript的:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
;(function( $ ) {
// Pre-compile template and "cache" it using closure
var resultTemplate = _.template($( "#resultTemplate" ).html());
// Subscribe to the new search tags topic
$.subscribe( "/search/tags", function( e, tags ) {
$( "#lastQuery" )
.html("<p>Searched for:<strong>" + tags + "</strong></p>");
});
// Subscribe to the new results topic
$.subscribe( "/search/resultSet", function( e, results ){
$( "#searchResults" ).empty().append(resultTemplate( results ));
});
// Submit a search query and publish tags on the /search/tags topic
$( "#flickrSearch" ).submit( function( e ) {
e.preventDefault();
var tags = $(this).find( "#query").val();
if ( !tags ){
return;
}
$.publish( "/search/tags", [ $.trim(tags) ]);
});
// Subscribe to new tags being published and perform
// a search query using them. Once data has returned
// publish this data for the rest of the application
// to consume
$.subscribe("/search/tags", function( e, tags ) {
$.getJSON( "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?", {
tags: tags,
tagmode: "any",
format: "json"
},
function( data ){
if( !data.items.length ) {
return;
}
$.publish( "/search/resultSet", { items: data.items } );
});
});
})( jQuery );
觀察者模式是在應用程序設計解耦許多不同的情況下非常有用,如果你還沒有使用它了,我建議拿起今天提到的預先寫好的實現方式之一,只是給它一個嘗試。這是比較容易的設計模式上手之一,但也是最有實力的一個。
調解模式
在Observer模式的部分,我們介紹了通過一個單一的對象竄多個事件源的方式。這也被稱為發布/訂閱或事件匯總。是很常見的,當面對這個問題開發商認為調解員,所以讓我們來看看它們的區別。
這本字典是指調解員作為中立的一方,在談判和解決沖突助攻。在我們的世界中,介體是一種行為的設計圖案,使我們能夠露出,通過該系統的不同部分可能進行通信的統一接口。
如果它出現一個系統具有部件之間有太多的直接關系,這可能是時間為具有部件通過代替通信控制的中心點。調解員促進通過確保,而不是稱呼對方明確的組件,它們的交互是通過這個中心的處理松耦合。這可以幫助我們分離系統和提高組件重用的可能性。
一個真實世界的比喻可能是一個典型的機場交通控制系統。塔(中保)處理什么飛機可以起飛和降落,因為所有通信(通知被監聽出或廣播)從平面做控制塔,而不是從平面到平面。集中式控制器的關鍵是這個系統的成功,這就是真正的軟件設計一個調解員發揮的作用。
另一個比喻是DOM事件冒泡和事件代表團。如果系統中的所有訂閱對文檔進行,而非單獨的節點,該文件有效地充當調停人。代替結合到單個節點的事件,有較高水平的對象給出通知有關的交互事件的用戶的責任。
當涉及到中介和事件聚合模式,也有一些時候它可能看起來像模式是可以互換的,由于實施相似之處。然而,這些模式的語義和意圖是非常不同的。
而且即使實現都使用一些相同的核心結構,我相信他們之間有一個明顯的區別。我還認為它們不應該被互換或在通信混淆的,因為差異。
一個簡單的中介
調解員是協調多個對象之間的交互(邏輯和行為)的對象。它使上何時調用哪些對象的基礎上,其它目的和輸入的動作(或不采取)的決定。
您可以使用一個單一的代碼行寫的調解人:
?
1
var mediator = {};
是的,當然這只是一個對象在JavaScript中的文字。再次,我們在這里談論的語義。調停的目的是控制對象之間的工作流程,我們真的不需要什么比對象文本來做到這一點了。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var orgChart = {
addNewEmployee: function(){
// getEmployeeDetail provides a view that users interact with
var employeeDetail = this.getEmployeeDetail();
// when the employee detail is complete, the mediator (the 'orgchart' object)
// decides what should happen next
employeeDetail.on("complete", function(employee){
// set up additional objects that have additional events, which are used
// by the mediator to do additional things
var managerSelector = this.selectManager(employee);
managerSelector.on("save", function(employee){
employee.save();
});
});
},
// ...
}
這個例子顯示了一些實用的方法,可以觸發并訂閱事件調解對象的一個??非常基本實現。
我經常提到的這種類型的對象是在過去“工作流”的對象,但事實是,它是一個中介。它是處理許多其他對象之間的工作流程,聚合的該工作流知識的責任成一個單一的對象的對象。其結果是工作流更容易理解和維護。
異同
還有,毫無疑問,我已經在這里顯示的事件聚合和調解員的例子之間的相似性。這些相似歸結為兩個主要項目:事件和第三方的對象。這些差異充其量膚淺的,雖然。當我們挖入圖案的意圖和看到,實施方式可以顯著地不同,圖案的性質變得更明顯。
活動
兩個事件聚合和介體使用事件,在上述的例子。事件聚合與事件顯然涉及 - 它在名稱后的所有。調解員只使用事件,因為它使生活容易與現代的JavaScript web應用框架的時候。沒有什么說的調解人必須用事件來構建。你可以建立回調方法調停,靠,或任何其他的一些手段交給調解員參考子對象。
差,那么,是為什么這兩個模式都使用事件。事件聚合器,作為圖案,被設計來處理事件。調解員,雖然只使用它們,因為它的方便。
第三方對象
這兩個事件聚合和調解員,在設計上,采用了第三方對象,以方便的事情。本次活動聚集本身是一個第三方的事件發布者和事件訂閱。它作為一個中心樞紐事件通過。介體也是第三方其它的目的,雖然。那么,是區別?我們為什么不叫事件聚合調解員?答案在很大程度上歸結到應用程序邏輯和工作流進行編碼。
在一個事件聚合的情況下,第三方對象是存在的只是從一個未知的數源事件的直通,促進一個未知的數字處理程序。需要被踢掉所有工作流和業務邏輯被直接放入觸發事件和處理事件的對象的對象。
在調解員的情況下,雖然,業務邏輯和工作流聚合到中介本身。介決定當一個對象應該叫其方法和屬性更新的基礎上,該介體知道的因素。它封裝了工作流程和過程,協調多個對象,以產生所期望的系統行為。參與這一工作流程中的各個對象的每個知道如何執行自己的任務。但它告訴對象時,在高于單個對象更高層次的決策執行的任務的調解人。
事件聚合方便了“射后不理”的溝通模式。觸發事件的對象,如果有任何用戶并不關心。它只是觸發事件和動作上。調解員,雖然可以使用事件作出決定,但它絕對不是“發射后不管”。介體關注一組已知的輸入或活動的,以便它可以促進和協調與一組已知的行動者??(對象)的附加行為。
關系:當要使用哪個
了解一個事件聚合和中介之間的異同是語義重要的原因。了解什么時候使用哪一種模式,雖然它是同樣重要的。基本的語義和模式的意圖并告知使用模式將幫助您了解更多細微的問題和必須作出細致入微的決定時的問題,但實際體驗。
事件聚合使用
一般情況下,一個事件聚合器是用來當你要么有太多的對象,直接聽,或者你有一個完全不相關的對象。
當兩個對象有直接的關系已經 - 說,父視圖和子視圖 - 則可能是使用一個事件聚合沒有什么好處。讓孩子認為觸發一個事件與父視圖可以處理該事件。在JavaScript框架而言,這是最常見于骨干的收集和型號,所有型號的事件冒泡并通過其父集合。一件收藏品經常使用模型事件來修改自己或其他型號的狀態。集合中的處理“中選擇”項目就是一個很好的例子。
jQuery的方法上作為一個事件聚合器是太多的對象來聽一個很好的例子。如果你有一個可以觸發一個“點擊”事件10,20或200 DOM元素,這可能是一個壞主意,設立一個監聽器上所有的人單獨。這可能會迅速惡化的應用和用戶體驗性能。相反,使用jQuery的上方法允許我們聚集所有的事件,減少的10,20,或200的事件處理程序的開銷降低到1。
間接的關系也是一個偉大的時間使用事件聚合。在現代的應用程序,這是很常見的有需要通信,但沒有直接的關系的多個視圖的對象。例如,一個菜單系統可能具有處理該菜單項的點擊次數的圖。但是,我們不希望在菜單中直接連接到顯示所有的細節和信息,單擊菜單項時內容的看法。具有連接在一起的內容和菜單會使代碼很難維持,從長遠來看。相反,我們可以使用事件聚合器來觸發“菜單:單擊:foo”的事件,并有一個“foo”的對象處理click事件,以顯示其屏幕上的內容。
使用中保
當兩個或多個對象有間接的工作關系的介體最好的應用,和業務邏輯或工作流需要決定這些對象的交互和協作。
一個向導界面就是一個很好的例子,如圖所示的“組織結構圖”的例子,上面。有促進向導的整個工作流程多個視圖。而不是讓他們直接引用對方死死的觀點耦合在一起,我們就可以去耦,并通過引入中介更明確它們之間的工作流模型。
調解員提取從實施細則的工作流程和在更高層次上產生更自然的抽象,我們展示以更快便知的工作流程是什么。我們不再需要深入到工作流程中的每個視圖的細節,看看有什么工作流程實際上是。
事件聚合器(發布/訂閱)和中介一起
一個事件聚合和介體,以及為什么這些圖案名字不應彼此互換之間的差的核心,是通過說明如何可以將它們一起使用示出最好的。一個事件聚合器菜單的例子是引入調解員以及完美的地方。
點擊菜單項可能會引發一系列的整個應用程序中的變化。其中的一些變化將是獨立于其它的,并且使用一個事件聚合為這是有意義的。其中一些變化可能是內部互相關聯,不過,可以使用介質制定這些變化。
調解員的話,可以設置為監聽事件聚合器。它可以運行它的邏輯和處理,以促進和坐標彼此相關的多個對象,但無關的原始事件源。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var MenuItem = MyFrameworkView.extend({
events: {
"click .thatThing": "clickedIt"
},
clickedIt: function(e){
e.preventDefault();
// assume this triggers "menu:click:foo"
MyFramework.trigger("menu:click:" + this.model.get("name"));
}
});
// ... somewhere else in the app
var MyWorkflow = function(){
MyFramework.on("menu:click:foo", this.doStuff, this);
};
MyWorkflow.prototype.doStuff = function(){
// instantiate multiple objects here.
// set up event handlers for those objects.
// coordinate all of the objects into a meaningful workflow.
};
在這個例子中,被點擊以正確的模式的菜單項時,該“menu:click:foo”事件將被觸發。在“MyWorkflow”對象的實例,假設一個已經實例化,將處理這一特定事件,并協調所有它知道的物體,以創建所需的用戶體驗和工作流程。
一個事件聚合和介體已被組合以創建在代碼和應用程??序本身既更加有意義的經驗。我們現在有通過事件聚合器的菜單和工作流程之間的清晰的分離,我們仍保持工作流本身的清潔和維護通過使用調解人。
優勢與劣勢
中保圖案的最大好處是它減少了從許多系統中的對象或組件之間所需的許多只多對一通信信道。添加新的發布者和訂閱相對容易由于解耦本的水平。
也許使用模式最大的缺點是,它可以引入單一故障點。配售模塊之間的協調也可能會導致性能的下降,因為他們總是間接地通信。由于松散耦合的性質,很難建立一個怎樣的系統可能只在看廣播反應。
這就是說,要提醒自己,分離的系統有一些其他的好處是有用的 - 如果我們的模塊直接互相溝通,更改模塊(例如,另一個模塊拋出異常)可以很容易地對我們的應用程序的其它部分的多米諾骨牌效應。這個問題是以下,相分離的系統一個關注的問題。
在一天結束的時候,緊耦合引起各種頭痛,這是另一種替代解決方案,但一個,如果正確實施,可以很好地工作。
中保比。正面
我們將很快覆蓋Facade模式,而是以供參考一些開發商也可能會問,是否有中保和門面模式之間的相似之處。他們這樣做既抽象現有模塊的功能,但也有一些細微的差別。
調解員集中在那里進行了明確由這些模塊引用的模塊之間的通信。在某種意義上,這是多向。門面但是只是定義了一個簡單的界面,模塊或系統,但不會增加任何額外的功能。系統中的其他模塊是不直接知道的外觀的概念,并且可以考慮單向的。
原型模式
GoF的參考原型模式是一種能夠創建基于克隆通過現有對象的模板對象。
我們可以把原型模式,因為是基于我們創建一個作為原型的其他對象的對象原型繼承。原型對象本身有效地用作藍本每個對象的構造函數創建。如果所使用的構造函數的原型含有一種叫物業name為例(按代碼示例低了下去),然后通過同樣的構造函數創建的每個對象也會有同樣的性質。
檢討現有(非JavaScript),文學這種模式的定義,我們可以再次找到對類的引用。現實情況是,原型繼承避免使用類干脆。沒有一個“定義”對象,也不在理論上一個核心對象。我們簡單地創建現有的功能對象的副本。
對使用的原型模式的好處是,我們正在與原型優勢的JavaScript所提供的本地,而不是試圖去模仿其他語言的特色工作。與其它的設計模式,這并非總是如此。
不僅是模式來實現繼承的一種簡單的方法,但它也可以配備了性能提升,以及:一個對象定義一個函數的時候,他們都通過引用創建(使所有子對象指向同一個函數)而不是創建自己的個人副本。
對于那些有興趣,真正的原型繼承,如5的ECMAScript標準定義,需要使用Object.create(這是我們以前在本節前面介紹過)。要提醒自己,Object.create創建了一個具有特定原型的對象,并可選地包含指定的屬性,以及(例如Object.create( prototype, optionalDescriptorObjects ))。
我們可以看到這足以證明在下面的例子:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var myCar = {
name: "Ford Escort",
drive: function () {
console.log( "Weeee. I'm driving!" );
},
panic: function () {
console.log( "Wait. How do you stop this thing?" );
}
};
// Use Object.create to instantiate a new car
var yourCar = Object.create( myCar );
// Now we can see that one is a prototype of the other
console.log( yourCar.name );
Object.create也可以讓我們輕松實現先進的概念,如繼承差的對象在哪里都能夠直接從其他對象繼承。我們在前面看到的那Object.create讓我們來初始化使用第二個提供的參數對象屬性。例如:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var vehicle = {
getModel: function () {
console.log( "The model of this vehicle is.." + this.model );
}
};
var car = Object.create(vehicle, {
"id": {
value: MY_GLOBAL.nextId(),
// writable:false, configurable:false by default
enumerable: true
},
"model": {
value: "Ford",
enumerable: true
}
});
這里的屬性可以對第二個參數進行初始化Object.create使用對象文本與類似于由使用的語法Object.defineProperties和Object.defineProperty我們看著前面的方法。
值得注意的是枚舉對象的屬性時,和原型關系可以帶來麻煩(如克羅克福德建議)包裹循環的內容中hasOwnProperty()檢查。
如果我們希望實現的原型模式而不直接使用Object.create,我們可以模擬模式按照上面的例子如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var vehiclePrototype = {
init: function ( carModel ) {
this.model = carModel;
},
getModel: function () {
console.log( "The model of this vehicle is.." + this.model);
}
};
function vehicle( model ) {
function F() {};
F.prototype = vehiclePrototype;
var f = new F();
f.init( model );
return f;
}
var car = vehicle( "Ford Escort" );
car.getModel();
注意:這種替代不允許用戶以相同的方式定義只讀屬性(作為vehiclePrototype可能如果不小心被改變)。
最后一種選擇的實現原型模式可能是以下幾點:
?
1
2
3
4
五
6
7
8
9
var beget = (function () {
function F() {}
return function ( proto ) {
F.prototype = proto;
return new F();
};
})();
人們可以從引用此方法vehicle的功能。但請注意,vehicle這里是模擬一個構造函數,因為原型模式不包括初始化任何概念超越的對象鏈接到一個原型。
命令模式
Command模式的目的是封裝方法調用,請求或操作成一個對象,并給了我們兩個參數的能力,并通過方法調用周圍,可以在我們的判斷來執行。此外,它使我們能夠分離對象從其中實現它們的對象上調用的動作,使我們的整體靈活性在換出混凝土更大程度的類(對象)。
混凝土類在基于類的編程語言方面最好的解釋,并與抽象類的想法。一個抽象類定義了一個接口,但并不一定對所有的其成員函數提供實現。它作為從該其他衍生的基類。它實現了缺失的功能派生類稱為具體類。
命令模式背后的一般思路是,它提供了我們分離發出從任何執行命令的命令,委派這個責任不同的對象,而不是的職責的裝置。
實施明智的,簡單的命令對象綁定在一起既是一個行動,并希望調用操作的對象。他們始終包括執行操作(如run()或execute())。根據需要具有相同接口的所有命令的對象可以容易地被交換,這被認為是該圖案的更大的好處之一。
為了證明我們將創建一個簡單的購車服務Command模式。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function(){
var carManager = {
// request information
requestInfo: function( model, id ){
return "The information for " + model + " with ID " + id + " is foobar";
},
// purchase the car
buyVehicle: function( model, id ){
return "You have successfully purchased Item " + id + ", a " + model;
},
// arrange a viewing
arrangeViewing: function( model, id ){
return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
}
};
})();
以一看上面的代碼,這將是微不足道的調用我們carManager通過直接訪問對象的方法。我們都可以原諒的想法有沒有錯-在技術上,它是完全有效的JavaScript。然而有場景中,這可能是不利的。
例如,想象一下,如果背后的核心API的carManager變化。這將需要直接訪問我們的應用程序中,這些方法也可以修改的所有對象。這可以被看作是耦合層,有效地違背盡可能松散耦合對象的面向對象的方法。相反,我們可以通過遠進一步抽象的API解決這個問題。
現在讓我們來擴展我們carManager,使我們在命令模式應用成果如下:接受可以在上執行任何命名方法carManager的對象,以及可能用于諸如汽車模型和ID的任何數據傳遞。
這是我們想怎么能夠實現:
?
1
carManager.execute( "buyVehicle", "Ford Escort", "453543" );
按照這種結構,我們現在應該增加一個定義為carManager.execute方法如下:
?
1
2
3
carManager.execute = function ( name ) {
return carManager[name] && carManager[name].apply( carManager, [].slice.call(arguments, 1) );
};
我們的最終樣本通話將因此如下所示:
?
1
2
3
4
carManager.execute( "arrangeViewing", "Ferrari", "14523" );
carManager.execute( "requestInfo", "Ford Mondeo", "54323" );
carManager.execute( "requestInfo", "Ford Escort", "34232" );
carManager.execute( "buyVehicle", "Ford Escort", "34232" );
Facade模式
當我們提出了一個門面,我們提出了一個外觀的世界可能掩蓋一個非常不同的現實。這是我們要檢討下一個模式背后的名字靈感 - Facade模式。這種模式提供了一個方便的高級接口的代碼較大的機身,隱藏其真正的潛在的復雜性。把它看成是簡化了被提交給其他開發者的API,東西幾乎總是提高了可用性。
外立面是可以經常在JavaScript庫像jQuery在這里可以看出,雖然實現可支持具有廣泛的行為,只是一個“門面”,或這些方法的有限抽象的方法呈現給公眾使用的結構模式。
這使我們能夠與門面,而不是直接在幕后子系統進行交互。每當我們使用jQuery的$(el).css()或$(el).animate()方法,我們實際上使用的是門面-簡單的公共接口,避免我們不得不手動調用得到一些行為的工作要求的jQuery核心的許多內部的方法。這也避免了需要手動的DOM API互動,保持狀態變量。
jQuery的核心方法,應考慮中間抽象。更直接的負擔,開發商是DOM API和外墻是什么使jQuery庫很容易使用。
要建立什么我們學到,Facade模式既簡化了類的接口,它也從使用它的代碼分離出來的類。這給我們的方式,有時可以比直接訪問子系統不容易出現誤差的子系統來間接相互作用的能力。門面的優點包括易于使用和實施模式通常體積小,占用空間。
讓我們來看看在行動模式。這是一個未經優化的代碼示例,但是在這里我們利用門面簡化的接口用于監聽事件的跨瀏覽器。我們通過創建可在一個人的代碼,這確實為特征的存在檢查,以便它可以提供安全和跨瀏覽器兼容的解決方案的任務中使用的常用方法做到這一點。
?
1
2
3
4
五
6
7
8
9
10
11
var addMyEvent = function( el,ev,fn ){
if( el.addEventListener ){
el.addEventListener( ev,fn, false );
}else if(el.attachEvent){
el.attachEvent( "on" + ev, fn );
} else{
el["on" + ev] = fn;
}
};
以類似的方式,我們都熟悉的jQuery的$(document).ready(..)。在內部,這實際上是被供電由稱為方法bindReady(),它是這樣做的:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
bindReady: function() {
...
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
document.attachEvent( "onreadystatechange", DOMContentLoaded );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
...
這是一個正面,在世界的其余部分只是使用由露出的有限接口的另一個例子$(document).ready(..)和更復雜的實現供電其保持從視線隱藏。
外立面不只是必須對自己使用的,但是。它們也可以與其他模式集成諸如模塊圖案。正如我們下面可以看到,我們的模塊模式的實例包含許多已私下定義的方法。然后一個門面來一個更簡單的API提供給訪問這些方法:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
var module = (function() {
var _private = {
i: 5,
get: function() {
console.log( "current value:" + this.i);
},
set: function( val ) {
this.i = val;
},
run: function() {
console.log( "running" );
},
jump: function(){
console.log( "jumping" );
}
};
return {
facade: function( args ) {
_private.set(args.val);
_private.get();
if ( args.run ) {
_private.run();
}
}
};
}());
// Outputs: "current value: 10" and "running"
module.facade( {run: true, val: 10} );
在這個例子中,主叫module.facade()確實會觸發一組私人行為模塊內,但再次,用戶并不關心這一點。我們已經取得了更容易讓他們消耗的功能,而不需要擔心的是執行層面的細節。
在抽象的注意事項
外立面一般有一些缺點,但有擔心值得注意的是性能。即,必須確定是否存在到抽象隱式成本門面提供給我們的實現,并且如果是這樣,這成本是否正當。回過頭來看看這個jQuery庫,我們大多數人都知道這兩個getElementById("identifier")和$("#identifier")可用于通過其ID來查詢頁面上的元素。
你知道那不過getElementById()對自己顯著快是數量級的高階?看看這個jsPerf測試,看看在每個瀏覽器級別的結果:http://jsperf.com/getelementbyid-vs-jquery-id。現在當然,我們必須記住,jQuery的(和灒-它的選擇器引擎)都做了很多幕后來優化我們的查詢(和jQuery對象,則返回不僅僅是一個DOM節點)。
與此特定門面的挑戰是,為了提供能夠接受并解析多種類型的查詢的優雅選擇器功能,還有抽象隱式成本。用戶不需要訪問jQuery.getById("identifier")或jQuery.getByClass("identifier")等等。這就是說,在權衡性能已經在實踐中多年來的測試,并給出jQuery的成功,簡單的外觀其實還算不錯的球隊。
當使用模式,盡量了解所涉及的任何性能成本,使他們是否值得抽象所提供的級別的呼叫。
工廠模式
工廠模式是關心創建對象的概念,另一個創建模式。在那里從在其類別中的其他模式不同的是,它沒有明確要求我們使用構造。相反,工廠可以提供用于創建對象,在這里我們可以指定希望創建工廠對象的類型的通用接口。
試想一下,我們有一個UI工廠,我們被要求創建一個類型的UI組件。而不是直接創建這個組件使用new運營商或通過其他造物的構造函數,我們要求工廠對象為一個新的組件,而不是。我們告知需要什么(例如“按鈕”,“小組”)的對象類型的工廠,并實例化這一點,就返回到我們的使用。
這是特別有用的,如果對象創建過程比較復雜,例如,如果它強烈地依賴于動態因素或應用程序配置。
這種模式的例子可以在UI庫中找到,如ExtJS的,其中,用于創建對象或組件的方法可以進一步子類。
以下是建立在我們以前使用構造圖形邏輯來定義汽車片段的例子。它演示了如何一個汽車廠可以使用工廠模式來實現:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Types.js - Constructors used behind the scenes
// A constructor for defining new cars
function Car( options ) {
// some defaults
this.doors = options.doors || 4;
this.state = options.state || "brand new";
this.color = options.color || "silver";
}
// A constructor for defining new trucks
function Truck( options){
this.state = options.state || "used";
this.wheelSize = options.wheelSize || "large";
this.color = options.color || "blue";
}
// FactoryExample.js
// Define a skeleton vehicle factory
function VehicleFactory() {}
// Define the prototypes and utilities for this factory
// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car;
// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function ( options ) {
switch(options.vehicleType){
case "car":
this.vehicleClass = Car;
break;
case "truck":
this.vehicleClass = Truck;
break;
//defaults to VehicleFactory.prototype.vehicleClass (Car)
}
return new this.vehicleClass( options );
};
// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
vehicleType: "car",
color: "yellow",
doors: 6 } );
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console.log( car instanceof Car );
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log( car );
方法1:修改VehicleFactory實例以使用卡車類
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
var movingTruck = carFactory.createVehicle( {
vehicleType: "truck",
state: "like new",
color: "red",
wheelSize: "small" } );
// Test to confirm our truck was created with the vehicleClass/prototype Truck
// Outputs: true
console.log( movingTruck instanceof Truck );
// Outputs: Truck object of color "red", a "like new" state
// and a "small" wheelSize
console.log( movingTruck );
方法2:子類VehicleFactory創建一個工廠類,建立卡車
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
function TruckFactory () {}
TruckFactory.prototype = new VehicleFactory();
TruckFactory.prototype.vehicleClass = Truck;
var truckFactory = new TruckFactory();
var myBigTruck = truckFactory.createVehicle( {
state: "omg..so bad.",
color: "pink",
wheelSize: "so big" } );
// Confirms that myBigTruck was created with the prototype Truck
// Outputs: true
console.log( myBigTruck instanceof Truck );
// Outputs: Truck object with the color "pink", wheelSize "so big"
// and state "omg. so bad"
console.log( myBigTruck );
當使用工廠模式
當應用于下列情形工廠模式可以是特別有用:
當我們的對象或組件設置涉及復雜的高水平
當我們需要方便地生成依賴于我們在環境中的對象的不同實例
當我們有許多小對象或組件的工作共享相同的屬性
在編寫與其他物體僅需要滿足API契約的實例對象(又名鴨打字)工作。這是去耦有用。
當不使用工廠模式
當應用到錯誤類型的問題,這種模式可以引入復雜的不必要的大量工作的應用程序。除非提供創建對象的接口是圖書館或框架的設計目標,我們正在寫,我會建議堅持明確的構造函數,以避免不必要的開銷。
由于這樣的事實,對象創建的過程中有效抽象接口后面,這也可以引入具有取決于這一過程可能只是多么復雜的單元測試的問題。
摘要工廠
也是有用要意識到的抽象工廠模式,其目的是封裝一組個別工廠的一個共同的目標。它分離實施從他們一般使用一組對象的細節。
一個抽象工廠應或用在一個系統必須獨立于產生它創建的對象的方式,它需要與多個對象類型的工作。
這是既簡單又容易理解的例子是一個汽車廠,它定義的方式來獲取或登記的車輛類型。抽象工廠可以被命名abstractVehicleFactory。抽象工廠將允許的類型,如“汽車”或“卡車”和混凝土工廠車輛的定義將實施僅滿足車輛合同(例如類Vehicle.prototype.drive和Vehicle.prototype.breakDown)。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
var abstractVehicleFactory = (function () {
// Storage for our vehicle types
var types = {};
return {
getVehicle: function ( type, customizations ) {
var Vehicle = types[type];
return (Vehicle ? new Vehicle(customizations) : null);
},
registerVehicle: function ( type, Vehicle ) {
var proto = Vehicle.prototype;
// only register classes that fulfill the vehicle contract
if ( proto.drive && proto.breakDown ) {
types[type] = Vehicle;
}
return abstractVehicleFactory;
}
};
})();
// Usage:
abstractVehicleFactory.registerVehicle( "car", Car );
abstractVehicleFactory.registerVehicle( "truck", Truck );
// Instantiate a new car based on the abstract vehicle type
var car = abstractVehicleFactory.getVehicle( "car", {
color: "lime green",
state: "like new" } );
// Instantiate a new truck in a similar manner
var truck = abstractVehicleFactory.getVehicle( "truck", {
wheelSize: "medium",
color: "neon yellow" } );
的混入模式
在傳統的編程語言如C ++和Lisp,混入是它提供可通過子類的子類或一組很容易地繼承了功能重復使用的目的功能的類。
子類
對于開發人員不熟悉子類,我們將通過一個簡短的初學者底漆他們潛入混入和裝飾前進一步。
子分級是指繼承屬性,用于從基或一個新的對象一個術語超類對象。在傳統的面向對象的程序設計,一類B是能延長另一個類A。這里我們考慮A一個超類和B子類A。因此,所有實例B繼承的方法A。B然而仍能夠定義自己的方法,包括最初定義的那些重寫方法A。
如果B需要調用的方法A已被重寫,我們將此稱為方法鏈接。如果B需要調用構造函數A(超類),我們稱此構造鏈接。
為了證明子分級,我們首先需要一個基礎對象,可以有創建的本身新的實例。讓我們建模此周圍的人的概念。
?
1
2
3
4
五
6
7
var Person = function( firstName, lastName ){
this.firstName = firstName;
this.lastName = lastName;
this.gender = "male";
};
接下來,我們將要指定一個新的類(對象),這是現有的子類Person對象。讓我們想象一下,我們要添加不同的特性來區分一個Person從Superhero而繼承的財產Person“超”。作為超級英雄與正常的人(如姓名,性別等)有著許多共同的特征,這應該有希望說明子類是如何工作的充分。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// a new instance of Person can then easily be created as follows:
var clark = new Person( "Clark", "Kent" );
// Define a subclass constructor for for "Superhero":
var Superhero = function( firstName, lastName, powers ){
// Invoke the superclass constructor on the new object
// then use .call() to invoke the constructor as a method of
// the object to be initialized.
Person.call( this, firstName, lastName );
// Finally, store their powers, a new array of traits not found in a normal "Person"
this.powers = powers;
};
Superhero.prototype = Object.create( Person.prototype );
var superman = new Superhero( "Clark", "Kent", ["flight","heat-vision"] );
console.log( superman );
// Outputs Person attributes as well as powers
該Superhero構造函數創建從繼承的對象Person。這種類型的對象具有在它上面鏈中的對象的屬性,如果我們已在設置默認值Person對象,Superhero是能夠與特定于它的對象的值覆蓋任何繼承的值。
混入
在JavaScript中,我們可以看看從混入繼承通過擴展功能收集的手段。我們定義每個新對象都有一個原型從它可以繼承進一步屬性。原型可以從其他對象的原型繼承,但更重要的是,可以為任意數量的對象實例定義屬性。我們可以利用這一點來促進功能的再利用。
混入允許對象從它們借(或繼承)的功能與復雜性的最小量。由于該模式運行良好與JavaScript的對象原型,它使我們從不只是一個混入共享功能相當靈活的方式,而是通過多重繼承有效很多。
它們可以被看作是可跨多個其他對象原型容易地共用屬性和方法的對象。試想一下,我們定義包含在一個標準的對象字面如下效用函數一個mixin:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
var myMixins = {
moveUp: function(){
console.log( "move up" );
},
moveDown: function(){
console.log( "move down" );
},
stop: function(){
console.log( "stop! in the name of love!" );
}
};
然后,我們可以輕松地擴展現有的構造函數的原型使用的輔助,如Underscore.js包括此行為_.extend()方式:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// A skeleton carAnimator constructor
function CarAnimator(){
this.moveLeft = function(){
console.log( "move left" );
};
}
// A skeleton personAnimator constructor
function PersonAnimator(){
this.moveRandomly = function(){ /*..*/ };
}
// Extend both constructors with our Mixin
_.extend( CarAnimator.prototype, myMixins );
_.extend( PersonAnimator.prototype, myMixins );
// Create a new instance of carAnimator
var myAnimator = new CarAnimator();
myAnimator.moveLeft();
myAnimator.moveDown();
myAnimator.stop();
// Outputs:
// move left
// move down
// stop! in the name of love!
正如我們所看到的,這允許在公共行為我們很容易地“混”進對象構造相當平凡。
在下面的例子中,我們有兩個構造函數:一輛汽車,一個mixin。我們現在要做的是增加(話說延長另一種方式)的車,所以它能夠繼承的混入,即確定具體方法driveForward()和driveBackward()。這一次,我們不會使用Underscore.js。
相反,這個例子將演示如何擴充構造,包括功能,而不需要重復這個過程,我們可以有充分的構造函數。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Define a simple Car constructor
var Car = function ( settings ) {
this.model = settings.model || "no model provided";
this.color = settings.color || "no colour provided";
};
// Mixin
var Mixin = function () {};
Mixin.prototype = {
driveForward: function () {
console.log( "drive forward" );
},
driveBackward: function () {
console.log( "drive backward" );
},
driveSideways: function () {
console.log( "drive sideways" );
}
};
// Extend an existing object with a method from another
function augment( receivingClass, givingClass ) {
// only provide certain methods
if ( arguments[2] ) {
for ( var i = 2, len = arguments.length; i < len; i++ ) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
}
// provide all methods
else {
for ( var methodName in givingClass.prototype ) {
// check to make sure the receiving class doesn't
// have a method of the same name as the one currently
// being processed
if ( !Object.hasOwnProperty.call(receivingClass.prototype, methodName) ) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
// Alternatively (check prototype chain as well):
// if ( !receivingClass.prototype[methodName] ) {
// receivingClass.prototype[methodName] = givingClass.prototype[methodName];
// }
}
}
}
// Augment the Car constructor to include "driveForward" and "driveBackward"
augment( Car, Mixin, "driveForward", "driveBackward" );
// Create a new Car
var myCar = new Car({
model: "Ford Escort",
color: "blue"
});
// Test to make sure we now have access to the methods
myCar.driveForward();
myCar.driveBackward();
// Outputs:
// drive forward
// drive backward
// We can also augment Car to include all functions from our mixin
// by not explicitly listing a selection of them
augment( Car, Mixin );
var mySportsCar = new Car({
model: "Porsche",
color: "red"
});
mySportsCar.driveSideways();
// Outputs:
// drive sideways
優點缺點
混入協助降低功能重復和增加的系統功能的再利用。凡有申請可能需要跨對象實例共享的行為,我們可以很容易地通過保持一個mixin這個共享功能,從而專注于實現只在我們的系統,這是真正獨特的功能避免任何重復。
這就是說,缺點是混入多一點值得商榷。一些開發商認為,注入功能集成到一個對象的原型是一個壞主意,因為它會導致兩種原型污染和不確定性,關于我們的功能原點的水平。在大型系統中,這可能是這種情況。
我認為,強大的文檔可以在最大限度地減少混亂關于混合函數源量協助,但與每一個模式,如果護理實施過程中采取了我們應該沒問題。
修飾模式
裝飾是旨在促進代碼復用結構設計模式。類似混入,它們可以被認為是另一種可行的替代對象子分級。
經典,裝飾提供給系統中的動態行為添加到現有類的能力。當時的想法是,該裝飾本身并不是對類的基本功能必不可少,否則會被烤成超本身。
它們可以被用來修改在這里我們要添加額外的功能,對象,而不需要使用它們大量修改底層代碼的現有系統。為什么開發人員使用他們的常見原因是他們的應用程序可能包含要求不同類型的對象量大的特點。想象一下,不必定義數百個不同的對象構造為說,一個JavaScript游戲。
對象的構造函數可以代表不同的球員 ??類型,每種類型具有不同的功能。一個指環王游戲可能需要構造函數Hobbit,Elf,Orc,Wizard,Mountain Giant,Stone Giant等等,但很容易被數以百計的這些。如果我們再在能力因素,試想不得不為能力的類型如的每個組合創建子類HobbitWithRing,HobbitWithSword,HobbitWithRingAndSword等。本是不是很實用,肯定是不可控的時候,我們在越來越多的不同能力的因素。
Decorator模式是不是嚴重依賴于如何創建對象,而是專注于擴展其功能的問題。而不是僅僅依賴于原型繼承,我們使用一個基本對象工作,并逐步添加裝飾物而提供額外的功能。我們的想法是,與其子類中,我們添加(裝飾)屬性或方法到基本對象,所以它是一個小更精簡。
添加新的屬性在JavaScript對象是一個非常簡單的過程所以考慮到這一點,一個非常簡單的裝飾可以實現如下:
例1:裝飾的構造新功能
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// A vehicle constructor
function Vehicle( vehicleType ){
// some sane defaults
this.vehicleType = vehicleType || "car";
this.model = "default";
this.license = "00000-000";
}
// Test instance for a basic vehicle
var testInstance = new Vehicle( "car" );
console.log( testInstance );
// Outputs:
// vehicle: car, model:default, license: 00000-000
// Lets create a new instance of vehicle, to be decorated
var truck = new Vehicle( "truck" );
// New functionality we're decorating vehicle with
truck.setModel = function( modelName ){
this.model = modelName;
};
truck.setColor = function( color ){
this.color = color;
};
// Test the value setters and value assignment works correctly
truck.setModel( "CAT" );
truck.setColor( "blue" );
console.log( truck );
// Outputs:
// vehicle:truck, model:CAT, color: blue
// Demonstrate "vehicle" is still unaltered
var secondInstance = new Vehicle( "car" );
console.log( secondInstance );
// Outputs:
// vehicle: car, model:default, license: 00000-000
這種類型的簡單執行的是功能性的,但它并沒有真正表現出所有的力量裝飾所提供的。對于這一點,我們首先要經過我的咖啡例子的變化從所謂的優秀圖書深入淺出設計模式弗里曼,Sierra和貝茨,這是圍繞一臺Macbook購買建模。
例2:裝飾對象使??用多個裝飾
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// The constructor to decorate
function MacBook() {
this.cost = function () { return 997; };
this.screenSize = function () { return 11.6; };
}
// Decorator 1
function memory( macbook ) {
var v = macbook.cost();
macbook.cost = function() {
return v + 75;
};
}
// Decorator 2
function engraving( macbook ){
var v = macbook.cost();
macbook.cost = function(){
return v + 200;
};
}
// Decorator 3
function insurance( macbook ){
var v = macbook.cost();
macbook.cost = function(){
return v + 250;
};
}
var mb = new MacBook();
memory( mb );
engraving( mb );
insurance( mb );
// Outputs: 1522
console.log( mb.cost() );
// Outputs: 11.6
console.log( mb.screenSize() );
在上面的例子中,我們的裝飾被覆蓋MacBook()超類對象.cost()函數返回的目前的價格Macbook加上升級的費用被指定。
它被認為一個裝飾作為原始Macbook它們不重寫對象的構造方法(例如screenSize()),以及我們可將其定義為的一部分的任何其它性質Macbook保持不變,完好。
確實沒有定義的接口在上述的例子,并從制作者到接收器移動時,我們移遠的確保的對象的責任符合一個接口。
偽古典裝飾
現在我們要首先檢查在JavaScript形式呈現的裝飾的變化專業JavaScript的設計模式(PJDP)由達斯汀·迪亞茲和羅斯Harmes。
不像一些早期的例子中,迪亞茲和Harmes堅持更緊密地如何裝飾在其他編程語言(如Java或C ++)使用一個“接口”,我們將詳細界定不久的概念實現的。
注: Decorator模式的這種特殊的變化提供了參考。如果發現它過于復雜,我建議選擇了前面介紹的簡單的實現之一。
接口
PJDP描述了裝飾作為被用于透明包裝相同的接口的其它對象中的對象的圖形。一個接口是定義對象的方法的一個方式應具有,但是,它實際上并不直接指定這些方法應該如何實現。
它們還可以指示什么參數的方法采取,但這被認為是可選的。
那么,我們為什么要在JavaScript中使用的界面?我們的想法是,他們是自我說明,并促進可重用性。從理論上講,接口也使通過確保對它們進行更改也必須實施它們的對象作出代碼更加穩定。
下面是使用鴨打字的JavaScript接口實現的一個例子 - 一種方式,可以幫助確定對象是否是基于它實現的方法構造函數/對象的實例。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Create interfaces using a pre-defined Interface
// constructor that accepts an interface name and
// skeleton methods to expose.
// In our reminder example summary() and placeOrder()
// represent functionality the interface should
// support
var reminder = new Interface( "List", ["summary", "placeOrder"] );
var properties = {
name: "Remember to buy the milk",
date: "05/06/2016",
actions:{
summary: function (){
return "Remember to buy the milk, we are almost out!";
},
placeOrder: function (){
return "Ordering milk from your local grocery store";
}
}
};
// Now create a constructor implementing the above properties
// and methods
function Todo( config ){
// State the methods we expect to be supported
// as well as the Interface instance being checked
// against
Interface.ensureImplements( config.actions, reminder );
this.name = config.name;
this.methods = config.actions;
}
// Create a new instance of our Todo constructor
var todoItem = new Todo( properties );
// Finally test to make sure these function correctly
console.log( todoItem.methods.summary() );
console.log( todoItem.methods.placeOrder() );
// Outputs:
// Remember to buy the milk, we are almost out!
// Ordering milk from your local grocery store
在上述中,Interface.ensureImplements提供了這兩個與嚴格的功能檢查和代碼Interface構造可以找到這里。
與接口的最大問題是,因為沒有內置的JavaScript中對他們的支持,有我們試圖仿效這可能不是一個理想的選擇另一種語言的一個特征的危險。輕量級接口,可以在不很大的性能開銷卻使用,我們接下來看看抽象裝飾使用此相同的概念。
摘要裝飾
為了證明這個版本的Decorator模式的結構,我們要想象我們有一個超類車型Macbook再次和存儲,可以讓我們“修飾”我們的Macbook與一些增強功能支付額外費用。
改進包括升級到4GB或8GB內存,雕刻,Parallel或情況。現在,如果我們使用的是單獨的子類的增強選項的每一種組合來模擬這一點,它可能是這個樣子:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
var Macbook = function(){
//...
};
var MacbookWith4GBRam = function(){},
MacbookWith8GBRam = function(){},
MacbookWith4GBRamAndEngraving = function(){},
MacbookWith8GBRamAndEngraving = function(){},
MacbookWith8GBRamAndParallels = function(){},
MacbookWith4GBRamAndParallels = function(){},
MacbookWith8GBRamAndParallelsAndCase = function(){},
MacbookWith4GBRamAndParallelsAndCase = function(){},
MacbookWith8GBRamAndParallelsAndCaseAndInsurance = function(){},
MacbookWith4GBRamAndParallelsAndCaseAndInsurance = function(){};
等等。
這將是一個不切實際的溶液作為新的子類將需要增強了可用的每個可能的組合。正如我們寧愿讓事情變得簡單不維護大集的子類,讓我們看看如何可以用來裝飾,以更好地解決這個問題。
而不是要求所有我們前面看到的組合,我們應該只需要創建五個新的裝飾類。被稱為對這些增強類的方法將被傳遞給我們的Macbook課。
在我們的下一個例子,透明裝飾環繞它們的組件和它們使用相同的接口,可有趣的互換。
下面是我們要定義為MacBook接口:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var Macbook = new Interface( "Macbook",
["addEngraving",
"addParallels",
"add4GBRam",
"add8GBRam",
"addCase"]);
// A Macbook Pro might thus be represented as follows:
var MacbookPro = function(){
// implements Macbook
};
MacbookPro.prototype = {
addEngraving: function(){
},
addParallels: function(){
},
add4GBRam: function(){
},
add8GBRam:function(){
},
addCase: function(){
},
getPrice: function(){
// Base price
return 900.00;
}
};
為了便于在需要以后我們添加為更多的選擇,一個抽象裝飾類與實現所需的默認方法定義的Macbook接口,它的其余選項將子類。摘要裝飾確保我們能夠獨立裝飾一個基類與盡可能多的裝飾,無需派生類為每個可能的組合需要不同的組合(還記得前面的例子嗎?)。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Macbook decorator abstract decorator class
var MacbookDecorator = function( macbook ){
Interface.ensureImplements( macbook, Macbook );
this.macbook = macbook;
};
MacbookDecorator.prototype = {
addEngraving: function(){
return this.macbook.addEngraving();
},
addParallels: function(){
return this.macbook.addParallels();
},
add4GBRam: function(){
return this.macbook.add4GBRam();
},
add8GBRam:function(){
return this.macbook.add8GBRam();
},
addCase: function(){
return this.macbook.addCase();
},
getPrice: function(){
return this.macbook.getPrice();
}
};
這是怎么回事上述樣本中的Macbook裝飾接受一個對象(一臺Macbook)作為我們的基礎組件使用。它使用Macbook我們前面定義的接口,并為每個方法只是調用組件上的方法相同。現在,我們可以創造我們的選項類可以加什么,只是使用的Macbook裝飾。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// First, define a way to extend an object a
// with the properties in object b. We'll use
// this shortly!
function extend( a, b ){
for( var key in b )
if( b.hasOwnProperty(key) )
a[key] = b[key];
return a;
}
var CaseDecorator = function( macbook ){
this.macbook = macbook;
};
// Let's now extend (decorate) the CaseDecorator
// with a MacbookDecorator
extend( CaseDecorator, MacbookDecorator );
CaseDecorator.prototype.addCase = function(){
return this.macbook.addCase() + "Adding case to macbook";
};
CaseDecorator.prototype.getPrice = function(){
return this.macbook.getPrice() + 45.00;
};
我們在這里所做的是覆蓋addCase()并getPrice()需要進行裝飾的方法和我們首先調用原始這些方法實現這一目標macbook,然后只需追加一個字符串或數值相應(如45.00)給他們。
因為一直頗多至今在本節所介紹的信息,讓我們嘗試在一個單一的例子,希望能突出我們已經學會了把它放在一起。
?
1
2
3
4
五
6
7
8
9
10
11
// Instantiation of the macbook
var myMacbookPro = new MacbookPro();
// Outputs: 900.00
console.log( myMacbookPro.getPrice() );
// Decorate the macbook
var decoratedMacbookPro = new CaseDecorator( myMacbookPro );
// This will return 945.00
console.log( decoratedMacbookPro.getPrice() );
由于裝飾可以動態修改的對象,他們對于改變現有系統的最佳模式。有時,它只是簡單的物體周圍的維護與各個子類每個對象類型的麻煩創建裝飾。這使得可能需要大量的子類對象顯著更直截了當的維護應用程序。
這個例子中的一個功能版本上可以找到JSBin。
裝飾使用jQuery
如同我們已經討論的其他型態,也有可與jQuery來實現的裝飾圖案的例子。jQuery.extend()讓我們來擴展(或合并)兩個或多個對象(和它們的屬性)在一起成為一個單一的對象在運行時。
在這種情況下,目標對象可以與新的功能,而不必破壞或覆蓋在源/超類對象的現有方法(盡管這是可以做到)裝飾。
在下面的例子中,我們定義了三個對象:默認設置,選項和設置。該任務的目的是裝飾defaults在發現附 ??加功能的對象optionssettings,我們必須:
(一)離開“默認”以不變的狀態,我們不會失去訪問它發現性能或功能的能力后點(B)增益使用裝飾性和功能中的“選項”中的能力
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var decoratorApp = decoratorApp || {};
// define the objects we're going to use
decoratorApp = {
defaults: {
validate: false,
limit: 5,
name: "foo",
welcome: function () {
console.log( "welcome!" );
}
},
options: {
validate: true,
name: "bar",
helloWorld: function () {
console.log( "hello world" );
}
},
settings: {},
printObj: function ( obj ) {
var arr = [],
next;
$.each( obj, function ( key, val ) {
next = key + ": ";
next += $.isPlainObject(val) ? printObj( val ) : val;
arr.push( next );
} );
return "{ " + arr.join(", ") + " }";
}
};
// merge defaults and options, without modifying defaults explicitly
decoratorApp.settings = $.extend({}, decoratorApp.defaults, decoratorApp.options);
// what we have done here is decorated defaults in a way that provides
// access to the properties and functionality it has to offer (as well as
// that of the decorator "options"). defaults itself is left unchanged
$("#log")
.append( decoratorApp.printObj(decoratorApp.settings) +
+ decoratorApp.printObj(decoratorApp.options) +
+ decoratorApp.printObj(decoratorApp.defaults));
// settings -- { validate: true, limit: 5, name: bar, welcome: function (){ console.log( "welcome!" ); },
// helloWorld: function (){ console.log( "hello world" ); } }
// options -- { validate: true, name: bar, helloWorld: function (){ console.log( "hello world" ); } }
// defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } }
優點缺點
開發者喜歡使用這種模式,因為它可以透明地使用,也是相當靈活 - 正如我們看到的,對象可以被包扎或“飾”新的行為,然后繼續,而無需擔心基本對象要使用被修改。在更廣的范圍內,這種模式也避免了我們需要依靠大量的子類來獲得同樣的好處。
然而,有缺點,我們應該認識到實施模式時的感覺。如果管理不善,它可以顯著我們的應用程序架構它引入了許多小的,但類似的物體進入我們的空間復雜化。這里關注的是,除了成為難以管理,其他開發商不熟悉的模式可能很難有時間抓為什么它被使用。
足夠評論或模式的研究應協助后者,但只要我們保持我們如何廣泛使用在我們的應用裝飾的句柄,我們應該在這兩方面的罰款。
飛錘
該享元模式是優化代碼是重復的,緩慢的一個經典的結構解決方案和低效共享數據。它旨在通過與相關對象(如應用程序配置,狀態等)共享盡可能多的數據,以盡量減少在應用程序中使用的內存。
該模式最早是由保羅·考爾德和馬克·林頓于1990年設想和包括戰斗機重量小于112lb拳擊重量級的名字命名的。這個名字本身飛錘從這個重量分級衍生,因為它是指小,重量輕(內存占用)模式的目的是幫助我們實現。
在實踐中,飛錘數據共享可涉及服用由多個對象的使用的幾個類似的對象或數據結構并將該數據放置到單個外部對象。我們可以通過這個對象傳遞給根據該數據,而不是在每一個存儲相同數據的那些。
使用飛鐵
有在其中可以應用Flyweight模式兩種方式。首先是在數據層,在那里我們處理的大批量存儲在存儲器中類似的對象之間共享數據的概念。
第二個是在其中飛錘可以用作中央事件管理器,以避免在我們希望有一些類似的行為父容器的事件處理程序安裝到每個子元素的DOM的層。
由于數據層就是輕量級模式最常用的傳統,我們就來看看這個第一。
飛鐵和共享數據
對于這種應用,大約有古典享元模式幾個概念,我們需要做到心中有數。在享元模式有兩種狀態的概念 - 內在和外在。可以通過我們的對象內部方法,他們絕對不能沒有功能需要內在的信息。外在信息然而,可取出并存儲在外部。
具有相同的固有的數據對象可以與單個共享對象,由一個工廠的方法創建的更換。這使我們可以減少隱式數據的總量被儲存相當顯著。
這樣做的好處是,我們能夠保持一個眼睛上已經被實例化,這樣,如果內在的狀態,從我們已經有了對象不同,新的副本只有永遠創建的對象。
我們使用管理器來處理外在狀態。這是如何實現的可變化,但一個方法這具有管理對象包含非本征狀態和它們所屬的輕量級對象的一個??中央數據庫。
實施古典飛鐵
由于享元模式還沒有被大量使用JavaScript近年來應用,許多我們可能會使用獲取靈感的實現來自于Java和C ++的世界。
我們先來看看飛鐵的代碼是我的JavaScript執行維基百科(享元模式的Java示例的http://en.wikipedia.org/wiki/Flyweight_pattern)。
我們將利用三種類型的在本實施方式中,其中列舉如下飛錘的組件組成:
輕量級對應一個接口,通過它飛鐵都能夠接收和作用于外在狀態
混凝土飛錘真正實現飛錘接口和商店內在狀態。混凝土飛鐵必須共享,并且能夠操作狀態是外在的
飛錘工廠管理flyweight對象并創建它們。它確保我們的飛鐵共享和管理它們作為一組可如果我們要求各個實例進行查詢的對象。如果一個對象有它返回它的組已經創建成功了,否則它增加了一個新的對象池并返回。
這些對應于在我們的實現以下定義:
CoffeeOrder:飛錘
CoffeeFlavor:混凝土飛錘
CoffeeOrderContext:助手
CoffeeFlavorFactory:飛錘廠
testFlyweight:我們的飛鐵的利用
鴨打孔“工具”
鴨沖孔允許我們而不一定需要修改運行源延伸的語言或解決方案的功能。由于這下一個解決方案需要使用一個Java關鍵字(implements)實現接口,在JavaScript中沒有找到本地,讓我們先沖鴨它。
Function.prototype.implementsFor 適用于一個對象的構造函數和接受一個父類(功能)或對象,并使用普通的繼承(對于函數)或虛擬繼承(的對象)從此無論是繼承。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
// Simulate pure virtual inheritance/"implement" keyword for JS
Function.prototype.implementsFor = function( parentClassOrObject ){
if ( parentClassOrObject.constructor === Function )
{
// Normal Inheritance
this.prototype = new parentClassOrObject();
this.prototype.constructor = this;
this.prototype.parent = parentClassOrObject.prototype;
}
else
{
// Pure Virtual Inheritance
this.prototype = parentClassOrObject;
this.prototype.constructor = this;
this.prototype.parent = parentClassOrObject;
}
return this;
};
我們可以用這個來修補缺少的implements通過具有功能明確繼承的接口關鍵字。下面,CoffeeFlavor實現了CoffeeOrder接口,并且必須包含為了它的接口方法為我們分配這些實現的一個對象供電的功能。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Flyweight object
var CoffeeOrder = {
// Interfaces
serveCoffee:function(context){},
getFlavor:function(){}
};
// ConcreteFlyweight object that creates ConcreteFlyweight
// Implements CoffeeOrder
function CoffeeFlavor( newFlavor ){
var flavor = newFlavor;
// If an interface has been defined for a feature
// implement the feature
if( typeof this.getFlavor === "function" ){
this.getFlavor = function() {
return flavor;
};
}
if( typeof this.serveCoffee === "function" ){
this.serveCoffee = function( context ) {
console.log("Serving Coffee flavor "
+ flavor
+ " to table number "
+ context.getTable());
};
}
}
// Implement interface for CoffeeOrder
CoffeeFlavor.implementsFor( CoffeeOrder );
// Handle table numbers for a coffee order
function CoffeeOrderContext( tableNumber ) {
return{
getTable: function() {
return tableNumber;
}
};
}
function CoffeeFlavorFactory() {
var flavors = {},
length = 0;
return {
getCoffeeFlavor: function (flavorName) {
var flavor = flavors[flavorName];
if (typeof flavor === "undefined") {
flavor = new CoffeeFlavor(flavorName);
flavors[flavorName] = flavor;
length++;
}
return flavor;
},
getTotalCoffeeFlavorsMade: function () {
return length;
}
};
}
// Sample usage:
// testFlyweight()
function testFlyweight(){
// The flavors ordered.
var flavors = new CoffeeFlavor(),
// The tables for the orders.
tables = new CoffeeOrderContext(),
// Number of orders made
ordersMade = 0,
// The CoffeeFlavorFactory instance
flavorFactory;
function takeOrders( flavorIn, table) {
flavors[ordersMade] = flavorFactory.getCoffeeFlavor( flavorIn );
tables[ordersMade++] = new CoffeeOrderContext( table );
}
flavorFactory = new CoffeeFlavorFactory();
takeOrders("Cappuccino", 2);
takeOrders("Cappuccino", 2);
takeOrders("Frappe", 1);
takeOrders("Frappe", 1);
takeOrders("Xpresso", 1);
takeOrders("Frappe", 897);
takeOrders("Cappuccino", 97);
takeOrders("Cappuccino", 97);
takeOrders("Frappe", 3);
takeOrders("Xpresso", 3);
takeOrders("Cappuccino", 3);
takeOrders("Xpresso", 96);
takeOrders("Frappe", 552);
takeOrders("Cappuccino", 121);
takeOrders("Xpresso", 121);
for (var i = 0; i < ordersMade; ++i) {
flavors[i].serveCoffee(tables[i]);
}
console.log(" ");
console.log("total CoffeeFlavor objects made: " + flavorFactory.getTotalCoffeeFlavorsMade());
}
轉換代碼來使用享元模式
接下來,讓我們繼續看看飛鐵通過實施一個系統來管理圖書館的所有書籍。最重要的元數據為每本書也許可以細分如下:
ID
標題
作者
類型
頁數
發行人ID
國際標準書號
我們也將需要以下屬性來跟蹤哪些成員已經檢查了某本書,他們已經檢查出來的日期,以及預期回報日期。
checkoutDate
checkoutMember
dueReturnDate
可用性
每本書將因此而被表示為如下,之前使用享元模式任何優化:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){
this.id = id;
this.title = title;
this.author = author;
this.genre = genre;
this.pageCount = pageCount;
this.publisherID = publisherID;
this.ISBN = ISBN;
this.checkoutDate = checkoutDate;
this.checkoutMember = checkoutMember;
this.dueReturnDate = dueReturnDate;
this.availability = availability;
};
Book.prototype = {
getTitle: function () {
return this.title;
},
getAuthor: function () {
return this.author;
},
getISBN: function (){
return this.ISBN;
},
// For brevity, other getters are not shown
updateCheckoutStatus: function( bookID, newStatus, checkoutDate, checkoutMember, newReturnDate ){
this.id = bookID;
this.availability = newStatus;
this.checkoutDate = checkoutDate;
this.checkoutMember = checkoutMember;
this.dueReturnDate = newReturnDate;
},
extendCheckoutPeriod: function( bookID, newReturnDate ){
this.id = bookID;
this.dueReturnDate = newReturnDate;
},
isPastDue: function(bookID){
var currentDate = new Date();
return currentDate.getTime() > Date.parse( this.dueReturnDate );
}
};
這可能正常工作最初是為書小型集合,然而由于庫擴展到包括多個版本,并提供每本書的副本較大的庫存,我們可能會發現管理體系運行一段時間慢。使用上千本書的對象可能會壓倒可用的內存,但我們可以用享元模式,以改善該優化我們的系統。
現在,我們可以如下分離我們的數據轉換成內在的和外在的狀態:相關的書對象(數據title,author等)是本征而檢出數據(checkoutMember,dueReturnDate等)被認為是外源性。有效地,這意味著只有一個預定對象需要書屬性的每個組合。它仍然是對象的數量可觀,但比我們以前有顯著減少。
我們的書的元數據的組合的以下單實例將在所有的一本書的與特定標題的拷貝共享。
?
1
2
3
4
五
6
7
8
9
10
11
// Flyweight optimized version
var Book = function ( title, author, genre, pageCount, publisherID, ISBN ) {
this.title = title;
this.author = author;
this.genre = genre;
this.pageCount = pageCount;
this.publisherID = publisherID;
this.ISBN = ISBN;
};
正如我們所看到的,外在的狀態已被刪除。一切都與庫退房將被移動到一個經理和對象數據現在是分段的,一個工廠,可用于實例化。
一個基本廠
現在讓我們來定義一個非常基本的工廠。我們打??算把它做的是執行檢查,看是否與特定的標題一書曾在系統內以前創建的; 如果它有,我們將返回它 - 如果不是,一本新書將被創建并存儲,以便它可以在以后訪問。這將確保我們只創造每一個獨特的內在一塊數據的一個副本:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Book Factory singleton
var BookFactory = (function () {
var existingBooks = {}, existingBook;
return {
createBook: function ( title, author, genre, pageCount, publisherID, ISBN ) {
// Find out if a particular book meta-data combination has been created before
// !! or (bang bang) forces a boolean to be returned
existingBook = existingBooks[ISBN];
if ( !!existingBook ) {
return existingBook;
} else {
// if not, let's create a new instance of the book and store it
var book = new Book( title, author, genre, pageCount, publisherID, ISBN );
existingBooks[ISBN] = book;
return book;
}
}
};
})();
管理外在狀態
接下來,我們需要存儲的地方,從書的對象中刪除的狀態 - 幸運的是經理(我們會被定義為一個Singleton)可以用于封裝它們。一個Book對象而這檢查了他們的庫成員的組合將被稱為圖書記錄。我們的經理會被存儲和雙方還將包括我們為Book類的輕量級優化過程中剝離出來結賬相關的邏輯。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
// BookRecordManager singleton
var BookRecordManager = (function () {
var bookRecordDatabase = {};
return {
// add a new book into the library system
addBookRecord: function ( id, title, author, genre, pageCount, publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability ) {
var book = bookFactory.createBook( title, author, genre, pageCount, publisherID, ISBN );
bookRecordDatabase[id] = {
checkoutMember: checkoutMember,
checkoutDate: checkoutDate,
dueReturnDate: dueReturnDate,
availability: availability,
book: book
};
},
updateCheckoutStatus: function ( bookID, newStatus, checkoutDate, checkoutMember, newReturnDate ) {
var record = bookRecordDatabase[bookID];
record.availability = newStatus;
record.checkoutDate = checkoutDate;
record.checkoutMember = checkoutMember;
record.dueReturnDate = newReturnDate;
},
extendCheckoutPeriod: function ( bookID, newReturnDate ) {
bookRecordDatabase[bookID].dueReturnDate = newReturnDate;
},
isPastDue: function ( bookID ) {
var currentDate = new Date();
return currentDate.getTime() > Date.parse( bookRecordDatabase[bookID].dueReturnDate );
}
};
})();
這些變化的結果是,所有這就是被從書中提取的數據的類,現在被存儲在BookManager的單(BookDatabase)的屬性-這相當比大量我們先前使用的對象的更有效。預訂檢出相關的方法現在也設在這里,因為他們對付那外在而非內在數據。
這個過程不加少許復雜我們的最終解決方案,但是它的時相比,已解決了性能問題的小問題。數據明智的,如果我們有同樣的書30份,我們現在只保存一次。此外,每個函數占用的內存。隨著輕量級模式這些功能在一個地方(在經理),而不是每個對象上不存在,因此節省了內存使用。對于我們存儲上述輕量級版本未優化只是鏈接到,因為我們使用的圖書構造函數的原型函數對象,但如果它是在其他的方式實現,將為每本書實例創建功能。
該享元模式和DOM
DOM(文檔對象模型)支持,使物體檢測事件兩種方法 - 無論是自上而下(捕獲事件)還是自下而上(事件冒泡)。
在事件捕獲,事件首先被最外層元件捕獲并傳播到最內的元素。在事件冒泡,事件被捕獲并提供給最內元件,然后傳播到外元件。
一個用于描述在這種情況下飛鐵的最好隱喻的被寫了加里·奇澤姆和它會有點像這樣:
嘗試在一個池塘的角度考慮的輕量級的。魚打開它的嘴(事件),氣泡上升到表面(鼓泡)蒼蠅位于頂飛開當氣泡到達表面(動作)。在這個例子中,我們可以很容易地調換魚開其口,以一個按鈕被點擊,氣泡冒泡效應和蒼蠅飛走了一些功能正在運行
鼓泡被介紹給處理情況,即單一事件(如點擊)可以通過在不同級別的DOM層次結構定義了多個事件處理程序進行處理的情況。其中,這種情況下,事件冒泡執行用于在可能的最低程度的特定元素定義的事件處理程序。從那里,事件冒泡之前去那些更高了包含元素。
飛鐵可以用于進一步調整的情況下鼓泡過程中,我們將會看到不久。
例1:集中事件處理
對于我們的第一個實際的例子,假設我們擁有一批在文檔中相似的元素同當用戶操作(例如點擊,鼠標懸停)是對他們進行執行類似的行為。
通常情況下我們做什么構建我們自己的手風琴組成部分,菜單或其他基于列表的控件時,綁定一個click事件在父容器(每個鏈接元素如$('ul li a').on(..)與其結合的點擊多個元素,我們可以很容易地附加一個飛錘來我們的容器,其可以監聽從下面來的事件的頂部,這些然后可以使用邏輯根據需要是簡單或復雜的處理。
的各類提到組分通常具有每個部分相同的重復標記(例如手風琴的每個部分),有一個很好的機會,可以點擊將是非常相似并且相對于類似的類附近的各元素的行為。我們將利用這些信息來建立基于以下飛錘一個非常基本的手風琴。
StateManager的一個命名空間在這里用來封裝我們的輕量級邏輯,而jQuery是用來綁定初始點擊一個容器div。為了確保在頁面上沒有其他的邏輯被附著類似手柄到容器中,首先施加一個解除綁定事件。
現在建立正好在容器中的子元素被點擊了什么,我們利用一種的target支票提供單擊的,不管它的父的元素的引用。然后,我們使用這些信息而實際上根本沒到事件發生到具體的孩子我們的頁面加載綁定來處理單擊事件。
HTML
?
1
2
3
4
五
6
7
8
9
10
11
<div id="container">
<div class="toggle" href="#">More Info (Address)
<span class="info">
This is more information
</span></div>
<div class="toggle" href="#">Even More Info (Map)
<span class="info">
<iframe src="http://www.map-generator.net/extmap.php?name=London&address=london%2C%20england&width=500...gt;"</iframe>
</span>
</div>
</div>
JavaScript的
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
var stateManager = {
fly: function () {
var self = this;
$( "#container" )
.unbind()
.on( "click", "div.toggle", function ( e ) {
self.handleClick( e.target );
});
},
handleClick: function ( elem ) {
elem.find( "span" ).toggle( "slow" );
}
};
這里的好處是,我們轉換許多獨立行動統一到一個共享的人(可能節省內存)。
例2:使用飛錘的性能優化
在我們的第二個例子中,我們引用可以使用飛鐵與jQuery實現了進一步的性能提升。
詹姆斯Padolsey以前寫過一篇文章,呼吁76個字節更快jQuery的,他提醒我們,每次的jQuery觸發了一個回調,而不管類型(過濾器,每一個,事件處理程序)的,我們能夠訪問函數的上下文(DOM元素通過與之相關的)this的關鍵字。
不幸的是,我們許多已成為用于包裝的想法this中$()或jQuery(),這意味著jQuery的的新實例被不必要地建造的每時間,而不是簡單地做這樣的:
?
1
2
3
4
五
6
7
8
9
10
11
$("div").on( "click", function () {
console.log( "You clicked: " + $( this ).attr( "id" ));
});
// we should avoid using the DOM element to create a
// jQuery object (with the overhead that comes with it)
// and just use the DOM element itself like this:
$( "div" ).on( "click", function () {
console.log( "You clicked:" + this.id );
});
詹姆斯本來想用jQuery的jQuery.text在以下范圍內,但他的觀點不同意一個新的jQuery對象必須在每次迭代創建:
?
1
2
3
$( "a" ).map( function () {
return $( this ).text();
});
現在,對于多余的包裝,盡可能使用jQuery的實用方法,這是更好地使用jQuery.methodName(例如jQuery.text),而不是jQuery.fn.methodName(例如jQuery.fn.text)其中MethodName代表一個實用工具,如each()或text。這避免了需要調用抽象進一步級別或每個我們的功能被稱為隨著時間的構造一個新的jQuery對象jQuery.methodName是什么庫本身使用在較低的水平,以功率jQuery.fn.methodName。
由于然而jQuery的方法并不是所有的都有相應的單節點功能,Padolsey制定了jQuery.single實用的想法。
這里的想法是,一??個單一的jQuery對象被創建并用于每個調用jQuery.single(有效意思只有一個jQuery對象被創建)。對于此實施可以在下面找到并作為我們合并為多個可能的對象數據轉換成一個多中心的單一結構,它在技術上是還一個飛錘。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
jQuery.single = (function( o ){
var collection = jQuery([1]);
return function( element ) {
// Give collection the element:
collection[0] = element;
// Return the collection:
return collection;
};
})();
在與鏈接動作的這樣的一個例子是:
?
1
2
3
4
五
6
$( "div" ).on( "click", function () {
var html = jQuery.single( this ).next().html();
console.log( html );
});
注意:雖然我們可以相信,只是緩存我們的jQuery代碼可能只提供等同的性能提升,Padolsey聲稱$。單()仍然是值得使用,可以更好的表現。這并不是說在所有不適用任何緩存,僅僅是意識到,這種方法可以幫助。有關$。單的詳細信息,我建議你閱讀Padolsey的全部職務。
JavaScript的MV *模式
在本節中,我們將審查三個非常重要的架構模式 - MVC(模型 - 視圖 - 控制器),MVP(模型 - 視圖 - 演示)和MVVM(模型 - 視圖 - 視圖模型)。在過去,這些模式已經被大量用于構建桌面和服務器端應用程序,但它只有在最近幾年,來應用到的JavaScript。
由于大多數目前使用這些模式,JavaScript開發人員選擇利用庫如Backbone.js的用于實現MVC / MV *式的結構,我們將比較解決方案如何現代化,如在他們的MVC的解釋有所不同相比,經典呈現這些模式。
首先讓我們現在覆蓋的基礎知識。
MVC
MVC是通過分離關注鼓勵提高應用程序的組織架構的設計模式。它強制從用戶界面(查看)業務數據(模型)的分離,與第三成分(控制器)傳統管理邏輯和用戶輸入。該模式由最初的設計特里夫Reenskaug在他的時間在它最初被稱為模型-視圖-控制器-編輯器的Smalltalk-80(1979年)的工作。MVC繼續向縱深于1995年的描述“設計模式:可復用面向對象軟件的元素”(在“四人幫”一書),在推廣它的使用發揮了作用。
的Smalltalk-80 MVC
了解一下原來的MVC模式正致力于解決,因為它是因為它的起源的日子突變相當嚴重是很重要的。早在上世紀70年代,圖形用戶界面是少之又少并稱為概念分居演示開始被用作手段,使該模型在現實世界中的概念域對象之間有明確的分工(如照片,一個人),并且被呈現給用戶的屏幕的展現對象。
Smalltalk的-80 MVC實現進一步采取這一概念,并有從用戶界面分離出應用程序邏輯的一個目標。該想法是去耦應用程序的這些部分也將允許的模型中應用的其他接口的重用。也有一些值得注意的Smalltalk左右-80的MVC架構有意思的觀點:
模型代表特定于域的數據,是無知的用戶界面(視圖和控制器)的。當模型發生改變,則通知其觀察員。
一個視圖中表示模型的當前狀態。用于讓視圖知道每當型號進行了更新或修改的觀察者模式。
演講被查看的照顧,但不只是一個單一的視圖和控制器 - 需要有一個視圖 - 控制器對每個部分或屏幕上顯示的元素。
在這雙控制器的作用是處理用戶交互(如按鍵事件和行動,例如點擊),為View決策。
當他們得知觀察者模式(一般時下的發布/訂閱的變化實現)被列為MVC架構的一部分,幾十年前開發者有時會感到驚訝。在Smalltalk-80的MVC中,視圖觀察模型。作為子彈點如上所述,任何時候,模型的變化,視圖作出反應。這方面的一個簡單的例子是股票市場數據備份的應用程序 - 應用程序是有用的,在我們的模型的任何更改數據應該導致查看被瞬間刷新秩序。
Martin Fowler的做了寫關于一個出色的工作起源 MVC多年來,如果感興趣的Smalltalk左右-80的MVC一些進一步的歷史信息,我建議你閱讀他的作品。
MVC對于JavaScript開發者
我們已經回顧了70年代,但現在讓我們回到這里和現在。到了近代,MVC模式已經被應用到各種不同的編程語言,包括最相關給我們的:JavaScript的。JavaScript的目前已擁有一批擁有的框架MVC的支持(或變化就可以了,這是我們稱之為MV *系列),允許開發者輕松地添加結構,他們的應用程序不花大力氣的。
這些框架包括骨干網,Ember.js和AngularJS的喜歡。考慮到由于缺乏結構避免“意大利面條”的代碼,它描述的代碼是非常難以閱讀或維持長期的重要性,它的當務之急是現代JavaScript開發明白這是什么模式提供。這使我們能夠有效地欣賞一下這些框架使我們能夠做不同。
我們知道,MVC由三個核心部件:
楷模
模型管理數據的應用程序。他們關心既不用戶接口也不表示層,而是代表一個應用程序可能需要的數據的唯一形式。當一個模型更改(例如,當它被更新),它通常會通知已發生的變化及其觀察員(如意見,我們會盡快覆蓋概念),使他們可以做出相應的反應。
為了進一步了解車型,讓我們想象,我們有一個JavaScript照片庫應用程序。在照片庫,因為它代表了一種獨特的特定于域的數據的照片的概念將值得自己的模式。這樣的模型可含有相關的屬性,如標題,圖像源和附加元數據。一個具體的照片將被保存在一個模型的實例和模型也可能是可重復使用的。下面,我們可以看到使用骨干實現一個非常簡單的模型的一個例子。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
var Photo = Backbone.Model.extend({
// Default attributes for the photo
defaults: {
src: "placeholder.jpg",
caption: "A default image",
viewed: false
},
// Ensure that each photo created has an `src`.
initialize: function() {
this.set( { "src": this.defaults.src} );
}
});
模型的內置功能橫跨框架而變化,但它是相當常見的他們支持的屬性,其中屬性表示所述模型的屬性的驗證,如一個型號標識符。當在現實世界的應用程序中使用的模型,我們一般也希望模型的持久性。持久性使我們能夠編輯和更新機型,其最近的狀態將被保存在任一知識:內存,在用戶的localStorage的數據存儲或與數據庫同步。
此外,模型還可以具有多個視圖觀察它。如果說,我們的相片模型包含的元數據,例如其位置(經度和緯度),即存在于照片(標識符的列表)和標簽列表的朋友,開發者可以決定提供單一視圖來顯示這三個方面的。
它并非罕見現代的MVC / MV *框架向組模型提供一個裝置一起(例如在骨干,這些基團被稱為“集合”)。在群體管理模式使我們能夠編寫基于從小組通知應用程序邏輯應該包含任何模式進行更改。這就避免了需要手動觀察單個模型實例。
模型的樣本組合成一個簡化的骨干集合可以看到下面。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var PhotoGallery = Backbone.Collection.extend({
// Reference to this collection's model.
model: Photo,
// Filter down the list of all photos
// that have been viewed
viewed: function() {
return this.filter(function( photo ){
return photo.get( "viewed" );
});
},
// Filter down the list to only photos that
// have not yet been viewed
unviewed: function() {
return this.without.apply( this, this.viewed() );
}
});
MVC的年長文本還可能包含參考模型的管理應用概念的狀態。在JavaScript應用程序狀態都有不同的內涵,通常指的是當前的“狀態”,即在用戶視圖或子視圖(具體數據)屏一個固定的點。國家是在看單頁應用中,需要模擬國家的概念時,這是定期討論的一個話題。
總結一下,車型主要關注業務數據。
意見
視圖是那可憑當前狀態的過濾視圖模型的可視化表示。雖然Smalltalk的意見是關于繪畫和維護一個位圖,JavaScript的意見是關于建立和維護一個DOM元素。
視圖通常觀察的模型,當模式的轉變,使以相應地更新本身通知。設計模式文獻通常指的意見,因為他們在應用程序的模型和控制器的知識是有限的“啞巴”。
用戶可以與視圖進行交互,這包括閱讀和編輯的能力(即獲取或設置屬性值)的模型。由于視圖是表示層,我們一般呈現給編輯以用戶友好的方式的能力和更新。例如,在我們前面討論過的原照片庫應用程序,模型編輯可以通過“編輯”視圖,誰選擇了一個特定的照片,用戶可以編輯其元數據來促進。
更新模型的實際任務落在控制器(我們將很快覆蓋)。
讓我們來探討意見遠一點使用香草的JavaScript樣本實現。下面我們可以看到,創建一個單一的圖片瀏覽,既消耗模型實例和一個控制器實例的功能。
我們定義了一個render()我們認為這是負責呈現的內容,程序內photoModel使用JavaScript模板引擎(下劃線模板)和更新我們的觀點,通過引用的內容photoEl。
在photoModel隨后增加了我們的render()回調為它的用戶之一,使得通過Observer模式,我們都可以觸發模型的變化更新視圖。
人們可能不知道在哪里的用戶交互來這里發揮作用。當用戶在視圖中的任何元素的點擊,這不是視圖的責任,知道下一步該怎么做。它依靠在控制器上做出這個決定吧。在我們的示例實現,這是通過添加事件偵聽器來實現photoEl,這將處理委托的點擊行為反饋給控制器,通過模型信息與它一起的情況下,它的需要。
這種結構的好處是,各成分起著根據需要使應用功能自己單獨的作用。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
var buildPhotoView = function ( photoModel, photoController ) {
var base = document.createElement( "div" ),
photoEl = document.createElement( "div" );
base.appendChild(photoEl);
var render = function () {
// We use a templating library such as Underscore
// templating which generates the HTML for our
// photo entry
photoEl.innerHTML = _.template( "#photoTemplate", {
src: photoModel.getSrc()
});
};
photoModel.addSubscriber( render );
photoEl.addEventListener( "click", function () {
photoController.handleEvent( "click", photoModel );
});
var show = function () {
photoEl.style.display = "";
};
var hide = function () {
photoEl.style.display = "none";
};
return {
showView: show,
hideView: hide
};
};
模板
在JavaScript框架,支持MVC / MV *的背景下,這是值得討論的簡單的JavaScript模板及其關系的意見,我們簡要地在最后一節在它感動。
長期以來,人們一直認為(和證明)性能不好的做法,通過手動字符串連接創建內存HTML標記大塊。開發商這樣做也曾被inperformantly通過他們的數據迭代,嵌套的div包裹,并使用這些過時的技術document.write注入了“模板”到DOM。因為這通常意味著保持與我們的標準標記腳本標記行內,它可以迅速變得既難以閱讀和更重要的是,保持這樣的災害,構建非平凡尺寸應用時尤其如此。
JavaScript的模板解決方案(如Handlebars.js和胡子)經常被用來定義模板的意見為標記(無論是外部存儲或使用自定義類型的腳本標記內 - 如文/模板)包含模板變量。變量可以使用變量語法delimitated(例如{{名}})和框架通常是足夠聰明,接受一個JSON形式的數據(模型實例可以轉換成),使得我們只需要維持清潔的車型關心,干凈的模板。最繁重的工作做的人口是由框架本身的照顧。這有大量的好處,選擇加入存儲模板外,因為這可以讓位給模板被動態加載在需要的基礎上,當談到構建大型應用時尤為如此。
下面我們可以看到HTML模板兩個例子。一個實現使用流行的Handlebars.js框架,另一個使用下劃線的模板。
Handlebars.js:
?
1
2
3
4
五
6
7
<li class="photo">
<h2>{{caption}}</h2>
<img class="source" src="{{src}}"/>
<div class="meta-data">
{{metadata}}
</div>
</li>
Underscore.js Microtemplates:
?
1
2
3
4
五
6
7
<li class="photo">
<h2><%= caption %></h2>
<img class="source" src="<%= src %>"/>
<div class="meta-data">
<%= metadata %>
</div>
</li>
請注意,模板本身沒有意見。開發人員一個Struts Model 2架構未來可能會覺得自己像一個模板* *是一個觀點,但事實并非如此。視圖是一個對象,觀察模型,并保持視覺表現了最新的。模板*可能*是指定部分甚至全部視圖對象的,以便它可以從模板規范生成的聲明性方式。
還值得一提的是,在傳統的Web開發,獨立的見解之間的導航需要使用頁面刷新。在單頁JavaScript應用程序,但是,一旦數據是通過Ajax的服務器獲取,它可以簡單地動態呈現在同一頁面中一個新的觀點,沒有任何這樣的刷新是必??要的。
導航的作用因而下降到一個“路由器”,這有助于管理應用程序的狀態(例如允許用戶書簽的特定視圖,他們已經導航到)。由于路由器,然而,既不是MVC的一部分,也不存在于每一個MVC的框架一樣,我不會在本節進入他們的更多細節。
總結,觀點是我們的應用程序的數據的可視化表示。
控制器
控制器模型和視圖這是經典的負責當用戶操縱視圖更新模型之間的中介。
在我們的照片庫應用程序,處理切換到編輯視圖作出特定照片的用戶,更新當用戶完成編輯一個特定的照片模式控制器將負責。
請記住,控制器履行MVC一個角色:視圖的策略模式的便利。在戰略模式方面,鑒于與會代表在視圖的自由裁量權的控制。所以,這就是策略模式是如何工作的。該視圖可以委托處理用戶事件到控制器的觀點認為合適的時候。視圖*可能*委托處理模式改變事件給控制器如果視圖認為合適,但這并不是控制器的傳統角色。
在大多數的JavaScript MVC框架,從一個通常被認為有損術語“MVC”,然而,它與控制器。造成這種情況的原因各不相同,但在我的真實想法,它是框架作者最初看MVC的服務器端解釋,認識到它不翻譯1:在客戶端1和重新解釋在C MVC意味著他們覺得東西更有意義。然而與此的問題是,它是主觀的,在這兩個理解古典MVC模式,當然控制器在現代框架的作用增加了復雜性。
作為一個例子,讓我們簡要回顧一下流行的架構框架Backbone.js的架構。主干包含模型和視圖(有點類似于我們之前審查),但實際上它并不具有真正的控制器。其次和路由器充當稍類似的控制器,但既不是實際上自己的控制器。
在這方面,相反的是可能會在正式文件或博客文章提到,骨干既不是真正的MVC / MVP也不MVVM框架。這其實不如考慮它的MV *家族的一員而以自己的方式方法架構。這里當然是沒有錯的,但經典的MVC和MV *區別開來,我們應該開始依靠從咨詢古典文學對前者幫助后者是很重要的。
在另一個庫(Spine.js)與Backbone.js的控制器
Spine.js
我們現在知道,控制器是當用戶更新視圖更新模型傳統上負責。這是有趣的是,在寫(骨干)的當時最流行 ??的JavaScript的MVC / MV *框架不會有它自己的控制器明確的概念。
因而它可以是有用的為我們從另一個MVC框架審查控制器欣??賞在實施方式的差異,并進一步說明框架如何nontraditionally接近控制器的作用。對于這一點,讓我們來看看從Spine.js樣本控制器:
在這個例子中,我們將有一個叫做控制器PhotosController將負責在申請個人照片。這將確保在視圖更新(例如,一個用戶編輯照片的元數據)相應的模型也會做。
注意:我們不會在很大程度上鉆研Spine.js可言,而只是采取什么樣的控制器可以做十英尺的看法:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Controllers in Spine are created by inheriting from Spine.Controller
var PhotosController = Spine.Controller.sub({
init: function () {
this.item.bind( "update", this.proxy( this.render ));
this.item.bind( "destroy", this.proxy( this.remove ));
},
render: function () {
// Handle templating
this.replace( $( "#photoTemplate" ).tmpl( this.item ) );
return this;
},
remove: function () {
this.el.remove();
this.release();
}
});
在脊柱,控制器被認為是一個應用程序的膠水,增加和響應DOM事件,呈現模板,并確保視圖和模型保持同步(這是有道理的在我們所知道的是一個控制器的上下文中)。
我們正在做在上面的例子是建立在聽眾update和destroy使用事件render()和remove()。如果照片的輸入被更新,我們重新渲染視圖以反映更改元數據。同樣,如果照片被從庫中刪除,我們從視圖中刪除它。在render()函數中,我們使用下劃線微模板(通過_.template()),以使與ID #photoTemplate一個JavaScript模板。這個簡單的返回用于填充的內容的編譯HTML字符串photoEl。
這是什么為我們提供的是管理模型和視圖之間的變化非常輕巧,簡單的方法。
Backbone.js的
后來在這一節,我們要重新審視骨干網和傳統的MVC的區別,但現在讓我們專注于控制器。
在骨干網,人們共享一個控制器同時與責任Backbone.View和Backbone.Router。前段時間骨干也曾經帶了它自己的Backbone.Controller,但作為命名此組件沒有意義在其中正在使用它的背景下,它后來被更名為路由器。
路由器處理更多一點控制器的責任,因為它是可能有綁定事件模型,并有我們的看法,以DOM事件和渲染響應。蒂姆Branyen(另一個基于Bocoup骨干貢獻者)此前還曾指出,這可能與不需要脫身Backbone.Router在所有的這一點,所以某種程度上使用路由器范式來想想大概是:
?
1
2
3
4
五
6
7
8
9
10
var PhotoRouter = Backbone.Router.extend({
routes: { "photos/:id": "route" },
route: function( id ) {
var item = photoCollection.get( id );
var view = new PhotoView( { model: item } );
$('.content').html( view.render().el );
}
});
總之,從本節的重點是:控制器管理應用模型和視圖之間的邏輯和協調。
什么是MVC給我們?
在MVC關注這種分離方便了應用程序的功能,簡單的模塊化和可實現:
整體更容易維護。當需要更新的應用程序來進行是非常清楚的變化是否以數據為中心,這意味著修改模型和可能的控制器,或者僅僅是視覺,這意味著修改意見。
解耦模型和視圖意味著它是顯著更直截了當寫業務邏輯單元測試
低層次的模型和控制器代碼重復(即,我們可能已被使用,而不是什么),橫跨應用程序淘汰
取決于應用和角色的分離的大小,這個模塊化允許負責核心邏輯開發者和開發者的用戶接口的工作,以同時工作
的Smalltalk-80 MVC在JavaScript
盡管大多數現代JavaScript框架的嘗試演變MVC范例,以更好地適應網絡應用開發的不同需求,有一個框架,它試圖堅持在Smalltalk-80發現模式的純粹的形式。Maria.js(https://github.com/petermichaux/maria)由彼得·米肖提供其忠實于MVCS起源的實現-模型是模型,視圖是視圖和控制器卻都控制器。雖然一些開發商可能會覺得一個MV *框架應解決更多的關注,這是一個有益的參考,以做到心中有數,如果你想一個JavaScript實現原MVC的。
更深層次的鉆研
在書中這一點上,我們應該有什么樣的MVC模式提供了一個基本的了解,但還是有它值得一提的一些有趣的信息。
GoF的不指MVC作為一種設計模式,而是認為這是一個組類來構建用戶界面。在他們看來,這實際上是三種經典設計模式的變化:觀察,策略和復合模式。取決于如何MVC已經在一個框架中實現,它也可以使用工廠與模板圖案。與MVC時GoF的書中提到這些圖案作為有用的附加 ??功能。
正如我們所討論的,模型代表的意見,而應用程序數據是用戶呈現在屏幕上的內容。因此,MVC依賴于(一些令人驚訝的是不包括在對MVC模式的許多文章)一些核心通信的觀察者模式。當模型改變了它通知其觀察的東西已被更新(查看) - 這也許是MVC中最重要的關系。這種關系的觀察者性質也是什么便于多個視圖被連接到相同的模型。
對于有興趣了解更多有關MVC的解耦性質(再次,取決于實現)的開發,格局的目標之一是幫助確定一個主題(數據對象)及其觀察員之間的一個一對多的關系。當一個主題的變化,及其觀察員更新。視圖和控制器有一個稍微不同的關系。控制器促進次向不同的用戶輸入作出響應,并且策略模式的一個例子。
概要
在審查了經典的MVC模式,我們現在應該明白它是如何使我們能夠清晰地在應用程序中分離關注。我們也應該明白現在怎么的JavaScript MVC框架可以在其MVC模式,它雖然比較開放的變化,還是全體的基本概念,原有格局所提供的解釋不同。
在審查一個新的JavaScript MVC / MV *框架,記住 - 這可能是有用的退后一步,回顧它是如何選擇進場架構(具體而言,它如何支持實現模型,視圖,控制器或其他替代品),因為這樣可以更好地幫助我們神交框架期望如何被使用。
MVP
模型-視圖-演示者(MVP)是專注于提高表現邏輯MVC設計模式的一種衍生物。它起源于一家名為Taligent公司在90年代初,當他們上了C ++ CommonPoint環境的模式工作。雖然這兩個MVC和MVP的目標跨多個組件關注點分離,它們之間存在著一些根本性的分歧。
對于本摘要的目的,我們將著重版本MVP的最適合的基于Web的架構。
模型,視圖與主持人
在MVP的P代表主持人。這其中包含該視圖的用戶界面的業務邏輯的組件。與MVC,從視圖中調用都被委托給主持人,這是從視圖中分離,并通過一個接口,而不是談論它。這使得各種有用的東西,如能嘲笑意見單元測試。
MVP的最常見的實現是其中之一采用了被動視圖,包含幾乎沒有任何邏輯(這是所有意圖和目的“啞”視圖)。如果MVC和MVP是不同的這是因為C和P做不同的事情。在MVP,在P模式時改變觀察模型和更新的意見。在P有效結合車型的看法,這是以前在MVC控制器召開的責任。
由視圖請求,主持人執行任何工作,做用戶請求和數據傳遞回給他們。在這方面,他們中檢索數據,處理它,并確定如何將數據應在視圖中顯示。在一些實現中,演示者還與一個服務層堅持數據(模型)相互作用。模型可能觸發事件,但它的作用主持人訂閱他們,以便它可以更新視圖。在這種被動的架構,我們沒有直接的數據綁定的概念。查看公開哪些演示者可以用它來設置數據的setter方法??。
從MVC這種變化的好處是,它增加了我們的應用程序的可測試性,并提供視圖和模型之間更清晰的分離。這不是沒有但其成本缺乏數據的模式綁定的支持往往意味著公司將不得不單獨照顧這個任務。
雖然一個共同實施被動視圖是實現接口的觀點,也有它的變化,包括使用可消除來自演示者視圖多一點的事件。由于我們沒有在JavaScript中的界面結構,我們使用超過這里顯式接口的協議。這是在技術上還是一個API,它可能是公平的,我們把它稱為從這個角度的接口。
也有一個監督控制器 MVP,這是更接近MVC和變化MVVM模式,因為它提供數據綁定從模型直接從觀。鍵-值觀察(志愿)插件(如德里克貝利的Backbone.ModelBinding插件)往往會帶來骨干擺脫被動的查看多地進入監督控制器或MVVM變化。
MVP或MVC?
MVP一般最常使用的企業級應用中,有必要重新使用盡可能多的表示邏輯盡可能。用非常復雜的觀點和用戶交互大量的應用程序可能會發現MVC并不完全在這里適合該法案為解決這個問題,可能意味著嚴重依賴于多個控制器。在MVP,這一切復雜的邏輯可以在演示,從而可以大大簡化維護進行封裝。
作為MVP的意見通過接口定義和接口在技術上是系統和視圖(除主持人等)之間的接觸止點,這種模式還允許開發人員編寫的表示邏輯,而無需等待的設計師,生產布局和圖形應用程序。
根據實現,MVP可能更容易比MVC自動單元測試。經常引述這樣做的原因是,演示者可以用作用戶接口的一個完整的模擬,因此可以進行單元測試獨立于其他組件。在我的經驗,這實際上取決于我們在執行MVP語言(有選擇了最有價值球員為一個JavaScript項目在一個比方說,ASP.net中有很大差異)。
在一天結束時,底層的顧慮我們可具有MVC定它們之間的差異主要是語義MVP的將有可能成立。只要我們清晰地分離問題納入模型,視圖和控制器(或演示)我們應該實現大多數同樣的好處,無論我們選擇的變化。
MVC,MVP和Backbone.js的
有極少數,如果聲稱實現他們的古典形式盡可能多的JavaScript開發人員并不認為MVC和MVP作為是相互排斥的MVC或MVP模式的架構JavaScript框架(我們其實更有可能看到MVP嚴格實施時看著Web框架,如ASP.net或GWT)。這是因為它可能在我們的應用程序的其他主持人/視圖邏輯,但仍然認為這是MVC的味道。
骨干貢獻者艾琳羅斯(總部位于波士頓的Bocoup)的贊同思想為這種方式時,她分離出來的意見進入自己不同的組件,她需要一些實際組裝它們給她。這可能是某個控制器途徑(如Backbone.Router,在本書后面介紹)或回調響應數據被取出。
話雖如此,但有些開發商確實感覺到Backbone.js的更適應MVP比它的MVC的描述。他們的觀點是:
在MVP更好的演示介紹了Backbone.View比控制器執行(查看模板之間,并綁定到它的數據層)
該模型擬合Backbone.Model(它不是在MVC模型有很大不同的話)
意見最能代表模板(如把手/胡子標記模板)
為了對此作出回應可能是該觀點也可以只是一個視圖(按照MVC),因為骨干具有足夠的靈活性讓它可以用于多種用途。MVC中的V和在MVP在P可以通過兩者來完成Backbone.View,因為他們能達到兩個目的:既渲染原子彈部件,組裝其他意見呈現這些組件。
我們還可以看出,在骨干控制器的責任與兩個Backbone.View和Backbone.Router共享,并在下面的例子中我們實際上可以看到,各方面都確實存在。
我們的骨干PhotoView使用Observer模式為“預訂”變為查看在該行的模型this.model.bind("change",...)。它還處理模板化的render()方法,但不同于某些其它實現中,用戶交互的視圖(見還處理events)。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
var PhotoView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
// Pass the contents of the photo template through a templating
// function, cache it for a single photo
template: _.template( $("#photo-template").html() ),
// The DOM events specific to an item.
events: {
"click img": "toggleViewed"
},
// The PhotoView listens for changes to
// its model, re-rendering. Since there's
// a one-to-one correspondence between a
// **Photo** and a **PhotoView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
this.model.on( "change", this.render, this );
this.model.on( "destroy", this.remove, this );
},
// Re-render the photo entry
render: function() {
$( this.el ).html( this.template(this.model.toJSON() ));
return this;
},
// Toggle the `"viewed"` state of the model.
toggleViewed: function() {
this.model.viewed();
}
});
另一個(完全不同)的意見是,骨干更加接近的Smalltalk-80 MVC,我們通過前面去了。
定期骨干博客德里克-貝利此前所說的那樣,這是最終最好不要強迫骨干,以適應任何具體的設計模式。設計模式應考慮靈活的指南應用程序如何可以是結構化,在這方面,骨干既不適合也不MVC MVP。相反,它借用了一些來自多個架構模式的最佳概念,并創建一個靈活的框架,只是效果很好。
它是不過值得理解的地方以及為什么這些概念的起源,所以我希望我的MVC和MVP的解釋已經幫助。說它主干路,MV *或任何幫助引用其應用程序架構的味道。大部分結構性JavaScript框架將采用自己拿古典圖案,無論是有意還是偶然,但重要的是,他們幫助我們開發這是有組織的,干凈的,可以很容易維護的應用程序。
MVVM
MVVM(模型視圖視圖模型)是基于MVC和MVP,它試圖以更清楚地從該應用程序中的業務邏輯和行為的分開的用戶接口(UI)的發展一種架構模式。為此,這種圖案的許多實現利用聲明性數據綁定的,以允許工作對從其它層次的分離。
這有利于同一個代碼庫中的幾乎同時發生的用戶界面和開發工作。UI開發人員編寫自己的文檔標記(HTML),其中模型和視圖模型是通過在應用程序的邏輯的開發人員保持內綁定到視圖模型。
歷史
MVVM(按名稱)最初是由微軟的Windows Presentation Foundation中(使用定義WPF)和Silverlight的,有正式于2005年被宣布約翰·格羅斯曼在博客中關于阿瓦隆(代號為WPF)。研究還發現,在Adobe Flex社區一些人氣,以替代簡單地使用MVC。
此前微軟采用MVVM名字,但是有在社會運動從MVP去MVPM:模型視圖的PresentationModel。Martin Fowler的寫了一篇文章在2004年PresentationModels回那些有興趣閱讀更多關于它。一個的想法的PresentationModel一直圍繞更長的時間比這篇文章,但它被認為是在理念的大突破,極大地捧紅了。
有相當多的沸沸揚揚的“alt.net”圈微軟宣布MVVM作為替代MVPM后。許多人聲稱該公司在GUI世界霸主地位是讓??他們有機會到社區接管作為一個整體,重命名現有的概念,因為它們高興的營銷目的。漸進人群的認可,雖然MVVM和MVPM是有效同樣的想法,他們來到略有不同的封裝。
近年來,MVVM一直用JavaScript實現在結構框架,如形式KnockoutJS,劍道MVVM和Knockback.js,來自社會的總體積極響應。
現在讓我們回顧了組成MVVM的三個組成部分。
模型
由于與MV *家族的其他成員,在MVVM模型代表特定于域的數據或信息,我們的應用程序將是工作。特定領域的數據,一個典型的例子是一個用戶帳戶(如姓名,頭像,電子郵件)或音樂曲目(如名稱,年份,專輯)。
模型保存信息,但通常不處理的行為。因為這不是他們的責任,他們不格式化的信息或數據的方式出現在瀏覽器的影響。相反,數據格式是由處理視圖,而行為被認為應該在與模型交互另一個層封裝業務邏輯 - 視圖模型。
唯一的例外往往是驗證和它被認為可以接受的模型驗證被用來定義或更新現有的模型數據(如沒有一個電子郵件地址為輸入滿足特定的正則表達式的要求是什么?)。
在KnockoutJS,秋款上述定義之下,但往往使Ajax調用到服務器端的服務來讀取和??寫入模型數據。
如果我們構建了一個簡單的Todo應用程序,KnockoutJS型號代表一個待辦事項可能如下所示:
?
1
2
3
4
五
var Todo = function ( content, done ) {
this.content = ko.observable(content);
this.done = ko.observable(done);
this.editing = ko.observable(false);
};
注:在我們調用的方法上面的代碼中的一個可能會注意到observable()在KnockoutJS命名空間ko。在KnockoutJS,觀測值是可以變更的有關通知用戶,并自動檢測依賴特殊的JavaScript對象。這使我們能夠在一個模型屬性的值被修改同步模型和的ViewModels。
視圖
與MVC中,視圖是用戶實際交互應用程序的一部分。它們是代表一個視圖模型的狀態的交互式用戶界面。在這個意義上說,鑒于被認為是主動而非被動的,但這也是如此意見MVC和MVP。在MVC,MVP和MVVM一個視圖也可以是被動的,但是這是什么意思?
被動僅查看輸出顯示,并且不接受任何用戶輸入。
這種觀點也可具有在我們申請的車型沒有真正的知識和可以由演示者操縱。MVVM的活動視圖包含數據綁定,事件和行為,需要的視圖模型的理解。雖然這些行為可以映射到的屬性,查看仍然是負責處理從視圖模型的事件。
它使這與視圖模型同步 - 這要記住的看法是不負責這里處理狀態是很重要的。
一個KnockoutJS視圖是一個簡單的HTML文檔與聲明綁定其鏈接到視圖模型。KnockoutJS視圖顯示從視圖模型的信息,傳遞命令給它(如一個元素上的用戶點擊)和更新作為視圖模型的狀態發生改變。模板生成使用從視圖模型數據標記可以但是也可以被用于此目的。
為了給出一個簡要的最初的例子,我們可以看一下的JavaScript框架MVVM為KnockoutJS它是如何讓一個視圖模型及其相關的綁定在標記的定義:
視圖模型:
?
1
2
3
4
var aViewModel = {
contactName: ko.observable("John")
};
ko.applyBindings(aViewModel);
視圖:
?
1
2
3
4
五
<p><input id="source" data-bind="value: contactName, valueUpdate: 'keyup'" /></p>
<div data-bind="visible: contactName().length > 10">
You have a really long name!
</div>
<p>Contact name: <strong data-bind="text: contactName"></strong></p>
我們的輸入文本框(源)獲得它從初始值contactName,自動更新該值時CONTACTNAME變化。由于數據綁定是雙向的,輸入到文本框將更新contactName相應因此它們的值總是保持同步。
雖然實施具體到KnockoutJS中,<div>包含了“你有一個很長的名字!” 文中還包含簡單的驗證(在一次又一次的數據綁定的形式)。如果輸入超過10個字符,它會顯示,否則會被隱藏起來。
移動到一個更高級的例子,我們可以回到我們的Todo應用。一個下調KnockoutJS查看該,包括所有必要的數據綁定可能如下所示。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id="todoapp">
<header>
<h1>Todos</h1>
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add"
placeholder="What needs to be done?"/>
</header>
<section id="main" data-bind="block: todos().length">
<input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-bind="foreach: todos">
<!-- item -->
<li data-bind="css: { done: done, editing: editing }">
<div class="view" data-bind="event: { dblclick: $root.editItem }">
<input class="toggle" type="checkbox" data-bind="checked: done">
<label data-bind="text: content"></label>
<a class="destroy" href="#" data-bind="click: $root.remove"></a>
</div>
<input class="edit' type="text"
data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/>
</li>
</ul>
</section>
</div>
注意,標記式的基本布局是相對直接的,含有一個輸入文本(new-todo用于添加新項目,togglers用于標記的項目,如完整和一個列表()todo-list中的表格)與模板的待辦事項li。
在上面標記的數據綁定可以細分如下:
輸入文本框new-todo有一個數據綁定的current屬性,它是存儲添加當前項目的價值在哪里。我們的視圖模型(如圖不久)觀察current屬性,也有反對有約束力的add事件。當按下回車鍵,add就會觸發事件,那么我們的視圖模型可以修剪的價值current,并根據需要將其添加到列表待辦事項
輸入框toggle-all已完成,如果點擊可以標記所有當前的項目。如果選中,則觸發allCompleted事件,這可以在我們的視圖模型中可以看出
該項目li擁有一流的done。當作為完成的任務標記,CSS類editing則相應標記。如果該項目雙擊,則$root.editItem回調將被執行
帶班的復選框toggle顯示的狀態done屬性
一個標簽包含待辦事項的文本值(content)
還有一個刪除按鈕將調用$root.remove點擊時的回調。
用于編輯模式的輸入文本框還持有待辦事項的值content。本enterKey次活動將設定的editing屬性設置為true或false
視圖模型
視圖模型可以認為作為數據轉換器的專用控制器。它改變模型??信息轉化為查看信息,傳遞命令,從視圖模型。
例如,讓我們想象,我們有一個包含UNIX格式的日期屬性的模型(如1333832407)。而不是我們的模型意識到的日期(例如04/07/2012 @下午5:00),其中,這將是必要的屬性轉換為它的顯示格式的用戶的視野的,我們的模型簡單地保持數據的原始格式。我們的視圖包含格式化的日期,我們的視圖模型充當兩者之間的中間人。
在這個意義上說,視圖模型可能在在一個多視圖模型來看著,但它確實處理大多數視圖的顯示邏輯。該視圖模型也可能會使幫助維護視圖的狀態,更新基于該行動的模型上查看視圖和觸發事件的方法。
綜上所述,視圖模型坐在我們的UI層后面。它暴露了一個視圖所需要的數據(從模型),可以被看作是源我們的意見去的數據和操作。
KnockoutJS解釋視圖模型為可在一個UI執行的數據和操作的表示。這不是UI本身,也不是持續存在的數據模型,而是一個層,它也能容納尚未被保存數據的用戶正在使用。淘汰賽的的ViewModels都沒有HTML標記的知識實現JavaScript對象。這種抽象的方式來執行這些允許他們保持簡單,意味著更復雜的行為根據需要在最上可以更容易地管理。
的部分KnockoutJS視圖模型為我們的Todo應用程序從而可能如下所示:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// our main ViewModel
var ViewModel = function ( todos ) {
var self = this;
// map array of passed in todos to an observableArray of Todo objects
self.todos = ko.observableArray(
ko.utils.arrayMap( todos, function ( todo ) {
return new Todo( todo.content, todo.done );
}));
// store the new todo value being entered
self.current = ko.observable();
// add a new todo, when enter key is pressed
self.add = function ( data, event ) {
var newTodo, current = self.current().trim();
if ( current ) {
newTodo = new Todo( current );
self.todos.push( newTodo );
self.current("");
}
};
// remove a single todo
self.remove = function ( todo ) {
self.todos.remove( todo );
};
// remove all completed todos
self.removeCompleted = function () {
self.todos.remove(function (todo) {
return todo.done();
});
};
// writeable computed observable to handle marking all complete/incomplete
self.allCompleted = ko.computed({
// always return true/false based on the done flag of all todos
read:function () {
return !self.remainingCount();
},
// set all todos to the written value (true/false)
write:function ( newValue ) {
ko.utils.arrayForEach( self.todos(), function ( todo ) {
//set even if value is the same, as subscribers are not notified in that case
todo.done( newValue );
});
}
});
// edit an item
self.editItem = function( item ) {
item.editing( true );
};
..
上面我們基本上提供了添加,編輯或刪除項目,以及邏輯來標記所有其余的項目為已完成注所需的方法:唯一的區別值得在我們的視圖模型前面的例子指出的是觀察到的數組。在KnockoutJS,如果我們希望檢測和響應單個對象上的變化,我們會用observables。然而,如果我們想檢測和響應的事物的集合的變化,我們可以使用observableArray來代替。如何使用一個簡單的例子觀測陣列可以如下所示:
?
1
2
3
4
五
// Define an initially an empty array
var myObservableArray = ko.observableArray();
// Add a value to the array and notify our observers
myObservableArray.push( 'A new todo item' );
注:上面我們審查的完整Knockout.js的Todo應用程序可以從抓住TodoMVC如果有興趣。
簡要回顧:視圖和視圖模型
意見及的ViewModels通信使用數據綁定和事件。正如我們在最初的視圖模型的例子所看到的,視圖模型并不僅僅暴露模型屬性同時也獲得了其他方法和功能,例如驗證。
我們的瀏覽處理它們自己的用戶界面的事件,它們映射到視圖模型是必要的。模型和視圖模型屬性同步,并通過雙向數據綁定更新。
觸發器(數據觸發器)也讓我們進一步在我們的模型屬性的狀態變化作出反應。
簡要回顧:視圖模型和模型
雖然它可能會出現在視圖模型是在MVVM模型完全負責,也有一些細微之處有了這層關系值得關注。該視圖模型可以公開一個模或者??模屬性數據綁定的目的,還可以包含用于讀取和操作視圖公開的屬性界面。
優點和缺點
現在,我們希望有什么MVVM以及它是如何工作的更好的欣賞。現在讓我們回顧優勢和用人模式的優缺點:
優點
MVVM便于用戶界面更易于并行開發和構建塊,它的功率
抽象化視圖,從而減少業務邏輯(或膠水)的數量在它后面的代碼需要
該視圖模型能夠比事件驅動的代碼更容易進行單元測試
該視圖模型(比查看更多機型)可以在不UI自動化,互動化的關注進行測試
缺點
對于簡單的用戶界面,MVVM可能是矯枉過正
雖然數據綁定可以聲明和漂亮的工作,他們可以是難度比必要的代碼,我們只需設置斷點調試
在不平凡的應用程序的數據綁定可以創造很多簿記。我們也不想的情況下就結了哪里綁定比對象綁定到重
在較大的應用程序,也可以是更難以設計視圖模型前面得到概括的必要量
MVVM隨著寬松的數據綁定
它的情況并不少見,從一個MVC或MVP背景審查MVVM和抱怨它的真正關注點分離JavaScript開發人員。也就是說,內聯數據綁定的數量保持在視圖的HTML標記。
我必須承認,當我第一次審查MVVM的實現(如KnockoutJS,擊退),我很驚訝,任何開發人員想回到昔日的天,我們與我們的標記混合邏輯(JavaScript的),并發現它很快難以維護。然而現實情況是,MVVM這是否為一些很好的理由(我們已經介紹),包括促進設計人員能夠更輕松地從他們的標記綁定到邏輯。
對于我們之間的純粹主義者,你會很高興知道,我們現在也可以大大降低我們的感謝數據綁定到稱為自定義綁定提供商功能如何依賴,介紹了KnockoutJS 1.3和提供,因為所有的版本。
默認KnockoutJS具有數據綁定提供商它搜索與任何元件data-bind對他們的屬性,如在下面的例子。
?
1
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?"/>
當供應商找到這個屬性的元素,它分析它,并把它變成使用當前數據上下文綁定對象。這是KnockoutJS的方式一直努力,讓我們以聲明綁定添加到KnockoutJS綁定到數據在這層因素。
一旦我們開始構建不再是微不足道的意見,我們最終可能與大量元素和屬性的綁定在標記會變得難以管理。然而,對于自定義綁定提供商而言,這已經不再是一個問題。
有約束力的提供者是兩件事情主要感興趣:
當給定一個DOM節點,它包含任何數據綁定?
如果節點傳遞的第一個問題,什么是綁定對象看起來像在當前的數據上下文?
結合供應商實現兩個功能:
nodeHasBindings:這需要在這并不一定必須是一個元件DOM節點
getBindings:當施加到當前數據上下文返回表示綁定對象
因此,一個骨架結合提供商可能如下所示:
?
1
2
3
4
五
6
7
8
9
var ourBindingProvider = {
nodeHasBindings: function( node ) {
// returns true/false
},
getBindings: function( node, bindingContext ) {
// returns a binding object
}
};
之前,我們得到充實這個供應商,讓數據綁定屬性簡要討論一下邏輯。
如果使用淘汰賽的MVVM當我們發現自己不滿意的應用邏輯過于扎成您查看的想法,我們可以改變這一點。我們可以實現的東西有點類似CSS類的名字綁定分配到的元素。瑞恩·尼邁耶(的knockmeout.net)已使用先前建議data-class這個避免混淆演示教學班,數據類,讓我們得到我們的nodeHasBindings功能支持這一點:
?
1
2
3
4
// does an element have any bindings?
function nodeHasBindings( node ) {
return node.getAttribute ? node.getAttribute("data-class") : false;
};
接下來,我們需要一個合理的getBindings()功能。當我們正在與CSS類的想法堅持,何不也考慮支持空格分隔類,允許我們不同元素之間共享綁定規格?
讓我們首先看一下我們的綁定將是什么樣子。我們創建一個對象來保存他們在那里我們的屬性名必須匹配,我們希望在我們的數據類使用的鍵。
注:沒有一個KnockoutJS應用程序使用傳統的數據綁定到與自定義綁定提供不顯眼的綁定轉換所需的工作很大。我們只是拉我們所有的數據綁定的屬性,在綁定對象按下面的數據類屬性替換它們,并把我們的綁定:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
var viewModel = new ViewModel( todos || [] ),
bindings = {
newTodo: {
value: viewModel.current,
valueUpdate: "afterkeydown",
enterKey: viewModel.add
},
taskTooltip: {
visible: viewModel.showTooltip
},
checkAllContainer: {
visible: viewModel.todos().length
},
checkAll: {
checked: viewModel.allCompleted
},
todos: {
foreach: viewModel.todos
},
todoListItem: function() {
return {
css: {
editing: this.editing
}
};
},
todoListItemWrapper: function() {
return {
css: {
done: this.done
}
};
},
todoCheckBox: function() {
return {
checked: this.done
};
},
todoContent: function() {
return {
text: this.content,
event: {
dblclick: this.edit
}
};
},
todoDestroy: function() {
return {
click: viewModel.remove
};
},
todoEdit: function() {
return {
value: this.content,
valueUpdate: "afterkeydown",
enterKey: this.stopEditing,
event: {
blur: this.stopEditing
}
};
},
todoCount: {
visible: viewModel.remainingCount
},
remainingCount: {
text: viewModel.remainingCount
},
remainingCountWord: function() {
return {
text: viewModel.getLabel(viewModel.remainingCount)
};
},
todoClear: {
visible: viewModel.completedCount
},
todoClearAll: {
click: viewModel.removeCompleted
},
completedCount: {
text: viewModel.completedCount
},
completedCountWord: function() {
return {
text: viewModel.getLabel(viewModel.completedCount)
};
},
todoInstructions: {
visible: viewModel.todos().length
}
};
....
然而有兩行從以上片段缺失-我們仍然需要我們的getBindings功能,將通過各自在我們的數據類屬性的關鍵,建立生成的對象從他們每個人的循環。如果我們檢測到綁定的對象是一個函數,我們使用的背景下我們目前的數據調用它this。我們完整的定制綁定提供者將如下所示:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
// We can now create a bindingProvider that uses
// something different than data-bind attributes
ko.customBindingProvider = function( bindingObject ) {
this.bindingObject = bindingObject;
// determine if an element has any bindings
this.nodeHasBindings = function( node ) {
return node.getAttribute ? node.getAttribute( "data-class" ) : false;
};
};
// return the bindings given a node and the bindingContext
this.getBindings = function( node, bindingContext ) {
var result = {},
classes = node.getAttribute( "data-class" );
if ( classes ) {
classes = classes.split( "" );
//evaluate each class, build a single object to return
for ( var i = 0, j = classes.length; i < j; i++ ) {
var bindingAccessor = this.bindingObject[classes[i]];
if ( bindingAccessor ) {
var binding = typeof bindingAccessor === "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor;
ko.utils.extend(result, binding);
}
}
}
return result;
};
};
因此,我們的的最后幾行bindings對象可以被定義如下:
?
1
2
3
4
五
6
7
// set ko's current bindingProvider equal to our new binding provider
ko.bindingProvider.instance = new ko.customBindingProvider( bindings );
// bind a new instance of our ViewModel to the page
ko.applyBindings( viewModel );
})();
我們在這里所做的是有效地定義構造函數為我們它接受一個對象(綁定),我們用它來查找我們的綁定綁定處理程序。使用數據類,如下所示,我們可以再重新寫的標記我們的應用程序查看:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="create-todo">
<input id="new-todo" data-class="newTodo" placeholder="What needs to be done?" />
<span class="ui-tooltip-top" data-class="taskTooltip" style="display: none;">Press Enter to save this task</span>
</div>
<div id="todos">
<div data-class="checkAllContainer" >
<input id="check-all" class="check" type="checkbox" data-class="checkAll" />
<label for="check-all">Mark all as complete</label>
</div>
<ul id="todo-list" data-class="todos" >
<li data-class="todoListItem" >
<div class="todo" data-class="todoListItemWrapper" >
<div class="display">
<input class="check" type="checkbox" data-class="todoCheckBox" />
<div class="todo-content" data-class="todoContent" style="cursor: pointer;"></div>
<span class="todo-destroy" data-class="todoDestroy"></span>
</div>
<div class="edit'>
<input class="todo-input" data-class="todoEdit'/>
</div>
</div>
</li>
</ul>
</div>
尼爾Kerkin放在一起使用上述完整TodoMVC演示應用程序,它可以訪問和周圍打了這里。
雖然它可能看起來像在上面的說明相當多的工作,現在我們有一個通用的getBindings方法寫的,這是一個很大簡單地重新使用它,并使用數據類,而不是嚴格的數據綁定的更多平凡的編寫我們KnockoutJS應用程序來代替。最終的結果是希望更清潔的標記從視圖被改變了我們的數據綁定到一個綁定的對象,而不是。
MVC比。MVP比。MVVM
這兩個MVP和MVVM是MVC的衍生物。它和它的衍生物之間的主要區別是依賴每個層對其他層以及它們彼此的緊密的約束。
在MVC中,視圖坐在我們的旁邊控制器架構之上。模型坐在下面的控制器,因此我們的觀點了解我們的控制器和控制器了解模型。在這里,我們的觀點有模型的直接訪問。然而,露出完整的模型視圖可能有安全和性能成本,這取決于我們的應用程序的復雜性。MVVM試圖避免這些問題。
在MVP中,控制器的角色被替換為主持人。主持人坐在同一水平的意見,聽從視圖和模型和調解他們之間的行動,這兩個事件。與MVVM,沒有綁定視圖來的ViewModels的機制,所以我們不是靠每個View實現一個接口,使演示者與觀交互。
因此MVVM允許我們創建一個可以包含的狀態和邏輯信息模型的特定視圖的子集,避免需要整個模型暴露于查看。不像MVP的主持人,一個ViewModel不需要參照。視圖可以綁定到視圖模型反過來揭露包含在模型到視圖的數據屬性。正如我們前面提到的,視圖的抽象意味著在它后面的代碼需要更少的邏輯。
其中一個缺點如此但是是需要的視圖模型和視圖,這可能有性能成本之間演繹的水平。在這種解釋的復雜性也可能有所不同 - 它可以作為復制數據簡單或操縱他們為我們想查看看到一個形式復雜。作為整個模型是現成的,可避免這種操縱的MVC沒有這個問題。
Backbone.js的比。KnockoutJS
了解MVC,MVP和MVVM之間的細微差別是很重要的,但開發商最終將詢問他們是否應該考慮使用基于什么我們學到了骨干KnockoutJS。以下注意事項可能會有所幫助在這里:
這兩個庫設計時考慮到不同的目標和它往往還不如剛剛選擇MVC或MVVM簡單
如果數據綁定和雙向溝通是你的主要關注點,KnockoutJS絕對是可以映射到JavaScript來go.Practically存儲在DOM節點的屬性或值的方式,這種方法的對象。
主干其難易程度RESTful服務一體化的過人之處,而KnockoutJS模型只是需要更新模型必須由開發者編寫的JavaScript對象和代碼。
KnockoutJS有一個專注于自動化UI綁定,這需要顯著更詳細的自定義代碼,如果試圖與骨干做到這一點。這是不是與骨干本身本身有問題,因為它有目的地試圖置身事外的UI。然而,擊退并試圖幫助這個問題。
隨著KnockoutJS,我們可以綁定自己的功能視圖模型觀測,這是任何時候執行的觀察到的變化。這使得我們的靈活性的水平相同可以在主干上找到
主干還內置了堅實的路由解決方案,同時KnockoutJS不提供路由選項的開箱。人們可以很容易但在填寫如使用本Alman的需要這種行為燒烤插件或獨立的路由系統像米勒梅德羅斯出色的 十字路口。
最后,我個人覺得KnockoutJS對于小型應用更適合,而骨干網的功能設置真正建立什么不平凡的時候眼前一亮。盡管如此,很多開發商都用了框架來寫不同復雜程度的應用程序,我建議在做這可能最適合您的項目作出決定之前在小規模嘗試兩者。
為進一步閱讀有關MVVM還是淘汰賽,我推薦以下相關文章:
作者MVVM的優勢
SO:什么是與MVVM的問題?
MVVM解釋
如何MVVM比較MVC?
在KnockoutJS自定義綁定
探索與淘汰賽TodoMVC
現代模塊化的JavaScript設計模式
退耦應用的重要性
在可擴展的JavaScript的世界里,當我們說一個應用程序是模塊化的,我們往往意味著它是由一組高度解耦,功能不同的部分存儲在模塊。松 ??散耦合通過去除方便的應用程序更易于維護的依賴在可能的情況。當這有效地實現,這是很容易看到如何改變一個系統的一個組成部分可能會影響到另一個。
不像一些更傳統的編程語言不過,JavaScript代碼當前迭代(ECMA-262)沒有提供的手段開發者在清潔,有組織的方式導入代碼這樣的模塊。這是與規范,沒有經過仔細思量,直到最近幾年在那里需要更多的有組織的JavaScript應用程序變得明顯關注的問題之一。
相反,開發人員目前都留給依傍的變化模塊或對象文字圖案,這是我們之前在書中覆蓋。隨著許多這些,模塊,腳本由一個單一的全局對象,它仍有 ??可能招致我們的架構命名沖突所描述的命名空間串成的DOM。但也沒有干凈的方式來處理依賴管理沒有一些手工勞動或第三方工具。
雖然這些問題本地解決方案將在抵達ES和諧(可能是JavaScript的下一個版本),好消息是,編寫模塊化的JavaScript從未如此簡單,我們可以從今天開始這樣做。
在本節中,我們將看看三種格式編寫模塊化的JavaScript:AMD,CommonJS的和建議的JavaScript,下一版本的和諧。
一個注腳本裝載機
這是很難討論AMD和CommonJS的模塊,而不談在房間里的大象- 腳本裝載機。在寫這本書的時候,腳本加載是一個目標的手段,這一目標是模塊化的JavaScript,可在今天的應用中使用-這一點,使用兼容的腳本加載器是不幸必要的。為了充分利用這一部分,我建議獲得了基本的了解流行的腳本加載工具如何工作等等的模塊格式的解釋在上下文中才有意義。
有許多在AMD和CommonJS的格式處理模塊加載很大的裝載機,但我個人的偏好RequireJS和curl.js。對這些工具的完整教程這本書的范圍,但我可以推薦閱讀約翰·漢恩的有關文章curl.js和詹姆斯·伯克的RequireJS更多的API文檔。
從生產的角度來看,這樣的模塊工作時,建議部署使用優化工具(如RequireJS優化器)來連接的腳本。有趣的是,與杏仁 AMD的墊片,RequireJS不需要在展開部位被軋制和人們可能考慮一個腳本裝載機可以開發之外很容易地移動。
這就是說,詹姆斯·伯克可能會說,能夠動態地加載腳本后,頁面加載仍然有它的用例和RequireJS可以用這個幫助了。考慮到這些票據,讓我們開始吧。
AMD
一個格式化為編寫模塊化的JavaScript在瀏覽器
對于AMD(異步模塊定義)格式的總體目標是提供模塊化的JavaScript開發人員現在可以使用的解決方案。它脫胎于使用XHR +評估和這種格式的支持者Dojo的現實世界的經驗要避免那些過去的??弱點受到任何未來的解決方案。
AMD的模塊格式本身是定義其中兩個模塊和依賴性,可模塊的建議異步加載。它有許多明顯的優點,包括被異步和由性質從而消除了緊密耦合的代碼和模塊身份之間的一個可能通常發現高度靈活。許多開發人員喜歡使用它,人們可以認為這是朝 ??著一個可靠的墊腳石模塊系統,提出了ES和諧。
AMD開始作為CommonJS的列表上的模塊格式的規范草案,但因為它是不能夠達到完全一致,格式的進一步發展移動到amdjs組。
今天,它是由項目,包括道場,MooTools的,螢火蟲,甚至jQuery的擁抱。盡管術語CommonJS的AMD格式已經在一次野外被看見,這是最好的參照,把它當成AMD或異步模塊的支持,而不是CommonJS的名單上的所有參與者都希望追求它。
注:有一段時間,該提案被作為模塊運輸/ C所指的時間,但是由于規范并沒有對現有的傳輸模塊CommonJS的齒輪,而是-定義模塊-它更有意義選擇AMD的命名約定。
入門模塊
前兩個概念值得注意約AMD是一家的理念define促進模塊定義和方法require來處理依賴裝載方法。定義用于定義使用下面的基于簽名的有名或無名的模塊:
?
1
2
3
4
五
define(
module_id /*optional*/,
[dependencies] /*optional*/,
definition function /*function for instantiating the module or object*/
);
正如我們可以通過內嵌批注知道的,module_id就是這是在同時使用非AMD拼接工具通常只要求一個可選的參數(可能還有一些其他的優勢情況下,它也是有用的)。如果這種說法忽略了,我們指以模塊為匿名。
當使用匿名模塊的工作,一個模塊的身份的想法是干的,使得它瑣碎,以避免文件名 ??和代碼的重復。因為代碼更輕便,它可以很容易地移動到其它位置(或周圍的文件系統),而不需要改變代碼本身或更改其模塊ID。考慮module_id類似的文件夾路徑的概念。
注:開發人員可以只通過使用AMD優化,與一個CommonJS的環境下工作,如運行在多種環境相同的代碼r.js。
回到define簽名,依賴參數表示這是由我們所定義的模塊和第三個參數(“定義函數”或“工廠函數”)所需的依賴數組是的執行來實例化模塊的功能。裸骨模塊可以被定義如下:
了解AMD:定義()
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
// A module_id (myModule) is used here for demonstration purposes only
define( "myModule",
["foo", "bar"],
// module definition function
// dependencies (foo and bar) are mapped to function parameters
function ( foo, bar ) {
// return a value that defines the module export
// (i.e the functionality we want to expose for consumption)
// create your module here
var myModule = {
doStuff: function () {
console.log( "Yay! Stuff" );
}
};
return myModule;
});
// An alternative version could be..
define( "myModule",
["math", "graph"],
function ( math, graph ) {
// Note that this is a slightly different pattern
// With AMD, it's possible to define modules in a few
// different ways due to it's flexibility with
// certain aspects of the syntax
return {
plot: function( x, y ){
return graph.drawPie( math.randomGrid( x, y ) );
}
};
});
需要在另一方面,通常使用在頂層JavaScript文件或模塊中,我們應該要動態地獲取依賴加載代碼。其使用的一個例子是:
了解AMD:需要()
?
1
2
3
4
五
6
7
8
9
// Consider "foo" and "bar" are two external modules
// In this example, the "exports" from the two modules
// loaded are passed as function arguments to the
// callback (foo and bar) so that they can similarly be accessed
require(["foo", "bar"], function ( foo, bar ) {
// rest of your code here
foo.doSomething();
});
動態加載的依賴
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
define(function ( require ) {
var isReady = false, foobar;
// note the inline require within our module definition
require(["foo", "bar"], function ( foo, bar ) {
isReady = true;
foobar = foo() + bar();
});
// we can still return a module
return {
isReady: isReady,
foobar: foobar
};
});
了解AMD:插件
以下是定義一個AMD兼容插件的例子:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
// With AMD, it's possible to load in assets of almost any kind
// including text-files and HTML. This enables us to have template
// dependencies which can be used to skin components either on
// page-load or dynamically.
define( ["./templates", "text!./template.md","css!./template.css" ],
function( templates, template ){
console.log( templates );
// do something with our templates here
}
});
注:雖然CSS!是包括在上述例子中加載的CSS相關性,它是要記住,這種方法有一些需要注意,如它不能被完全可能建立在CSS是滿載是很重要的。這取決于我們如何對待我們的構建過程中,它也可能導致CSS被包括在優化文件的依賴關系,因此使用CSS在這種情況下,謹慎加載的依賴。如果有興趣做上述情況,我們也可以探索@ VIISON的RequireJS CSS插件進一步這里。
加載AMD模塊使用RequireJS
?
1
2
3
4
五
6
7
8
require(["app/myModule"],
function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});
這個例子可以隨心所欲地為來看待requirejs(["app/myModule"], function(){})這表明正在使用的加載器的頂尖水平全局。這是如何揭開序幕不同的AMD裝載機模塊的頂級裝載然而與define()功能,如果它是通過當地要求所有require([])示例適用于這兩種類型的裝載機(curl.js和RequireJS)的。
加載AMD模塊使用curl.js
?
1
2
3
4
五
6
7
8
9
curl(["app/myModule.js"],
function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});
模塊,遞延依賴
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
// This could be compatible with jQuery's Deferred implementation,
// futures.js (slightly different syntax) or any one of a number
// of other implementations
define(["lib/Deferred"], function( Deferred ){
var defer = new Deferred();
require(["lib/templates/?index.html","lib/data/?stats"],
function( template, data ){
defer.resolve( { template: template, data:data } );
}
);
return defer.promise();
});
AMD模塊,道場
定義使用Dojo的AMD兼容模塊是相當直接的。根據上述,在一個數組作為第一個參數定義任何模塊依賴,并提供一個回調(工廠),一旦依賴已經被加載,這將執行該模塊。例如:
?
1
2
3
4
五
6
define(["dijit/Tooltip"], function( Tooltip ){
//Our dijit tooltip is now available for local use
new Tooltip(...);
});
需要注意的模塊,它由一個Dojo異步加載,RequireJS或標準,現在可以既消耗的匿名性質dojo.require()模塊加載。
沒有與模塊的引用是知道這里有用的一些有趣的陷阱。雖然引用模塊的AMD-倡導的方式宣告他們與一組匹配參數的依賴關系列表,這是不是上了年紀的Dojo 1.6版本系統支持 - 它真的只適用于兼容AMD-裝載機。例如:
?
1
2
3
4
五
6
define(["dojo/cookie", "dijit/Tooltip"], function( cookie, Tooltip ){
var cookieValue = cookie( "cookieName" );
new Tooltip(...);
});
這個擁有超過嵌套命名空間的許多優點,模塊不再需要直接引用完整的命名空間的每一次 - 我們所要求的是“道場/曲奇”的依賴,曾經化名為參數的路徑,可以通過該變量引用。這樣就不需要重復鍵入了“道場”。在我們的應用。
最后的疑難雜癥要注意的是,如果我們希望繼續使用舊的Dojo構建系統,或者希望舊的模塊遷移到這個新的AMD風格,下面的更詳細的版本,使更容易遷移。請注意,Dojo和的dijit和引用為依賴太:
?
1
2
3
4
define(["dojo", "dijit', "dojo/cookie", "dijit/Tooltip"], function( dojo, dijit ){
var cookieValue = dojo.cookie( "cookieName" );
new dijit.Tooltip(...);
});
AMD模塊設計模式(道場)
正如我們在前面章節所看到的,設計模式可以提高我們如何處理結構化的解決方案,共同發展的問題非常有效。約翰·漢恩給有關AMD模塊的設計模式,涵蓋了辛格爾頓,裝飾,中介和其他人,我的一些精彩演講強烈建議您檢查出他的幻燈片,如果我們得到一個機會。
AMD的設計模式的選擇可以在下面找到。
Decorator模式:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
// mylib/UpdatableObservable: a Decorator for dojo/store/Observable
define(["dojo", "dojo/store/Observable"], function ( dojo, Observable ) {
return function UpdatableObservable ( store ) {
var observable = dojo.isFunction( store.notify ) ? store :
new Observable(store);
observable.updated = function( object ) {
dojo.when( object, function ( itemOrArray) {
dojo.forEach( [].concat(itemOrArray), this.notify, this );
});
};
return observable;
};
});
// Decorator consumer
// a consumer for mylib/UpdatableObservable
define(["mylib/UpdatableObservable"], function ( makeUpdatable ) {
var observable,
updatable,
someItem;
// make the observable store updatable
updatable = makeUpdatable( observable ); // `new` is optional!
// we can then call .updated() later on if we wish to pass
// on data that has changed
//updatable.updated( updatedItem );
});
Adapter模式
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
// "mylib/Array" adapts `each` function to mimic jQuerys:
define(["dojo/_base/lang", "dojo/_base/array"], function ( lang, array ) {
return lang.delegate( array, {
each: function ( arr, lambda ) {
array.forEach( arr, function ( item, i ) {
lambda.call( item, i, item ); // like jQuery's each
});
}
});
});
// Adapter consumer
// "myapp/my-module":
define(["mylib/Array"], function ( array ) {
array.each( ["uno", "dos", "tres"], function ( i, esp ) {
// here, `this` == item
});
});
AMD模塊使用jQuery
不像道場,jQuery的真的只配備了一個文件,但是考慮到庫的基于插件的性質,我們可以證明它是多么直截了當定義使用它低于AMD模塊。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
define(["js/jquery.js","js/jquery.color.js","js/underscore.js"],
function( $, colorPlugin, _ ){
// Here we've passed in jQuery, the color plugin and Underscore
// None of these will be accessible in the global scope, but we
// can easily reference them below.
// Pseudo-randomize an array of colors, selecting the first
// item in the shuffled array
var shuffleColor = _.first( _.shuffle( "#666","#333","#111"] ) );
// Animate the background-color of any elements with the class
// "item" on the page using the shuffled color
$( ".item" ).animate( {"backgroundColor": shuffleColor } );
// What we return can be used by other modules
return {};
});
然而有一些從這個例子缺失和它的注冊的概念。
jQuery的注冊作為一個異步兼容模塊
其中一個在jQuery的1.7降落的主要特點是注冊的jQuery作為異步模塊的支持。有使用異步模塊的格式將數字兼容腳本裝載機(包括RequireJS和卷曲),其能夠裝載的模塊,這意味著較少的黑客需要得到的東西的工作。
如果開發者想使用AMD和不希望自己的jQuery版本泄露到全球的空間,他們應該叫noConflict在使用jQuery的頂層模塊。另外,由于jQuery的多個版本可以是一個頁面上有一些一個AMD裝載機必須考慮特殊的考慮,因此jQuery的只與已經認識到這些問題,這是由裝載機指定指示的AMD裝載機寄存器define.amd.jQuery。RequireJS和卷曲是兩個加載器,這樣做
命名AMD提供的既是堅固和安全的大多數用例安全毯。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
// Account for the existence of more than one global
// instances of jQuery in the document, cater for testing
// .noConflict()
var jQuery = this.jQuery || "jQuery",
$ = this.$ || "$",
originaljQuery = jQuery,
original$ = $;
define(["jquery"], function ( $ ) {
$( ".items" ).css( "background","green" );
return function () {};
});
為什么AMD更好的選擇對于編寫模塊化的JavaScript?
為如何處理定義靈活的模塊一份明確的建議。
顯著比現在全局命名空間和更清潔的<script>標簽解決方案,我們許多人的依賴。有宣布獨立模塊和依賴關系,他們可能有一個干凈的方式。
模塊定義封裝,幫助我們避免了全局命名空間的污染。
按理說作品比一些替代解決方案(如CommonJS的,我們將著眼于不久)更好。它不具有與跨域,本地或調試問題,并且沒有被使用于服務器端工具的依賴。大多數AMD裝載機支持在瀏覽器中加載模塊,而構建過程。
提供了包括一個單一文件中的多個模塊“運輸”的做法。像CommonJS的其他方法還沒有對傳輸格式達成一致。
如果這是需要有可能延遲加載腳本。
注:上述許多可對YUI的模塊加載策略可以說為好。
相關閱讀
該RequireJS指南AMD
什么是加載模塊,AMD最快的方法?
AMD與CommonJS的,有什么更好的格式?
AMD是更好地為網絡比CommonJS的模塊
未來是不是模塊框架
AMD不再是一個CommonJS的規格
在發明的JavaScript模塊格式和文字裝載機
AMD的郵件列表
什么腳本裝載機和框架的支持AMD?
在瀏覽器:
RequireJS http://requirejs.org
curl.js http://github.com/unscriptable/curl
bdLoad http://bdframework.com/bdLoad
Yabble http://github.com/jbrantly/yabble
PINF http://github.com/pinf/loader-js
(和更多)
服務器端:
RequireJS http://requirejs.org
PINF http://github.com/pinf/loader-js
AMD結論
已經采用了AMD的一些項目,我的結論是,它蜱大量的復選框開發商造成嚴重的應用程序可能會從一個更好的模塊格式的愿望。它避免了需要擔心全局,支持命名模塊,不需要服務器轉型發揮作用,是用于依賴管理一種樂趣。
這也是使用Backbone.js的,ember.js或任意數量的保持組織的其他應用程序的結構框架的模塊化開發一個極好的補充。
作為AMD已經為Dojo和CommonJS的世界里近兩年來被大量討論,我們知道這是有時間成熟和發展。我們也知道它已經在野外被一些大公司打造不平凡的應用程序(IBM,BBC的iPlayer)等,如果它不工作,機會是他們應該已經放棄了它的實戰檢驗,但沒有。
盡管如此,仍有一些AMD可以改進的地方。誰使用的格式了一段時間的開發人員可能會覺得AMD的樣板/封裝代碼是一個惱人的開銷。雖然我有同樣的憂慮,有工具,如瓦羅,可以幫助解決這些問題的工作,我認為,從整體上看,隨著采用AMD的優點遠遠多于缺點。
CommonJS的
優化的服務器模塊格式
該CommonJS的模塊建議指定一個簡單的API來聲明模塊服務器端和AMD不同嘗試將覆蓋更廣泛的問題,如IO,文件系統,承諾等等。
該格式是提出了CommonJS的 -其目的是設計,原型和規范的JavaScript API的志愿者工作組。到目前為止,他們已經嘗試批準為標準模塊和包。
入門
從結構的角度來看,一個CommonJS的模塊是JavaScript的一個可重復使用的一塊其中出口提供給任何相關的代碼的特定對象。AMD不同,通常有這樣的周圍沒有任何模塊函數封裝(所以我們不會看到define這里的例子)。
CommonJS的模塊主要包含兩個主要部分:一個名為自由變量exports,其中包含一個模塊希望提供給其他模塊和對象require的模塊可以使用導入其他模塊的功能的出口。
了解CommonJS的:需要()和出口
?
1
2
3
4
五
6
7
8
9
10
// package/lib is a dependency we require
var lib = require( "package/lib" );
// behaviour for our module
function foo(){
lib.log( "hello world!" );
}
// export (expose) foo to other modules
exports.foo = foo;
出口的基本消費
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// define more behaviour we would like to expose
function foobar(){
this.foo = function(){
console.log( "Hello foo" );
}
this.bar = function(){
console.log( "Hello bar" );
}
}
// expose foobar to other modules
exports.foobar = foobar;
// an application consuming "foobar"
// access the module relative to the path
// where both usage and module files exist
// in the same directory
var foobar = require("./foobar").foobar,
test = new foobar();
// Outputs: "Hello bar"
test.bar();
AMD當量的第一例CommonJS的
?
1
2
3
4
五
6
7
8
9
10
11
12
13
define(function(require){
var lib = require( "package/lib" );
// some behaviour for our module
function foo(){
lib.log( "hello world!" );
}
// export (expose) foo for other modules
return {
foobar: foo
};
});
這是可以做到像AMD支持簡化CommonJS的纏繞特性。
消費多個依賴
app.js
?
1
2
3
4
五
6
7
8
9
10
var modA = require( "./foo" );
var modB = require( "./bar" );
exports.app = function(){
console.log( "Im an application!" );
}
exports.foo = function(){
return modA.helloWorld();
}
bar.js
?
1
exports.name = "bar";
foo.js
?
1
2
3
4
require( "./bar" );
exports.helloWorld = function(){
return "Hello World!!"
}
什么裝載機和框架的支持CommonJS的?
在瀏覽器:
curl.js http://github.com/unscriptable/curl
SproutCore的1.1 http://sproutcore.com
PINF http://github.com/pinf/loader-js
服務器端:
節點http://nodejs.org
獨角鯨https://github.com/tlrobinson/narwhal
堅持http://www.persvr.org/
Wakanda http://www.wakandasoft.com/
是CommonJS的適合瀏覽器?
有開發商覺得CommonJS的是更適合于服務器端的開發這是一個原因還有目前水平的分歧在哪一種格式應,將用作和諧預時代前進的事實標準。一些對CommonJS的參數包括一張紙條,上面很多CommonJS的API的解決面向服務器的功能,其中之一就根本無法實現在JavaScript的瀏覽器級別的-例如,IO,系統和JS可以考慮通過自然unimplementable其功能。
這就是說,它是非常有用的知道如何構建CommonJS的模塊,而不管這樣我們就可以更好地理解它們是如何界定可到處被使用模塊時。這在客戶端和服務器上的應用模塊包括驗證,轉換和模板引擎。一些開發商已經接近選擇使用哪種格式的方法是選擇了CommonJS的當一個模塊可以在服務器端環境中使用,并使用AMD如果不是這種情況。
作為AMD模塊能夠使用的插件,可以這樣定義構造函數和功能更精細的東西,這個是有道理的。CommonJS的模塊只能定義可以是與繁瑣,如果我們試圖獲得構造出他們的工作對象。
雖然它超出了本節的范圍,人們可能還注意到,有不同類型的討論AMD和CommonJS的時候提到“規定”的方法。用類似的命名約定的關注當然是混亂和社會各界正在分裂,在全球需要的功能的優點。在這里約翰·漢恩的建議是,與其稱之為“需要”,這很可能將無法實現全球和內蒙兩地通知用戶不同的要求,這可能更有意義重命名的全球裝載 ??機法別的目標(如:庫的名稱)。這是出于這個原因,像curl.js裝載機使用curl(),而不是require。
相關閱讀
揭秘CommonJS的模塊
JavaScript的成長
該RequireJS札記CommonJS的
采取嬰孩步驟,Node.js和受CommonJS的 - 創建自定義模塊
異步CommonJS的模塊瀏覽器
在CommonJS的郵件列表
AMD && CommonJS的競爭,但同樣有效的標準
AMD和CommonJS的有效模塊格式,不同的最終目標。
AMD采用了瀏覽器的第一的發展方針,選擇了異步行為和簡化的向后兼容,但它不具有文件I / O的任何概念。它支持對象,函數,構造函數,字符串,JSON和許多其他類型的模塊,在瀏覽器中運行的本身。這是令人難以置信的靈活性。
CommonJS的,另一方面需要一個服務器第一的方針,假設同步行為,沒有全球性的行李,并試圖應付未來的(在服務器上)。我們的意思是,由于CommonJS的支持展開模塊,它可以感覺有點更貼近ES.next/Harmony規格,釋放我們的define(),AMD公司實施包裝。CommonJS的模塊但是只支持對象的模塊。
UMD:AMD和CommonJS的兼容模塊插件
對于那些希望創建可以在瀏覽器都和服務器端環境中工作模塊的開發,現有的解決方案可以考慮稍有欠缺。為了幫助減輕這些的,詹姆斯·伯克,我和其他一些開發人員創建的UMD(通用模塊定義)https://github.com/umdjs/umd。
UMD是一個實驗性的模塊格式,允許符合可在寫作時流行的腳本加載技術的全部或大部分客戶端和服務器的環境中工作模塊的定義。雖然(還)其它模塊格式的想法可能是艱巨的,我們將介紹UMD簡要的完整性考慮。
我們最初開始采取看看在AMD規范的支持簡化CommonJS的包裝定義UMD。為希望如同它們是CommonJS的模塊寫模塊的開發者,以下CommonJS的兼容的格式可以使用:
基本AMD混合格式
?
1
2
3
4
五
6
7
8
define( function ( require, exports, module ){
var shuffler = require( "lib/shuffle" );
exports.randomize = function( input ){
return shuffler.shuffle( input );
}
});
要注意,一個模塊是真正僅視為CommonJS的模塊,如果它不包含一個依賴陣列和定義函數包含在最小的一個參數是多么重要。這也將無法在某些設備上正常工作(如PS3)。有關上述包裝的詳細信息,請參閱http://requirejs.org/docs/api.html#cjsmodule。
有鑒于此進一步,我們希望提供一個數字,不僅僅是AMD和CommonJS的工作不同的模式,而且還解決了那些希望開發這樣的模塊有與其他環境中很常見的兼容性問題的開發人員。
一個這樣的變化,我們可以看到下面讓我們使用CommonJS的,AMD或瀏覽器全局來創建一個模塊。
使用CommonJS的,AMD或瀏覽器全局來創建一個模塊
定義一個模塊commonJsStrict,這取決于另一個名為模塊b。該模塊的名稱由文件名 ??和文件名 ??,導出的全局名稱相同的最佳實踐暗示。
如果模塊b也使用相同類型的樣板中的瀏覽器,它會創建一個全球性.b的使用。如果我們不希望支持的瀏覽器補丁全球,我們可以刪除root和傳遞this的第一個參數頂端的功能。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
(function ( root, factory ) {
if ( typeof exports === 'object' ) {
// CommonJS
factory( exports, require('b') );
} else if ( typeof define === 'function' && define.amd ) {
// AMD. Register as an anonymous module.
define( ['exports', 'b'], factory);
} else {
// Browser globals
factory( (root.commonJsStrict = {}), root.b );
}
}(this, function ( exports, b ) {
//use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
該UMD庫包含變化涵蓋在瀏覽器中最佳狀態工作,這些最適合提供出口,那些最適合CommonJS的運行時間,甚至那些最適合定義jQuery插件,我們將看看在下次模塊。
jQuery插件,在所有環境中正常運行
UMD提供使用jQuery插件工作兩種模式 - 其中一個定義了與AMD和瀏覽器全局,另一個也可以在CommonJS的環境中工作,工作得很好的插件。jQuery是不太可能在大多數CommonJS的被使用的環境,以便記住這一點,除非我們與它沒有發揮它很好的環境中工作。
現在,我們將定義一個芯和一個擴展該核心組成插件。核心插件加載到一個$.core命名空間中,然后可以通過命名空間模式使用插件擴展輕松擴展。通過腳本標簽加載的插件自動填充plugin下空間core(即$.core.plugin.methodName())。
該模式可以是相當不錯的,因為與擴展插件可以訪問在基本定義的屬性和方法,或用少許調整,覆蓋默認行為,以便它可以擴展到做更多的工作。也沒有需要一個裝載機做任何的這個功能齊全。
對于正在做什么的詳細信息,請參見下面的代碼示例中內嵌批注。
usage.html中
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="pluginCore.js"></script>
<script type="text/javascript" src="pluginExtension.js"></script>
<script type="text/javascript">
$(function(){
// Our plugin "core" is exposed under a core namespace in
// this example, which we first cache
var core = $.core;
// Then use use some of the built-in core functionality to
// highlight all divs in the page yellow
core.highlightAll();
// Access the plugins (extensions) loaded into the "plugin"
// namespace of our core module:
// Set the first div in the page to have a green background.
core.plugin.setGreen( "div:first");
// Here we're making use of the core's "highlight" method
// under the hood from a plugin loaded in after it
// Set the last div to the "errorColor" property defined in
// our core module/plugin. If we review the code further down,
// we can see how easy it is to consume properties and methods
// between the core and other plugins
core.plugin.setRed("div:last");
});
</script>
pluginCore.js
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Module/Plugin core
// Note: the wrapper code we see around the module is what enables
// us to support multiple module formats and specifications by
// mapping the arguments defined to what a specific format expects
// to be present. Our actual module functionality is defined lower
// down, where a named module and exports are demonstrated.
//
// Note that dependencies can just as easily be declared if required
// and should work as demonstrated earlier with the AMD module examples.
(function ( name, definition ){
var theModule = definition(),
// this is considered "safe":
hasDefine = typeof define === "function" && define.amd,
// hasDefine = typeof define === "function",
hasExports = typeof module !== "undefined" && module.exports;
if ( hasDefine ){ // AMD Module
define(theModule);
} else if ( hasExports ) { // Node.js Module
module.exports = theModule;
} else { // Assign to common namespaces or simply the global object (window)
( this.jQuery || this.ender || this.$ || this)[name] = theModule;
}
})( "core", function () {
var module = this;
module.plugins = [];
module.highlightColor = "yellow";
module.errorColor = "red";
// define the core module here and return the public API
// This is the highlight method used by the core highlightAll()
// method and all of the plugins highlighting elements different
// colors
module.highlight = function( el,strColor ){
if( this.jQuery ){
jQuery(el).css( "background", strColor );
}
}
return {
highlightAll:function(){
module.highlight("div", module.highlightColor);
}
};
});
pluginExtension.js
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Extension to module core
(function ( name, definition ) {
var theModule = definition(),
hasDefine = typeof define === "function",
hasExports = typeof module !== "undefined" && module.exports;
if ( hasDefine ) { // AMD Module
define(theModule);
} else if ( hasExports ) { // Node.js Module
module.exports = theModule;
} else {
// Assign to common namespaces or simply the global object (window)
// account for for flat-file/global module extensions
var obj = null,
namespaces,
scope;
obj = null;
namespaces = name.split(".");
scope = ( this.jQuery || this.ender || this.$ || this );
for ( var i = 0; i < namespaces.length; i++ ) {
var packageName = namespaces[i];
if ( obj && i == namespaces.length - 1 ) {
obj[packageName] = theModule;
} else if ( typeof scope[packageName] === "undefined" ) {
scope[packageName] = {};
}
obj = scope[packageName];
}
}
})( "core.plugin", function () {
// Define our module here and return the public API.
// This code could be easily adapted with the core to
// allow for methods that overwrite and extend core functionality
// in order to expand the highlight method to do more if we wish.
return {
setGreen: function ( el ) {
highlight(el, "green");
},
setRed: function ( el ) {
highlight(el, errorColor);
}
};
});
UMD并不打算取代AMD也不CommonJS的,而只是提供了希望得到他們的代碼在今天更多的環境中工作的開發人員的一些補充援助。如需了解更多信息或對本實驗格式獻計獻策,看https://github.com/umdjs/umd。
深入閱讀
“ 使用AMD裝載機編寫和管理模塊化的JavaScript,”約翰·漢恩
“ 揭秘CommonJS的模塊,”亞歷克斯楊
“ AMD模塊模式:辛格爾頓,”約翰·漢恩
“ 隨處運行的JavaScript模塊樣板代碼,”克里斯Zyp也
“ 標準和JavaScript的模塊和jQuery的建議,”詹姆斯·伯克
ES和諧
模塊的未來
TC39,負責確定ECMAScript和其未來迭代的語法和語義的標準組織是由一些非常聰明的開發者。有些開發商(如亞歷克斯·羅素)一直保持在過去幾年中使用的JavaScript進行大規模的發展演變密切關注和敏銳地編寫更模塊化的JS意識到需要更好的語言功能。
出于這個原因,目前有對一些令人興奮的補充包括靈活的語言提案模塊可在客戶端兩者和服務器,一個上工作模塊加載和更多。在本節中,我們將探討使用擬在ES.next模塊的語法,所以我們可以得到什么樣的來一嘗代碼示例。
注意:盡管和諧是仍處于建議階段,我們已經可以嘗試ES.next的(部分)功能,滿足模塊化的JavaScript寫的感謝谷歌的原生支持Traceur編譯器。起床,在一分鐘內用Traceur運行,請閱讀本入門指南。還有一個JSConf 介紹一下這是值得考慮的,如果想了解更多有關項目。
模塊,進口和出口
通過AMD和CommonJS的模塊部分讀過你可能熟悉模塊依賴關系(進口)和模塊的出口(或公共API /變量,我們讓其他模塊消耗)的概念。在ES.next,這些概念已經在一個稍微更簡潔的方式與使用被指定的依賴提出的import關鍵字。export是不是我們所期望的,很多開發商會看代碼樣本低了下去,瞬間抓住他們很大的不同。
進口報關綁定組件出口作為局部變量,可能被重命名為避免名稱沖突/沖突。
出口報關聲明一個模塊的本地綁定是外部可見的,使得其他模塊可以讀取的出口,但不能修改它們。有趣的是,模塊可能出口子模塊,但不能導出已在其他地方定義的模塊。所以他們的外部名稱從當地的名字不同,我們還可以重命名出口。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
module staff{
// specify (public) exports that can be consumed by
// other modules
export var baker = {
bake: function( item ){
console.log( "Woo! I just baked " + item );
}
}
}
module skills{
export var specialty = "baking";
export var experience = "5 years";
}
module cakeFactory{
// specify dependencies
import baker from staff;
// import everything with wildcards
import * from skills;
export var oven = {
makeCupcake: function( toppings ){
baker.bake( "cupcake", toppings );
},
makeMuffin: function( mSize ){
baker.bake( "muffin", size );
}
}
}
加載從遠程源模塊
該模塊建議還迎合這是基于遠程使其簡單化加載從外部位置模塊模塊(如第三方庫)。下面是我們上面并利用它定義模塊拉動的例子:
?
1
2
3
module cakeFactory from "http://addyosmani.com/factory/cakes.js";
cakeFactory.oven.makeCupcake( "sprinkles" );
cakeFactory.oven.makeMuffin( "large" );
模塊加載器API
提出的模塊加載描述了高度受控的環境中加載模塊動態API。支撐在裝載機簽名包括load(url, moduleInstance, error) 用于裝載模塊,createModule(object, globalModuleReferences)和其它。
下面是我們最初定義的模塊動態加載另一個例子。需要注意的是,我們的模塊中拉出從遠程源最后一個例子不同的是,模塊加載API是更適合于動態環境。
?
1
2
3
4
Loader.load( "http://addyosmani.com/factory/cakes.js",
function( cakeFactory ){
cakeFactory.oven.makeCupcake( "chocolate" );
});
CommonJS的類模塊服務器
對于開發商誰更感興趣的服務器環境,提出了ES.next模塊系統不只是受限于看著在瀏覽器模塊。下面舉例來說,我們可以看到建議在服務器上使用CommonJS的類模塊:
?
1
2
3
// io/File.js
export function open( path ) { ... };
export function close( hnd ) { ... };
?
1
2
3
4
五
6
7
8
9
10
// compiler/LexicalHandler.js
module file from "io/File";
import { open, close } from file;
export function scan( in ) {
try {
var h = open( in ) ...
}
finally { close( h ) }
}
?
1
2
3
4
module lexer from "compiler/LexicalHandler";
module stdlib from "@std";
//... scan(cmdline[0]) ...
班,構造函數,吸氣和二傳手
一個類的概念始終與較真的一個有爭議的問題,我們目前有兩種回落對JavaScript的相處原型性質或通過使用框架或抽象,提供能夠使用類的形式定義了脫糖到相同的原型的行為。
在和諧,類已提出了與構造函數和(最終)一些真正意義上的隱私沿著語言。在下面的例子中,提供內嵌批注,以有助于解釋類是如何構成的。
通過閱讀,我們也可能會注意到缺少這里所說的“功能”的。這是不是一個錯字錯誤:TC39一直在有意識地努力,以減少我們對濫用function關鍵字的一切,并希望是,這將有助于簡化我們如何編寫代碼。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Cake{
// We can define the body of a class" constructor
// function by using the keyword "constructor" followed
// by an argument list of public and private declarations.
constructor( name, toppings, price, cakeSize ){
public name = name;
public cakeSize = cakeSize;
public toppings = toppings;
private price = price;
}
// As a part of ES.next's efforts to decrease the unnecessary
// use of "function" for everything, you'll notice that it's
// dropped for cases such as the following. Here an identifier
// followed by an argument list and a body defines a new method
addTopping( topping ){
public( this ).toppings.push( topping );
}
// Getters can be defined by declaring get before
// an identifier/method name and a curly body.
get allToppings(){
return public( this ).toppings;
}
get qualifiesForDiscount(){
return private( this ).price > 5;
}
// Similar to getters, setters can be defined by using
// the "set" keyword before an identifier
set cakeSize( cSize ){
if( cSize < 0 ){
throw new Error( "Cake must be a valid size -
either small, medium or large" );
}
public( this ).cakeSize = cSize;
}
}
ES和諧結論
正如我們所看到的,和諧可能附帶一些令人興奮的新增加,這將緩解模塊化的應用和處理的問題,如依賴管理的發展。
目前,在今天的瀏覽器中使用的語法和諧我們最好的選擇是通過一個transpiler如谷歌Traceur或Esprima。還有一些項目,如要求HM這允許我們使用模塊的和諧與AMD。我們最好的賭注然而,直到我們有規范的定稿是AMD(在瀏覽器模塊)和CommonJS的(對于那些在服務器上)。
相關閱讀
答:首先看一下即將JavaScript的模塊
大衛·赫爾曼于JavaScript / ES.Next(視頻)
ES和諧模塊的建議
ES和諧模塊語義/結構原理
ES和諧類提案
結論
在本節中,我們審查了若干可供利用現代模塊格式編寫模塊化的JavaScript選項。
這些格式有許多優于單獨使用模塊模式包括:避免了需要管理的全局變量,靜態和動態依賴管理更好的支持,改善了與腳本裝載機的兼容性,服務器和更多的模塊,更好的兼容性。
總之,我建議您嘗試了什么本章中被認為是這些格式提供了強大功能和靈活性,可以更好的組織我們的應用程序顯著的幫助很大。
設計模式的jQuery
jQuery是目前最流行的JavaScript DOM操作庫,并提供了一??個安全的,跨瀏覽器的方式與DOM交互的抽象層。有趣的是,該庫也作為如何設計模式可以有效地用于創建的API是可讀和易于使用的例子。
雖然在許多情況下寫的jQuery核心貢獻者并沒有特意使用特定的模式,無論他們存在那里,從學習有用的。讓我們來看看什么的一些模式,以及它們是如何在API中使用。
Composite模式
的組合模式描述了一組,可以以同樣的方式的對象的單個實例可以是被處理的對象。
這使我們能夠以統一的方式處理這兩個單獨的對象和組合物,這意味著相同的行為將被無論我們是否正在與一個項目或千工作的應用。
在jQuery中,當我們運用方法的一個或多個元素的集合,我們可以在一個統一的方式既選擇返回一個jQuery對象對待兩套。
這是通過使用下面的jQuery選擇的代碼示例演示。在這里,它可能是添加active類都選擇了一個元素(例如,使用一個唯一的ID的元素)或一組具有相同標記名或類的元素,而無需額外的努力:
?
1
2
3
4
五
6
7
8
// Single elements
$( "#singleItem" ).addClass( "active" );
$( "#container" ).addClass( "active" );
// Collections of elements
$( "div" ).addClass( "active" );
$( ".item" ).addClass( "active" );
$( "input" ).addClass( "active" );
jQuery的addClass()實現可以直接使用本地的回路(或jQuery的jQuery.each()/ jQuery.fn.each()),以迭代通過集合為了應用該方法既單品或組。翻翻我們可以看到這款源確實是這樣的:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
addClass: function( value ) {
var classNames, i, l, elem,
setClass, c, cl;
if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
jQuery( this ).addClass( value.call(this, j, this.className) );
});
}
if ( value && typeof value === "string" ) {
classNames = value.split( rspace );
for ( i = 0, l = this.length; i < l; i++ ) {
elem = this[ i ];
if ( elem.nodeType === 1 ) {
if ( !elem.className && classNames.length === 1 ) {
elem.className = value;
} else {
setClass = " " + elem.className + " ";
for ( c = 0, cl = classNames.length; c < cl; c++ ) {
if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
setClass += classNames[ c ] + " ";
}
}
elem.className = jQuery.trim( setClass );
}
}
}
}
return this;
}
Adapter模式
該適配器模式轉換的接口對象或類與特定系統兼容的接口。
適配器基本上允許對象或類的共同作用通常不能因自己的不兼容的接口。該適配器轉換調用它的接口進入調用原來的界面,并實現這通常是相當小所需的代碼。
我們可能已使用的適配器的一個例子是jQuery的jQuery.fn.css()方法。它有助于正常化的接口如何樣式可以跨多個瀏覽器的應用,使得它瑣碎我們使用一個簡單的語法,其適于使用什么瀏覽器實際上支持幕后:
?
1
2
3
4
五
6
7
8
9
// Cross browser opacity:
// opacity: 0.9; Chrome 4+, FF2+, Saf3.1+, Opera 9+, IE9, iOS 3.2+, Android 2.1+
// filter: alpha(opacity=90); IE6-IE8
// Setting opacity
$( ".container" ).css( { opacity: .5 } );
// Getting opacity
var currentOpacity = $( ".container" ).css('opacity');
相應的jQuery芯cssHook這使得上述可能的下面可以看到:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
get: function( elem, computed ) {
// IE uses filters for opacity
return ropacity.test( (
computed && elem.currentStyle ?
elem.currentStyle.filter : elem.style.filter) || "" ) ?
( parseFloat( RegExp.$1 ) / 100 ) + "" :
computed ? "1" : "";
},
set: function( elem, value ) {
var style = elem.style,
currentStyle = elem.currentStyle,
opacity = jQuery.isNumeric( value ) ?
"alpha(opacity=" + value * 100 + ")" : "",
filter = currentStyle && currentStyle.filter || style.filter || "";
// IE has trouble with opacity if it does not have layout
// Force it by setting the zoom level
style.zoom = 1;
// if setting opacity to 1, and no other filters
//exist - attempt to remove filter attribute #6652
if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
// Setting style.filter to null, "" & " " still leave
// "filter:" in the cssText if "filter:" is present at all,
// clearType is disabled, we want to avoid this style.removeAttribute
// is IE Only, but so apparently is this code path...
style.removeAttribute( "filter" );
// if there there is no filter style applied in a css rule, we are done
if ( currentStyle && !currentStyle.filter ) {
return;
}
}
// otherwise, set new filter values
style.filter = ralpha.test( filter ) ?
filter.replace( ralpha, opacity ) :
filter + " " + opacity;
}
};
Facade模式
正如我們在本書的前面審查,但外觀模式提供了一個更簡單的抽象接口的代碼較大(可能更復雜)身上。
外立面可以經常發現整個jQuery庫和處理DOM操作,動畫和特別感興趣,跨??瀏覽器的Ajax開發者提供容易獲得實現。
以下是jQuery的門面$.ajax():
?
1
2
3
4
$.get( url, data, callback, dataType );
$.post( url, data, callback, dataType );
$.getJSON( url, data, callback );
$.getScript( url, callback );
這些被轉換的場景背后:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// $.get()
$.ajax({
url: url,
data: data,
dataType: dataType
}).done( callback );
// $.post
$.ajax({
type: "POST",
url: url,
data: data,
dataType: dataType
}).done( callback );
// $.getJSON()
$.ajax({
url: url,
dataType: "json",
data: data,
}).done( callback );
// $.getScript()
$.ajax({
url: url,
dataType: "script",
}).done( callback );
什么是更有趣的是,上面的外墻其實是門面在自己的權利,躲在幕后的復雜性很大。
這是因為jQuery.ajax()在jQuery的核心實現是一個不平凡的一段代碼,至少可以說。在它至少標準化XHR(XMLHttpRequest的)之間的跨瀏覽器的差異,使平凡的我們執行常用的HTTP操作(例如get,post等),工作,Deferreds等。
因為它會采取整章顯??示所有與上述有關的外墻,這里的代碼是不是在jQuery的核心正火XHR的代碼:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Functions to create xhrs
function createStandardXHR() {
try {
return new window.XMLHttpRequest();
} catch( e ) {}
}
function createActiveXHR() {
try {
return new window.ActiveXObject( "Microsoft.XMLHTTP" );
} catch( e ) {}
}
// Create the request object
jQuery.ajaxSettings.xhr = window.ActiveXObject ?
/* Microsoft failed to properly
* implement the XMLHttpRequest in IE7 (can't request local files),
* so we use the ActiveXObject when it is available
* Additionally XMLHttpRequest can be disabled in IE7/IE8 so
* we need a fallback.
*/
function() {
return !this.isLocal && createStandardXHR() || createActiveXHR();
} :
// For all other browsers, use the standard XMLHttpRequest object
createStandardXHR;
...
雖然下面的代碼塊也高于實際的jQuery XHR(A級jqXHR)的實現,它的方便門面,我們實際上是最常見的交互:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
// Request the remote document
jQuery.ajax({
url: url,
type: type,
dataType: "html",
data: params,
// Complete callback (responseText is used internally)
complete: function( jqXHR, status, responseText ) {
// Store the response as specified by the jqXHR object
responseText = jqXHR.responseText;
// If successful, inject the HTML into all the matched elements
if ( jqXHR.isResolved() ) {
// Get the actual response in case
// a dataFilter is present in ajaxSettings
jqXHR.done(function( r ) {
responseText = r;
});
// See if a selector was specified
self.html( selector ?
// Create a dummy div to hold the results
jQuery("<div>")
// inject the contents of the document in, removing the scripts
// to avoid any 'Permission Denied' errors in IE
.append(responseText.replace(rscript, ""))
// Locate the specified elements
.find(selector) :
// If not, just inject the full result
responseText );
}
if ( callback ) {
self.each( callback, [ responseText, status, jqXHR ] );
}
}
});
return this;
}
</div>
觀察者模式
正如我們之前另一種模式是觀察者(發布/訂閱)模式。這是在一個系統中的對象可以訂閱其它目的和當感興趣的事件發生時被他們通知。
jQuery的核心已經內置了支持發布/訂閱制樣了幾年,現在,它是指為自定義事件。
在早期版本的庫,獲得這些自定義的事件是可以使用jQuery.bind()(訂閱), jQuery.trigger()(發布)和jQuery.unbind()(退訂),但在最近的版本中這是可以做到jQuery.on(),jQuery.trigger()和jQuery.off()。
下面,我們可以看到這是在實踐中使用的一個示例:
?
1
2
3
4
五
6
7
8
9
10
// Equivalent to subscribe(topicName, callback)
$( document ).on( "topicName", function () {
//..perform some behaviour
});
// Equivalent to publish(topicName)
$( document ).trigger( "topicName" );
// Equivalent to unsubscribe(topicName)
$( document ).off( "topicName" );
調用jQuery.on()并jQuery.off()最終通過jQuery的事件系統。到的Ajax類似,作為本實施是比較長,就可以代替看在何處以及如何對定制事件的實際事件處理附:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
jQuery.event = {
add: function( elem, types, handler, data, selector ) {
var elemData, eventHandle, events,
t, tns, type, namespaces, handleObj,
handleObjIn, quick, handlers, special;
...
// Init the element's event structure and main handler,
//if this is the first
events = elemData.events;
if ( !events ) {
elemData.events = events = {};
}
...
// Handle multiple events separated by a space
// jQuery(...).bind("mouseover mouseout", fn);
types = jQuery.trim( hoverHack(types) ).split( " " );
for ( t = 0; t < types.length; t++ ) {
...
// Init the event handler queue if we're the first
handlers = events[ type ];
if ( !handlers ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
// Only use addEventListener/attachEvent if the special
// events handler returns false
if ( !special.setup || special.setup.call( elem, data,
//namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
對于那些喜歡使用傳統的命名方案觀察者模式,本Alman創建圍繞為我們提供了訪問上述方法的簡單包裝jQuery.publish(),jQuery.subscribe和jQuery.unsubscribe方法。我以前與他們有關本書前面,但是我們可以看到包裝在下面滿。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
(function( $ ) {
var o = $({});
$.subscribe = function() {
o.on.apply(o, arguments);
};
$.unsubscribe = function() {
o.off.apply(o, arguments);
};
$.publish = function() {
o.trigger.apply(o, arguments);
};
}( jQuery ));
在最近版本的jQuery,一物多用的回調對象(jQuery.Callbacks)的提供,使用戶能夠編寫基于回調列出新的解決方案。一個這樣的解決方法寫使用此功能是另一種發布/訂閱系統。這方面的一個實施如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
var topics = {};
jQuery.Topic = function( id ) {
var callbacks,
topic = id && topics[ id ];
if ( !topic ) {
callbacks = jQuery.Callbacks();
topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
};
if ( id ) {
topics[ id ] = topic;
}
}
return topic;
};
然后可以使用如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
// Subscribers
$.Topic( "mailArrived" ).subscribe( fn1 );
$.Topic( "mailArrived" ).subscribe( fn2 );
$.Topic( "mailSent" ).subscribe( fn1 );
// Publisher
$.Topic( "mailArrived" ).publish( "hello world!" );
$.Topic( "mailSent" ).publish( "woo! mail!" );
// Here, "hello world!" gets pushed to fn1 and fn2
// when the "mailArrived" notification is published
// with "woo! mail!" also being pushed to fn1 when
// the "mailSent" notification is published.
// Outputs:
// hello world!
// fn2 says: hello world!
// woo! mail!
迭代器模式
迭代器是一種設計模式,其中的迭代器(對象,使我們能夠通過一個集合的所有元素遍歷)訪問聚合對象順序的元素,而無需暴露它的基本形式。
迭代封裝的特定迭代是如何發生的內部結構。在jQuery的情況下jQuery.fn.each()迭代,我們實際上能夠使用底層代碼后面jQuery.each()通過集合迭代,而無需看到或理解的代碼提供這種能力的幕后工作。
這是一個可以考慮的門面,我們明確地處理有關問題的迭代的特殊情況的模式。
?
1
2
3
4
五
6
7
$.each( ["john","dave","rick","julian"], function( index, value ) {
console.log( index + ": "" + value);
});
$( "li" ).each( function ( index ) {
console.log( index + ": " + $( this ).text());
});
在這里,我們可以看到的代碼jQuery.fn.each():
?
1
2
3
4
// Execute a callback for every element in the matched set.
each: function( callback, args ) {
return jQuery.each( this, callback, args );
}
緊接著后面的代碼jQuery.each()用于處理通過迭代對象的方法有兩種:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
each: function( object, callback, args ) {
var name, i = 0,
length = object.length,
isObj = length === undefined || jQuery.isFunction( object );
if ( args ) {
if ( isObj ) {
for ( name in object ) {
if ( callback.apply( object[ name ], args ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.apply( object[ i++ ], args ) === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isObj ) {
for ( name in object ) {
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
break;
}
}
}
}
return object;
};
懶惰初始化模式
延遲初始化是一種設計模式,它允許我們推遲昂貴的過程,直到需要他們的第一個實例。這樣的一個例子是.ready()在jQuery函數只有一旦DOM是準備執行的回調。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
$( document ).ready( function () {
// The ajax request won't attempt to execute until
// the DOM is ready
var jqxhr = $.ajax({
url: "http://domain.com/api/",
data: "display=latest&order=ascending"
})
.done( function( data ) ){
$(".status").html( "content loaded" );
console.log( "Data output:" + data );
});
});
jQuery.fn.ready()由供電jQuery.bindReady(),如下圖所示:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
bindReady: function() {
if ( readyList ) {
return;
}
readyList = jQuery.Callbacks( "once memory" );
// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
return setTimeout( jQuery.ready, 1 );
}
// Mozilla, Opera and webkit support this event
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent( "onreadystatechange", DOMContentLoaded );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
// If IE and not a frame
// continually check to see if the document is ready
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch(e) {}
if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
},
而在jQuery的核心不能直接使用,一些開發商也可能是熟悉通過插件,如惰性加載的概念這個。
惰性加載是有效相同延遲初始化并且是一種技術,其中一個頁上的附加數據在需要時被加載(例如,當用戶已經滾動到的頁面的末尾)。近年來這種格局已經變得相當突出,可目前同時在Twitter和Facebook的用戶界面上找到。
代理模式
有些時候,有必要對我們能夠控制的對象的背后接入和上下文而這正是代理模式可能是有用的。
它可以幫助我們控制在一個昂貴的對象應該被實例化,提供了先進的方式來引用的對象或修改對象的功能在具體情況下的特定方式。
在jQuery的核心,一個jQuery.proxy()方法存在哪些作為輸入接受一個函數,并返回一個新的,這將始終有一個特定的上下文。這確保了的值this的函數中是我們期望的值。
在那里,這是非常有用的一個例子是,當我們在一個范圍內利用定時器click事件處理程序。試想一下,我們有以下處理程序添加任何計時器之前:
?
1
2
3
4
$( "button" ).on( "click", function () {
// Within this function, "this" refers to the element that was clicked
$( this ).addClass( "active" );
});
如果我們希望在之前添加一個硬盤延遲active添加類,我們可以使用setTimeout()來實現這一目標。不幸的是,一個小問題,這個解決方案:無論功能傳遞給setTimeout()將有不同的值,this該函數內。這反而參考window對象,這是不是我們的愿望。
?
1
2
3
4
五
6
7
$( "button" ).on( "click", function () {
setTimeout(function () {
// "this" doesn't refer to our element!
// It refers to window
$( this ).addClass( "active" );
});
});
要解決這個問題,我們可以使用jQuery.proxy()來實現一個類型的代理模式。通過與我們想分配給功能和價值調用它this它實際上將返回保留我們正確的上下文中的愿望值的函數。這里是如何做到這一點看看:
?
1
2
3
4
五
6
7
8
9
10
$( "button" ).on( "click", function () {
setTimeout( $.proxy( function () {
// "this" now refers to our element as we wanted
$( this ).addClass( "active" );
}, this), 500);
// the last "this" we're passing tells $.proxy() that our DOM element
// is the value we want "this" to refer to.
});
jQuery的實施jQuery.proxy()可以發現如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
if ( typeof context === "string" ) {
var tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
var args = slice.call( arguments, 2 ),
proxy = function() {
return fn.apply( context, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
return proxy;
}
Builder模式
當與DOM的工作中,我們經常要動態地構造新的元素 - 這個過程可以在復雜性取決于最終的標記增加,屬性,我們希望我們的構建元素包含屬性。
復雜的元素需要被定義的時候特別小心,尤其是如果我們想要的靈活性,無論是字面定義我們的要素最終的標記(這會導致混亂),或采取更可讀的面向對象的路由,而不是。其建設我們復雜的DOM對象了一種機制,是獨立于對象本身給了我們這樣的靈活性,這正是建造模式提供。
助洗劑允許我們通過僅指定對象的類型和內容,從創建或明確表示對象的過程中屏蔽我們構造復雜的對象。
jQuery的美元符號允許我們只是做這,因為它提供了許多不同手段,動態建立新的jQuery(和DOM)對象,由完整的標記或者傳遞一個元素,部分標記和內容或使用jQuery建設:
?
1
2
3
4
五
6
7
8
9
$( '<div class="foo">bar</div>' );
$( '<p id="test">foo <em>bar</em></p>').appendTo("body");
var newParagraph = $( "<p />" ).text( "Hello world" );
$( "<input />" )
.attr({ "type": "text", "id":"sample"})
.appendTo("#container");
下面是jQuery的核心內部的一個片段jQuery.prototype方法,從標記傳遞到了jQuery對象的構建有助于jQuery()選擇。不管是否document.createElement被用來建立新的元件,對元件的引用(發現或創建)被注入到返回的對象,以便進一步的方法,例如.attr(),可在其上之后容易使用。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// HANDLE: $(html) -> $(array)
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context;
doc = ( context ? context.ownerDocument || context : document );
// If a single string is passed in and it's a single tag
// just do a createElement and skip the rest
ret = rsingleTag.exec( selector );
if ( ret ) {
if ( jQuery.isPlainObject( context ) ) {
selector = [ document.createElement( ret[1] ) ];
jQuery.fn.attr.call( selector, context, true );
} else {
selector = [ doc.createElement( ret[1] ) ];
}
} else {
ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
}
return jQuery.merge( this, selector );
jQuery插件設計模式
jQuery插件開發已經發展了近幾年。我們不再只有一個方式來寫插件,但許多。在現實中,某些插件的設計模式可能工作比別人一個特定的問題或組件更好。
有些開發者可能希望使用jQuery UI的小部件廠 ; 它非常適合復雜的,靈活的用戶界面組件。有些人可能不。
有些人可能喜歡構建他們的插件更像模塊(類似于模塊模式),或使用更現代的模塊格式如AMD。
有些人可能會希望自己的插件,利用原型繼承的力量。其他人可能希望使用自定義的事件或發布/訂閱插件從傳達給他們的應用程序的其余部分。等等。
我開始注意到了一些努力創造一種尺寸適合所有的jQuery插件樣板后去想插件模式。雖然這樣的樣板在理論上是一個偉大的想法,但現實是,我們很少寫插件在一個固定的方式,使用單一模式的所有的時間。
讓我們假設,我們已經盡了手在某些時候寫我們自己的jQuery插件,我們很舒適的放在一起一些作品。它的功能。它做什么,它需要做的,但也許我們覺得它可以更好的構建。也許這可以更靈活或可以被設計為解決更多的開發者通常遇到的各種問題。如果這聽起來很熟悉,那么你可能會發現本章很有用。在這里面,我們要探討一些已經在野外其他開發商行之有效的jQuery插件模式。
注:本章是針對中級到高級開發者,但我們將簡要回顧一些jQuery插件基本面開始。
如果你不覺得挺為此做好準備,只是還沒有,我很高興為您推薦官方的jQuery 插件/創作指導,本Alman的插件風格指南和雷米夏普的“ 一個寫得不好的jQuery插件的跡象。”作為閱讀材料之前,在開始本節。
模式
jQuery插件有幾個具體的規則,這是原因對他們是如何在整個社會實現了難以置信的多樣性之一。在最基本的層面上,我們可以簡單地通過添加新的功能屬性jQuery的編寫插件jQuery.fn對象,如下所示:
?
1
2
3
$.fn.myPluginName = function () {
// our plugin logic
};
這是偉大的緊湊,但下面會是一個更好的基礎建立在:
?
1
2
3
4
五
(function( $ ){
$.fn.myPluginName = function () {
// our plugin logic
};
})( jQuery );
在這里,我們包裹我們的插件邏輯匿名函數。為了確保我們使用的$是一個速記創建jQuery和其他JavaScript庫之間沒有沖突的跡象,我們只是把它傳遞給這個封閉,它映射到美元符號。這保證了它不能由它執行的范圍之外的任何的影響。
寫這個模式的另一種方法是使用jQuery.extend(),這使我們能夠在一次定義多種功能和有時更有意義的語義:
?
1
2
3
4
五
6
7
(function( $ ){
$.extend($.fn, {
myplugin: function(){
// your plugin logic
}
});
})( jQuery );
現在我們已經回顧了一些jQuery插件的基本面,但很多還可以做得更多,以進一步利用這個。一個輕量級的開始是我們將要探討的第一個完整的插件設計模式,它涵蓋了我們可以使用基本的日常插件的一些最佳實踐發展,考慮到實用價值的常見的問題。
注意
雖然大多數低于模式加以說明,我建議你閱讀過的代碼中的注釋,因為他們將提供更深入地了解為什么某些應用的最佳實踐。
我還要提到的是,這一切決不可能沒有以前的工作,投入和jQuery社區的其他成員的意見。我列出了他們內嵌每個模式,使人們可以,如果有興趣對他們個人的工作讀了。
“輕量級開始'模式
讓我們開始我們的插件模式的深入了解的東西基本遵循最佳實踐(包括那些jQuery插件創作指南)。這種模式非常適合開發誰或者是新插件的開發或誰只是想實現簡單的東西(如工具插件)。輕量級開始使用下列內容:
常見的最佳做法,如分號放在函數調用之前(我們為什么在評論下方穿過)
window, document, undefined 作為參數傳入。
一個基本的默認對象。
相關的初始創建和勞動要素的分配邏輯一個簡單的插件構造函數。
擴展為默認的選項。
周圍的構造函數,這有助于避免諸如多實例化問題的輕量級封裝。
堅持為最大化可讀性jQuery的核心風格指南。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*!
* jQuery lightweight plugin boilerplate
* Original author: @ajpiano
* Further changes, comments: @addyosmani
* Licensed under the MIT license
*/
// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {
// undefined is used here as the undefined global
// variable in ECMAScript 3 and is mutable (i.e. it can
// be changed by someone else). undefined isn't really
// being passed in so we can ensure that its value is
// truly undefined. In ES5, undefined can no longer be
// modified.
// window and document are passed through as local
// variables rather than as globals, because this (slightly)
// quickens the resolution process and can be more
// efficiently minified (especially when both are
// regularly referenced in our plugin).
// Create the defaults once
var pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
// jQuery has an extend method that merges the
// contents of two or more objects, storing the
// result in the first object. The first object
// is generally empty because we don't want to alter
// the default options for future instances of the plugin
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype.init = function () {
// Place initialization logic here
// We already have access to the DOM element and
// the options via the instance, e.g. this.element
// and this.options
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if ( !$.data(this, "plugin_" + pluginName )) {
$.data( this, "plugin_" + pluginName,
new Plugin( this, options ));
}
});
}
})( jQuery, window, document );
用法:
?
1
2
3
$("#elem").defaultPluginName({
propertyName: "a custom value"
});
深入閱讀
插件/創作,jQuery的
“ 一個寫得不好的jQuery插件的跡象,”雷米夏普
“ 如何創建自己的jQuery插件,”以利亞莊園
“ 風格的jQuery插件和為什么重要,”本·阿爾蒙
“ 創建你的第一個jQuery插件,第2部分,”安德魯Wirick
“完成”窗口小部件工廠模式
而jQuery插件創作指南是一個偉大的介紹插件的開發,它并不能幫助掩蓋走,我們必須處理定期就共同插件水暖任務。
jQuery的UI部件廠是這個問題,幫助我們建立一個基于面向對象原則復雜的,有狀態的插件的解決方案。它還簡化了通信與我們的插件的情況下,混淆了一些基本的插件工作時,我們將不得不代碼重復的任務。
狀態插件幫助我們跟蹤其當前狀態,也讓我們已經初始化后更改插件的性能。
一個關于Widget Factory中的偉大的事情是,大多數的jQuery UI庫的實際使用它作為其組成部分的魅力。這意味著,如果我們正在尋找在超出了此模式的結構進一步的指導,我們不會把眼光局限在GitHub的jQuery用戶界面庫(https://github.com/jquery/jquery-ui)。
這個jQuery UI部件工廠模式涵蓋??了幾乎所有受支持的默認出廠方法,包括觸發事件。按照過去的模式,注釋包含在所有的使用和進一步指導在內部注釋給出的方法。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*!
* jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
* Author: @addyosmani
* Further changes: @peolanha
* Licensed under the MIT license
*/
;(function ( $, window, document, undefined ) {
// define our widget under a namespace of your choice
// with additional parameters e.g.
// $.widget( "namespace.widgetname", (optional) - an
// existing widget prototype to inherit from, an object
// literal to become the widget's prototype );
$.widget( "namespace.widgetname", {
//Options to be used as defaults
options: {
someValue: null
},
//Setup widget (e.g. element creation, apply theming
//, bind events etc.)
_create: function () {
// _create will automatically run the first time
// this widget is called. Put the initial widget
// setup code here, then we can access the element
// on which the widget was called via this.element.
// The options defined above can be accessed
// via this.options this.element.addStuff();
},
// Destroy an instantiated plugin and clean up
// modifications the widget has made to the DOM
destroy: function () {
// this.element.removeStuff();
// For UI 1.8, destroy must be invoked from the
// base widget
$.Widget.prototype.destroy.call( this );
// For UI 1.9, define _destroy instead and don't
// worry about
// calling the base widget
},
methodB: function ( event ) {
//_trigger dispatches callbacks the plugin user
// can subscribe to
// signature: _trigger( "callbackName", [eventObject],
// [uiObject] )
// e.g. this._trigger( "hover", e /*where e.type ==
// "mouseenter"*/, { hovered: $(e.target)});
this._trigger( "methodA", event, {
key: value
});
},
methodA: function ( event ) {
this._trigger( "dataChanged", event, {
key: value
});
},
// Respond to any changes the user makes to the
// option method
_setOption: function ( key, value ) {
switch ( key ) {
case "someValue":
// this.options.someValue = doSomethingWith( value );
break;
default:
// this.options[ key ] = value;
break;
}
// For UI 1.8, _setOption must be manually invoked
// from the base widget
$.Widget.prototype._setOption.apply( this, arguments );
// For UI 1.9 the _super method can be used instead
// this._super( "_setOption", key, value );
}
});
})( jQuery, window, document );
用法:
?
1
2
3
4
五
var collection = $("#elem").widgetName({
foo: false
});
collection.widgetName("methodB");
深入閱讀
jQuery的UI部件廠
“ 介紹狀態插件和小部件工廠,”道格Neiner
“ 窗口小部件工廠 ”(解釋),斯科特·岡薩雷斯
“ 了解的jQuery UI部件:一個教程 ”,在0300黑客
嵌套命名空間插件模式
正如我們在書中覆蓋以前,我們的命名空間的代碼是為了避免在全局命名空間的其他對象和變量沖突的方式。因為我們想在頁面上的另一個腳本使用相同的變量或插件的名稱作為我們的事件打破捍衛我們的插件他們是非常重要的。由于全局命名空間的好公民,我們也必須做到最好不要阻止其他開發商從腳本,因為同樣的問題執行。
JavaScript并沒有真正有內置的命名空間的支持,其他語言做的,但它確實具有可以被用來實現類似的效果的對象。采用頂層對象作為我們命名空間的名稱,我們可以很容易地檢查具有相同名稱的頁面上的其他對象的存在。如果這樣的對象不存在,那么,我們將其定義; 如果它確實存在的話,我們只需用我們的插件擴展。
對象(或者說,對象文字)可被用于創建嵌套的命名空間,如namespace.subnamespace.pluginName等。但是,為了簡單起見,下面的命名空間樣板應該告訴我們,我們需要開始使用這些概念的一切。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*!
* jQuery namespaced "Starter" plugin boilerplate
* Author: @dougneiner
* Further changes: @addyosmani
* Licensed under the MIT license
*/
;(function ( $ ) {
if (!$.myNamespace) {
$.myNamespace = {};
};
$.myNamespace.myPluginName = function ( el, myFunctionParam, options ) {
// To avoid scope issues, use "base" instead of "this"
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $( el );
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data( "myNamespace.myPluginName", base );
base.init = function () {
base.myFunctionParam = myFunctionParam;
base.options = $.extend({},
$.myNamespace.myPluginName.defaultOptions, options);
// Put our initialization code here
};
// Sample Function, Uncomment to use
// base.functionName = function( parameters ){
//
// };
// Run initializer
base.init();
};
$.myNamespace.myPluginName.defaultOptions = {
myDefaultValue: ""
};
$.fn.mynamespace_myPluginName = function
( myFunctionParam, options ) {
return this.each(function () {
(new $.myNamespace.myPluginName( this,
myFunctionParam, options ));
});
};
})( jQuery );
用法:
?
1
2
3
$("#elem").mynamespace_myPluginName({
myDefaultValue: "foobar"
});
深入閱讀
“ 命名空間在JavaScript中,”安格斯克羅爾
“ 用你的$ .fn jQuery的命名空間,”瑞恩佛羅倫薩
“ JavaScript的命名空間,”彼得·米肖
“ 模塊和命名空間在JavaScript中,”阿克塞爾Rauschmayer先生
自定義事件插件模式(與小部件廠)
在JavaScript的設計模式書的部分,我們討論了觀察者模式,后來又到覆蓋jQuery的自定義事件,它提供了實現發布/訂閱類似的解決方案的支持。這同一個模式可以寫入jQuery插件時使用。
這里的基本想法是,當一些有趣的事情在我們的應用程序出現在頁面中的對象可以發布事件通知。其他的對象,則認購(或收聽)這些事件,并作出相應的反應。這導致我們的應用是顯著更解耦的邏輯,因為每個對象不再需要直接與彼此通信。
在下面的jQuery UI部件工廠模式,我們將實現一個基本的自定義的基于事件的發布/訂閱系統,讓我們的插件,從我們的應用程序的其他部分,這將是負責發布這些訂閱事件通知。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*!
* jQuery custom-events plugin boilerplate
* Author: DevPatch
* Further changes: @addyosmani
* Licensed under the MIT license
*/
// In this pattern, we use jQuery's custom events to add
// pub/sub (publish/subscribe) capabilities to widgets.
// Each widget would publish certain events and subscribe
// to others. This approach effectively helps to decouple
// the widgets and enables them to function independently.
;(function ( $, window, document, undefined ) {
$.widget( "ao.eventStatus", {
options: {
},
_create: function() {
var self = this;
//self.element.addClass( "my-widget" );
//subscribe to "myEventStart"
self.element.on( "myEventStart", function( e ) {
console.log( "event start" );
});
//subscribe to "myEventEnd"
self.element.on( "myEventEnd", function( e ) {
console.log( "event end" );
});
//unsubscribe to "myEventStart"
//self.element.off( "myEventStart", function(e){
///console.log( "unsubscribed to this event" );
//});
},
destroy: function(){
$.Widget.prototype.destroy.apply( this, arguments );
},
});
})( jQuery, window, document );
// Publishing event notifications
// $( ".my-widget" ).trigger( "myEventStart");
// $( ".my-widget" ).trigger( "myEventEnd" );
用法:
?
1
2
3
var el = $( "#elem" );
el.eventStatus();
el.eventStatus().trigger( "myEventStart" );
深入閱讀
“ jQuery UI的小部件之間的通信,”本杰明Sternthal
原型繼承與DOM到對象Bridge模式
如前所述,在JavaScript中覆蓋,我們沒有的,我們會發現在等經典編程語言類的傳統觀念,但我們確實有原型繼承。與原型繼承,對象從另一個對象繼承。我們可以將這個概念jQuery插件開發。
Yepnope.js作者亞歷克斯·塞克斯頓和jQuery團隊成員斯科特·岡薩雷斯已經看過這個主題的細節。總之,他們發現,有組織模塊化開發,明確地分離,定義了從插件生成過程的插件本身可以是有益的邏輯對象。
這樣做的好處是,測試我們的插件代碼變得顯著容易,我們也能調整的東西幕后工作的方式,而不改變所使用我們實現任何對象的API的方式。
在塞克斯頓的關于這個主題的文章,他實現了一個橋梁,使我們對我們的一般邏輯附加到一個特定的插件,我們在下面的模式已經實現。
之一的這種模式的其它優點是,我們不必不斷重復同樣的插件初始化代碼,從而確保后面干發展的概念被保持。一些開發商也可能會發現這種模式比別人更容易閱讀。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*!
* jQuery prototypal inheritance plugin boilerplate
* Author: Alex Sexton, Scott Gonzalez
* Further changes: @addyosmani
* Licensed under the MIT license
*/
// myObject - an object representing a concept we wish to model
// (e.g. a car)
var myObject = {
init: function( options, elem ) {
// Mix in the passed-in options with the default options
this.options = $.extend( {}, this.options, options );
// Save the element reference, both as a jQuery
// reference and a normal reference
this.elem = elem;
this.$elem = $( elem );
// Build the DOM's initial structure
this._build();
// return this so that we can chain and use the bridge with less code.
return this;
},
options: {
name: "No name"
},
_build: function(){
//this.$elem.html( "<h1>"+this.options.name+"</h1>" );
},
myMethod: function( msg ){
// We have direct access to the associated and cached
// jQuery element
// this.$elem.append( "<p>"+msg+"</p>" );
}
};
// Object.create support test, and fallback for browsers without it
if ( typeof Object.create !== "function" ) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
// Create a plugin based on a defined object
$.plugin = function( name, object ) {
$.fn[name] = function( options ) {
return this.each(function() {
if ( ! $.data( this, name ) ) {
$.data( this, name, Object.create( object ).init(
options, this ) );
}
});
};
};
用法:
?
1
2
3
4
五
6
$.plugin( "myobj", myObject );
$("#elem").myobj( {name: "John"} );
var collection = $( "#elem" ).data( "myobj" );
collection.myMethod( "I am a method");
深入閱讀
“ 使用繼承模式組織大型jQuery的應用程序,”亞歷克斯·塞克斯頓
“ 如何管理大型應用程序使用jQuery或任何 ”(進一步討論),亞歷克斯·塞克斯頓
“ 極品原型繼承的實際例子,”Neeraj辛格
“ 原型繼承在JavaScript中,”道格拉斯·克羅克福德
jQuery UI的小部件廠橋模式
如果你喜歡基礎上產生的最后設計模式的對象插件的想法,那么你可能有興趣在叫jQuery用戶界面的Widget廠找到了一個方法$.widget.bridge。
這個橋基本上用作所創建使用JavaScript對象之間的中間層$.widget和jQuery的核心API,提供了更多的內置在溶液中實現基于對象的插件定義。實際上,我們能夠創建使用自定義構造函數狀態插件。
此外,$.widget.bridge提供了訪問的一些其他功能,包括以下內容:
公共和私有方法的處理正如人們所期望的古典OOP(即public方法被暴露,而私有方法的調用是不可能的)。
自動保護,防止多個初始化。
自動生成一個傳遞的對象的實例,而選擇的內部在其中的存儲$.data緩存。
選項??可以更改初始化后。
有關如何使用這種模式的更多信息,請參見下面的在線評論:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/*!
* jQuery UI Widget factory "bridge" plugin boilerplate
* Author: @erichynds
* Further changes, additional comments: @addyosmani
* Licensed under the MIT license
*/
// a "widgetName" object constructor
// required: this must accept two arguments,
// options: an object of configuration options
// element: the DOM element the instance was created on
var widgetName = function( options, element ){
this.name = "myWidgetName";
this.options = options;
this.element = element;
this._init();
}
// the "widgetName" prototype
widgetName.prototype = {
// _create will automatically run the first time this
// widget is called
_create: function(){
// creation code
},
// required: initialization logic for the plugin goes into _init
// This fires when our instance is first created and when
// attempting to initialize the widget again (by the bridge)
// after it has already been initialized.
_init: function(){
// init code
},
// required: objects to be used with the bridge must contain an
// "option". Post-initialization, the logic for changing options
// goes here.
option: function( key, value ){
// optional: get/change options post initialization
// ignore if you don't require them.
// signature: $("#foo").bar({ cool:false });
if( $.isPlainObject( key ) ){
this.options = $.extend( true, this.options, key );
// signature: $( "#foo" ).option( "cool" ); - getter
} else if ( key && typeof value === "undefined" ){
return this.options[ key ];
// signature: $( "#foo" ).bar("option", "baz", false );
} else {
this.options[ key ] = value;
}
// required: option must return the current instance.
// When re-initializing an instance on elements, option
// is called first and is then chained to the _init method.
return this;
},
// notice no underscore is used for public methods
publicFunction: function(){
console.log( "public function" );
},
// underscores are used for private methods
_privateFunction: function(){
console.log( "private function" );
}
};
用法:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
// connect the widget obj to jQuery's API under the "foo" namespace
$.widget.bridge( "foo", widgetName );
// create an instance of the widget for use
var instance = $( "#foo" ).foo({
baz: true
});
// our widget instance exists in the elem's data
// Outputs: #elem
console.log(instance.data( "foo" ).element);
// bridge allows us to call public methods
// Outputs: "public method"
instance.foo("publicFunction");
// bridge prevents calls to internal methods
instance.foo("_privateFunction");
深入閱讀
“ 使用$ .widget.bridge Widget的工廠之外,”埃里克·海因茲
jQuery Mobile的小工具與小部件廠
jQuery Mobile的是一個jQuery項目框架,鼓勵的,無論在流行的移動設備和平臺,并在桌面上工作普遍存在的Web應用程序的設計。而不是寫為每個設備或OS獨特的應用,我們簡單地寫代碼一次,它應理想的時刻上的許多A,B和C級的瀏覽器的運行在那里。
背后jQuery Mobile的基本面,也可以適用于插件和小工具的開發。
是什么在接下來的模式有趣的是,雖然有寫“手機” - 優化部件很小,細微的差別,那些熟悉使用jQuery UI控件工廠模式從早期應該能夠掌握這個在旁邊,沒有時間。
下面的移動優化的部件有一些比我們之前看到的標準的UI部件的模式有趣的差異:
$.mobile.widget作為現有部件的原型可以繼承被引用。對于標準的小工具,通過任何這樣的原型是不必要的基本發展,但使用這個jQuery移動特定窗口部件原型提供了進一步的“選項”格式化內部訪問。
在_create(),引導提供官方jQuery Mobile的小部件如何處理元素的選擇,選擇了基于角色的方法,更好地滿足JQM加價。這是不是在所有地說,不建議標準選擇,只是,這種做法可能會更有意義給jQuery Mobile的頁面結構。
還提供了評論形式的準則申請對我們的插件的方法pagecreate,以及選擇通過數據的角色和數據屬性插件應用程序。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*!
* (jQuery mobile) jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
* Author: @scottjehl
* Further changes: @addyosmani
* Licensed under the MIT license
*/
;(function ( $, window, document, undefined ) {
// define a widget under a namespace of our choice
// here "mobile" has been used in the first argument
$.widget( "mobile.widgetName", $.mobile.widget, {
// Options to be used as defaults
options: {
foo: true,
bar: false
},
_create: function() {
// _create will automatically run the first time this
// widget is called. Put the initial widget set-up code
// here, then we can access the element on which
// the widget was called via this.element
// The options defined above can be accessed via
// this.options
// var m = this.element,
// p = m.parents( ":jqmData(role="page")" ),
// c = p.find( ":jqmData(role="content")" )
},
// Private methods/props start with underscores
_dosomething: function(){ ... },
// Public methods like these below can can be called
// externally:
// $("#myelem").foo( "enable", arguments );
enable: function() { ... },
// Destroy an instantiated plugin and clean up modifications
// the widget has made to the DOM
destroy: function () {
// this.element.removeStuff();
// For UI 1.8, destroy must be invoked from the
// base widget
$.Widget.prototype.destroy.call( this );
// For UI 1.9, define _destroy instead and don't
// worry about calling the base widget
},
methodB: function ( event ) {
//_trigger dispatches callbacks the plugin user can
// subscribe to
// signature: _trigger( "callbackName", [eventObject],
// [uiObject] )
// e.g. this._trigger( "hover", e /*where e.type ==
// "mouseenter"*/, { hovered: $(e.target)});
this._trigger( "methodA", event, {
key: value
});
},
methodA: function ( event ) {
this._trigger( "dataChanged", event, {
key: value
});
},
// Respond to any changes the user makes to the option method
_setOption: function ( key, value ) {
switch ( key ) {
case "someValue":
// this.options.someValue = doSomethingWith( value );
break;
default:
// this.options[ key ] = value;
break;
}
// For UI 1.8, _setOption must be manually invoked from
// the base widget
$.Widget.prototype._setOption.apply(this, arguments);
// For UI 1.9 the _super method can be used instead
// this._super( "_setOption", key, value );
}
});
})( jQuery, window, document );
用法:
?
1
2
3
4
五
var instance = $( "#foo" ).widgetName({
foo: false
});
instance.widgetName( "methodB" );
我們還可以自初始化這個小部件每當創建于jQuery Mobile的新的一頁。jQuery Mobile的的網頁插件調度創建時,jQuery Mobile的頁面(通過發現事件data-role="page"屬性)首先被初始化。我們可以偵聽事件(稱為“pagecreate”),每當創建一個新的頁面會自動運行我們的插件。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
$(document).on("pagecreate", function ( e ) {
// In here, e.target refers to the page that was created
// (it's the target of the pagecreate event)
// So, we can simply find elements on this page that match a
// selector of our choosing, and call our plugin on them.
// Here's how we'd call our "foo" plugin on any element with a
// data-role attribute of "foo":
$(e.target).find( "[data-role="foo"]" ).foo( options );
// Or, better yet, let's write the selector accounting for the configurable
// data-attribute namespace
$( e.target ).find( ":jqmData(role="foo")" ).foo( options );
});
現在,我們可以簡單地引用包含我們的控件和腳本pagecreate在運行jQuery Mobile的網站頁面綁定,它會自動運行像任何其他jQuery Mobile的插件。
RequireJS和jQuery的UI部件廠
正如我們覆蓋的部分現代模塊設計模式,RequireJS是AMD兼容的腳本加載器,它提供了可管理的封裝內的模塊應用邏輯干凈的解決方案。
它能夠加載正確的順序模塊(通過其訂單插件),簡化了通過結合其優異的r.js優化腳本的過程,并提供了手段對每個模塊為基礎定義動態依賴。
在下面的樣板模式,我們證明了AMD(因此RequireJS)兼容的jQuery UI部件如何可以定義為執行以下操作:
允許插件模塊依賴的定義,建立在前面介紹以前的jQuery UI的小部件工廠模式之上。
演示一種方法在HTML模板資產通過創建模板控件(使用Underscore.js微模板)。
包括調整,我們可以讓我們的小部件模塊,如果我們希望在以后通過它傳遞給RequireJS優化的快速提示。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*!
* jQuery UI Widget + RequireJS module boilerplate (for 1.8/9+)
* Authors: @jrburke, @addyosmani
* Licensed under the MIT license
*/
// Note from James:
//
// This assumes we are using the RequireJS+jQuery file, and
// that the following files are all in the same directory:
//
// - require-jquery.js
// - jquery-ui.custom.min.js (custom jQuery UI build with widget factory)
// - templates/
// - asset.html
// - ao.myWidget.js
// Then we can construct the widget as follows:
// ao.myWidget.js file:
define( "ao.myWidget", ["jquery", "text!templates/asset.html", "underscore", "jquery-ui.custom.min"], function ( $, assetHtml, _ ) {
// define our widget under a namespace of our choice
// "ao" is used here as a demonstration
$.widget( "ao.myWidget", {
// Options to be used as defaults
options: {},
// Set up widget (e.g. create element, apply theming,
// bind events, etc.)
_create: function () {
// _create will automatically run the first time
// this widget is called. Put the initial widget
// set-up code here, then we can access the element
// on which the widget was called via this.element.
// The options defined above can be accessed via
// this.options
// this.element.addStuff();
// this.element.addStuff();
// We can then use Underscore templating with
// with the assetHtml that has been pulled in
// var template = _.template( assetHtml );
// this.content.append( template({}) );
},
// Destroy an instantiated plugin and clean up modifications
// that the widget has made to the DOM
destroy: function () {
// this.element.removeStuff();
// For UI 1.8, destroy must be invoked from the base
// widget
$.Widget.prototype.destroy.call( this );
// For UI 1.9, define _destroy instead and don't worry
// about calling the base widget
},
methodB: function ( event ) {
// _trigger dispatches callbacks the plugin user can
// subscribe to
// signature: _trigger( "callbackName", [eventObject],
// [uiObject] )
this._trigger( "methodA", event, {
key: value
});
},
methodA: function ( event ) {
this._trigger("dataChanged", event, {
key: value
});
},
// Respond to any changes the user makes to the option method
_setOption: function ( key, value ) {
switch (key) {
case "someValue":
// this.options.someValue = doSomethingWith( value );
break;
default:
// this.options[ key ] = value;
break;
}
// For UI 1.8, _setOption must be manually invoked from
// the base widget
$.Widget.prototype._setOption.apply( this, arguments );
// For UI 1.9 the _super method can be used instead
// this._super( "_setOption", key, value );
}
});
});
用法:
index.html的:
?
1
<script data-main="scripts/main" src="http://requirejs.org/docs/release/1.0.1/minified/require.js"></script>
main.js
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
require({
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min",
"jqueryui": "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min",
"boilerplate": "../patterns/jquery.widget-factory.requirejs.boilerplate"
}
}, ["require", "jquery", "jqueryui", "boilerplate"],
function (req, $) {
$(function () {
var instance = $("#elem").myWidget();
instance.myWidget("methodB");
});
});
深入閱讀
“ 快速模塊化的代碼使用jQuery和RequireJS,”詹姆斯·伯克
“ jQuery的最好的朋友,”亞歷克斯·塞克斯頓
“ 管理依賴隨著RequireJS,”魯斯蘭Matveev
在全球范圍內和每個呼叫可覆蓋選項(最好的選擇模式)
對于我們的下一個模式,我們將看看在配置選項和默認設置插件的最佳手段。方式我們大多數人可能熟悉定義插件選項是通過對象文本默認的$.extend(),在我們的基本的插件樣板展示。
但是,如果我們與許多定制選項的插件,我們希望用戶能夠覆蓋全局的或是每次呼叫的水平工作,那么我們就可以多一點優化結構的東西。
相反,參照一個Options對象明確的插件命名空間中定義的(例如$fn.pluginName.options)與合并這與通過對在最初調用它的插件,通過任何選項,用戶可以選擇是插件初始化或壓倒一切的過程中傳球選擇通過插件以外的選項(如下所示)。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*!
* jQuery "best options" plugin boilerplate
* Author: @cowboy
* Further changes: @addyosmani
* Licensed under the MIT license
*/
;(function ( $, window, document, undefined ) {
$.fn.pluginName = function ( options ) {
// Here's a best practice for overriding "defaults"
// with specified options. Note how, rather than a
// regular defaults object being passed as the second
// parameter, we instead refer to $.fn.pluginName.options
// explicitly, merging it with the options passed directly
// to the plugin. This allows us to override options both
// globally and on a per-call level.
options = $.extend( {}, $.fn.pluginName.options, options );
return this.each(function () {
var elem = $(this);
});
};
// Globally overriding options
// Here are our publicly accessible default plugin options
// that are available in case the user doesn't pass in all
// of the values expected. The user is given a default
// experience but can also override the values as necessary.
// e.g. $fn.pluginName.key ="otherval";
$.fn.pluginName.options = {
key: "value",
myMethod: function ( elem, param ) {
}
};
})( jQuery, window, document );
用法:
?
1
2
3
$("#elem").pluginName({
key: "foobar"
});
深入閱讀
jQuery的Pluginization和隨行要點,本Alman
一個高度可配置的和可變的插件模式
在這個模式中,類似亞歷克斯塞克斯頓的原型繼承的插件模式,為我們的插件邏輯不嵌套在一個jQuery插件本身。我們不是用一個構造函數和一個對象上它的原型定義的字面定義我們的插件邏輯。然后jQuery的用于插件對象的實際實例。
定制是采取一個新的水平通過采用兩個小動作,其中一個我們在以前的模式已經看到:
選項??可以覆蓋全球和每個元素的集合/
選項可以在定制每個元件通過HTML5數據屬性等級(如下所示)。這有利于可以應用到元素的集合,但隨即開始定制,而不需要實例化每個元件具有不同的默認值的插件行為。
我們沒有看到在野外選擇后者過于頻繁,但它可以是一個顯著清潔的解決方案(只要我們不介意內聯的方式)。如果想知道這可能是有用的,想象編寫插件拖動一大組元素。我們可以去定制自己的選項,如下所示:
?
1
2
3
4
$( ".item-a" ).draggable( {"defaultPosition":"top-left"} );
$( ".item-b" ).draggable( {"defaultPosition":"bottom-right"} );
$( ".item-c" ).draggable( {"defaultPosition":"bottom-left"} );
//etc
但使用我們的在線模式的做法,以下是可能的:
?
1
$( ".items" ).draggable();
?
1
2
3
html
<li class="item" data-plugin-options="{"defaultPosition":"top-left"}"></div>
<li class="item" data-plugin-options="{"defaultPosition":"bottom-left"}"></div>
等等。我們很可能具有用于這些方法中的一個的偏好,但它僅僅是另一種變化值得意識到的。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
* "Highly configurable" mutable plugin boilerplate
* Author: @markdalgleish
* Further changes, comments: @addyosmani
* Licensed under the MIT license
*/
// Note that with this pattern, as per Alex Sexton's, the plugin logic
// hasn't been nested in a jQuery plugin. Instead, we just use
// jQuery for its instantiation.
;(function( $, window, document, undefined ){
// our plugin constructor
var Plugin = function( elem, options ){
this.elem = elem;
this.$elem = $(elem);
this.options = options;
// This next line takes advantage of HTML5 data attributes
// to support customization of the plugin on a per-element
// basis. For example,
// <div class="item" data-plugin-options="{'message':'Goodbye World!'}"></div>
this.metadata = this.$elem.data( "plugin-options" );
};
// the plugin prototype
Plugin.prototype = {
defaults: {
message: "Hello world!"
},
init: function() {
// Introduce defaults that can be extended either
// globally or using an object literal.
this.config = $.extend( {}, this.defaults, this.options,
this.metadata );
// Sample usage:
// Set the message per instance:
// $( "#elem" ).plugin( { message: "Goodbye World!"} );
// or
// var p = new Plugin( document.getElementById( "elem" ),
// { message: "Goodbye World!"}).init()
// or, set the global default message:
// Plugin.defaults.message = "Goodbye World!"
this.sampleMethod();
return this;
},
sampleMethod: function() {
// e.g. show the currently configured message
// console.log(this.config.message);
}
}
Plugin.defaults = Plugin.prototype.defaults;
$.fn.plugin = function( options ) {
return this.each(function() {
new Plugin( this, options ).init();
});
};
// optional: window.Plugin = Plugin;
})( jQuery, window, document );
用法:
?
1
2
3
$("#elem").plugin({
message: "foobar"
});
深入閱讀
“ 創建高度可配置的jQuery插件,”馬克·達格利什
“ 寫作高度可配置的jQuery插件,第2部分,”馬克·達格利什
怎樣做一個好插件超越模式?
在一天結束的時候,設計模式只是一個方面編寫維護的jQuery插件。有考慮一些價值等因素,我想和大家分享我自己的標準來選擇第三方插件來解決一些其他問題。我希望這有助于提高你的插件項目的整體質量:
質量
堅持這兩方面中的JavaScript和jQuery,你寫的最佳實踐。正在作出努力,通過皮棉無論jsHint或JSLint的插件代碼?是插件最佳寫的嗎?
代碼樣式
是否插件遵循一致的代碼風格指南,如jQuery的核心風格指南?如果沒有,你的代碼至少比較干凈,可讀性?
兼容性
哪個版本的jQuery插件兼容?它已經過測試,最新的jQuery-git的構建或最新的穩定?如果插件是jQuery的1.6之前寫的,那么它可能有屬性和特性的問題,因為他們接觸的方式在該版本中改變。
jQuery的提供改進和機會,為項目的jQuery的新版本,提高對核心函數庫提供什么。有了這個來自偶爾破損(主要是在主要版本),因為我們對事情做一個更好的方式移動。我希望看到在必要時或插件作者更新他們的代碼,至少,在新版本測試自己的插件,以確保一切正常。
可靠性
該插件都應該有它自己的一套單元測試。不僅這些證明它實際上起到預期的,但它們也可以提高設計而不破壞它的最終用戶。我認為單元測試任何嚴重的jQuery插件,是為生產環境中必不可少的,而且他們并不難寫。對于一個優秀的引導與QUnit自動JavaScript的測試,你可能有興趣在“ 自動化的JavaScript測試使用QUnit,”由喬恩Zaefferer。
性能
如果插件需要執行需要大量處理或DOM的大量操作任務,應該遵循最佳做法的基準,以幫助減少這一點。使用jsPerf.com測試代碼到一個段),以及它如何執行在不同的瀏覽器和b)發現什么,如果有的話,可能會進一步優化。
文檔
如果強度是其他開發者使用插件,確保它的有據可查的。文件的API和插件是如何被使用。有什么方法和選項做插件的支持?是否有用戶需要知道的任何陷阱?如果用戶無法弄清楚如何使用插件,他們很可能會尋找一個替代。這是有很大幫助的評論你的插件代碼也。這是迄今為止最好的禮物,你可以提供其他的開發者。如果有人認為他們可以瀏覽您的代碼基礎不夠好,使用或改進它,那么你已經做得很好。
維修的可能性
當發布一個插件,估計多少時間,可能需要維護和支持。我們都喜歡與社區共享我們的插件,但是需要設置的人來回答問題,地址問題和持續改進能力的預期。這可以簡單地通過說明該項目的意圖維護支持,在前期完成README文件。
結論
在本章中,我們探索了一些節省時間的設計模式,并可以用來改善jQuery插件如何編寫最佳實踐。一些更適合某些使用情況比別人,但我希望整個這些模式是非常有用的。
記住,在選擇的模式時,它是實際的是重要的。不要使用插件模式只是為了它的緣故,相反,投資時間在了解的基礎結構,并建立它如何妥善解決您的問題或適合你試圖建立的組件。
命名空間模式
在本節中,我們將探討的模式在JavaScript的命名空間。命名空間可被認為是代碼單元下的唯一標識符的邏輯分組。所述標識符可以以多種名稱空間中引用和每個標識符本身可以包含其自身的嵌套(或子)的命名空間的層次結構。
在應用程序的開發,我們聘請了一些重要的原因命名空間。在JavaScript中,他們幫助我們避免碰撞與全局命名空間中的其他對象或變量。它們還用于幫助組織的功能塊中的碼基,以便它可以更容易地引用和使用是非常有用的。
命名空間任何嚴重的腳本或應用程序是至關重要的,因為它可從另一個腳本使用頁面上的事件打破維護我們的代碼是很重要的同一變量或方法的名稱我們。隨著數量的第三方定期注入的頁面,這些天的標記,這可能是一個普遍的問題,我們都需要在我們的事業某一點來解決。作為全球命名空間的乖巧的“公民”,這也是我們必須盡力同樣不能阻止其他開發人員的腳本同樣的問題,由于執行。
雖然JavaScript不真的有內置的對像其他語言的名稱空間的支持,它確實有它可用來達到類似的效果對象和關閉。
命名空間基礎
命名空間可以在幾乎任何嚴重的JavaScript應用程序被發現。除非我們用一個簡單的代碼片段工作,這是我們必須竭盡所能,確保我們正在實施正確的命名空間,因為它不只是簡單的回升,這也將避免第三方代碼重挫我們自己。我們將在這一節被檢查模式是:
單全局變量
前綴命名空間
對象的文字符號
嵌套命名空間
立即調用的函數表達式
命名空間注入
1.單全局變量
在JavaScript的命名空間的一個流行的模式是選擇了一個全局變量作為我們的主要參考對象。這方面的一個框架的實施,我們返回具有的功能和屬性的對象可以發現如下:
?
1
2
3
4
五
6
7
8
var myApplication = (function () {
function(){
//...
},
return{
//...
}
})();
雖然這適用于某些情況下,跟單全局變量模式的最大挑戰是確保沒有其他人使用相同的全局變量名,因為我們有在頁面中。
2.前綴命名空間
一種解決上述問題,如由提到彼得米修,是使用前綴命名空間。它是在心臟一個簡單的概念,但這個想法是我們選擇一個唯一的前綴的命名空間,我們希望使用(在本例中,myApplication_),然后定義任何方法,變量或其他物體前綴后如下:
?
1
2
3
4
五
var myApplication_propertyA = {};
var myApplication_propertyB = {};
function myApplication_myMethod(){
//...
}
這是從減少存在于全局范圍的特定變量的可能性的角度有效,但要記住,一個唯一命名的對象可以有同樣的效果。
此之外,與圖案的最大的問題是,它可能會導致大量的全局對象的一次我們的應用程序開始增長。還有我們的前綴沒有全局命名空間中使用的任何其他開發商相當嚴重依賴,所以要小心,如果選擇使用此。
欲了解更多關于彼得對單一的全局變量格局的看法,看他們的出色崗位http://michaux.ca/articles/javascript-namespacing。
3.對象的文字符號
對象的文字符號(我們也涵蓋了本書的模塊模式部分)可以被認為包含關鍵的集合對象:值對用冒號分隔每個對鍵和值,其中鍵也可以代表新的名稱空間。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
var myApplication = {
// As we've seen, we can easily define functionality for
// this object literal..
getInfo:function(){
//...
},
// but we can also populate it to support
// further object namespaces containing anything
// anything we wish:
models: {},
views: {
pages: {}
},
collections: {}
};
你也可以選擇直接添加屬性命名空間:
?
1
2
3
4
五
6
7
8
9
10
11
12
myApplication.foo = function(){
return "bar";
}
myApplication.utils = {
toString:function(){
//...
},
export: function(){
//...
}
}
對象文本沒有污染全局命名空間的優勢,但協助組織代碼和參數邏輯。他們是真正有益的,如果我們希望創建可擴展,以支持深度嵌套輕松可讀的結構。與簡單的全局變量,對象文本往往也考慮到測試一個變量是否存在由同一名稱,因此發生碰撞的幾率是顯著減少。
在接下來的示例中,我們展示了許多不同的方式,使我們可以檢查是否一個變量(對象或插件命名空間)已經存在,它定義,如果它不。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
// This doesn't check for existence of "myApplication" in
// the global namespace. Bad practice as we can easily
// clobber an existing variable/namespace with the same name
var myApplication = {};
// The following options *do* check for variable/namespace existence.
// If already defined, we use that instance, otherwise we assign a new
// object literal to myApplication.
//
// Option 1: var myApplication = myApplication || {};
// Option 2: if( !MyApplication ){ MyApplication = {} };
// Option 3: window.myApplication || ( window.myApplication = {} );
// Option 4: var myApplication = $.fn.myApplication = function() {};
// Option 5: var myApplication = myApplication === undefined ? {} : myApplication;
我們會經常看到開發商選擇了選項1或選項2 - 他們都是直截了當的理解和在他們的歸宿方面等價的。
方案3假設我們在全局命名空間正在努力,但它也可以寫為:
?
1
myApplication || (myApplication = {});
這種變化假定myApplication已經初始化,所以它只是一個參數/參數場景在下面的示例中非常有用:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function foo() {
myApplication || ( myApplication = {} );
}
// myApplication hasn't been initialized,
// so foo() throws a ReferenceError
foo();
// However accepting myApplication as an
// argument
function foo( myApplication ) {
myApplication || ( myApplication = {} );
}
foo();
// Even if myApplication === undefined, there is no error
// and myApplication gets set to {} correctly
選項??4可以寫的jQuery插件,其中有用:
?
1
2
3
4
五
6
7
8
// If we were to define a new plugin..
var myPlugin = $.fn.myPlugin = function() { ... };
// Then later rather than having to type:
$.fn.myPlugin.defaults = {};
// We can do:
myPlugin.defaults = {};
這將導致更好的壓縮(縮小),并可以節省范圍查找。
選項5有點類似于方案4,但它是一個長期的形式,評估是否myApplication是undefined內聯這樣的,它的定義為對象如果不是,否則設置為現有的值myApplication如果是這樣。
它示出只是為了被透徹,但在大多數的情況下,選項1-4將以上滿足大多數需要著想。
當然還有方差的大量對象文本如何以及在何處使用的組織和結構的代碼。對于較小的應用希望暴露特定自我封閉模塊嵌套的API,我們可能只是發現自己使用透露出模塊模式,這是我們之前在書中涵蓋:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var namespace = (function () {
// defined within the local scope
var privateMethod1 = function () { /* ... */ },
privateMethod2 = function () { /* ... */ }
privateProperty1 = "foobar";
return {
// the object literal returned here can have as many
// nested depths as we wish, however as mentioned,
// this way of doing things works best for smaller,
// limited-scope applications in my personal opinion
publicMethod1: privateMethod1,
// nested namespace with public properties
properties:{
publicProperty1: privateProperty1
},
// another tested namespace
utils:{
publicMethod2: privateMethod2
}
...
}
})();
對象文本的好處是,他們為我們提供了一個非常優雅的鍵/值語法工作; 之一,我們能夠很容易地封裝任何明顯的邏輯或功能為我們清晰地從他人分開,并為擴展我們的代碼了堅實的基礎的方式應用。
然而,一個可能的缺點是,對象文本具有成長為長語法結構的潛力。選擇加入到利用嵌套命名圖案(也使用相同的圖案作為其基底)
這種模式有許多其他有用的應用了。除了命名空間,它是經常利益脫鉤我們的應用程序的默認配置為可無需很容易地修改在我們的整個代碼庫搜索只是改變他們一個區域 - 對象文本為此工作的偉大。下面是一個假想的對象字面配置的例子:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var myConfig = {
language: "english",
defaults: {
enableGeolocation: true,
enableSharing: false,
maxPhotos: 20
},
theme: {
skin: "a",
toolbars: {
index: "ui-navigation-toolbar",
pages: "ui-custom-toolbar"
}
}
}
需要注意的是JSON對象是文字符號的一個子集,有它和上面之間真的只有輕微的語法差異(比如JSON鍵必須是字符串)。如果由于任何原因一個人希望使用JSON存儲配置數據,而不是(發送到后端時,例如用于簡單存儲),隨意。欲了解更多關于對象文本模式,我建議你閱讀麗貝卡墨菲的優秀文章的話題,因為她介紹我們沒有在摸幾方面。
4.嵌套命名空間
對象字面的模式的延伸是嵌套的命名空間。這是用另一種常見的模式,由于這樣的事實,即使命名空間已經存在,它不可能在同一嵌套的孩子做提供了碰撞的風險較低。
這看起來很熟悉?
?
1
YAHOO.util.Dom.getElementsByClassName("test");
雅虎YUI庫的舊版本經常使用嵌套對象命名空間格局。在我的時間作為AOL的工程師,我們也在我們的許多較大的應用程序使用這種模式。嵌套命名空間的樣本實現可能是這樣的:
?
1
2
3
4
五
6
7
8
9
10
11
12
var myApp = myApp || {};
// perform a similar existence check when defining nested
// children
myApp.routers = myApp.routers || {};
myApp.model = myApp.model || {};
myApp.model.special = myApp.model.special || {};
// nested namespaces can be as complex as required:
// myApp.utilities.charting.html5.plotGraph(/*..*/);
// myApp.modules.financePlanner.getSummary();
// myApp.services.social.facebook.realtimeStream.getLatest();
注意:為模塊那里使用沙盒API主機對象,遠不及遠較淺命名空間YUI3如何處理名字空間上述不同。
我們也可以選擇申報新的嵌套的命名空間/屬性索引屬性如下:
?
1
2
3
myApp["routers"] = myApp["routers"] || {};
myApp["models"] = myApp["models"] || {};
myApp["controllers"] = myApp["controllers"] || {};
這兩個選項是可讀的,有組織的和提供命名空間提供了以類似的方式應用到我們可以在其他語言被用來相對安全的方式。然而,唯一真正需要注意的是,它需要我們的瀏覽器的JavaScript引擎首次定位對myApp對象,然后挖下去,直到獲得我們真正想要使用的功能。
這可能意味著執行查找工作的量增加,但開發者如Juriy Zaytsev先前已經測試并發現相較于“嵌套”的方式單一對象命名空間之間的性能差異是相當微不足道的。
5.立即調用的函數表達式(IIFE)■
在本書的前面,我們簡要介紹一個概念IIFE(立即調用的函數表達式),它實際上是一個匿名函數,它被定義后立即調用。如果這聽起來熟悉,那是因為你可能有以前遇到過被稱為自動執行(或自調用)匿名函數,但是我個人覺得本Alman的IIFE命名更準確。在JavaScript中,因為這兩個變量,這樣的背景下明確定義的函數可能只在它的內部訪問,函數調用提供了一種簡單的方法實現隱私。
IIFEs是一種流行的方式來封裝應用程序邏輯來保護它從全局命名空間,但也有自己的命名空間的世界中使用。
IIFEs的例子可以發現如下:
?
1
2
3
4
五
// an (anonymous) immediately-invoked function expression
(function () { /*...*/ })();
// a named immediately-invoked function expression
(function foobar () { /*..*/ })();
的自動執行功能,這比IIFEs相當不同的例子,可以在下面找到:
?
1
2
3
4
五
// named self-executing function
function foobar () { foobar(); }
// anonymous self-executing function
var foobar = function () { arguments.callee(); }
回到IIFEs,第一IIFE例子稍微擴展版可能看起來像:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
var namespace = namespace || {};
// here a namespace object is passed as a function
// parameter, where we assign public methods and
// properties to it
(function( o ){
o.foo = "foo";
o.bar = function(){
return "bar";
};
})( namespace );
console.log( namespace );
雖然可讀,這個例子可以顯著擴大了解決共同發展問題,如隱私(公共/私有函數和變量),以及方便的命名空間擴展定義的級別。讓我們通過一些更多的代碼:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// namespace (our namespace name) and undefined are passed here
// to ensure 1. namespace can be modified locally and isn't
// overwritten outside of our function context
// 2. the value of undefined is guaranteed as being truly
// undefined. This is to avoid issues with undefined being
// mutable pre-ES5.
;(function ( namespace, undefined ) {
// private properties
var foo = "foo",
bar = "bar";
// public methods and properties
namespace.foobar = "foobar";
namespace.say = function ( msg ) {
speak( msg );
};
namespace.sayHello = function () {
namespace.say( "hello world" );
};
// private method
function speak(msg) {
console.log( "You said: " + msg );
};
// check to evaluate whether "namespace" exists in the
// global namespace - if not, assign window.namespace an
// object literal
})( window.namespace = window.namespace || {} );
// we can then test our properties and methods as follows
// public
// Outputs: foobar
console.log( namespace.foobar );
// Outputs: You said: hello world
namespace.sayHello();
// assigning new properties
namespace.foobar2 = "foobar";
// Outputs: foobar
console.log( namespace.foobar2 );
擴展當然是關鍵的,以任何可伸縮命名空間圖案和IIFEs可以用來實現這一很容易。在下面的例子中,我們的“空間”再度作為參數傳遞給我們的匿名函數傳遞,然后延伸(或飾)與進一步的功能:
?
1
2
3
4
五
6
7
8
9
10
11
// let's extend the namespace with new functionality
(function( namespace, undefined ){
// public method
namespace.sayGoodbye = function () {
namespace.say( "goodbye" );
}
})( window.namespace = window.namespace || {});
// Outputs: goodbye
namespace.sayGoodbye();
如果您想了解更多關于這個模式,我建議你閱讀本的IIFE后獲取更多信息。
6.命名空間注入
命名空間注入上IIFE,我們使用一個函數包裝內“注入”為特定名字空間的方法和屬性另一變型這個作為命名空間代理。這種模式提供的好處是功能性的行為多個對象或命名空間便于應用和應用一組的基礎方法時,應以后建立在(例如getter和setter)可以派上用場。
這個模式的缺點是,有可能更容易或更優的方法來實現這一目標(例如,深對象擴展/合并),我先前在部分覆蓋。
下面我們可以看到這種模式在行動,在這里我們用它來 ??填充行為兩個命名空間的一個例子:最初定義(utils的)一個又一個,我們動態地創建為utils的功能分配的一部分(被稱為新的命名空間工具)。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var myApp = myApp || {};
myApp.utils = {};
(function () {
var val = 5;
this.getValue = function () {
return val;
};
this.setValue = function( newVal ) {
val = newVal;
}
// also introduce a new sub-namespace
this.tools = {};
}).apply( myApp.utils );
// inject new behaviour into the tools namespace
// which we defined via the utilities module
(function () {
this.diagnose = function(){
return "diagnosis";
}
}).apply( myApp.utils.tools );
// note, this same approach to extension could be applied
// to a regular IIFE, by just passing in the context as
// an argument and modifying the context rather than just
// "this"
// Usage:
// Outputs our populated namespace
console.log( myApp );
// Outputs: 5
console.log( myApp.utils.getValue() );
// Sets the value of `val` and returns it
myApp.utils.setValue( 25 );
console.log( myApp.utils.getValue() );
// Testing another level down
console.log( myApp.utils.tools.diagnose() );
安格斯·克羅爾還先前建議使用呼叫API提供上下文和論據之間的自然分離的想法。這種模式可以感覺到更像一個模塊的創造者,而是作為模塊仍然提供的封裝解決方案,我們將簡要介紹它徹底的緣故:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// define a namespace we can use later
var ns = ns || {},
ns2 = ns2 || {};
// the module/namespace creator
var creator = function( val ){
var val = val || 0;
this.next = function () {
return val++
};
this.reset = function () {
val = 0;
}
}
creator.call( ns );
// ns.next, ns.reset now exist
creator.call( ns2, 5000 );
// ns2 contains the same methods
// but has an overridden value for val
// of 5000
如所提到的,這種類型的圖案的是用于分配一個類似堿設置的功能到多個模塊或??命名空間是有用的。我不過才真正建議使用它,其中明確聲明對象/關閉直接訪問內部功能沒有意義。
先進的命名空間模式
現在我們來探討一些先進的模式和事業上更大的應用程序時,我發現寶貴的-其中有一些需要重新思考傳統方法的應用程序命名空間。我會注意,我不是在提倡以下任何作為的方式來命名,而是我在實踐中發現的工作方式。
自動化嵌套命名空間
正如我們已經回顧,嵌套的命名空間可以為代碼單元結構提供一個有組織的層次結構。這樣的空間的一個例子可以是以下:application.utilities.drawing.canvas.2d。這也可以使用對象字面量模式進行擴展:
?
1
2
3
4
五
6
7
8
9
10
11
var application = {
utilities:{
drawing:{
canvas:{
2d:{
//...
}
}
}
}
};
一個與此模式的明顯挑戰是,我們要創建的每個附加層需要定義在我們的頂級命??名空間有些家長的孩子還有一個目的。當多個深度需要在復雜性我們的應用程序的增加這變得特別費勁。
如何解決這個問題得到更好的解決呢?在JavaScript的模式,斯托揚斯特凡提出了自動定義現有的全局變量下嵌套的命名空間非常高明的辦 ??法。他建議一個便捷方法,它接受一個字符串參數為巢,解析這個并自動填充我們與需要的對象基地命名空間。
他建議使用方法如下,我已經更新,它是與多個命名空間更容易重復使用泛型函數:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
// top-level namespace being assigned an object literal
var myApp = myApp || {};
// a convenience function for parsing string namespaces and
// automatically generating nested namespaces
function extend( ns, ns_string ) {
var parts = ns_string.split("."),
parent = ns,
pl;
pl = parts.length;
for ( var i = 0; i < pl; i++ ) {
// create a property if it doesn't exist
if ( typeof parent[parts[i]] === "undefined" ) {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
}
// Usage:
// extend myApp with a deeply nested namespace
var mod = extend(myApp, "modules.module2");
// the correct object with nested depths is output
console.log(mod);
// minor test to check the instance of mod can also
// be used outside of the myApp namesapce as a clone
// that includes the extensions
// Outputs: true
console.log(mod == myApp.modules.module2);
// further demonstration of easier nested namespace
// assignment using extend
extend(myApp, "moduleA.moduleB.moduleC.moduleD");
extend(myApp, "longer.version.looks.like.this");
console.log(myApp);
Web檢查的輸出:
其中一個會以前有顯式聲明的各種巢為他們命名為對象,這個現在可以很容易地使用一個單一的代碼,更清潔的線來實現的。
依賴聲明模式
現在我們要探索未成年人增強到我們稱之為依賴宣言模式嵌套命名空間格局。我們都知道,對對象的局部引用可以減少總體查找時間,但讓我們這適用于命名空間,看看它是如何可能看起來在實踐中:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
// common approach to accessing nested namespaces
myApp.utilities.math.fibonacci( 25 );
myApp.utilities.math.sin( 56 );
myApp.utilities.drawing.plot( 98,50,60 );
// with local/cached references
var utils = myApp.utilities,
maths = utils.math,
drawing = utils.drawing;
// easier to access the namespace
maths.fibonacci( 25 );
maths.sin( 56 );
drawing.plot( 98, 50,60 );
// note that the above is particularly performant when
// compared to hundreds or thousands of calls to nested
// namespaces vs. a local reference to the namespace
與這里的局部變量的工作幾乎總是比具有頂層全局(egmyApp)工作速度更快。它也比都訪問每一個后續行嵌套屬性/子命名空間更方便,更高性能和能提高在更復雜的應用程序的可讀性。
斯托揚建議,宣布我們的函數范圍的頂部(采用單變量模式)函數或模塊所需的本地化命名空間,這就要求一個依賴聲明的模式。一本提供的好處是依賴定位和解決他們的下降,我們應該有需要時動態加載模塊到我們的命名空間中的可擴展架構。
在我看來,這種模式在模塊級工作時,定位一個命名空間由一組的方法使用效果最好。在每個功能級別本地化的命名空間,特別是在有空間的依賴之間的重疊顯著將是我會建議避免在可能的情況。取而代之的是,它進一步向上,只是讓他們都訪問相同的參考。
深對象擴展
另一種方法來自動命名空間是深刻的對象擴展。使用對象的文字符號定義的命名空間可以很容易地擴展(或合并)與其他物體(或命名空間),這樣兩個命名空間的屬性和功能,可在同一個命名空間后合并下訪問。
這東西是已經取得相當容易與現代的JavaScript框架來實現(例如見jQuery的$ .extend),但是,如果用看JS香草來擴展對象(命名空間),下面的程序可能會有所幫助。
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// extend.js
// Written by Andrew Dupont, optimized by Addy Osmani
function extend( destination, source ) {
var toString = Object.prototype.toString,
objTest = toString.call({});
for ( var property in source ) {
if ( source[property] && objTest === toString.call(source[property]) ) {
destination[property] = destination[property] || {};
extend(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
};
console.group( "objExtend namespacing tests" );
// define a top-level namespace for usage
var myNS = myNS || {};
// 1. extend namespace with a "utils" object
extend(myNS, {
utils:{
}
});
console.log( "test 1", myNS);
// myNS.utils now exists
// 2. extend with multiple depths (namespace.hello.world.wave)
extend(myNS, {
hello:{
world:{
wave:{
test: function(){
//...
}
}
}
}
});
// test direct assignment works as expected
myNS.hello.test1 = "this is a test";
myNS.hello.world.test2 = "this is another test";
console.log( "test 2", myNS );
// 3. what if myNS already contains the namespace being added
// (e.g. "library")? we want to ensure no namespaces are being
// overwritten during extension
myNS.library = {
foo:function () {}
};
extend( myNS, {
library:{
bar:function(){
//...
}
}
});
// confirmed that extend is operating safely (as expected)
// myNS now also contains library.foo, library.bar
console.log( "test 3", myNS );
// 4. what if we wanted easier access to a specific namespace without having
// to type the whole namespace out each time?
var shorterAccess1 = myNS.hello.world;
shorterAccess1.test3 = "hello again";
console.log( "test 4", myNS);
//success, myApp.hello.world.test3 is now "hello again"
console.groupEnd();
注:以上實現不跨瀏覽器的所有對象兼容,只能證明的概念來考慮。人們可以發現Underscore.js extend()方法更簡單,更跨瀏覽器的實現開始與http://documentcloud.github.com/underscore/docs/underscore.html#section-67。或者,從核心中提取jQuery的$ .extend()方法的版本可以在這里找到:https://github.com/addyosmani/jquery.parts。
對于將要在其應用程序使用jQuery開發,可以實現具有完全相同的對象的名稱空間擴展$.extend如下:
?
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// top-level namespace
var myApp = myApp || {};
// directly assign a nested namespace
myApp.library = {
foo:function(){
//...
}
};
// deep extend/merge this namespace with another
// to make things interesting, let's say it's a namespace
// with the same name but with a different function
// signature: $.extend( deep, target, object1, object2 )
$.extend( true, myApp, {
library:{
bar:function(){
//...
}
}
});
console.log("test", myApp);
// myApp now contains both library.foo() and library.bar() methods
// nothing has been overwritten which is what we're hoping for.
為了徹底性的著想,請參閱這里的jQuery $ .extend等同于本節中的命名空間實驗的其余部分。
建議
回顧我們在本節探討了命名空間的模式??,我會親自使用對于大多數大型應用程序的選項是嵌套對象與對象面值模式命名空間。如果可能的話,我會實現這個使用自動嵌套的命名空間,但是這僅僅是一個個人喜好。
IIFEs和單全局變量可以正常工作在中小型范圍的應用,但是,更大的代碼庫需要兩個命名空間和深亞命名空間的要求,促進可讀性和鱗片一個簡潔的解決方案。我覺得這種模式實現了所有這些目標很好。
我也建議嘗試一些建議先進的實用方法的命名空間擴展,因為他們確實可以節省時間我們從長遠來看。
結論
以上就是本介紹冒險進入在JavaScript中和jQuery設計模式的世界 - 我希望你發現它是有益的。
設計模式很容易讓我們建立在誰擁有超過幾十年來定義的解決方案,以具有挑戰性的問題和架構開發者的肩上。這本書的內容應該有希望提供足夠的信息來使用我們覆蓋在自己的腳本,插件和Web應用程序的模式上手。
我們要知道這些模式,但它也必須了解如何以及何時使用它們是很重要的。雇用他們之前研究每個模式的利弊。取出來的時候有圖案進行實驗,以充分理解他們所提供的,并基于模式的真正價值你的應用程序的使用情況判斷。
如果我鼓勵你在這一領域的興趣,并進一步您想了解更多關于設計模式,則對這一領域提供通用軟件的開發和當然,JavaScript的一批優秀的標題。
我很高興地建議:
“ 格局對企業應用架構 ”由Martin Fowler
“ JavaScript的模式 ”的斯托揚斯特凡
感謝您閱讀學習JavaScript的設計模式。有關學習JavaScript更多的教育材料,請隨時讀取我更多關于我的博客http://addyosmani.com或在Twitter上@addyosmani。
直到下一次,最好的運氣在JavaScript中的冒險之旅!
參考
設計原則和設計模式-羅伯特·馬丁? http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
拉爾夫·約翰遜- ACM的特刊模式和模式語言- http://www.cs.wustl.edu/~schmidt/CACM-editorial.html
山坡工程設計模式庫- http://hillside.net/patterns/
臨JavaScript的設計模式-羅斯Harmes和達斯汀·迪亞茲http://jsdesignpatterns.com/
設計模式定義- http://en.wikipedia.org/wiki/Design_Patterns
模式和軟件術語http://www.cmcrossroads.com/bradapp/docs/patterns-intro.html
粒設計模式的好處-杰夫Juday http://articles.techrepublic.com.com/5100-10878_11-5173591.html
JavaScript的設計模式- Subramanyan古漢http://www.slideshare.net/rmsguhan/javascript-design-patterns
什么是設計模式,做我需要他們?-詹姆斯Moaoriello http://www.developer.com/design/article.php/1474561
軟件設計模式-亞歷巴內特http://alexbarnett.net/blog/archive/2007/07/20/software-design-patterns.aspx
評估軟件設計模式-羅德Gunni http://www.rode.dk/thesis/
SourceMaking設計模式http://sourcemaking.com/design_patterns
辛格爾頓- Prototyp.ical http://prototyp.ical.ly/index.php/2007/03/01/javascript-design-patterns-1-the-singleton/
JavaScript的模式-斯托揚Stevanov - http://www.slideshare.net/stoyan/javascript-patterns
堆棧溢出-在JavaScript中(討論)設計模式實現http://stackoverflow.com/questions/24642/what-are-some-examples-of-design-pattern-implementations-using-javascript
設計模式的元素-賈里德閥芯http://www.uie.com/articles/elements_of_a_design_pattern/
堆棧溢出-實用JS設計模式的例子(討論)http://stackoverflow.com/questions/3722820/examples-of-practical-javascript-object-oriented-design-patterns
尼古拉斯Zakas -在JavaScript中第1部分設計模式http://www.webreference.com/programming/javascript/ncz/column5/
堆棧溢出-在jQuery的設計模式http://stackoverflow.com/questions/3631039/design-patterns-used-in-the-jquery-library
分類設計模式通過AntiClue -伊利斯·尼爾森http://www.anticlue.net/archives/000198.htm
設計模式,模式語言和框架-道格拉斯施密特http://www.cs.wustl.edu/~schmidt/patterns.html
獻愛心模塊模式-基督教海爾曼http://www.wait-till-i.com/2007/07/24/show-love-to-the-module-pattern/
軟件設計變得簡單- Anoop Mashudanan http://www.scribd.com/doc/16352479/Software-Design-Patterns-Made-Simple
JavaScript的設計模式-克勞斯·柯門達http://www.klauskomenda.com/code/javascript-programming-patterns/
簡介的JavaScript模塊模式https://www.unleashed-technologies.com/blog/2010/12/09/introduction-javascript-module-design-pattern
設計模式解釋- http://c2.com/cgi/wiki?DesignPatterns
混入解釋http://en.wikipedia.org/wiki/Mixin
與四人幫的設計模式工作在JavaScript http://aspalliance.com/1782_Working_with_GoFs_Design_Patterns_in_JavaScript_Programming.all
使用的Object.create http://stackoverflow.com/questions/2709612/using-object-create-instead-of-new
t3knomanster的JavaScript設計模式- http://t3knomanser.livejournal.com/922171.html
JavaScript的優勢-對象文本http://stackoverflow.com/questions/1600130/javascript-advantages-of-object-literal
JavaScript類模式-利亞姆麥克倫南http://geekswithblogs.net/liammclennan/archive/2011/02/06/143842.aspx
了解代理jQuery中- http://stackoverflow.com/questions/4986329/understanding-proxy-in-jquery
Observer模式使用JavaScript - http://www.codeproject.com/Articles/13914/Observer-Design-Pattern-Using-JavaScript
在談到Observer模式- http://www.javaworld.com/javaworld/javaqa/2001-05/04-qa-0525-observer.html
- JavaScript手冊
- Array函數
- String函數
- Date函數
- Mach函數
- Regexp函數
- Location函數
- Window 函數
- Other函數
- History函數
- Navigator函數
- Event函數
- Dom函數
- Json函數
- Sea.js手冊
- JavaScript學習總結
- 1.基礎部分
- 2.對象部分
- 3.BOM和DOM詳解
- 4.function函數部分
- 5.原型和原型鏈詳解
- 6.數據類型和JSON格式
- 7.Ajax和Http狀態字
- 8.正則表達式
- 9.事件詳解
- 前端相關網址
- 前端干貨文章
- JavaScript字符串常用的一些方法
- 前端開發知識體系
- JavaScript速成課
- 移動端開發技巧
- 移動端Web頁面問題解決方案
- 20個常用的CSS技巧
- 學習JavaScript設計模式
- 前端開發學習總結