Backbone.js是什么?
Backbone.js是一個JavaScript MVC框架,提供了良好的代碼組織能力,可以方便地將應用程序解耦成可以復用的部分,為建立大型的單頁面應用提供框架支持,目前的版本是0.9.10(注:現在已到1.2.1版本)。通過將應用程序分解成MVC模式中不同職責的模塊,帶來了以下幾點好處。
降低維護成本。數據、控制器、視圖的更新都是獨立進行的,互相之間都是松散耦合的。
解耦數據和視圖,便于直接對業務邏輯進行單元測試。
便于團隊合作,負責UI開發和業務邏輯開發的工程師可以分工并行工作。對于Web前端開發來講,負責HTML模板和CSS的界面工程師和負責業務邏輯的JavaScript工程師可以協同工作。
Backbone.js算是比較輕量的MVC框架,所謂輕量,是說它只關注一個框架應該關注的基本的事情——如何給應用分層、如何組織各種功能的代碼。至于在實際開發中需要用到的Utils或UI組件,Backbone.js則沒有提供任何支持。但Backbone.js所依賴的Underscore.js是一個功能比較全面的非侵入式工具函數類庫,算是在Utils方面的一個補充。
輕量并不意味著功能薄弱。首先,Backbone.js的精髓是它定義前端MVC的方式和編碼哲學,并依據這些規定了如何去給代碼分層,因此Backbone.js能夠讓前端工程在可維護性和擴展性上都得到質的提升;同時,由于其良好且易于理解的結構,各個模塊之間都是松散耦合的,雖然目前官方并沒有提供根據實際需求build文件的功能,但如果你愿意,完全可以自己手工刪掉源碼中的Bakcbone.View只使用Model和Collection;后,Backbone.js的任何一個部分都是非常容易擴展的。因此,Backbone.js的功能實際上非常強大的。下面將介紹Backbone.js的主要組件(架構如圖1所示)。

圖1 架構圖
Backbone.Events和Backbone.Sync
Backbone.Events和Backbone.Sync兩個組件是Backbone.js異步通信、事件驅動的編程模型的基礎。
Backbone.js中所有的組件都通過_.extend()的方法“繼承”了Backbone.Events所提供的功能,可以維護一套自己的事件訂閱和回調列表。通過Events.on(event,[callback],[context])和Events.off([event], [callback], [context])兩個方法來實現對事件的訂閱和取消訂閱。

早期版本中的事件訂閱和取消訂閱是通過Events.bind()和Events.unbind()兩個方法實現的,目前的版本中還保留了這兩個別名方法,但不推薦使用。
Backbone.js的所有組件都有一些內置的事件,可以查閱官方文檔。除了預置事件外,通過Events.trigger(event,[*args])方法也可以方便地觸發自定義事件。

從0.9版開始,Backbone.Events提供了Events.listenTo(other,event,callback)和Events.stopListening([other],[event],[callback])兩個新方法來通過另外一種形式實現事件的訂閱和取消訂閱。
與on()和of f()不同,這種方式將監聽的主動權轉換了。舉個例子來說:有對象A和對象B,B.on('someThingHappened',A.doSomeThing)是當對象BsomeThingHappened時候知對象A去doSomeThing;而A.listenTo(B,'someThingHappend',A.doSomeThing)是對象A主動去盯著對象B,當它someThingHappend的時候去doSomeThing。
第二種方式大的意義是變被動為主動,從而實現了IoC(Inversionofcontrol,控制反轉)。監聽者和被監聽者之間沒有了耦合,只要被監聽的對象能夠拋出指定的事件,就可以和監聽者組合在一起,甚至不需要去關心被監聽對象的類型,這對代碼的復用和行為抽象有很大的幫助。在測試層面,可以輕易地把被監聽對象換成mock的測試代碼來模擬真實情況。
Backbone.Sync則將同服務器的通信封裝了起來,當Collection和Model需要和服務器通信交換數據時,會去調用Backbone.Sync中對應的方法并發送請求,如果服務器端支持RESTfulAPI就可以將整個通信過程描述得非常優雅并易于擴展。Sync的實現可以是jQuery.ajax()的封裝,也可以是其他的類庫提供的異步通信工具的封裝。
Backbone.Model和Backbone.Collection
Backbone.js中的Model和Collection共同構成了MVC中的M層。Model的本質就是一組以keyvalue形式保存的數據,可以通過Backbone.Model.extend()來定義自己的Model。

上面的示例代碼中defaults屬性定義了一組默認值,當Model初始化時,如果沒有指定defualts中所定義的屬性的值,就會用默認值來填充Model;initialize()方法會在Model被實例化時調用,用來進行一些初始化的操作;validate()方法會在Model的save()或set()方法被調用時執行,可以根據具體需求進行擴展。
通過Model的getters和setters可以實現對Model中屬性的讀寫,并且當set()方法被調用時,Model會將屬性變化的事件廣播給所有訂閱者(通常是視圖),驅動視圖重新渲染或其他關聯的Model的數據更新。
Collection是一組Model的集合,通過Collection可以將一組數據結構相同的Model有序地組織在一起,進行批量操作和管理等。同時,Collection代理了Undersore.js中眾多用來操作Collection的工具方法,例如find、filter、map等。
Model和Collection都可以通過RESTfulAPI同服務器進行數據交換。
Backbone.View
Backbone.View是基于Backbone.js開發的Web App中的核心部分,負責用戶交互事件的捕捉和處理、把用戶輸入導向Model或Collection、渲染視圖、操作DOM等。Backbone.js的內部實現依賴$變量,因此DOM操作的庫可以在jQuery、Zepto或Ender等中選擇。從目前的情況來看,在桌面瀏覽器中,Sizzle.js(jQuer y所使用的SelectorEngine)的性能還是甩開Zepto幾條街的,因此面向桌面瀏覽器的開發還是推薦使用jQuery,移動端考慮到文件體積等因素推薦使用Zepto。
對于一個Backbone.View來講,重要的就是$el屬性,$el是一個jQuery對象(取決于所采用的DOM操作類庫),是一個視圖的外層容器。容器所采用的HTML標簽可以通過tagName屬性來指定,可以是ul也可以是header或其他任何標簽,默認情況下是div。
容器內部的DOM渲染可以通過模板引擎來完成。Underscore.js本身提供了一個_.template()的方法,因此Backbone.js不需要額外的模板引擎支持。當然,如果有特殊的需求,例如和后端共用模板文件,也可以選用Mustache等其他的模板引擎。

這樣一個View就被渲染到界面上了。上面的代碼中還監聽了Model的change事件,當數據發生變化時,驅動視圖重新渲染。當Model的數據比較豐富時,只有一個屬性變化就重新渲染整個視圖顯然會帶來性能上的隱患,因此這里的佳實踐就是把render的過程break-down成粒度更細的片段。

值得注意的是,當一個View不再被需要時,一定要記得銷毀,除了銷毀DOM對象外,也要銷毀所有的事件監聽器。在只有Events.on()和Events.off()的年代,由于銷毀View時需要逐一取消訂閱所有的消息,經常由于忘記解除某個綁定導致產生被稱為GhostView的垃圾對象,既無法被釋放也無法被回收。這也是Events.listentTo()方法帶來的另外一個好處——只要調用Events.stopListening()方法即可將此對象的所有事件監聽器銷毀。
所有的DOM事件也是通過$el來代理的,在Backbone.View中可以通過以下方法來方便地管理DOM事件。

Backbone.js的內部實現里,在View的構造方法中調用View. initialize( )后將繼續調用View.delegateEvents()方法,這個方法將解析events屬性所定義的事件和回調列表,并將全部事件代理到$el對象上。由于使用的是事件代理,某些不支持冒泡的DOM事件則必須另外監聽,如滾動條事件。

Backbone.Router和Backbone.History
Router是用來在URLHash和特定的動作或視圖之間做映射的。

后一句History.start()是告訴Backbone.js開始對URLHash的變化進行監視,也可以隨時通過History.stop()來停止監視。同時,如果目標平臺是支持HTML5Histor yAPI的,那么在start時傳入{pushstate:true}的參數,就可以去掉URL中的#字符,對SEO有一定幫助。
Backbone.js的適用場景
經常能在各種場合聽到前端工程師們討論“你們的XXX是用什么做的啊?”“為什么不用XXX啊?”這樣的問題。前端的類庫和框架林林總總,算在一起數量沒有一千也有五百,因此在面對一個新項目時難免會產生選擇恐懼癥。
但說到底,技術方案都是由需求決定的,任何一個類庫或框架都有其適用范圍和佳的使用場景,Backbone.js也不例外。Backbone.js的佳使用場景是大型的單頁面應用:通過RESTfulAPI同服務器通信,然后根據數據的變化來驅動視圖重新渲染,整個程序建立在異步通信和事件驅動的編程模型之上。
單頁面應用給用戶帶來的使用體驗是沉浸式的、相對重型的,對于普通的Web Page和數據相對穩定、視圖不需要頻繁重新渲染的場景來講,Backbone.js顯然就沒有用武之地了。
Backbone.js和MV*不得不說的那點事兒
四十年前,Trygve Reenskaug基于Smartalk語言設計出了MVC模式,經過幾十年的發展,MVC出現了眾多的衍生。而我們今日說的MVC在不加特殊說明的情況下,通常指的是在服務器端Web應用開發中大量使用的WebMVC。
對于典型MVC模式來講,View是無法直接獲得用戶輸入的,而Controller則是用戶輸入和View之間的橋梁。但在瀏覽器中,View層的載體是HTML,用戶輸入和交互行為都是基于HTML的,對事件的捕捉、輸入都由瀏覽器代勞了,并且輸入會首先進入View層,因此對于前端開發來講,嚴格意義的MVC是無法實現的。
因此,包括Backbone.js在內的JavaScript MVC框架的實現并沒有嚴格遵循MVC的定義,Controller的部分職責被轉移到了View層。Backbone.js對于前端MVC的定義非常易于理解,但對于沒接觸過MVC模式的同學來說在初期會有一些迷惑,原因是Backbone.js核心組件的命名。Backbone.js的核心組件包括Backbone.Collection、Backbone.Model、Backbone.View和Backbone.Router,而在早期的版本中,Backbone.Router組件的名稱是Backbone.Controller,這很容易讓人直接將其和MVC三層中的C層聯系起來。但事實上,Backbone.Controller的作用是根據URLHash來在對應的行為和事件中做路由的,其功能同MVC中的C相比要簡單很多,因此在0.5前后Controller改名叫做Router了。從實體代碼的角度看,View層其實是模板代碼和Backbone.View中部分代碼的綜合體,而Backbone.View中剩余的部分才是MVC模式中Controller的概念,負責操作View以及數據在View和Model層中的流轉。
對于前端開發來說,用戶直接面對的一層并不是Controller而是View,用戶輸入也會首先進入View,因而用MVP和MVVM模式來描述架構更加合理。相比AngularJS等框架,Backbone.js的模式顯然更易于理解,學習曲線比較平緩,因為它并沒有引入過多的需要重新認知和理解的新概念而是盡量在靠近傳統的MVC模式,對于以前接觸過MVC模式開發的同學來說非常容易上手,而AngularJS中的Directive等概念還是需要一定認知成本的。
但如果從架構的角度討論,AngularJS其實是更為純粹并更接近嚴格意義上的MVC模式。為了把View的功能提升到一個應有的高度,引入了Directive的概念,通過擴展HTML標簽和自定義屬性來描述View,在Directive中來解析這些擴展出來的內容,理解成本和代碼的復雜程度都有所提高,但View層功能則得到了質的提升。
反觀Backbone.js,并沒有在前端開發中真正的View的載體HTML上做太多文章,即便采用模板引擎也僅僅是把數據和HTML組合起來。但得益于其強大的擴展性,可以很容易將Knockout等Data-binding框架集成進來,從而實現MVVM的架構和分層。例如前文提到將render的過程Breakdown就完全可以用Data-binding來取代,省掉了手工更新DOM的煩瑣。
Backbone.js在豌豆莢PC客戶端2.0中的實踐
豌豆莢PC客戶端2.0的UI是完全建立在Web前端基礎上的。借助Backbone.js,豌豆莢PC客戶端在開發大型單頁面應用中做了大量的實踐。通過在客戶端中捆綁一個Webkit引擎并對其進行擴展,使得跑在Webview中的前端代碼跳出沙箱的限制,可以操作文件系統并調用系統API,以此來進行桌面應用的開發。這樣做的好處有以下幾點。
極大提高開發效率:桌面應用的開發中,UI開發效率一直很低,但借助HTML5和CSS3的新功能,Web前端可以輕易地做出精細程度和交互體驗都不輸桌面應用的UI,但開發效率和維護成本都極大降低。
易于跨平臺:UI依賴于Webkit引擎,而Webkit引擎本身是跨平臺的,因此可以很容易地移植到Web上或其他桌面平臺。
快速迭代和改進:由于維護和擴展成本的下降,可以快速的將原型設計產品化并進行驗證,提高產品迭代和改進的速度。
但與此同時,用Web前端技術開發桌面應用也要面臨巨大的挑戰。首先就是內存消耗。用戶在使用瀏覽器和使用桌面應用時的心理預期是不一樣的,即使一段有內存泄漏問題的前端代碼跑在瀏覽器里,當出現運行緩慢時大不了一下刷新頁面,但對桌面應用來講,大多數情況下沒有刷新Webview的機會,因此必須對內存實現更精細的控制。由于Webview本身依賴于GC,前端無法主動管理和回收內存,因此必須借助ChromeDeveloper tools中的Profiles等工具查找出現內存泄漏的地方從而進行改善,這也依賴于大量的經驗積累。
其次是運行速度和界面響應速度。由于Webview是單線程的,但單頁面應用面臨的是數百倍于Web Page的業務邏輯復雜度,同時業務邏輯的執行和UI共用一個線程,如何優化執行速度也是一個很大的挑戰。雖然目前WebUI的界面流暢程度無法完全達到桌面應用的水平,但依然是很有競爭力且有提高空間的。(責編:陳秋歌)
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。