若是將一個Model對象比喻成數據庫中的一條記錄,那麼Collection就是一張數據表。它表示爲一個模型集合類,用於存儲和管理一系列相同類型的模型對象。前端
一、建立集合
集合用於組織和管理多個模型,但它並非必須的,若是你的某個模型對象是惟一的(單例),那麼你不必將它放到集合中。數據庫
咱們來看一個建立集合的例子:數組
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '' } }); // 定義集合類 var BookList = Backbone.Collection.extend({ model: Book }); // 建立一系列模型對象 var book1 = new Book({ name: 'Effective Java中文版(第2版)' }); var book2 = new Book({ name: 'JAVA核心技術卷II:高級特性(原書第8版)' }); var book3 = new Book({ name: '精通Hibernate:Java對象持久化技術詳解(第2版)' });
在這個例子中,咱們定義了模型類Book和集合類BookList,而後建立了3個模型對象,並將它們放到一個集合對象中。(你能夠在控制檯輸出books.models屬性,用來查看集合中包含的模型對象列表)服務器
咱們爲了建立3個Book模型對象,對Book類顯式實例化了3次,其實Model自己已經提供了更簡單的方法來複制一個模型,例如:異步
var book1 = new Book({ name: 'Effective Java中文版(第2版)' }); var book2 = book1.clone(); book2.set('name', 'JAVA核心技術卷II:高級特性(原書第8版)'); var book3 = book1.clone(); book3.set('name', '精通Hibernate:Java對象持久化技術詳解(第2版)');
在這段代碼中,咱們使用模型的clone()方法來複制一個和當前對象相同(包括數據)的新對象,這能夠簡化咱們建立模型的流程。
在實例化集合對象時,除了能夠向構造函數中添加已經建立好的模型列表(就像上面的例子那樣),咱們還能夠直接傳遞模型數據,集合會自動將這些數據轉換爲模型對象,例如:函數
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '' } }); // 定義集合類 var BookList = Backbone.Collection.extend({ model: Book }); var models = [{ name: 'Effective Java中文版(第2版)' }, { name: 'JAVA核心技術卷II:高級特性(原書第8版)' }, { name: '精通Hibernate:Java對象持久化技術詳解(第2版)' }]; // 建立集合對象 var books = new BookList(models);
運行這個例子,並在控制檯輸出books.models屬性,你能夠看到集合中存儲的是Book模型類的實例,而並不是咱們在models數組中聲明的原始數據。性能
這是由於咱們在聲明BookList集合類的時候,就已經設置了model屬性,該屬性指向集合中存儲的模型對象的構造函數,當咱們傳遞原始數據時,集合會自動建立model中定義的模型類,並將原始數據傳遞給它。
(若是你在定義集合類的時候沒有設置model,那麼集合會默認將原始數據轉換爲Backbone.Model類的實例)fetch
咱們之因此要使用extend來繼承Backbone.Collection類,是由於咱們但願定義一個本身的集合類,並向其中擴展更多的自定義方法。若是你的集合類僅僅是用於簡單地存儲和管理模型對象,且Backbone.Collection類所提供的方法已經能夠知足你的要求,那麼你能夠直接實例化一個Backbone.Collection,同時也能夠像上面同樣實現原始數據和模型對象的自動轉換,例如:this
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '' } }); var models = [{ name: 'Effective Java中文版(第2版)' }, { name: 'JAVA核心技術卷II:高級特性(原書第8版)' }, { name: '精通Hibernate:Java對象持久化技術詳解(第2版)' }]; // 建立集合對象 var books = new Backbone.Collection(models, { model: Book });
在本例中,咱們沒有經過extend定義本身的集合類,而是直接實例化Collection類。咱們依然傳入了原始數據,但同時咱們在構造函數的第2個參數(配置對象)中設置了model屬性,Collection經過它就能知道要將原始數據轉換爲哪一個模型類的實例。url
二、向集合中添加模型
集合提供了3個方法容許咱們動態地向集合中動態插入模型:
add():向集合中的指定位置插入模型,若是沒有指定位置,默認追加到集合尾部
push():將模型追加到集合尾部(與add方法的實現相同)
unshift():將模型插入到集合頭部
這些方法很容易理解,但咱們仍是經過一個例子來講明:
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '', price: 0 } }); // 建立集合對象 var books = new Backbone.Collection(null, { model: Book }); books.add({ name: '構建高性能Web站點', price: 56.30 }); books.push({ name: '深刻分析Java Web技術內幕', price: 51.80 }); books.unshift({ name: '編寫高質量代碼:Web前端開發修煉之道', price: 36.80 }); books.push({ name: '基於MVC的JavaScript Web富應用開發', price: 42.50 }, { at: 1 }); books.unshift({ name: 'RESTful Web Services Cookbook中文版', price: 44.30 }, { at: 2 }); // 在控制檯輸出集合中的模型列表 console.dir(books.models);
在例子中,咱們經過3個方法向集合中添加了多個模型,最後,咱們在控制檯輸出了集合中的模型列表。請仔細觀察和分析咱們調用的方法,以及最終列表中模型的排列順序:
a、這些方法的做用和上面介紹的同樣,用於將模型添加到集合中不一樣的位置,但當咱們設置了at配置以後,它們就變得徹底同樣了,由於它們會忽略自身的規則,將模型插入到at所指向的位置。
b、當數據被成功添加到集合中時,集合會觸發add事件,執行全部監聽add事件的方法。除非咱們在調用add()方法時設置了silent配置項,則會忽略事件的觸發。
三、操做集合中的模型
在Underscore中,提供了許多對對象和數組集合進行操做的方法,這些方法已經被Backbone添加到Collection類的原型中。這意味着你可使用Underscore中的集合方法來操做Collection集合中的數據,例如each()、map()、find()等方法。
但有一些方法咱們仍是要單獨介紹它們,由於Collection對這些方法進行了重寫,它們和Underscore中的同名方法不徹底相同。(這也是爲何我要在上面單獨介紹模型的添加方法)
刪除模型:
集合類提供了3個方法用於從集合中移除模型對象,分別是:
remove():從集合中移除一個或多個指定的模型對象
pop():移除集合尾部的一個模型對象
shift():移除集合頭部的一個模型對象
這些方法與添加的方法是對應的,並且當模型被移除成功後,會觸發集合對象的remove事件,除非你在移除時使用了silent配置。
這些方法很容易理解,但仍是讓咱們經過一個簡單的例子來講明:
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '', price: 0 } }); // 定義初始化數據 var data = [{ name: '構建高性能Web站點', price: 56.30 }, { name: '深刻分析Java Web技術內幕', price: 51.80 }, { name: '編寫高質量代碼:Web前端開發修煉之道', price: 36.80 }, { name: '基於MVC的JavaScript Web富應用開發', price: 42.50 }, { name: 'RESTful Web Services Cookbook中文版', price: 44.30 }] // 建立集合對象 var books = new Backbone.Collection(data, { model: Book }); books.remove(books.models[2]); books.pop(); books.shift(); // 在控制檯輸出集合中的模型列表 console.dir(books.models);
在本例中,咱們分別調用了remove()方法移除了集合中第2個模型,調用pop()方法移除了最後一個模型,調用shift()方法移除了第一個模型。最後咱們在控制檯輸出集合中剩下的模型列表,請查看控制檯輸出結果,它和你想象的結果是一致的。
在集合中查找模型:
Collection定義了一系列用於快速從集合中查找咱們想要的模型的方法,包括:
get():根據模型的惟一標識(id)查找模型對象
getByCid():根據模型的cid查找模型對象
at():查找集合中指定位置的模型對象
where():根據數據對集合的模型進行篩選
前面介紹數據模型時咱們提到,每一個模型對象都有一個惟一標識(id),它與數據庫中記錄的id保持同步。實際上,每一個模型對象內部還會自動建立一個cid,它用來標識每個模型(請注意將id和cid區分開,它們沒有任何關係)。
集合對象提供了兩個方法用於根據id和cid來查找模型對象,分別是get()方法和getByCid()方法,例如:
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '', price: 0 } }); // 定義初始化數據 var data = [{ id: 1001, name: '構建高性能Web站點', price: 56.30 }, { id: 1002, name: '深刻分析Java Web技術內幕', price: 51.80 }, { id: 1003, name: '編寫高質量代碼:Web前端開發修煉之道', price: 36.80 }, { id: 1004, name: '基於MVC的JavaScript Web富應用開發', price: 42.50 }, { id: 1005, name: 'RESTful Web Services Cookbook中文版', price: 44.30 }] // 建立集合對象 var books = new Backbone.Collection(data, { model: Book }); // 根據id和cid查找模型對象 var book1 = books.get(1001); var book2 = books.getByCid('c2'); // 在控制檯輸出模型 console.dir(book1); console.dir(book2);
本例中,咱們從集合中根據模型的id和cid查找出2個模型對象,但實際開發中咱們不會直接在代碼中寫出模型的id和cid。
id應該是從服務器接口進行同步獲取到的。
cid應該是在以前已經記錄下某個模型的cid,再根據它從集合中查找的。
at()方法根據咱們給定的索引,從集合中查找對應位置的模型,咱們在上面的例子中追加如下代碼:
// 根據索引查找模型對象
var book3 = books.at(1);
// 在控制檯輸出模型
console.dir(book3);
最後,咱們還能夠經過where()方法,實現相對複雜的查找規則,例如:
// 根據price從集合中查找模型
var book4 = books.where({
price: 51.80
});
// 在控制檯輸出模型
console.dir(book4);
請查看控制檯輸出的結果:where()方法用於給定一個或多個數據,查找並返回集合中匹配數據的模型。該方法返回一個數組,所以可以包含一個或多個結果。
當咱們調用get()、getByCid()和at()方法沒有找到到匹配對象時,會返回undefined,而where()方法在沒有找到匹配對象時會返回一個空數組。你可使用Underscore中的isEmpty()方法檢查返回值是否爲空,由於它能檢查到空數組和空對象。
四、自動排序
咱們經常使用數組的sort()方法對元素進行排序,Underscore也提供了sortBy()方法實現更爲複雜的集合排序。但在Backbone的集合對象中,爲咱們提供了集合元素的實時排序,當任何模型對象被插入到集合中時,都會按照預約的排序規則放到對應的位置。
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '', price: 0 } }); // 建立集合對象 var books = new Backbone.Collection(null, { model: Book, comparator: function(m1, m2) { var price1 = m1.get('price'); var price2 = m2.get('price'); if (price1 > price2) { return 1; } else { return 0; } } }); books.add({ name: '構建高性能Web站點', price: 56.30 }); books.push({ name: '深刻分析Java Web技術內幕', price: 51.80 }); books.unshift({ name: '編寫高質量代碼:Web前端開發修煉之道', price: 36.80 }); books.push({ name: '基於MVC的JavaScript Web富應用開發', price: 42.50 }, { at: 1 }); books.unshift({ name: 'RESTful Web Services Cookbook中文版', price: 44.30 }, { at: 2 }); // 在控制檯輸出集合中的模型列表 console.dir(books.models);
這個例子和咱們前面介紹添加方法時的例子相同,但集合中存儲的模型順序卻不同,由於咱們在建立集合對象時設置了comparator方法。咱們不須要手動調用該方法,由於它會在新模型被添加到集合中時自動被調用,並按照方法中定義的規則對集合中的數據進行從新排序。
comparator方法接收兩個參數,表示臨近的兩個模型對象,你須要經過返回值表示它們的排序規則,這和JavaScript中原生的sort()方法是同樣的。
當咱們設置了comparator方法後,全部關於元素位置的方法和參數都會失效,例如push()、unshift()方法和at參數等。
須要注意的是:comparator方法在不少時候都是很是有用的(例如顯示動態數據列表時),由於它能保證咱們獲取到的數據始終都是按規則排列的,但在集合中的數據量太多時,它可能會耗費不少的資源和事件來實時確保數據的排序規則。這時,你能夠手動調用集合對象的sort()方法在須要的進行手動排序。
8.五、從服務器獲取集合數據
Collection也提供了兩個與服務器進行交互的方法:
fetch():用於從服務器接口獲取集合的初始化數據,覆蓋或追加到集合列表中
create():在集合中建立一個新的模型,並將其同步到服務器
咱們先來看一個fetch()方法例子:
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '', price: 0 } }); // 定義集合類 var BookList = Backbone.Collection.extend({ model: Book, url: '/service' }); // 建立集合對象, 並從服務器同步初始化數據 var books = new BookList(); books.fetch({ success: function(collection, resp) { // 同步成功後在控制檯輸出集合中的模型列表 console.dir(collection.models); } });
咱們前面討論過模型的數據同步,須要與服務器同步數據,須要設置一個url或urlPath指定服務器接口地址。同步集合數據時也不例外,本例中咱們設置服務器接口地址爲/service,接口返回數據爲:
[{ "id": "1001", "name": "構建高性能Web站點", "price": "56.30" }, { "id": "1002", "name": "深刻分析Java Web技術內幕", "price": "51.80" }, { "id": "1003", "name": "編寫高質量代碼:Web前端開發修煉之道", "price": "36.80" }, { "id": "1004", "name": "基於MVC的JavaScript Web富應用開發", "price": "42.50" }, { "id": "1005", "name": "RESTful Web Services Cookbook中文版", "price": "44.30" }]
咱們在實例化集合對象以後,調用fetch()與服務器接口進行數據同步,並在同步成功後將集合中的數據列表輸出在控制檯。請運行例子中的代碼,你能看到控制檯中輸出的結果,它包含5個模型對象,正是咱們服務器接口返回的這些數據。
在調用fetch()方法同步集合數據時,默認會以覆蓋的方式進行,這意味着集合在同步以前的數據將丟失。咱們能夠在調用fetch()方法時傳遞add參數來通知集合進行添加,而不是覆蓋,例如:
var books = new BookList(); books.add({ id: 1000, name: 'Thinking in Java', price: 395.70 }); books.fetch({ add: true, success: function(collection, resp) { // 同步成功後在控制檯輸出集合中的模型列表 console.dir(collection.models); } });
咱們修改了例子中的代碼,在實例化集合對象後,咱們向集合中添加了一條數據,而後從服務器同步了5條數據(請注意在調用fetch()方法時咱們設置了add參數爲true)。在控制檯輸出的結果中,以前的數據並無被覆蓋掉。
數據在成功同步到集合中以後,會觸發reset事件,咱們能夠經過監聽該事件從而進行下一步操做(好比將集合中的數據顯示到頁面中)。
集合的數據同步與模型的數據同步有許多類似之處(例如你能夠重載parse()方法來對服務器返回的數據進行解析,使其能順利被添加到集合中),這裏就再也不重複討論。
集合提供的另外一個create()方法,是根據集合的model所指向的模型類,建立一個模型對象,並把該對象添加到集合中,最後將數據同步到服務器接口。
咱們經過例子來講明create()方法的使用:
var books = new BookList(); // 建立一個模型 books.create({ name: 'Thinking in Java', price: 395.70 }, { success: function(model, resp) { // 添加成功後, 在控制檯輸出集合中的模型列表 console.dir(books.models); } });
請將這段代碼替換到前面的例子中,並查看運行效果。
(經過抓包咱們能看到Request Method爲POST,若是建立的模型中包含id,則Request Method爲PUT,這與咱們以前講模型的save()方法是相同的。)
集合對象默認會先將模型添加到集合中,再提交到服務器接口,不管接口返回是否成功,新建的模型對象都會被添加到集合中。咱們能夠經過傳遞wait配置,來控制只有在服務器返回成功以後(響應狀態碼爲200),纔將模型對象添加到集合中。
在Backbone內部,create()方法是經過add()方法將新建立的模型添加到集合中的,所以咱們通常經過監聽add事件,來對新模型進行下一步操做。
六、將數據批量同步到服務器
上一節咱們討論過,Backbone中集合提供了數據同步和建立的方法與服務器進行交互,但實際上這可能並不能知足咱們的需求。例如:當咱們須要對數據進行批量地添加、修改和刪除操做時,就須要在Collection的基礎上擴展本身的方法。
在下面的例子中,我擴展了對集合中的模型數據批量同步的方法:
// 定義模型類 var Book = Backbone.Model.extend({ defaults: { name: '', price: 0 } }); // 定義BookList類 var BookList = Backbone.Collection.extend({ model: Book, url: '/service', // 將集合中全部的模型id鏈接爲一個字符串並返回 getIds: function() { return _(this.models).map(function(model) { return model.id; }).join(','); }, // 將集合中全部模型提交到服務器接口 createAll: function(options) { return Backbone.sync.call(this, 'create', this, options); }, // 修改集合中的全部模型數據 updateAll: function(options) { return Backbone.sync.call(this, 'update', this, options); }, // 刪除集合中全部的模型 deleteAll: function(options) { var result = Backbone.sync.call(this, 'delete', this, _.extend({ url: this.url + '/' + this.getIds() }, options)); this.remove(this.models); return result; } }); // 建立集合對象 var books = new BookList(); // 當集合觸發reset事件時, 對數據進行批量同步 books.on('reset', function() { books.createAll(); books.updateAll(); books.deleteAll(); }); // 從服務器接口同步默認數據 books.fetch();
來分析這個例子:
咱們定義了BookList集合類,並擴展了createAll()、updateAll()和deleteAll()方法(稍後咱們再討論這3些方法的做用)。
而後咱們實例化了一個BookList對象books,並監聽了reset事件,reset事件會在從服務器同步數據成功以後被觸發。
接着咱們調用fetch()方法從服務器接口獲取默認數據(默認數據跟前面例子中返回的數據一致),獲取成功後,reset事件的監聽函數將被執行。
咱們能夠在reset事件的監聽函數中作許多的事情(例如寫入業務邏輯),但這裏爲了更直觀地演示,我直接調用books的自定義方法進行批量同步。
首先調用的是createAll()方法,它將把當前集合中的全部數據同步到服務器接口,以Request Method爲POST方式告訴服務器接口建立並保存這些數據。
在createAll()方法中,咱們調用Backbone.sync()方法發送異步請求,請注意sync()方法的第2個參數,它是一個模型或集合對象,當操做爲create或update時,在sync()方法內部會調用該對象的toJSON()方法,並將toJSON()方法的返回值做爲Request Payload請求數據發送到服務器接口。
你能夠經過抓包並分析請求信息,來更好地理解它。
(toJSON()方法默認會返回模型或集合的數據對象,你能夠經過重載該方法來自定義須要發送的請求數據)
咱們假設集合中的數據已經被用戶批量修改過,因此咱們經過updateAll()方法將最新的數據提交到服務器接口。
updateAll()方法與createAll()方法幾乎相同,它們的區別在於updateAll()方法在修改數據時傳遞給sync()方法的操做名爲update而不是create,而發送給服務器的Request Method爲PUT而不是POST。
最後咱們經過deleteAll()方法,通知服務器刪除集合中的模型,並從本地集合對象中刪除這些數據。
deleteAll()方法與createAll()和updateAll()方法有些不一樣,由於deleteAll()方法發送給服務器的Request Method爲DELETE方式,這種方式下不能直接調用toJSON()方法將數據發送給接口,所以咱們須要手動組裝和發送數據。
在本例中,咱們定義了getIds()方法用於將集合中的全部模型的id鏈接起來,在deleteAll()方法中,咱們調用sync()方法發送了Request Method爲DELETE的請求,並在URL中將集合中的全部模型id傳遞給接口進行刪除。