初探Backbone

前言

終於到週末了,幾經轉折老夫工做終於落實,幹起了移動前端,因而真正進入了HTML5的開發時代。javascript

這週一開始入職,進來後發現真的像農村的來到了城市,不少東西不懂的,又要落實租房的問題,生活工做擰到了一坨。css

因爲上次去河南折騰了一番,經濟也在亮紅燈,因而第一週有點渾渾噩噩的感受,還沒作什麼就結束了。html

新的團隊的人感受都有點生猛,老同事徹底沒壓力,另外一個新同事適應的很快,竟然都能進入框架開發了,難道這邊真這麼多高手?小的這邊亞歷山大啊。前端

如今項目使用的是jquery+requireJS+backbone,前兩天咱們初略的學習了下requireJS,要說好熟悉純粹是扯淡,只不過有個大概印象罷了。java

咱們今天的任務是學習一番backbone,等熟悉後明天就把這幾個傢伙串起來試試,由於下週二就有任務了,不努力就等嗝屁吧。jquery

Backbone簡介

中文API:http://www.csser.com/tools/backbone/backbone.js.htmlweb

英文API:http://backbonejs.org/ajax

Backbone是構建javascript應用程序的一個優秀的類庫。他簡潔、輕量級、功能實在。json

backbone採用MVC模式,自己提供了模型、控制器和視圖從而咱們應用程序的骨架便造成。數組

backbone依賴於underscore,他是一個類庫,提供了60多個函數處理數組操做、函數綁定,以及javascript模板機制。

因而咱們來個圖看看幾個好基友吧:

有了初步印象後,咱們來一步步看看他都幹了些神馬。

模型

模型是保存應用程序數據的地方。咱們能夠把模型看作對應用程序原始數據的精心抽象,而且添加了一些工具函數和事件。

咱們可使用Backbone.Model的extend方法來建立Backbone模型:

var User = Backbone.Model.extend({
    initialize: function () { }
});

extend的第一個參數是一個對象,他成爲了模型實例的屬性;

第二個參數是可選的類屬性的哈希,經過屢次調用extend能夠生成模型的子類,他們將繼承父親全部類和實例屬性:

var User = Backbone.Model.extend({
    //實例屬性
    instanceProperty: 'foo'
}, {
    //類屬性
    classProperty: 'bar'
});
assertEqual(User.instanceProperty, 'foo');
assertEqual(User.classProperty, 'bar');

當模型實例化時,他的initialize方法能夠接受任意實例參數,其工做原理是backbone模型自己就是構造函數,因此可使用new生成實例:

var User = Backbone.Model.extend({
    initialize: function (name) {
        this.set({name: name});
    }
});
var user = new User('刀狂劍癡');
assertEqual(user.get('name'), '刀狂劍癡');

Ps: assertEqual判斷相等

模型和屬性

使用set和get方法設置獲取實例的屬性:

var user = new User();
user.set({ name: '葉小釵' });
user.get('name'); //葉小釵
user.attributes;//{name: '葉小釵'}

咱們看到其實user.arributes是一個對象字面量,咱們不會直接操做他,由於咱們使用get/set方法能夠進行咱們的驗證流程。

咱們使用validate方法來校驗一個實例屬性,默認狀況沒有任何驗證,如果咱們須要驗證的話:

var User = Backbone.Model.extend({
    validate: function (attr) {
        if (!attr.name || attr.name.length < 3) {
            return '名稱長度太短';
        }
    }
});

若是屬性合法,validate不會理睬之,不合法能夠返回錯誤字符串或者Error對象,校驗失敗get/set方法就會觸發error事件:

var user = new User();
user.bind('error', function (model, error) {
    //錯誤處理
});
user.set({ name: '0' });
//給特定集合添加一個錯誤處理程序
user.set({ name: '0' }, { error: function (model, error) { } });

使用hash名爲default的對象來指定默認屬性,在建立一個實例模型時,任何沒有指定值的屬性都會被設置爲默認值:

var Chat = Backbone.Model.extend({
    defaults: { name: '葉小釵'}
});
assertEqual((new Chat).get('name'), '葉小釵');

集合

在backbone中,模型實例的數據存放在多個集合中,爲何模型之間要使用獨立的集合,其緣由有點複雜,但在實際中咱們常常這麼作(雖然我還沒作過)。

例如建立Twitter須要兩個集合followers和followees,二者都有User的實例填充數據,雖然兩個集合都是來自同一模型,可是各自包含了不一樣的實例數組,因此獨立造成集合。

針對模型,能夠經過擴展backbone.collection來建立一個集合:

var Users = Backbone.Collection.extend({
    model: User
});

在上面的例子中,咱們覆蓋了model屬性來指定與集合相關聯的模型(這裏是User模型),雖然這個步驟不是必須的,可是爲該集合設置一個默認的模型指向每每能派上大用場。

一般集合會包含單個模型的一個實例,而不是不一樣模型的多個實例。

在建立一個集合時,能夠傳遞一個模型數組,好比backbone模型,若是定義了一個初始化實例函數,在初始化時就會調用之:

var users = new Users([{ name: '葉小釵' }, { name: '素還真'}]);

另外一種方法是使用add方法爲集合添加模型:

users.add({ name: '葉小釵' });
users.add([{ name: '葉小釵' }, { name: '素還真' }]);

在爲集合添加模型時會觸發add事件:

users.bind('add', function (user) {
    //...
});
//移除一個模型
users.bind('remove', function (user) {
    //...
});
//根據模型id獲取模型
var user = users.get('moduleId');
//集合中模型被修改後出發change事件
var user = new User({ name: '葉小釵' });
var users = new Backbone.Collection();
users.bind('change', function (rec) {
//改變一個記錄
});
users.add(user);
user.set({ name; '素還真'});

控制集合內部順序

一個集合內部元素順序能夠經過comparator方法控制,該方法的返回值即是你但願集合內部排序的規則:

var Users = Backbone.Collection.extend({
    comparator: function (user) {
        return user.get('name');
    }
});

返回值能夠是值或者數字,具體例子咱們下次有機會來試試看。

視圖

backbone的視圖並非模板,而是一些控制類,他們處理模型的表現。

在不少MVC中視圖通常指html或者模板,他們在控制器中處理事件和渲染,但backbone中視圖:

視圖表明一個UI邏輯塊,負責一個簡單的DOM內容
var UserView = Backbone.View.extend({
    initialize: function () { },
    render: function () { }
});

無論視圖有沒有被插入頁面,每一個視圖都知道當前的Dom元素,即this.el,el是從視圖的tagName、className或者id等屬性中建立的元素,沒有這些值el就是空div:

var UserView = Backbone.View.extend({
    tagName: 'span',
    className: 'users'
});
var userView = new UserView();//<span class="users"></span>

如果但願視圖綁定到頁面上已存在的元素上,直接指定el就好(必須在頁面加載後才能指定哦,否則找不到):

var UserView = Backbone.View.extend({
    el: $('.usets')
});
//也能夠實例化一個視圖時傳遞el(tagName、className\id)
new UserView({ id: 'id' });

渲染視圖

每一個視圖都有一個render方法,默認狀況下沒有任何操做,一旦視圖須要重繪便會調render方法,不一樣的視圖用不一樣功能的函數來覆蓋函數,以處理模板渲染,並使用新的html來更新el:

var TodoView = Backbone.View.extend({
    template: _.template($('#tmpt').html()),
    render: function () {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    }
});

backbone自己並不知道咱們是怎麼渲染視圖的,咱們能夠本身生產元素也可使用模板類庫(通常用這個)。

在前面的代碼中,咱們使用了this.model的本地屬性,他指向一個模型實例,在實例化時傳遞到視圖中,模型的toJSON方法實際上返回模型未加工時的原始屬性,能夠在模板中使用:

new TodoView({ model: new Todo });

委託事件

經過委託,backbone的視圖提供了一種添加事件到el的簡單快捷的方法:

var TodoView = Backbone.View.extend({
    events: {
        'change input[type=checkbox]': 'toggleDone',
        'click .destroy': 'clear'
    },
    toggoleDone: function () { },
    clear; function () {}
});

events對象爲{'eventType selector': 'callback'}這種格式,selector是可選的,不提供便綁定值el上。

委託利用了事件冒泡機制,意思是能夠一直觸發而無論el內容是否改變(估計相似於delegate吧)。

上面的callback事件觸發時,他的上下文是視圖當前上下午,因此this中的this.model/el均可以使用。

綁定和上下文

事實上,每當視圖的模型發生變化時,就會觸發change事件,而後調用該函數,這就意味這應用程序的視圖及HTML和與之對應的模型數據是同步的。

var TodoView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render', 'close');
        this.model.bind('change', this.render);
    },
    close: function () { }
});

須要注意在回調函數中的上下文已經改變,Underscore提供一個函數:

_.bindAll(context, func);

他將函數名字和一個上下文綁定,bindAll保證了全部給定的函數老是在指定的上下文中被調用,由於函數上下文總在胡亂變化,這樣作頗有用。

模型銷燬須要視圖綁定delete事件,觸發時刪除el便可:

var TodoView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render', 'close');
        this.model.bind('change', this.render);
        this.model.bind('delete', this.remove);
    },
    remove: function () { $(this.el).remove() }
});

控制器

backbone的控制器將應用程序的狀態和url的hash片斷關聯在一塊兒,使url地址可分享,也能夠做爲書籤使用。

本質上,控制器是一些路由和函數組成,當導航到這些路由時那些函數便調用:

routes : {
    'help': 'help', //#help
    'search/:query': 'search', //#search/kiwis
    'search/:query/p:page': 'search', //#search/kiwis/p7
    'file/*peth': 'file' //#file/any/path.txt
}

參數以「:」開始,而後是參數名,當路由被調用時,全部參數都會傳遞到他的函數,*爲通配符,和參數一將和匹配的值一塊兒傳遞到路由的函數中。

路由是以哈希對象中定義順序倒敘進行解析的,來建立一個控制器吧:

var PageController = Backbone.Controller.extend({
    routes: {
        'help': 'help', //#help
        'search/:query': 'search', //#search/kiwis
        'search/:query/p:page': 'search', //#search/kiwis/p7
        'file/*peth': 'file' //#file/any/path.txt
    },
    index: function () { },
    help: function () { },
    search: function () { }
});

當用戶被導航到「http://....com#search/param」時,無論手輸仍是後退都會調用search函數,並傳遞其參數param

如果但願ajax與seo更加友好,路由前綴就必須是「!/」,同時服務器端還須要作一點調整。

服務器同步

默認狀況下,模型發生變化(保存),backbone就會使用ajax與服務器通信(Backbone.sync),成功便更新客戶端模型。

要使用這個屬性便須要定義url,而且在服務器端處理rest形式請求,backbone會處理餘下任務:

var User = Backbone.Model.extend({
    url: '/users'
});

url既能夠是一個字符串也能夠是一個函數返回字符串,backbone增刪查改幾個函數對應映射:

create => post /collection
read => get /collection[/id]
update => put /collection/id
delete => delete /collection/id

例如,咱們要建立一個User實例,backbone會發送一個post請求道/uesrs,更新一個user實例,會發送至/users/id節點,服務器響應時會返回一個json格式的對象

如果要使用save(attr, option)函數將模型保存至服務器,能夠隨意傳遞一個由屬性和請求項組成的hash對象,如果模型有id,假設該模型在服務器上以存在,存在就是put請求,否則就post請求添加數據:

var user = new User();
user.set({ name: '葉小釵' });
user.save(null, {
    success: function () {
        //保存成功
    }
});

全部save都是異步的,經過success或者failure來監聽ajax回調,咱們通常與jquery合用了。

填充集合

咱們已經知道了如何建立/更新模型,可是第一次咱們如何獲取模型數據呢?

這就是backbone集合出現的緣由,他們用來請求遠程數據並保存至本地,和模型相似,必須給集合指定一個url來設置其數據源,如果沒有則默認使用與之關聯的模型url:

var Followers = Backbone.Collection.extend({
    model: User,
    url: '/user'
});
//fetch用於刷新模型,該函數會請求數據,如果遠程數據和當前模型不一致,將觸發change事件
Followers.fetch();

集合的fetch函數將發送一個get請求道服務器,獲取遠程數據,最後刷新集合觸發refresh事件。

可使用refresh函數手動刷新集合,傳入一個模型對象便可,在第一次設置頁面時候用這個方法很方便,這裏和頁面加載後發送get請求不同,咱們能夠傳遞json對象給refresh,而預先填充到集合中,好比:

Users.refresh({name; ''});

自定義行爲

在backbone視圖讀取或者保存模型到服務器時都會調用backbone.sync方法,咱們能夠覆蓋該方法來改變其默認行爲(存入xml、本地存儲):

/*
method 即是crud方法(create、read、update、delete)
model 須要保存的模型
options 請求可選項,包括成功失敗函數
*/
Backbone.sync = function (method, model, options) {
    options.success(model);
};

每一個模型或者集合只能覆蓋各自的sync函數。

咱們來看一個擴展HTML5本地存儲的例子:

//全部待完成的項都保存至本地存儲命名空間「todos」中
Toto.prototype.localStorage = new Store('todos');
//重寫backbone.sync
Backbone.sync = function (method, model, options) {
    var resp, store = model.localStorage || model.collection.localStorage;
    switch (method) {
        case 'read': resp = model.id ? store.find(model) : store.findAll(); break;
        case 'create': resp = store.create(model); break;
        case 'update': resp = store.update(model); break;
        case 'delete': resp = store.destroy(model); break;
    }
    if (resp) {
        options.success(resp);
    } else {
        options.error('not find');
    }
}

簡單實戰-官方版

首先,咱們來看一個官方給出的例子:

具體代碼各位能夠去下載,其中用到了不少類庫咱們也暫時不去管它,咱們如今就來試試能不能作。

這是一個簡單的to-do列表應用程序,咱們但願能進行增刪查改操做,在頁面刷新後仍能保持數據。

頁面結構(核心)

 1 <html lang="en">
 2 <head>
 3     <meta charset="utf-8">
 4     <title>Backbone.js Todos</title>
 5     <link rel="stylesheet" href="todos.css" />
 6 </head>
 7 <body>
 8     <div id="todoapp">
 9         <header>
10             <h1>
11                 Todos</h1>
12             <input id="new-todo" type="text" placeholder="What needs to be done?">
13         </header>
14         <section id="main">
15             <input id="toggle-all" type="checkbox">
16             <label for="toggle-all">
17                 Mark all as complete</label>
18             <ul id="todo-list">
19             </ul>
20         </section>
21     </div>
22     <script src="js/json2.js" type="text/javascript"></script>
23     <script src="js/jquery.js" type="text/javascript"></script>
24     <script src="js/underscore.js" type="text/javascript"></script>
25     <script src="js/backbone.js" type="text/javascript"></script>
26     <script src="js/backbone.localStorage.js" type="text/javascript"></script>
27     <script src="js/todos.js" type="text/javascript"></script>
28 </body>
29 </html>

頁面結構很簡單,其核心爲:

一個文本框(#new-todo),用於建立新to-do

一個列表(#todo-list),用於展現

下面是基本的Todo模型,其擁有content與done屬性,同時提供toggle方法設置done屬性:

//todo是備忘錄的意思哦
var Toto = Backbone.Model.extend({
    defaults: {
        done: false
    },
    toggle: function () {
        //設置作了點擊就沒作,設置沒作點擊就作了...
        this.save({ done: !this.get('done') });
    }
});

而後咱們定義一個todo的集合,也是保存todo模型的地方:

var TodoList = Backbone.Collection.extend({
    model: Todo,
    localStorage: new Store('todos'), //全部項目保存至todos命名空間
    //過濾已完成的項目
    done: function () {
        return this.filter(function (todo) {
            return todo.get('done');
        });
    },
    remaining: function () {
        return this.without.applay(this, this.done);
    }
});
var Todos = new TodoList();

由於這裏用的是本地存儲因此引入了backbone.localstorage,下一步咱們作用於顯示的視圖

 1 var TodoView = Backbone.View.extend({
 2     tagName: 'li', //視圖時一個個li標籤
 3     template: $('#item-template').template(), //獲取模板
 4     events: {
 5         'change .check': 'toggleDone',
 6         'dblclick .todo-content': 'edit',
 7         'click .todo-destroy': 'destroy',
 8         'keypress .todo-input': 'updateOnEnter',
 9         'blur .todo-input': 'close'
10     },
11     initialize: function () {
12         //確保在正確做用域
13         _.bindAll(this, 'render', 'close', 'remove');
14         //監聽存儲模板修改以肯定是否修改el
15         this.model.bind('change', this.render);
16         this.model.bind('destroy', this.render);
17     },
18     render: function () {
19         //使用存儲模板更新el
20         var el = jQuery.tmpl(this.template, this.model.toJSON());
21         $(this.el).html(el);
22         return this;
23     },
24     tiggleDone: function () {
25         this.model.toggle();
26     },
27     edit: function () {
28         $(this.el).addClass('editing');
29         this.input.focus();
30     },
31     close: function () {//關閉編劇狀態
32         this.model.save({ content: this.input.val() });
33         $(this.el).removeClass('editing');
34     },
35     //按下回車鍵結束編輯
36     updateOnEnter: function (e) {
37         if (e.keyCode == 13)
38             e.target.blur();
39     },
40     remove: function () {
41         $(this.el).remove();
42     },
43     destroy: function () {
44         this.model.destroy();
45     }
46 });

咱們將血多事件委託給管理更新,完成和刪除todo視圖,例如每當複選框變化toggleDone就會調用,並切換done屬性,最後又會觸發change事件,致使視圖從新渲染。

來看看咱們的模板:

<script type="text/template" id="item-template">
<div class="view">
    <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
    <label><%- title %></label>
    <a class="destroy"></a>
</div>
<input class="edit" type="text" value="<%- title %>" />
</script>
<script type="text/template" id="stats-template">
<% if (done) { %>
    <a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
<% } %>
<div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
</script>

PS:我這裏寫了大概思想,有不一致的地方各位看看就行。

最後咱們來一個終止:

 1 var AppView = Backbone.View.extend({
 2     el: #('#todoapp'),//綁定至現有骨架
 3     events: {
 4         'keypress #new-todo': 'createOnEnter',
 5         'click .todo-clear a': 'clearCompleted'
 6     },
 7     /*
 8     初始化時,將相關事件綁定給todos集合,當添加或者修改集合中元素時觸發事件,
 9     經過載入可能存在本地存儲中的記錄來初始化數據
10     */
11     initailize: function () {
12         _.bindAll(this, 'addOne', 'addAll', 'render');
13         this.input = this.$('#new-todo');
14         Todos.bind('add', this.addOne);
15         Todos.bind('refresh', this.addAll);
16         Todos.fetch();
17     },
18     addOne: function (todo) {
19         var view = new TodoView({model: todo});
20         this.$('#todo-list').append(view.render().el);
21     },
22     //......
23 });
24 
25 var App = new AppView();

當頁面首次加載後,Todos集合將填充數據,而後觸發refresh事件,將調研addAll來獲取todo模型,生成todoview視圖,並將它們添加至#todo-list中。

當有新的todo模型添加至Todos時,會觸發add事件,調用addOne作到最後的視圖更新。

簡單總結

至此跌跌撞撞咱們將官網的例子看了,不知道各位買帳不???

反正我有點不買帳,由於我感受本身仍是有點迷糊,因此咱們下面再本身寫一個例子試試吧。

簡單實戰-demo

稍後繼續。。。。。。

 

結語

好了,咱們第一階段的學習基本結束,其中還差一個實戰的例子我稍後補上,因而咱們就應該對backbone有初步的認識了

明天咱們的任務是結合requireJS於backbone作一點點小練習,因而咱們後面就能說咱們精通requireJS於backbone了。。。。。。

補充

剛剛在這裏看到了幾張不錯的圖,給貼出來吧

[原創]Backbone源碼分析-Backbone架構+流程圖

Backbone-_thumb2

Backbone-事件

Backbone-模型

Backbone-集合

Backbone架構-路由

Backbone-視圖

相關文章
相關標籤/搜索