網頁制作poluoluo文章簡介:正在使用的類似框架比如:Struts, Ruby on Rails, 和CakePHP。 MVC 起源於用戶界面的發展。借助於它布局客戶端應用程序的結構。讓我們一起來看下MVC是什麼。看看我們如何在一個項目中用mvc重寫它。並且思考一些現在已 經存在的MVC框架。
JavaScript MVC
中文:http://blog.youmila.com/?p=423 —from yapollo.li@gmail.com
英文:http://www.alistapart.com/articles/javascript-mvc/ —from Jonathan Snook
javascript 已經從一個“小演員”發展成為舞台的中心”人物“。它的足跡已經遍布我們的服務器和發展計劃的一覽表中,並且正在持續增長中。因此我們必須思考怎樣才能提 高我們的javascript代碼的重用性和更容易維護性呢?或許,MVC能夠給我們一些好的提示。
MVC對於後端應用程序開發及其開發人員來說是一個熟悉的術語。
正在使用的類似框架比如:Struts, Ruby on Rails, 和CakePHP。 MVC 起源於用戶界面的發展。借助於它布局客戶端應用程序的結構。讓我們一起來看下MVC是什麼。看看我們如何在一個項目中用mvc重寫它。並且思考一些現在已 經存在的MVC框架。
What is MVC?(MVC是什麼?)
這個縮寫詞已經在前面提到了6次,如果你從來沒有聽說過,那一定迫不及待的逍遙知道MVC代表什麼,MVC代表Model-View-Controller. 它是一個將應用程序分成3個部分的設計模式:model層是數據層,view層是數據對用戶的表現形式,controller層是用戶交互采取的行為動作。
追溯到1978年在Xeroc PARC, Trygve Reenskau發表了 recalled the origin of the MVC concept (PDF):(這篇文章成為了MVC的起源)
這部分原文我就不翻譯了哈(保留原味的好哈):
There are four roles in this user interaction paradigm. The human User has a mental model of the information he is currently working with. The object playing the Model role is the computer’s internal and invisible representation of this information. The computer presents different aspects of the information through objects playing the View role, several Views of the same model objects can be visible on the computer screen simultaneously. Objects playing the Controller role translate User commands into suitable messages for the View or Model objects as needed.
換句話說,用戶在做某件事情時,這件事情被轉到controller這邊,並且controller知道下一步去做什麼,一般來說 controller會從model層這邊請求數據,並且把獲取到的數據放到view層並且顯示給用戶。但是這樣的劃分層結構,對於一個網站或者是web 應用程序來說意味著什麼呢?
靜態文檔時web頁面的基礎,給我們服務的每個頁面都反映了他們在服務器那一刻的信息狀態。但是我們得到不止是原始數據,而是包含原始數據的xhtml或者html數據,並且通過已經預定義的css渲染後的漂亮的頁面。
多年前,如果你想去修改原始數據,服務端必須提供一個可以文本輸入的頁面去做改動。那時候我們把改動後的信息發送給服務端,並且等待服務端返回ok 的反饋後才能搞定。每次都是完整的請求一個新的頁面,然後在等待服務器反饋,這樣讓我們用戶感到乏味不堪,甚至當你出現錯誤的時候,還需要重新鍵入原來輸 入的信息。
後來,web早期的黑暗迎來了他們的拯救者,铠甲騎士 –javascript和ajax。
他們結束以前的整個頁面請求的方式,可以單個元素發送用戶請求到後端服務器。
並且也允許用戶頁面發送請求的時候,繼續響應用戶的其他操作。
現在我們需要在javascript和ajax的發展和運用中采用MVC的模式分離代碼:
比如說:在某些情況下,分離可能是不需要的,甚至某些情況下,分離會造成很多不必要的程序冗長。當我們的應用程序便得越來越復雜,需要 javascript在網站的多數部分的交互操作的時候。我們把javascript分離進入MVC模式能夠產生出更多元化,更重復利用的代碼。
javscript是個傻瓜,他不會明白html將要告訴用戶什麼或者用戶想在這個頁面完成什麼。所以我們作為開發者,就必須告訴我們的javascript,用戶的輸入意味著什麼。
思考下面的例子,如果我們需要驗證表單中的數據,我們可以設置一個事件來處理這個任務,在這個任務中,事件處理函數去遍歷表單中的字段列表,並且確定怎樣去反饋出錯誤的結果。
function validateForm(){
var errorMessage = ‘The following errors were found:
‘;
if (document.getElementById(’email’).value.length == 0) {
errorMessage += ‘You must supply an email address
‘;
}
document.getElementById(’message’).innerHTML = errorMessage;
}
上面得這個方法能工作,但是不夠靈活,比如我們如果想要增加個字段驗證,或者另一個頁面有不同表單驗證。那我們就不得不拷貝這個函數的大部分代碼為我們每次新增加字段驗證。
第一步是奔向模塊化,並且分離就是在表單的字段中添加語義化的東西。
比如對於email驗證的表單字段我們可以這樣做:
<input type="text" class="required email">
這樣我們的javascript會遍歷所有的表單字段從class中拖出這個屬性從那執行相應的程序。(這裡的class屬性保留了雙重含義,一個是css的樣式設定另一個就是js的目標對象。多麼便捷哈!~)
上面這種方式和頁面的結構已經語義化標記緊密纏繞在一起,但是這種方式也有一定限制條件,比如沒有條件判定式,而且在html標記中不能構造條件邏輯。比如:我們說如果一個字段完成,需要另一個唯一的字段。(可能你要說能但是很笨的方法。)
<input name="other" type="checkbox" /> Other
<textarea class="dependson-other"></textarea>
在上面得這個例子當中我們用了前綴 dependson 指出 textarea是依靠 checkbox才出現的。為了避免這種拙劣的方法,我們可以在javascript中定義這塊業務邏輯。
當我們在html中增加語義化標記以及元數據的時候,我們最終的目的是獲取信息給javascript。但是以javascript方式描述數據是相對比較方便的。
這裡就是一個例子:(其實就是用json方式描述)
var fields = {
'other': {
required:true
},
'additional: {
'required': {
'other':{checked:true},
'total':{between:[1,5]}
},
'only-show-if': {
'other': {checked:true}
}
}
};
在這個例子中附加字段會有幾個依賴關系。依賴關系中的每一個都被描述了。並且有各種各樣的信息定義在其中。在這種情況下,附加字段需要兩個字段滿足條件,並且附加字段只有在用戶選擇other的checkbox時才會顯示出來。
在這時候,javascript被用於如何驗證發生後字段和業務邏輯的描述定義,在這一層中我們已經分離了一部分數據進入他們自己的對象中。但是這些驗證仍然期望在有效的變量數據中應用。希望在頁面中能夠展示出相應的錯誤摘要。
盡管這個例子已經有一點分離。但是這裡驗證過程仍然有很多的依賴性,比如:數據的驗證和驗證的後的報錯結果顯示仍然存在緊密的耦合。
那就讓我們思考一下怎樣才能用mvc模式構建我們的代碼。並且然後構建我們表單驗證的例子。
既然mvc有三個主要組成部分,那麼我們的程序也要相應的劃分成至少3個主要對象。
分離model層進入它自己的對象是比較容易的,正如我們早期看到那個表單驗證的例子,這個常常發生的很自然。
讓我們來看那下另外一個例子吧。假設我們有一個日歷事件,這個事件的數據將會存儲在它自身的對象中,增加到對象的中的方法是抽象了直接與數據交互的過程。這些方法經常被增刪改查的任務調用,比如:創建,讀取,更新,刪除等操作。
var Events = {我們需要一個方法去描述數據。所以我們增加這個metadata字段來描述數據的類型以及數據的限定條件。
上面這個增刪改查的任務也在服務器端保存狀態改變信息,在這個例子中,delete函數從本地的對象存儲中刪除了數據的登記之後就將刪除對象的id通過ajax發送一個刪除命令請求到服務器端,然後從服務器端刪除。
在存儲數據的時候,使用命名鍵值的方法,這是最快最有效率從對象中取回數據的方法。
這種方式通常和數據庫主鍵一樣。(上面得例子用了number類型的id)。對於一個事件日歷來說,按照月份劃分存儲數據會更實用。這樣做就不用遍歷所有的事件去發現那個需要的在頁面中渲染的事件了。你會發現這樣做事最好的辦法。
在mvc模式中,view負責接收數據並且決定數據如何顯示。view層可以用頁面已存在的html,也可以從服務器端請求一個新的html組件, 還可以自己通過dom創建新的html元素。合並提供的數據以視圖的形式顯示給用戶,有一點很重要,就是view層並不關心數據來自哪裡,或者怎麼獲取 到,它只負責取走數據使用。
View.EventsDialog = function(CalendarEvent) {‘ +
‘
‘;
html = html.replace(/\{[^\}]*\}/g, function(key){
return CalendarEvent[key.slice(1,-1)] || ”;
});
var el = document.getElementById(’eventshell’);
el.innerHTML = html;
}
var Events.data = {
‘112′: { ‘name’: ‘Party time!’, ‘date’: ‘2009-10-31′ },
‘113′: { ‘name’: ‘Pressies!’, ‘date’: ‘2009-12-25′ }
}
View.EventsDialog(Events.data['112']); // edits item 112
觀察上面得程序我們能夠發現它包含三個部門:
EventsDialog 函數對CalendarEvent這個json對象的name和date屬性進行了解析顯示。Events的data屬性存儲了日歷的時間。 View.EventsDialog的調用使112顯示。
這裡Events Dialog的view能夠被擴展,加入一個附加函數能夠使它進行有效的交互。在下面得例子中,Events Dialog被給了一個open方法和一個close方法。通過這樣做能夠使view提高自己的感知能力,同時也能夠被controller層更好的使用 view層,並不需要知道它實現的細節。
var dialog = new View.EventsDialog(eventObject);
dialog.open();
dialog.close();
使視野變得覺察到數據模型和數據檢索的方法是一容易墜入的陷阱. 分離這些函數不過是想讓他們在其他方面能重新使用這個dialog。在這個例子當中,如果分離了事件的數據和dialog,那麼我們能總結dialog屬 於view層中,dialog不只適用events類的模型,也能應用到其他模型。
View.Dialog = function(data) {‘;
delete data.name;
for(var key in data) {
html += ‘
‘;
}
var el = document.getElementById(’eventshell’);
el.innerHTML = html;
}
我們現在有一個共有的方法去訪問一個任意對象的元素,而不僅僅是事件對象。在下一個需要dialog的項目中,我們可以合並這部分代碼並且使用它。
很多JavaScript框架都是以這數據不可知論來設計的。比如yui controller ,jQuery UI widgets, ExtJS, 和 Dojo Dijit 從頭到位都是把通用性(泛用性)放在第一位來建立的。
一般來說,view層不能運行他們自己的方法,舉個例子來說,dialog(對話框)不能自己控制開關,應該由controller(控制器)–控制層來控制它是否開關。
如果一個用戶點擊了一個dialog中的保存按鈕,這個點擊動作由控制器來接收,控制器發送一個動作來決定dialog應該做什麼。可能是關閉dialog也能是顯示正在處理..一旦數據保存了,ajax完成後會觸發控制器發出另一個隱藏指令來關閉dialog
無論怎樣,在有些情況下view層也能夠運行它自己的方法。比如一個view頁面中有一個以slide形式展示的輸入框,並且允許用戶提取裡面內容 的時候,view會自己處理交互操作的,讓slide的內容顯示出來,這個時候就不需要controller(控制器)來操作這個交互了。
現在,從 model層到view層數據是怎樣獲取到得呢?這就是通過controller層做的。controller激活是在事件發生以後,多半是在頁面載入或 者用戶發起的行為事件。一個事件處理程序被分配到一個controller(控制器)層的方法是做用戶的競標。
Controllers.EventsEdit = function(event) {當數據在在各種情況下使用的時候這種模式確實很方便。舉個例子:
我們正在編輯的日歷上顯示的事件,我們點擊刪除按鈕,現在需要去消除dialog(對話框)和日歷上的事件,然後從服務器中刪除該事件。
controller的行為就變得相對容易理解和簡單了。這是建立可維護應用程序的關鍵。
現在我們了解了怎樣去分解我們的代碼到他們的構成部分。讓我們重新回來開始部分的表單驗證的例子,我們怎樣才能用MVC模式去設計它以達到最大靈活性。
該模型確定數據是否正確或不使用的方法。它不關心如何呈現概要的視圖。它只是需要報告哪些字段沒有達到水平。
以前我們做過的那個例子當中,有一個簡單的變量“fields
”存儲了我們數據模型的元數據格式,我們能夠用一個定義理解和檢測被給數據的方法去擴展那個對象,該方法能夠遍歷所有的數據並且比對他們在內部元數據類型中定義的需求。
為了使我們的數據有效,我們提供一個數組key /value (鍵/值)對。key就是名字,value就是用戶鍵入的字段的內容。
var data = [var invalid = MyModel.validate(data);
我們的無效變量現在包含任何沒有驗證字段列表,現在我們要傳遞這些數據到view層中在頁面中顯示這些錯誤。
在這種情況下,我們需要在頁面中顯示一條錯誤信息。這個顯示工作由view層完成,顯示的數據來自controller提供。view層會用這些數據構建一個錯誤信息顯示給用戶,我們已經寫好了,並且可以在許多情況下使用它。
View.Message = function(messageData, type){‘ +
‘
‘;
}
message += ‘
‘;
el.innerHTML = message;
}
View.Message.prototype.show() {
/* provide a slide-in animation */
}
這裡的type 是css的一個class name,給用戶自定義message的類型樣式入口,
這個函數遍歷所有的message數據並把他們顯示在頁面中。
我們的model層存儲了我們的數據並且能夠告訴我們數據是否有效,view層給用戶顯示成功或者失敗的消息,就剩下最後一步了,就是用戶表單提交的時候驗證表單信息。
/* use the event binding of your friendly neighbourhoodMyController.validateForm = function(event){
var data = [];
data['other'] = document.getElementById(’other’).checked;
var invalidFields = MyModel.validate(data);
if (invalid.length) {
event.preventDefault();
// generate the view and show the message
var message = new View.Message(invalidFields, ‘error’);
message.show();
}
}
好了我們完成了,model層和view層的方法我們以後還能重復利用哈
javascript mvc正在流行起來,但是 深入的理解怎樣在你的工作中運用它會更有幫助。你可以自己做,也可以用已經存在的javascript mvc框架
下面是幾個javascript mvc 框架:
你的應用程序是否需要一個框架,這依賴於應用程序的復雜性。如果它是個簡單的應用程序,那麼使用框架來做就不值當了。
請記住,推出自己的MVC框架並不是一種死板,正如你想的那樣它可以更靈活。
和其他的開發一樣,你一定要決定取捨,這種分離是否值當。如果只是一個簡單程序,只有幾個函數,那這種類型的分離就是顯得有些過分了。如果是個復雜的大程序那這種MVC模式的分離將會獲益匪淺!