若是将一个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传递给接口进行删除。