一、準備階段
1.1 框架選型
隨著對MV*架構模式的逐步理解,越來越發覺對于一般的業務場景,mvvm是前端架構的不二選擇。在我所了解的框架范圍內,與mvvm架構模式最貼合的框架只有angular,無論是雙向數據綁定、指令,還有強化的html標簽,angular提出的很多新概念都像是為mvvm理論量身定做的一套外衣。很可惜angular對ie8的支持不太友好,兼容ie8的angular插件也非常稀缺,對于國內環境,ie8仍然占有較大的市場份額,無奈之下只能轉向輕量級的backbone。
1.2 官方文檔vs電子書
通過閱讀backbone的官網簡介,總結下backbone特點(并非優點):
- 提供了基本的MVC結構,可以做到業務邏輯與視圖的分離(MV*框架最基本的功能)
- 內置支持restful風格的API(做過項目后會發覺,內置的那套基本沒什么用,自定義會更合適)
- 路由的支持(大部分MV*框架也都支持,也不是亮點)
- 方便與第三方插件集成(因為backbone太輕量了,對于插件沒什么要求,這勉強可以算優點)
- 兼容ie8(對比其他MV*框架,感覺這是唯一的優點,也是選擇它的理由)
- 粗粒度的單向數據綁定(缺點,每次更新視圖,都只能全部更新,當然你可以自己寫局部更新,會比較繁瑣)
- 太輕量了(缺點,連angular這種重量級的框架寫法都會五花八門,backbone這種就更別提了,新手根本無法駕馭它寫出優良的代碼)
由于backbone功能單薄,無法形成固有的mvc或是mvp模式,但是為了讓它變得好用,我們可以嘗試增強它的功能,讓它更接近我們想要的mvvm框架。mvvm框架最大特點是:雙向數據綁定,于是通過google:backbone data binding,找到了以下幾個backbone插件:Backbone.ModelBinder、Rivets.js、Backbone.Stickit。通過測試發現Rivets.js很好用,可惜不兼容ie8,ModelBinder配置不夠靈活,于是Stickit成為最佳選擇。
關于框架的學習,我的思路是這樣的:官方文檔是一定要看的,但是沒必要從頭看到尾,官方開頭那部分介紹是一定要看的,那里會告訴你框架是什么樣的,有什么特性,而具體的API只要看能反應出框架特性的那幾個API就夠了,其他的都是用來查的。有些API不用查,你也知道它肯定有,就像你學完java后學C++,你知道C++里肯定有for循環。
<b>只看官網文檔是遠遠不夠的,文檔只是告訴你可以這樣用,但是沒有告訴你該不該這樣用,所以當你大體了解了一個框架的基礎知識后,應該找一本名叫《xxx框架最佳實踐》的電子書,了解下怎么用才合理,對于輕量級的框架尤其如此。</b>
學計算機知識,一個wiki百科就夠了(要翻墻),千萬不要看國內某百科,連自己貼吧的內容都可以當參考文獻,實在是太不靠譜了。
二、實踐階段
準備階段做得越多,實踐起來就越輕松,項目需求分析階段,開發人員的時間如果用來做技術調研、框架選型、編寫demo、測試性能、編寫非業務組件等工作,時間還是會比較緊張的。總結了一下項目中的問題及解決方案:
2.1 如何編寫model層代碼
對于mvc新手,經常會誤解m的含義,認為m只是表示數據。實際上mvc是按照職責來劃分的,而非數據類型。m表示業務層,即包含表示業務邏輯的數據模型,同時也包含操作這些數據的方法。反應到backbone項目中,m層的寫法如下:
var Product = Backbone.Model.extend({
// 業務數據模型
defaults: {
name: “”,
price: 0
},
// 操作數據的方法
create: function() {...},
remove: function() {...},
modify: function() {...},
query: function() {...}
});
不要在view中直接調用沒有業務含義的底層方法:fetch、save等,這將導致model與view耦合在一起,view層的代碼變得繁雜且難以維護。
2.2 事件與邏輯分離
backbone提供了View類,很多人并沒有意識到事件與邏輯的解耦,他們通常這樣寫:
var ProductView = Backbone.View.extend({
// 注冊事件
events: {
"click #saveBtn": "create",
....
},
// 創建
create: function() {
// 針對click準備一些數據,可能涉及到dom操作
var data = ....;
// 執行創建的邏輯
...
}
});
當觸發創建邏輯的事件不止一個時,會變成這樣:
var ProductView = Backbone.View.extend({
// 注冊事件
events: {
"click #saveBtn": "createForClick",
"blur #xxInput": "createForBlur",
....
},
// 創建for click
createForClick: function() {
// 針對click準備一些數據,可能涉及到dom操作
var data = ....;
// 執行創建的邏輯
...
},
// 創建for blur
createForBlur: function() {
// 針對blur準備一些數據,可能涉及到dom操作
var data = ....;
// 執行創建的邏輯
...
}
});
此時你會發現處理邏輯的代碼重復了,所以將事件與邏輯分離的一個優點是:邏輯代碼可以復用,正確的寫法如下:
var ProductView = Backbone.View.extend({
// 注冊事件
events: {
"click #saveBtn": "createForClick",
"blur #xxInput": "createForBlur",
....
},
// 響應click事件的創建
createForClick: function() {
// 針對click準備一些數據,可能涉及到dom操作
var data = ....;
// 執行創建的邏輯
this.create(data);
},
// 響應blur事件的創建
createForBlur: function() {
// 針對blur準備一些數據,可能涉及到dom操作
var data = ....;
// 執行創建的邏輯
this.create(data);
},
// 執行創建
create: function(data) {...}
});
事件與邏輯的分離最大的好處是可以方便的進行單元測試,以上面為例,只需針對create方法進行測試,就能驗證邏輯的正確性,而非邏輯的dom操作是不在單元測試范圍之內的。
2.3 凈化路由代碼
backbone提供了路由功能,可以方便的根據網址跳轉到指定的視圖,很多人的寫法是這樣的:
var AppRouter = Backbone.Router.extend({
"route1": "createView1",
"route2": "createView2",
....,
createView1: function() {
// 1.操作model層方法,獲取視圖所需數據
...
// 2.new一個視圖對象,傳遞數據到視圖中
...
// 3.其他的邏輯
...
},
createView2: function() {
// 1.操作model層方法,獲取視圖所需數據
...
// 2.new一個視圖對象,傳遞數據到視圖中
...
// 3.其他的邏輯
...
}
});
隨著需求的增加,創建視圖的邏輯會變的越來越復雜,整個路由的代碼會顯得非常臃腫,還有一個問題,不單單路由里需要創建視圖,其他的地方也會用到,這個時候,重復的代碼又出現了。解決這個問題的方案是抽取出創建視圖的邏輯,實際項目中我抽取了一個文件夾叫作:controllers,里面以業務功能為單位,存放創建視圖邏輯的js文件,比如處理產品的controller,可以定義為productCtrl.js,代碼如下:
var productCtrl= (function() {
var create = function() {
// 1.操作model層方法,獲取視圖所需數據
...
// 2.new一個視圖對象,傳遞數據到視圖中
...
// 3.其他的邏輯
...
};
return {
create: create
};
});
此時路由的代碼就變得非常清爽了,同時其他地方需要創建視圖時,只需要調用productCtrl.create()
即可,路由代碼:
var AppRouter = Backbone.Router.extend({
"route1": "createView1",
"route2": "createView2",
....,
createView1: function() {
productCtrl.create();
},
createView2: function() {
...
}
});
2.4 引入bower-installer優化bower文件結構
前端構建時需要合并第三方的js、css文件,但是每個插件的目錄結構不盡相同,導致grunt命令寫起來非常繁瑣,為了盡量統一處理,引入了bower-installer插件,主要目的是抽取出插件的核心文件,將其放入規則統一的目錄中,方便進一步處理。 bower-installer的github地址:https://github.com/blittle/bower-installer
2.4.1 bower-installer使用
- 安裝:npm install -g bower-installer
- 運行:bower-installer
具體配置參考github上的文檔
2.4.2 基于bower-installer引入第三方插件的流程(以jquery為例)
- 配置bower.json文件:"jquery": "1.11.3"
- 執行bower install下載jquery插件
- 執行bower installer抽取jquery核心文件到bower_main_files目錄
- 引入文件:在index.html中引入插件的根目錄由bower_components改為bower_main_files
2.5 mvvm的缺陷
開發過程一切都很順利,只踩到兩個坑:<b>性能</b>、<b>復雜界面的邏輯處理</b>,而這兩個坑是由mvvm的基因決定的。
mvvm的雙向數據綁定可以讓開發變得非常便利,但同時也帶來了兩個問題:
- 為了將model與dom中的元素一一對應進行綁定,即便最簡單的界面,也會消耗大量性能,當需要一次性加載大量數據時,性能問題就會凸顯。
- 數據綁定是基于觀察者模式構建的,當界面中存在多個組件,且組件間有復雜交互時,代碼很難跟蹤,調試會變得非常困難,當修改了某個model時,你無法清晰的了解后面會發生什么。
針對mvvm的缺點,以下業務場景是不適合的:
- 由于極端的用戶體驗要求,需要一次性渲染大量數據,不能分頁的場景。
- 組件多且交互過復雜的場景。
據說angular2.0也傾向單向數據流,估計也是考慮到這個原因,react+flux也推崇單向數據流,也許這是一種趨勢。但對于一般的業務場景來說,雙向數據綁定還是非常實用的,所以具體采用什么方案還是要結合具體的業務場景。
三、總結
- backbone.js是一個功能單薄的框架,它需要其他插件的輔助才能變得好用。
- 沒有了解過xxx最佳實踐,就不要輕易在項目里使用,不然往往會得到xxx不好用的錯誤結論。
- 沒有一個模式永遠是最佳的,只有適合業務場景的模式才是最佳模式。