15 年前,許多人都使用 Perl 和 ColdFusion 之類的工具構建網站。我們經常編寫可以在頁面頂部查詢數據庫的腳本,對數據應用必要的轉換,以及在同一個腳本底部顯示數據。這類架構適合於向網站添加簡單 的 “Contact us” 表單。然而,隨著應用程序變得更加復雜,這種方法無法進行相應的擴展來處理更大的復雜問題。大部分 Web 應用程序現在已經對模型-視圖-控制器 (MVC) 架構進行了標准化,使用單獨的代碼實現業務邏輯、顯示邏輯和用戶交互(路由)邏輯。湧現出從 Spring MVC 到 Rails 的各種框架可以幫助您快速實現基於 MVC 的 Web 應用程序。
幾年前,jQuery 是用於構建客戶端 JavaScript 應用程序的主流庫。然而,隨著應用程序中的 JavaScript 的復雜性日益增加,jQuery 成為一項處理復雜性的必要不充分技術。例如,用於待辦事項 (to-do) 列表的單頁面應用程序可以包含一個緊急待辦事項列表、一個完整的待辦事項列表、一個當日待辦事項列表,以及一個過期待辦事項列表。在刪除某個待辦事項時會 怎樣?如果任務很緊急但已過期,您可能需要手動編寫代碼來從視圖中的三個或四個不同位置中刪除該事項。如果刪除某個對象後需要您刪除或更改屏幕上顯示的其 他相關對象,這樣復雜性就會變得無法控制。
客戶端 MVC 框架旨在解決此類問題,並且大多數框架都表現出色。但是您如何從許多 JavaScript 客戶端 MVC 框架中選擇合適的框架。本文將從較高的層面簡要介紹其中一些最流行的框架。以及如何針對給定的用例選擇合適的框架。
Backbone.js
在使用率方面,Backbone 是目前為止最流行的客戶端 MVC 框架。它被廣泛應用於各個開發社區,Rails 開發人員對它的采用率一直較高,並出現了許多廣受歡迎的資源,比如 thoughtbot(一家備受尊敬的 Rails 咨詢公司)推出了 Backbone on Rails(參見 參考資料)。Backbone.js 的優勢在於它與具象狀態傳輸 Web 服務實現了良好的集成。如果您對後端數據使用 RESTful JavaScript Object Notation (JSON) 模型並遵循 Backbone 所期望的約定(與 Rails 中的約定匹配),那麼您不需要編寫任何代碼就可以將 Backbone 連接到服務器,從而節省大量的時間。
在 Backbone 中,應用程序包含集合(用戶或文章)、模型(單個用戶或文章)、視圖和路由器。Backbone.js 中的視圖是非預定的 (nonprescriptive),允許您使用自己喜歡的 JavaScript 模板或框架。路由器結合了 Rail 風格的路由器和一個傳統的 MVC 控制器,負責獲得給定的 URL 並通知框架要運行的代碼。清單 1 中的 Backbone.js 路由器代碼給出了一個示例。
清單 1. 樣例 Backbone.js 路由器代碼
var Workspace = Backbone.Router.extend({
routes: {
“help”: “help”, // #help “search/:query”: “search”, // #search/kiwis “search/:query/p:page”: “search” // #search/kiwis/p7 },
help: function() {
....
},
search: function(query, page) {
.....
}
});
Backbone.js 附帶了一個 Underscore.js 副本。Underscore.js 是一組實用工具,可以通過更加功能化的方式簡化 JavaScript 的編寫,並支持一系列有用的基於集合的操作。它還包括 Backbone.history,後者可以幫助您巧妙地處理頁面導航。
Backbone.js 主要優勢在於它與服務器的自動集成。如果這樣做適合您的用例,那麼學習如何使用 Backbone.js 將是值得的。您可以通過一些框架在一兩個小時之內初步掌握可能需要花一到兩天學習的Backbone.js 基礎知識。這非常適合比較大的項目,這類項目至少持續幾周的時間。
Backbone.js 仍然算不上一種完好的解決方案。您可能需要編寫相當數量的代碼來處理潛在的內存洩露等問題。您還可能需要試驗幾種方法來查看呈現內容,之後才能找到真正滿足需求的方法。
Spine.js
Spine.js 通常與 Backbone.js 進行比較;它受到 Backbone.js 的影響,並在使用率方面與前者接近。Spine.js 包含類、模型、控制器和視圖,這比 Backbone.js 引入的集合更加傳統一些。
Spine.js 使用 CoffeeScript(參見 參考資料)編寫,這使它更加簡練且(依我看來)更易於讀取源代碼。要了解 Spine.js 如何工作,您需要熟悉 CoffeeScript。然而,您不必使用 CoffeeScript 構建 Spine.js 應用程序。但是,如果已使用 CoffeeScript 進行了構建,您可以訪問 CoffeeScript 特性(如類)。CoffeeScript 使用原型繼承而非經典繼承,因而無法支持在本地 JavaScript 中的類。CoffeeScript 使用了一些非常標准的模式,為希望使用它們的開發人員提供類。如果使用純 JavaScript 編寫 Spine.js 應用程序,您只需使用 ,後者使您不需要編寫 CoffeeScript 代碼就可以訪問類。
Spine.js 中的模型、控制器和視圖都使用類實現,因此可以同時編寫類和實例方法。模型負責處理業務邏輯,屬於模塊類,您可以擴展並包括其他模塊,從而混合重用屬性和 功能。模型可以自動序列化到 JSON 中,通過僅使用本地存儲實現持久化。或者可以使用 Asynchronous JavaScript + XML (Ajax) 將對象持久化到服務器中。和 Backbone.js 一樣,Spine.js 現在提供了合理的默認設置,可以通過 Ajax 實現持久化,但是仍可以在必要時編寫自己的特定實現,並且非常簡單。清單 2 展示了來自一個 Spine.js 應用程序中的 CoffeeScript 代碼的示例。
清單 2. Spine.js 應用程序中的 CoffeeScript
class Contact extends Spine.Model
@configure “Contact”, “first_name”, “last_name”
@filter: (query) ->
@select (c) ->
c.first_name.indexOf(query) is not -1
fullName: -> [@first_name, @last_name].join(‘ ’)
Spine.js 和 Backbone.js 兩者之間最主要區別是它們處理服務器交互的方式。Backbone.js 在顯示響應之前將等待服務器響應。如果試圖刪除、插入或更新某個元素,用戶界面 (UI) 直到操作成功完成才會刷新。而 Spine.js 側重於即時更新 UI,而且在進行後台處理時處理 Ajax 服務器。這種更新是一種非常重要的實踐,也是在這兩種優化的擁有良好編檔的流行框架之間選擇時需要考慮的的主要因素。
如果您的目標是創建一種客戶端體驗,而對服務器狀態的更新是次要的,那麼 Spine.js 可能是一種更好的選擇。如果仍然使用服務器來檢查狀態變化的有效性則 Backbone.js 可能更適合。Spine.js 提供了響應性更好的 UI。但是,如果顯示成功刪除某個元素,只是讓服務器發送一個響應,不允許您刪除該項,因為該項正在被其他人使用,那麼會發生什麼?針對這個問題存在一些 應急方案,但是通常來講 Spine.js 更加適合用戶操作自有(而非共享)數據。Spine.js 的一個常見用例就是購物車,其中所有驗證都可以在客戶端處理。
Knockout
人們可能會爭論目前為止討論的這些工具是否是原本意義上的真正的 MVC 框架。Knockout 明確實現了模型-視圖-視圖-模型 (MVVM),而不是經典的 MVC。但是,不要因此而妨礙到您的決策制定。在選擇框架時,更重要的是查看所提供的功能而非首字母縮略詞或分類。
Knockout.js 在熟悉 MVVM 模型的 Microsoft .NET 開發人員之間特別受歡迎。對於主要問題是將模型狀態通過聲明的方式綁定到視圖用例,Knockout.js 是非常好的選擇。Knockout.js 對於前面提到的示例待辦事項應用程序是非常理想的選擇,該應用程序的主待辦事項列表的子集都有自己的視圖,在刪除某個待辦事項後需要更新所有的列表。
在 Knockout.js 中,您將創建模型、視圖模型和視圖。與在 Spine.js 和 Backbone.js 中一樣,負責處理業務邏輯、驗證和與遠程服務器交互的 Ajax(假設您不僅僅是創建一個本地應用程序)。視圖模型代碼負責保留和操作模型數據。例如一個視圖模型可能包含添加、編輯以及從列表中刪除內容項的方 法。視圖模型非常貼近於傳統的 MVC 架構中的控制器。視圖就是一些模板,包含將信息呈現到屏幕的標記。在 Knockout.js 中,這些可以通過聲明的方式綁定到視圖模型(方便入門)。一些學員可以在一個小時內掌握並使用 Knockout,並可在三個小時內構建非凡 (non-trivial) 應用程序。
一般來講,Knockout.js 比較適合較小、較簡單的項目。人們往往將 Backbone.js 或 Spine.js 用於更大、更復雜的項目。也就是說,有經驗的 Knockout.js 開發人員可以創建非常復雜、同時又易於維護的應用程序。如果考慮使用 Knockout.js,您也應當考慮 Angular.js 和 Sammy.js(參見 參考資料),後兩者是兩種相對輕量級、易於啟動的框架。
Batman.js
Batman.js 是一種有趣的新框架,由 JSConf 在 2011 年推出,但是又經過了幾個月的時間才能夠通過下載獲取。Batman.js 已經開始受到一些喜歡並得到開發 MVC 應用程序的程序員的關注。表面上看,Batman 在易於入門、支持視圖聲明綁定方面與 Knockout.js 類似。Batman.js 提供了一些其他功能,包括可選的全棧 (full-stack) 框架,用於自動代碼生成器、構建工具甚至後端 Node.js 服務器代碼,可以實現您的服務器端 API。
和 Knockout.js 一樣,Batman.js 也使用視圖綁定。清單 3展示了一些樣例視圖代碼。
清單 3. Batman.js 中的視圖代碼示例
<ul id=“items">
<li data-foreach-todo=“Todo.all” data-mixin=“animation”>
<input type=“checkbox” data-bind=“todo.isDone” data-event-change=“todo.save” />
<label data-bind=“todo.body” data-addclass-done=“todo.isDone”
data-mixin=“editable”></label>
<a data-event-click=“todo.destroy">delete</a>
</li>
<li><span data-bind=“Todo.all.length”></span>
<span data-bind=“‘item’ | pluralize Todo.all.length”></span></li>
</ul>
清單 3中的代碼是有效的 HTML5,包含一些額外的屬性,供 Batman 綁定數據和事件。在 Batman.js 中,您的應用程序包含模型、視圖和控制器。模型支持驗證功能,能夠實現生命周期事件,包括一個內置的恆等映射 (identity map),並且可以被告知(主動記錄樣式)如何堅持使用 、Batman.RestStorage
、Batman.RailsStorage
或自定實現。視圖為一些 JavaScript 類,呈現用純 HTML 編寫的模板,還有一些用 data-*
屬性綁定模型數據並觸發事件處理器的構件。控制器為一些永久對象,處理來自視圖的事件,訪問模型數據,並呈現相應的視圖。
選擇一種 JavaScript 框架
如果您正在從事一個長期的大項目,那麼了解 Backbone.js 或 Spine.js 很有必要,因為它們獲得了廣泛的采用,可以解決您可能遇到的問題。然而,即使有了這些項目,您要明白您不是有必要使用一個成熟的服務器端 MVC 框架,而是還需繼續編寫基礎架構代碼。
嘗試使用在視圖中用了聲明式綁定的框架將非常有必要。此類框架具有與 Backbone.js 之類的項目不同的優缺點。如果考慮使用聲明式視圖綁定,那麼花些時間研究一下更新的 Batman.js 框架提供的額外功能。雖然 Batman.js 不像其他框架那麼流行,但它正在快速發展,而且提供了比普通客戶端 MVC 框架更豐富的特性。
在不同框架中進行原型化,感受一下這些框架的用法,這樣做非常有必要。特別是對於客戶端 MVC 框架來說,原型化是從不同選項中進行選擇的最快速、最有效的方法之一。一種方法是讓每個團隊成員花一到兩天的時間,使用不同的框架進行原型化,然後進行回 顧並討論結果。最壞的情況是,如果您還有一對框架需要從中進行選擇,那麼再額外花一天左右的時間構建二者的概念,直到選出最適合您的用例的框架。
考慮靈活性。仔細考慮您可以做哪些工作來降低對框架的依賴性,這對於許多框架來說是一個艱巨的任務。為將來 12 到 18 個月內遷移到另一個框架制定一個備份方案,以防您發現需求和所選的框架沒有按照預期進行。
結束語
JavaScript 客戶端 MVC 框架仍然不夠成熟。這個領域正在發生快速改變,缺少一致認可的最佳實踐。對於較大的項目 Backbone.js 和 Spine.js 都是非常流行、具有良好支持的。如果傾向於聲明視圖綁定,那麼 Knockout.js 和 Batman.js 則都是不錯的選擇。