您现在的位置:首页 >> 前端 >> 内容

Backbone入门指南(五):Collection(数据模型集合)

时间:2017/1/24 10:31:00 点击:

  核心提示:8. Collection (数据模型集合)如果将一个Model对象比喻成数据库中的一条记录,那么Collection就是一张数据表。它表示为一个模型集合类,用于存储和管理一系列相同类型的模型对象。8...

8. Collection (数据模型集合)

如果将一个Model对象比喻成数据库中的一条记录,那么Collection就是一张数据表。它表示为一个模型集合类,用于存储和管理一系列相同类型的模型对象。

8.1 创建集合

集合用于组织和管理多个模型,但它并不是必须的,如果你的某个模型对象是唯一的(单例),那么你没必要将它放到集合中。

我们来看一个创建集合的例子:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:''

}

});

//定义集合类

varBookList=Backbone.Collection.extend({

model:Book

});

//创建一系列模型对象

varbook1=newBook({

name:'EffectiveJava中文版(第2版)'

});

varbook2=newBook({

name:'JAVA核心技术卷II:高级特性(原书第8版)'

});

varbook3=newBook({

name:'精通Hibernate:Java对象持久化技术详解(第2版)'

});

//创建集合对象

varbooks=newBookList([book1,book2,book3]);

在这个例子中,我们定义了模型类Book和集合类BookList,然后创建了3个模型对象,并将它们放到一个集合对象中。(你可以在控制台输出books.models属性,用来查看集合中包含的模型对象列表)

我们为了创建3个Book模型对象,对Book类显式实例化了3次,其实Model本身已经提供了更简单的方法来复制一个模型,例如:

varbook1=newBook({

name:'EffectiveJava中文版(第2版)'

});

varbook2=book1.clone();

book2.set('name','JAVA核心技术卷II:高级特性(原书第8版)');

varbook3=book1.clone();

book3.set('name','精通Hibernate:Java对象持久化技术详解(第2版)');

在段代码中,我们使用模型的clone()方法来复制一个和当前对象相同(包括数据)的新对象,这可以简化我们创建模型的流程。

在实例化集合对象时,除了可以向构造函数中添加已经创建好的模型列表(就像上面的例子那样),我们还可以直接传递模型数据,集合会自动将这些数据转换为模型对象,例如:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:''

}

});

//定义集合类

varBookList=Backbone.Collection.extend({

model:Book

});

varmodels=[{

name:'EffectiveJava中文版(第2版)'

},{

name:'JAVA核心技术卷II:高级特性(原书第8版)'

},{

name:'精通Hibernate:Java对象持久化技术详解(第2版)'

}];

//创建集合对象

varbooks=newBookList(models);

运行这个例子,并在控制台输出books.models属性,你可以看到集合中存储的是Book模型类的实例,而并非我们在models数组中声明的原始数据。

这是因为我们在声明BookList集合类的时候,就已经设置了model属性,该属性指向集合中存储的模型对象的构造函数,当我们传递原始数据时,集合会自动创建model中定义的模型类,并将原始数据传递给它。

(如果你在定义集合类的时候没有设置model,那么集合会默认将原始数据转换为Backbone.Model类的实例)

我们之所以要使用extend来继承Backbone.Collection类,是因为我们希望定义一个自己的集合类,并向其中扩展更多的自定义方法。如果你的集合类仅仅是用于简单地存储和管理模型对象,且Backbone.Collection类所提供的方法已经可以满足你的要求,那么你可以直接实例化一个Backbone.Collection,同时也可以像上面一样实现原始数据和模型对象的自动转换,例如:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:''

}

});

varmodels=[{

name:'EffectiveJava中文版(第2版)'

},{

name:'JAVA核心技术卷II:高级特性(原书第8版)'

},{

name:'精通Hibernate:Java对象持久化技术详解(第2版)'

}];

//创建集合对象

varbooks=newBackbone.Collection(models,{

model:Book

});

在本例中,我们没有通过extend定义自己的集合类,而是直接实例化Collection类。我们依然传入了原始数据,但同时我们在构造函数的第2个参数(配置对象)中设置了model属性,Collection通过它就能知道要将原始数据转换为哪个模型类的实例。

8.2 向集合中添加模型

集合提供了3个方法允许我们动态地向集合中动态插入模型:

add():向集合中的指定位置插入模型,如果没有指定位置,默认追加到集合尾部

push():将模型追加到集合尾部(与add方法的实现相同)

unshift():将模型插入到集合头部

这些方法很容易理解,但我们还是通过一个例子来说明:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:'',

price:0

}

});

//创建集合对象

varbooks=newBackbone.Collection(null,{

model:Book

});

books.add({

name:'构建高性能Web站点',

price:56.30

});

books.push({

name:'深入分析JavaWeb技术内幕',

price:51.80

});

books.unshift({

name:'编写高质量代码:Web前端开发修炼之道',

price:36.80

});

books.push({

name:'基于MVC的javascriptWeb富应用开发',

price:42.50

},{

at:1

});

books.unshift({

name:'RESTfulWebServicesCookbook中文版',

price:44.30

},{

at:2

});

//在控制台输出集合中的模型列表

console.dir(books.models);

在例子中,我们通过3个方法向集合中添加了多个模型,最后,我们在控制台输出了集合中的模型列表。请仔细观察和分析我们调用的方法,以及最终列表中模型的排列顺序:

这些方法的作用和上面介绍的一样,用于将模型添加到集合中不同的位置,但当我们设置了at配置之后,它们就变得完全一样了,因为它们会忽略自身的规则,将模型插入到at所指向的位置。

当数据被成功添加到集合中时,集合会触发add事件,执行所有监听add事件的方法。除非我们在调用add()方法时设置了silent配置项,则会忽略事件的触发。

8.3 操作集合中的模型

在Underscore中,提供了许多对对象和数组集合进行操作的方法,这些方法已经被Backbone添加到Collection类的原型中。这意味着你可以使用Underscore中的集合方法来操作Collection集合中的数据,例如each()、map()、find()等方法。

但有一些方法我们还是要单独介绍它们,因为Collection对这些方法进行了重写,它们和Underscore中的同名方法不完全相同。(这也是为什么我要在上面单独介绍模型的添加方法)

删除模型:

集合类提供了3个方法用于从集合中移除模型对象,分别是:

remove():从集合中移除一个或多个指定的模型对象

pop():移除集合尾部的一个模型对象

shift():移除集合头部的一个模型对象

这些方法与添加的方法是对应的,而且当模型被移除成功后,会触发集合对象的remove事件,除非你在移除时使用了silent配置。

这些方法很容易理解,但还是让我们通过一个简单的例子来说明:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:'',

price:0

}

});

//定义初始化数据

vardata=[{

name:'构建高性能Web站点',

price:56.30

},{

name:'深入分析JavaWeb技术内幕',

price:51.80

},{

name:'编写高质量代码:Web前端开发修炼之道',

price:36.80

},{

name:'基于MVC的javascriptWeb富应用开发',

price:42.50

},{

name:'RESTfulWebServicesCookbook中文版',

price:44.30

}]

//创建集合对象

varbooks=newBackbone.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()方法,例如:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:'',

price:0

}

});

//定义初始化数据

vardata=[{

id:1001,

name:'构建高性能Web站点',

price:56.30

},{

id:1002,

name:'深入分析JavaWeb技术内幕',

price:51.80

},{

id:1003,

name:'编写高质量代码:Web前端开发修炼之道',

price:36.80

},{

id:1004,

name:'基于MVC的javascriptWeb富应用开发',

price:42.50

},{

id:1005,

name:'RESTfulWebServicesCookbook中文版',

price:44.30

}]

//创建集合对象

varbooks=newBackbone.Collection(data,{

model:Book

});

//根据id和cid查找模型对象

varbook1=books.get(1001);

varbook2=books.getByCid('c2');

//在控制台输出模型

console.dir(book1);

console.dir(book2);

本例中,我们从集合中根据模型的id和cid查找出2个模型对象,但实际开发中我们不会直接在代码中写出模型的id和cid。

id应该是从服务器接口进行同步获取到的。

cid应该是在之前已经记录下某个模型的cid,再根据它从集合中查找的。

at()方法根据我们给定的索引,从集合中查找对应位置的模型,我们在上面的例子中追加以下代码:

//根据索引查找模型对象

varbook3=books.at(1);

//在控制台输出模型

console.dir(book3);

最后,我们还可以通过where()方法,实现相对复杂的查找规则,例如:

//根据price从集合中查找模型

varbook4=books.where({

price:51.80

});

//在控制台输出模型

console.dir(book4);

请查看控制台输出的结果:where()方法用于给定一个或多个数据,查找并返回集合中匹配数据的模型。该方法返回一个数组,因此能够包含一个或多个结果。

当我们调用get()、getByCid()和at()方法没有找到到匹配对象时,会返回undefined,而where()方法在没有找到匹配对象时会返回一个空数组。你可以使用Underscore中的isEmpty()方法检查返回值是否为空,因为它能检查到空数组和空对象。

8.4 自动排序

我们常常使用数组的sort()方法对元素进行排序,Underscore也提供了sortBy()方法实现更为复杂的集合排序。但在Backbone的集合对象中,为我们提供了集合元素的实时排序,当任何模型对象被插入到集合中时,都会按照预定的排序规则放到对应的位置。

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:'',

price:0

}

});

//创建集合对象

varbooks=newBackbone.Collection(null,{

model:Book,

comparator:function(m1,m2){

varprice1=m1.get('price');

varprice2=m2.get('price');

if(price1>price2){

return1;

}else{

return0;

}

}

});

books.add({

name:'构建高性能Web站点',

price:56.30

});

books.push({

name:'深入分析JavaWeb技术内幕',

price:51.80

});

books.unshift({

name:'编写高质量代码:Web前端开发修炼之道',

price:36.80

});

books.push({

name:'基于MVC的javascriptWeb富应用开发',

price:42.50

},{

at:1

});

books.unshift({

name:'RESTfulWebServicesCookbook中文版',

price:44.30

},{

at:2

});

//在控制台输出集合中的模型列表

console.dir(books.models);

这个例子和我们前面介绍添加方法时的例子相同,但集合中存储的模型顺序却不一样,因为我们在创建集合对象时设置了comparator方法。我们不需要手动调用该方法,因为它会在新模型被添加到集合中时自动被调用,并按照方法中定义的规则对集合中的数据进行重新排序。

comparator方法接收两个参数,表示临近的两个模型对象,你需要通过返回值表示它们的排序规则,这和javascript中原生的sort()方法是一样的。

当我们设置了comparator方法后,所有关于元素位置的方法和参数都会失效,例如push()、unshift()方法和at参数等。

需要注意的是:comparator方法在很多时候都是非常有用的(例如显示动态数据列表时),因为它能保证我们获取到的数据始终都是按规则排列的,但在集合中的数据量太多时,它可能会耗费很多的资源和事件来实时确保数据的排序规则。这时,你可以手动调用集合对象的sort()方法在需要的进行手动排序。

8.5 从服务器获取集合数据

Collection也提供了两个与服务器进行交互的方法:

fetch():用于从服务器接口获取集合的初始化数据,覆盖或追加到集合列表中

create():在集合中创建一个新的模型,并将其同步到服务器

我们先来看一个fetch()方法例子:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:'',

price:0

}

});

//定义集合类

varBookList=Backbone.Collection.extend({

model:Book,

url:'/service'

});

//创建集合对象,并从服务器同步初始化数据

varbooks=newBookList();

books.fetch({

success:function(collection,resp){

//同步成功后在控制台输出集合中的模型列表

console.dir(collection.models);

}

});

我们前面讨论过模型的数据同步,需要与服务器同步数据,需要设置一个url或urlPath指定服务器接口地址。同步集合数据时也不例外,本例中我们设置服务器接口地址为/service,接口返回数据为:

[{

"id":"1001",

"name":"构建高性能Web站点",

"price":"56.30"

},{

"id":"1002",

"name":"深入分析JavaWeb技术内幕",

"price":"51.80"

},{

"id":"1003",

"name":"编写高质量代码:Web前端开发修炼之道",

"price":"36.80"

},{

"id":"1004",

"name":"基于MVC的javascriptWeb富应用开发",

"price":"42.50"

},{

"id":"1005",

"name":"RESTfulWebServicesCookbook中文版",

"price":"44.30"

}]

我们在实例化集合对象之后,调用fetch()与服务器接口进行数据同步,并在同步成功后将集合中的数据列表输出在控制台。请运行例子中的代码,你能看到控制台中输出的结果,它包含5个模型对象,正是我们服务器接口返回的这些数据。

在调用fetch()方法同步集合数据时,默认会以覆盖的方式进行,这意味着集合在同步之前的数据将丢失。我们可以在调用fetch()方法时传递add参数来通知集合进行添加,而不是覆盖,例如:

varbooks=newBookList();

books.add({

id:1000,

name:'ThinkinginJava',

price:395.70

});

books.fetch({

add:true,

success:function(collection,resp){

//同步成功后在控制台输出集合中的模型列表

console.dir(collection.models);

}

});

我们修改了例子中的代码,在实例化集合对象后,我们向集合中添加了一条数据,然后从服务器同步了5条数据(请注意在调用fetch()方法时我们设置了add参数为true)。在控制台输出的结果中,之前的数据并没有被覆盖掉。

数据在成功同步到集合中之后,会触发reset事件,我们可以通过监听该事件从而进行下一步操作(比如将集合中的数据显示到页面中)。

集合的数据同步与模型的数据同步有许多相似之处(例如你可以重载parse()方法来对服务器返回的数据进行解析,使其能顺利被添加到集合中),这里就不再重复讨论。

集合提供的另一个create()方法,是根据集合的model所指向的模型类,创建一个模型对象,并把该对象添加到集合中,最后将数据同步到服务器接口。

我们通过例子来说明create()方法的使用:

varbooks=newBookList();

//创建一个模型

books.create({

name:'ThinkinginJava',

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事件,来对新模型进行下一步操作。

8.6 将数据批量同步到服务器

上一节我们讨论过,Backbone中集合提供了数据同步和创建的方法与服务器进行交互,但实际上这可能并不能满足我们的需求。例如:当我们需要对数据进行批量地添加、修改和删除操作时,就需要在Collection的基础上扩展自己的方法。

在下面的例子中,我扩展了对集合中的模型数据批量同步的方法:

//定义模型类

varBook=Backbone.Model.extend({

defaults:{

name:'',

price:0

}

});

//定义BookList类

varBookList=Backbone.Collection.extend({

model:Book,

url:'/service',

//将集合中所有的模型id连接为一个字符串并返回

getIds:function(){

return_(this.models).map(function(model){

returnmodel.id;

}).join(',');

},

//将集合中所有模型提交到服务器接口

createAll:function(options){

returnBackbone.sync.call(this,'create',this,options);

},

//修改集合中的所有模型数据

updateAll:function(options){

returnBackbone.sync.call(this,'update',this,options);

},

//删除集合中所有的模型

deleteAll:function(options){

varresult=Backbone.sync.call(this,'delete',this,_.extend({

url:this.url+'/'+this.getIds()

},options));

this.remove(this.models);

returnresult;

}

});

//创建集合对象

varbooks=newBookList();

//当集合触发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传递给接口进行删除。

Tags:BA AC CK KB 
作者:网络 来源:qfgg3的专栏