Backbone框架淺析

Backbone是前端mvc開發模式的框架。它可以讓view和model相分離,讓代碼結構更清晰簡答,開發進度加快,維護代碼方便。可是,如今出了一種mvvm框架,它是下一代前端mvc開發模式的框架,表明做是Angular.js,改天有時間去研究下。如今先來研究下Backbone框架。javascript

Backbone提供了Model, Collection, View,Events,Controller(Router)。Model 用來建立數據,校驗數據,綁定事件,存儲數據到服務器端;View 用來展現數據。如此這般,在前端也作到了數據和顯示分離。Backbone依賴於Underscore.js,這是一個有不少經常使用函數的js文件。同時依賴jQuery等庫來操做DOM和Ajax請求。css

1. Backbone.Events
Events能夠被添加到任何一個javascript對象中,一旦對象與Events合體,就能夠自定義事件了。
var obj = {};       //js對象
_.extend(obj, Backbone.Events);     //把Backbone.Events擴展到obj對象中。這時這個對象就擁有操做事件的方法了。_是underscore.js的對象,至關於jquery.js中的$。
obj.bind('data', function(data) {
  console.log('Receive Data: ' + data);
 });
obj.trigger('data', 'I/'m an Backbone.event');      //打印Receive Data: I'm an Backbone.event
obj.unbind('data');
obj.trigger('data', 'I/'m an Backbone.event');
另外,若是事件不少,能夠給事件加上命名空間,例如"change:selection"。屬性事件會先於正常事件觸發。好比:html

咱們先監聽了change事件,而後再監聽了change:name屬性事件,但change事件(改變name的值)在觸發時,老是會先觸發屬性事件,而後再觸發change事件。若是改變的不是name的值而是其餘的值,這裏只會觸發change事件,而不會觸發change:name屬性事件。前端

2. Backbond.Controller(新版本是Router)
Backbone提供了前端的url#fragment的路由支持,而且能夠把他們綁定到Action和Event中去。
注意:在使用前端路由的功能以前,必定要調用一次Backbone.history.start()。
var controller = Backbone.Controller.extend({
     routes: {
         "": "home",
         "!/comments": "comments",
         "!/mentions": "mentions",
         "!/:uid": "profile",
         "!/:uid/following": "following",
         "!/:uid/followers": "followers",
         "!/:uid/status/:id": "status",
         "!/search/users/:query": "user_search",
         "!/search/:query": "search"
     },
  initialize: function(){...}  ,
     home: function(){...} ,
     comments: function() {...} ,
     mentions: function() {...} ,
     profile: function(a) {...} ,
     status: function(a, b) {...} ,
     following: function(a) {...} ,
     followers: function(a) {...} ,
     user_search: function(a) {...} ,
     search: function(a) {...}
}); java

var custom = new controller();  jquery

Backbone.history.start();  ajax

這時當頁面URL HASH發生變化時,就會執行所綁定的方法。數據庫

Backbone.Controller.extend({}) 的用法。{}裏面要有一個routes的哈希表,提供了路由和方法名的鍵值對。
因此咱們看到http://shuo.douban.com/#!/comments對應着下面的comments方法。這個頁面對應着「最新回覆」模塊。
咱們還能夠看到#fragment裏面有!這個符號,這個是給搜索引擎識別用的,咱們下次在談。另外,還有:uid, :query, :id這些符號,這是動態的參數,uid、query、id都是這些參數的參數名,所以
http://shuo.douban.com/#!/search/users/豆瓣
就對應着"!/search/users/:query": "user_search",這個路由,繼而能夠用user_search()來處理。
順便提一句,當url匹配後,會觸發一個和Action名字有關的事件,好比"!/comments": "comments",若是訪問了http://shuo.douban.com/#!/comments,就會觸發"route:comments"的事件.json

Backbone默認會經過Hash的方式來記錄地址的變化,對於不支持onhashchange的低版本瀏覽器,會經過setInterval心跳監聽Hash的變化,所以你沒必要擔憂瀏覽器的兼容性問題。
若是你的項目並不複雜,但你卻深深喜歡它的某個特性(多是數據模型、視圖管理或路由器),那麼你能夠將這部分源碼從Backbone中抽取出來,由於在Backbone中,各模塊間的依賴並非很強,你能輕易的獲取並使用其中的某一個模塊。瀏覽器

3. Backbone.View
View並不操做html或者css,全部的操做留給了各類各樣的JS模板庫。View有兩個做用:1.監聽事件.2.展現數據.

var view = Backbone.View.extend({
     model: User, //這個View的模型
     className: "components cross",
     template: $("#user-info-template").html(),
     initialize: function() {    //new view({})就會調用這個初始化方法
    _.bindAll(this, "render");
    this.model.bind("change", this.render)    //模型User綁定change事件
     },
     render: function() {
    var a = this.model;
    $(this.el).html(Mustache.to_html(this.template, a.toJSON()));    //使用了Mustache模板庫,來解析模板,把模型User中的數據,轉換成json,顯示在模板中
           $(this.el).find(".days").html(function() {   //再進行細微的改變
               var b = a.get("created_at");     //取到模型User中的created_at的值
               return b;
    });
    return this ;
  }
});
在initialize中,一旦User類(模型)觸發了change事件就會執行render方法,繼而顯示新的視圖。
render方法中老是有個約定俗稱的寫法的。this.el是一個DOM對象,render的目的就是把內容填到this.el中。this.el會根據view提供的tagName, className, id屬性建立,若是一個都沒有,就會建立一個空的DIV。
更新完this.el後,咱們還應該return this;這樣才能繼續執行下面的鏈式調用(若是有的話)。
咱們也能夠用$(view.el).remove()或者view.remove()很方便的清空DOM。
View層有一個委託事件的機制。
var view = Backbone.View.extend({
     className: "likers-manager",
  template: $("#likers-components-template").html(),   //模板HTML
  events: {
         "click .btn-more": "loadMore"    
     },
  initialize: function() {    //new view({}),就會調用
           _.bindAll(this, "render", "updateTitle", "loadOne", "loadAll", "loadMore");   //調用underscore的bingAll方法
     },
  render: function() { ... } ,
  updateTitle: function() { ... } ,
     loadOne: function(a) { ... } ,
  loadAll: function() { ... } ,
     loadMore: function(a) { ... }
});
在這裏面有個events的鍵值對,格式爲{"event selector": "callback"},其中click爲事件,.btn-more是基於this.el爲根的選擇器,這樣一旦約定好,當用戶點擊.btn-more的元素時,就會執行loadMore方法

4. Backbone.Model

Model 用來建立數據,校驗數據,存儲數據到服務器端。Models還能夠綁定事件。好比用戶動做變化觸發 model 的 change 事件,全部展現此model 數據的 views 都會接收到 這個 change 事件,進行重繪。
最簡單的定義以下:
var Game = Backbone.Model.extend({});

稍微複雜一點
var Game = Backbone.Model.extend({
  initialize: function(){
    
     },
  defaults: {
             name: 'Default title',
             releaseDate: 2011,
  }
});

initialize 至關於構造方法,初始化時調用(new時調用)
簡單實用:
var portal = new Game({ name: "Portal 2", releaseDate: 2011});

var release = portal.get('releaseDate');

portal.set({ name: "Portal 2 by Valve"});

此時數據還都在內存中,須要執行save方法纔會提交到服務器。
portal.save();

5. Backbone.Collection(集合)
實際上,至關於Model的集合。

須要注意的是,定義Collection的時候,必定要指定Model。 下面讓咱們爲這個集合添加一個方法,以下:
var GamesCollection = Backbone.Collection.extend({
   model : Game,
   old : function() {
    return this.filter(function(game) {
         return game.get('releaseDate') < 2009;
     });
 }

});

集合的使用方法以下:
var games = new GamesCollection
games.get(0);

固然,也能夠動態構成集合,以下:
 var GamesCollection = Backbone.Collection.extend({
   model : Game,
   url: '/games'

});

var games = new GamesCollection
games.fetch();

這邊的url告訴collection到哪去獲取數據,fetch方法會發出一個異步請求到服務器,從而獲取數據構成集合。(fetch實際上就是調用jquery的ajax方法)

模板解析是Underscore中提供的一個方法。且Underscore是Backbone必須依賴的庫。
模板解析方法能容許咱們在HTML結構中混合嵌入JS代碼,就像在JSP頁面中嵌入JAVA代碼同樣:
 <ul>
  <% for(var i = 0; i < len; i++) { %>
  <li><%=data[i].title%></li>
  <% } %>
</ul>
經過模板解析,咱們不須要在動態生成HTML結構時,使用拼接字符串的方法,更重要的是,咱們能夠將視圖中的HTML結構獨立管理(例如:不一樣的狀態可能會顯示不一樣的HTML結構,咱們能夠定義多個單獨的模板文件,按需加載和渲染便可)。

在Backbone中,你可使用on或off方法綁定和移除自定義事件。在任何地方,你均可以使用trigger方法觸發這些綁定的事件,全部綁定過該事件的方法都會被執行,如:
var model = new Backbone.Model();
model.on('custom', function(p1, p2) {

 });
 model.on('custom', function(p1, p2) {

});
model.trigger('custom', 'value1', 'value2');   //將調用以上綁定的兩個方法
model.off('custom');
 model.trigger('custom');

// 觸發custom事件,但不會執行任何函數,已經事件中的函數已經在上一步被移除 

若是你熟悉jQuery,你會發現它們與jQuery中的bind、unbind和trigger方法很是相似。
在單頁應用中,咱們經過JavaScript來控制界面的切換和展示,並經過AJAX從服務器獲取數據。

可能產生的問題是,當用戶但願返回到上一步操做時,他可能會習慣性地使用瀏覽器「返回」和「前進」按鈕,而結果倒是整個頁面都被切換了,由於用戶並不知道他正處於同一個頁面中。
對於這個問題,咱們經常經過Hash(錨點)的方式來記錄用戶的當前位置,並經過onhashchange事件來監聽用戶的「前進」和「返回」動做,但咱們發現一些低版本的瀏覽器(例如IE6)並不支持onhashchange事件,只有可使用setInterval。

Underscore還提供了一些很是實用的函數方法,如:函數節流、模板解析等。Underscore是Backbone必須依賴的庫,由於在Backbone中許多實現都是基於Underscore。
相信你對jQuery必定不會陌生,它是一個跨瀏覽器的DOM和AJAX框架。
而對於Zepto你能夠理解爲「移動版的jQuery」,由於它更小、更快、更適合在移動終端設備的瀏覽器上運行,它與jQuery語法相同,所以你能像使用jQuery那樣使用它。

服務器提供的數據接口須要兼容Backbone的規則,對於一個新的項目來講,咱們能夠嘗試使用這套規則來構建接口。但若是你的項目中已經有一套穩定的接口,你可能會擔憂接口改造的風險。
不要緊,咱們能夠經過重載Backbone.sync方法來適配現有的數據接口,針對不一樣的客戶端環境,咱們還能夠實現不一樣的數據交互方式。例如:用戶經過PC瀏覽器使用服務時,數據會實時同步到服務器;而用戶經過移動終端使 用服務時,考慮到網絡環境問題,咱們能夠先將數據同步到本地數據庫,在合適的時候再同步到服務器。而這些只須要你重載一個方法就能夠實現。

Model是Backbone中全部數據模型的基類,用於封裝原始數據,並提供對數據進行操做的方法,咱們通常經過繼承的方式來擴展和使用它。

 Backbone中的Model就像是映射出來的一個數據對象,它能夠對應到數據庫中的某一條記錄,並經過操做對象,將數據自動同步到服務器數據庫。(Collection就像映射出的一個數據集合,它能夠對應到數據庫中的某一張或多張關聯表)。

整個Backbone的源碼用一個自調用匿名函數包裹,避免污染全局命名空間。
(function() {
   Backbone.Events // 自定義事件
   Backbone.Model // 模型構造函數和原型擴展
  Backbone.Collection // 集合構造函數和原型擴展
  Backbone.Router // 路由配置器構造函數和原型擴展
  Backbone.History // 路由器構造函數和原型擴展
   Backbone.View // 視圖構造函數和原型擴展
  Backbone.sync // 異步請求工具方法
  var extend = function (protoProps, classProps) { ... } // 自擴展函數
   Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = extend; // 自擴展方法
}).call(this);
Backbone 會自動判斷瀏覽器對 pushState 的支持,以作內部的選擇。 不支持 pushState 的瀏覽器將會繼續使用基於錨點的 URL 片斷。

Events是Backbone中全部其它模塊的基類,不管是Model、Collection、View仍是Router和History,都繼承了Events中的方法( unbind,bind,on,off,trigger,stopListening )。
咱們沒法直接實例化一個Events對象。
你須要注意監聽函數的調用順序,all事件總會在其它事件中的監聽函數都執行完畢以後觸發,同一個事件中若是綁定了多個監聽函數,那它們將按照函數綁定時的順序依次調用。

實際上咱們通常並不會重載模塊類的constructor方法,由於在Backbone中全部的模塊類都提供了一個initialize方法,用於避免在子類中重載模塊類的構造函數,當模塊類的構造函數執行完成後會自動調用initialize方法。模型的方法:

  • get()方法用於直接返回數據
  • escape()方法先將數據中包含的HTML字符轉換爲實體形式(例如它會將雙引號轉換爲&quot;形式)再返回,用於避免XSS攻擊。
  • previous()方法接收一個屬性名,並返回該屬性在修改以前的狀態;
  • previousAttributes()方法返回一個對象,該對象包含上一個狀態的全部數據。

須要注意的是,previous()和previousAttributes()方法只能在數據修改過程當中調用(即在模型的change事件和屬性事件中調用)。

在調用模型的unset()和clear()方法清除模型數據時,會觸發change事件,咱們也一樣能夠在change事件的監聽函數中經過previous()和previousAttributes()方法獲取數據的上一個狀態。

Backbone中每個模型對象都有一個惟一標識,默認名稱爲id,

id應該由服務器端建立並保存在數據庫中,在與服務器的每一次交互中,模型會自動在URL後面加上id,而對於客戶端新建的模型,在保存時不會在URL後加上id標識,舉個例子:

// 定義Book模型類
var Book = Backbone.Model.extend({
  urlRoot : '/service'
});

// 建立實例
var javabook = new Book({
  id : 1001,
  name : 'Thinking in Java',
  author : 'Bruce Eckel',
  price : 395.70
});

// 保存數據
javabook.save();

你能夠抓包查看請求記錄,你能看到請求的接口地址爲:http://localhost/service/1001

  其中localhost是個人主機名。

  service是該模型的接口地址,是咱們在定義Book類時設置的urlRoot。

  1001是模型的惟一標識(id),咱們以前說過,模型的id應該是由服務器返回的,對應到數據庫中的某一條記錄,但這裏爲了能直觀的測試,咱們假設已經從服務器端拿到了數據,且它的id爲1001。

若是同時設置了urlRoot和url參數,url參數的優先級會高於urlRoot。
(另外一個細節是,url參數不必定是固定的字符串,也能夠是一個函數,最終使用的接口地址是這個函數的返回值。)
javabook.save(null, {
  url: '/myservice'
});

在這個例子中,咱們在調用save()方法的時候傳遞了一個配置對象,它包含 一個url配置項,最終抓包看到的請求地址是http://localhost/myservice。所以你能夠得知,經過調用方法時傳遞的url參數優 先級會高於模型定義時配置的url和urlRoot參數。

模型的parse()方法默認不會對數據進行解析,所以咱們只須要重載該方法,就能夠適配上面的數據格式了

// 定義Book模型類
var Book = Backbone.Model.extend({
    urlRoot : '/service',
    // 重載parse方法解析服務器返回的數據
    parse : function(resp, xhr) {
        var data = resp.data[0];
        return {
            id : data.bookId,
            name : data.bookName,
            author : data.bookAuthor,
            price : data.bookPrice
        }
    }
});

另外值得注意的一點是:咱們經常會在數據保存成功後,對界面作一些改變。此時你能夠經過許多種方式實現,例如經過save()方法中的success回調函數。

但我建議success回調函數中只要作一些與業務邏輯和數據無關的、單純的界面展示便可(就像控制加載動畫的顯示隱藏),若是數據保存成功以後涉及到業務邏輯或數據顯示,你應該經過監聽模型的change事件,並在監聽函數中實現它們。雖然Backbone並無這樣的要求和約束,但這樣更有利於組織你的代碼。

在Backbone中,全部與服務器交互的邏輯都定義在 Backbone.sync方法中,該方法接收method、model和options三個參數。若是你想從新定義它,能夠經過method參數獲得須要進行的操做(枚舉值爲create、read、update和delete),經過model參數獲得須要同步的數據,最後根據它們來適配你本身定義的 規則便可。

固然,你也能夠將數據同步到本地數據庫中,而不是服務器接口,這在開發終端應用時會很是適用。

 

 

 

加油!

相關文章
相關標籤/搜索