backbone库学习-Collection

时间:2021-06-21 15:29:42

backbone库的结构:

http://www.cnblogs.com/nuysoft/archive/2012/03/19/2404274.html

本文所有例子来自于http://blog.csdn.net/eagle_110119/article/details/8842007

1.1  collection结构

var Collection = Backbone.Collection = function(models, options){}
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false};
_.extend(Collection.prototype, Events,{})
var methods = [];
_.each(methods, function(method){})
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
_.each(attributeMethods, function(method){})

先看第一个例子1.1.1

// 定义模型类
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版)'
}); // 创建集合对象
var books = new BookList([book1, book2, book3]);

先看第一部分1.1.1-1

// 定义集合类
var BookList = Backbone.Collection.extend({
model : Book
});

找到Backbone.Collection.extend()方法

Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
var Collection = Backbone.Collection = function(models, options) {}

extend方法

var extend = function(protoProps, staticProps) {//第一个参数传入子类原型上,第二个参数传入子类构造器中
var parent = this;
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {//检查protoProps是否拥有constructor属性(不考虑原型上)
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };//借用构造器,this指向model构造器,让子类实例化时,可以获取父类构造器的成员
} // Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);//将父类和staticProps上的属性成员统统传给child的构造器上
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;//临时构造器的方式完成继承,Surrogate属于中间件,子类实例修改不会影响父类原型,可以让子类实例获取父类原型上的成员 // Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);//将自定义信息绑定到子类原型上 // Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype; //_super_属性方便子类直接访问父类原型 return child; //返回子类构造器
};

跟Backbone.model.extend一样,这里就不过多解释了。例子中的{model:Book}将被绑定到Collection的原型上,实例化Collection后,即可调用。

继续看例子1.1.1-2

// 创建集合对象
var books = new BookList([book1]);

我们不用之前的例子,我先来个简单的。

看一下构造器

var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();//原型上的_reset方法,第一次初始化,以后就是重置
this.initialize.apply(this, arguments);//空的init方法,用的时候,可以自己修改
if (models) this.reset(models, _.extend({silent: true}, options));
};

实例化时,有三个方法,分别是_reset,initialize和reset三个方法。先看_reset方法

// 重置
_reset: function() {
this.length = 0;
this.models = [];
this._byId = {};
}

算是重置,也算是初始化。

initialize方法

initialize: function(){}

用的时候,需要我们自己去定义。

最后看一下reset方法

reset: function(models, options) {
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
options.previousModels = this.models;
this._reset();//重置
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return this;
},

reset这个方法关联到两个方法_removeReference和add方法。

先看下_removeReference方法

_removeReference: function(model) {
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
}

如果this.models存在model实例,这个方法主要是实现清除工作,删除掉model的collection属性(后面会提),去掉绑定的事件。

再看一下add方法

add: function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
}

这里给出addOptions的初始值:

var addOptions = {add: true, remove: false};

进入set方法

set: function(models, options) {
options = _.defaults({}, options, setOptions);//如果options的某个参数名对应的参数值为空,则将setOptions对应参数名的参数值赋给它
if (options.parse) models = this.parse(models, options);//没重写之前,只返回models
if (!_.isArray(models)) models = models ? [models] : [];//转成数组
var i, l, model, attrs, existing, sort;
var at = options.at;
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
var toAdd = [], toRemove = [], modelMap = {};
var add = options.add, merge = options.merge, remove = options.remove;//默认情况 true false false
var order = !sortable && add && remove ? [] : false;
// Turn bare objects into model references, and prevent invalid models
// from being added.
for (i = 0, l = models.length; i < l; i++) {
if (!(model = this._prepareModel(attrs = models[i], options))) continue;//主要是将Model实例绑定上collection属性
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(model)) { //对象队列,在初始化没有任何信息
if (remove) modelMap[existing.cid] = true;
if (merge) {
attrs = attrs === model ? model.attributes : options._attrs;
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
} // This is a new model, push it to the `toAdd` list.
} else if (add) {
toAdd.push(model);//将model实例推进数组中,保存 // Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);//绑定all事件,这样是执行两遍all事件
this._byId[model.cid] = model;//用cid与对应的mode实例关联
if (model.id != null) this._byId[model.id] = model;//有id的话,再用id与对应的model实例关联
}
if (order) order.push(existing || model);//初始化时order为false
delete options._attrs;
}
// Remove nonexistent models if appropriate.
if (remove) {//初始化时remove为false
for (i = 0, l = this.length; i < l; ++i) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
}
if (toRemove.length) this.remove(toRemove, options);
} // See if sorting is needed, update `length` and splice in new models.
if (toAdd.length || (order && order.length)) {
if (sortable) sort = true;
this.length += toAdd.length;//length保存model实例的个数
if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));
} else {
if (order) this.models.length = 0;
push.apply(this.models, order || toAdd);//实例上创建models属性,存放toAdd
}
} // Silently sort the collection if appropriate.
if (sort) this.sort({silent: true});//默认sort为false if (options.silent) return this;
// Trigger `add` events.
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
} // Trigger `sort` if the collection was sorted.
if (sort || (order && order.length)) this.trigger('sort', this, options);
return this;
}

重要的部分在for循环这块,详细看一下for部分

for (i = 0, l = models.length; i < l; i++) {
if (!(model = this._prepareModel(attrs = models[i], options))) continue;//主要是将Model实例绑定上collection属性
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(model)) { //对象队列,在初始化没有任何信息
if (remove) modelMap[existing.cid] = true;
if (merge) {
attrs = attrs === model ? model.attributes : options._attrs;
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
} // This is a new model, push it to the `toAdd` list.
} else if (add) {
toAdd.push(model);//将model实例推进数组中,保存 // Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);//绑定all事件,这样是执行两遍all事件
this._byId[model.cid] = model;//用cid与对应的mode实例关联
if (model.id != null) this._byId[model.id] = model;//有id的话,再用id与对应的model实例关联
}
if (order) order.push(existing || model);//初始化时order为false
delete options._attrs;
}

先看一下this._prepareModel方法

_prepareModel: function(attrs, options) {
//这里判断attrs,如果是Model创建的,那直接在attrs上加上collection即可
if (attrs instanceof Model) {//instanceof 能在实例的原型对象链中找到该构造函数的prototype属性所指向的原型对象,attrs实际上通过Model构造器new出来的
if (!attrs.collection) attrs.collection = this;//让实例的collection保存Collection的n实例化对象
return attrs;
}
//如果不是,那系统会自动实例化一次,并为Model实例绑上collection属性
options || (options = {});
options.collection = this;
//在options中也加入这个属性,这里为什么要在options里面加入该属性,其实是因为this的问题,此时this是Collection的实例,
//一旦new过Model后,Model里的this就是model了,这里设置options.collection主要是让其传入Model中,实例化的时候,便于绑定,这样model实例也拥有collection属性
var model = new this.model(attrs, options);//调用model的构造器,实例化对象 根据具体信息创建model实例
if (!model.validationError) return model;
this.trigger('invalid', this, attrs, options);//触发invalid事件
return false;//返回不是Model实例
}

if后面的continue,表示代码始终往下走,实例化对Model对象,为每个实例化对象添加collection属性。

再看一下get方法

get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
}

因为this._byId在_reset方法中初始化中是空对象,默认get取出undefined,将加工好的每个model实例放入toAdd数组中,为所有model绑定all事件,触发函数是this._onModeEvent(),最后用cid与每个对应的model实例关联。

ok,回到reset方法

if (!options.silent) this.trigger('reset', this, options);

因为_.extend({silent: true}, options),这个reset事件没有触发。返回this,以上完成collection的实例化。

1.2 往collection添加model

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

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

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

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

1.2.1  add

先看下例子1.2.1-1:

var Book = Backbone.Model.extend({
defaults : {
name : '',
price : 0
}
}); // 创建集合对象
var books = new Backbone.Collection(null, {
model : Book
}); books.add({
name : '构建高性能Web站点',
price : 56.30
});

注意这个collection实例化的方式,它传入的参数是null和{model:Book},和之前的定义比较一下

var books = new Backbone.Collection(null, {
model : Book
}); var BookList = Backbone.Collection.extend({
model : Book
});

collection需要有一个与之关联的model,这两种方式都可以将model与collection关联,这里有疑问为什么一定要呢?原因在这(_prepareModel方法中)

var model = new this.model(attrs, options);//调用model的构造器,实例化对象

如果不将model与collection相互关联这个this.model将没有值,上述的两种方法都可以是现实this.model指向Model构造器。

看一下add方法

add: function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
}

之前将实例化collection的时候描述过了将add的信息传入set方法中。我们看一下最后一部分的set代码:

if (sort) this.sort({silent: true});//默认sort为false

            if (options.silent) return this;
// Trigger `add` events.
for (i = 0, l = toAdd.length; i < l; i++) {//处理toAdd数组的model实例
(model = toAdd[i]).trigger('add', model, this, options);//触发add事件
}
// Trigger `sort` if the collection was sorted.
if (sort || (order && order.length)) this.trigger('sort', this, options);
return this;

添加会触发add的监听事件。这个事件需要你自己定义,事件名为add。库会在这个时候从toAdd中依次取model实例来触发add事件。

add执行添加部分的代码

 if (order) this.models.length = 0;
push.apply(this.models, order || toAdd);//实例上创建models属性,存放toAdd。原型add所走的

1.2.2  push

例子1.2.2-1:(跟1.2.1-1差不多)

books.push({
name : '深入分析Java Web技术内幕',
price : 51.80
});

看一下原型上的push方法

push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: this.length}, options));
return model;
}

push方法调用了add和_prepareModel方法,这里我们大概总结下,_prepareModel的作用

_prepareModel:主要将你传入的对象类似{name:xxx,price:xxxx}的形式,制作成Model的实例,这里我们需要强调一点就是,你可以将这些对象先传入Model的构造器生成model实例,再传入collection里来,也可以将这些对象直接传入collection中,collection会检测这写对象是否为Model的实例,如果不是会调用Model构造器,根据这个信息生成Model的实例。最后为每一个model添加collection属性

1.2.3  unshift

先上例子

books.unshift({
name : '编写高质量代码:Web前端开发修炼之道',
price : 36.80
});

看一下unshift方法

unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));//注意这个at的值,这个值会影响插入的顺序
return model;//返回插入的model实例
}

at值的影响在set方法中的体现在这:

if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));//使用splice方法完成插入,因为apply的参数需要一个数组,at值将决定在哪插入
} else {
if (order) this.models.length = 0;
push.apply(this.models, order || toAdd);//实例上创建models属性,存放toAdd
}

可以清楚的看到当我们使用unshift时,传入的at为0,也就是在数组的第一个位置插入,刚才的push则是传递的this.length,这个this.length哪里定义的,就在set方法中

this.length += toAdd.length;//length保存model实例的个数

这样push操作,就是在数组的最后一位插入。而add底层就是数组的原生push,也是在最后添加。

1.3  从collection中删除model

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

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

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

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

1.3.1  remove

先看下例子1.3.1-1

// 定义模型类
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方法。

remove: function(models, options) {
//console.log(models);
//console.log(options);
models = _.isArray(models) ? models.slice() : [models];//将models这个类数组集合转成数组
options || (options = {});
var i, l, index, model;
for (i = 0, l = models.length; i < l; i++) {
model = this.get(models[i]);//根据cid取到该对象
if (!model) continue;
delete this._byId[model.id];//删除id关联的信息
delete this._byId[model.cid];//删除cid关联的信息
index = this.indexOf(model);//返回要删除Model所在的位置
this.models.splice(index, 1);//获取该位置删除该Model实例
this.length--;//长度减一
if (!options.silent) {//如果设置了silent属性,将不执行remove回调。
options.index = index;
model.trigger('remove', model, this, options);
}
this._removeReference(model);//删除该Model实例拥有的collection属性
}
return this;
}

看一下_removeReference()方法

_removeReference: function(model) {
if (this === model.collection) delete model.collection;//删除掉对collection属性,即对
model.off('all', this._onModelEvent, this);//去除all监听事件
}

1.3.2  pop

将例子修改下1.3.2-1

books.pop();

看一下pop

pop: function(options) {
var model = this.at(this.length - 1);//调用原型上的at方法,返回最后一个成员
console.log(model);
this.remove(model, options);//调用remove方法,删除最后一个
return model;
}

再来看下at方法

at: function(index) {
return this.models[index];//类数组拥有这个属性
}

pop实际上还是调用的remove方法,通过at方法找到最后一个成员将其删除

1.3.3   shift

将例子修改一下1.3.3-1

books.shift();

看一下shift方法

shift: function(options) {
var model = this.at(0);//取到第一个成员
this.remove(model, options);//执行remove方法
return model;
}

跟pop很类似。

1.4   在集合中查找模型

Collection定义了一系列用于快速从集合中查找我们想要的模型的方法,包括:

get():根据模型的唯一标识(id)查找模型对象

getByCid():根据模型的cid查找模型对象

at():查找集合中指定位置的模型对象

where():根据数据对集合的模型进行筛选

1.4.1  get

先看个例子1.4.1-1

// 定义模型类
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);

我们一个个来,先看get

var book1 = books.get(1001); 

下面是get代码:

get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
}

这段代码,我们之前有见过。在我们创建Model实例的时候,给每个Model实例配上一个id,我们可以根据这个id来查找。这个this._byId设定值的过程,是在set方法中完成的,如果有记不清的可以翻看之前的set方法部分。

1.4.2   getByCid

例子:

var book2 = books.getByCid('c2');  

关于cid的生成,我们在Model部分已经说过了。大家查看underscore的uniqueId方法,这里我的源码中没有getByCid的方法,这段我们先滤过。

1.4.3   at

例子

var book3 = books.at(1);  

at方法之前也已经说过,它传入的参数是数组的索引。还是看一下源码吧

at: function(index) {
return this.models[index];//类数组拥有这个属性
}

1.4.4   where

例子:

var book4 = books.where({
price : 51.80
});

我们来看一下where方法

where: function(attrs, first) {
if (_.isEmpty(attrs)) return first ? void 0 : []; //检测attrs是否为空
return this[first ? 'find' : 'filter'](function(model) {//这里调用的filter是underscore里的方法
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
}

这里调用的是this.filter方法,但是原型上并没有很明显的命名出来,那它是在声明的呢?看代码:

var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty', 'chain']; // Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);//调用underscore相应的方法执行
};
});

看一下underscore的each方法

var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) { //是否支持原生forEach方法
obj.forEach(iterator, context);//采用原生的forEach方法
} else if (obj.length === +obj.length) {//如果obj是数组或者是类数组
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;//让数组中的成员作为参数传入过滤器中运行
}
} else {
var keys = _.keys(obj);//返回值集合,考虑对象的情况
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;//让对象中的成员作为参数传入过滤器中运行
}
}
}

我们简单理解下,_.each帮助了Collection.prototype绑定一系列方法,当Collection的实例需要调用这些方法时,注意看_.each的返回值是_[method].apply(_,args),表示调用的实际是underscore.js中对应的方法。注意之前还有一段代码

args.unshift(this.models);

将上下文也传给underscore,这样运行不会出错。

这样,分析where方法实则变得非常简单,核心也就是那个筛选器了。我们看一下

function(model) {//这里调用的filter是underscore里的方法
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
}

通过get方法,去匹配model实例集中相应属性的值,相同则返回true,在underscore中,filter方法会将其记录,存放入数组中,返回给你。

1.5  自动排序

在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);

注意实例化Collection时,传入的参数

// 创建集合对象
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;
}
}
});

在我们分析comparator之前,我们回头看一下Collection的构造器

var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();//原型上的_reset方法,第一次初始化,以后就是重置
this.initialize.apply(this, arguments);//空的init方法,用的时候,可以自己修改
if (models) this.reset(models, _.extend({silent: true}, options));//models没有值,使用options的model
};

如果我们将comparator:function(){}传入构造器中,这里的options.comparator将不等于undefined。将实例的comparator属性指向该方法。

在set方法中,找到相关内容:

var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;

例子中用了如add,push,unshift等方法。之前我们分析发现像push和unshift方法使用时,都传递一个at值,前者为数组长度减一,后者为0.其实这里的代码存在问题,导致排序不成功。为了实现效果,我们先修改一下。

var sortable = this.comparator  && options.sort !== false;
var sortAttr = _.isFunction(this.comparator) ? this.comparator : null;

只要设置了comparator,就不需要管是通过如何方式加入model的sortable就一直会是true。

if (sortable) sort = true;//将sort置为true

继续

if (sort) this.sort({silent: true});//默认sort为false

我们看一下sort方法

sort: function(options) {
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
options || (options = {}); // Run sort based on type of `comparator`.
if (_.isString(this.comparator) || this.comparator.length === 1) {//判断是否是函数
this.models = this.sortBy(this.comparator, this);
} else {
this.models.sort(_.bind(this.comparator, this));//调用数组原生的sort进行排序
}
if (!options.silent) this.trigger('sort', this, options);//系统设置了silent,这里不触发sort事件
return this;
}

底层看来是调用了数组的sort方法进行排序,如果设置了silent将不会触发sort事件。细心的朋友会看到_.bind这个方法。我们来简单看一下:

_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));//是否支持原生bind
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));//判断调用者是否是function,如果不是调用传入的上下文context执行func
ctor.prototype = func.prototype;//如果是的话,缺少上下文。
var self = new ctor;//将self的原型指向func的原型
ctor.prototype = null;//清空ctor构造器的原型
var result = func.apply(self, args.concat(slice.call(arguments)));//调用func,此时func的上下文是self,那self的空间又可以扩展到func的原型。
if (Object(result) === result) return result;//只有当是字符串是才返回匹配结果,这里也存在问题。
return self;
};
};

这个_.bind提供了三种方式

1.第一种:使用原生bind,不清楚的朋友可以查询一下API

2.第二种:传入的信息拥有上下文的,采用func.apply(context)调用

3.第三种:如果没有上下文,我们需要创建上下文。文中给出了一个很不错的创建上下文的方式,这里源代码的判断写的并不是非常好,最后的判断有问题,应该修改一下:

return result

直接返回result,不用判断了。这是本人的一点想法,有问题或者不同意见的,大家一起讨论。

1.6   从服务器获取集合数据

backbone库的Collection提供几种方式与服务器交互

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

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

1.6.1  fetch

例子1.6.1-1

// 定义模型类
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);
}
});

看一下fetch方法:

fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;//将options.parse设置为true
var success = options.success;//获取自定义的success方法,正确回调
var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options);
if (success) success(collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
wrapError(this, options);//绑定错误回调
return this.sync('read', this, options);//调用sync方法,参数名read
}

这里可以看出,系统在内部,将我们自定义的success封装在系统定义的success方法中,因为系统还要在成功回调之后,触发一个叫sync的监听事件。

进入sync方法看一下:

sync: function() {
return Backbone.sync.apply(this, arguments);//调用Backbone.sync,上下文为this
}

好吧,到了Backbone.sync,这个方法之前model中分析过了。通过第三方ajax发送请求,这个方法主要是组装了请求内容。

1.6.2   create

看下例子1.6.2-1

var books = new BookList();
// 创建一个模型
books.create({
name : 'Thinking in Java',
price : 395.70
}, {
success : function(model, resp) {
// 添加成功后, 在控制台输出集合中的模型列表
console.dir(books.models);
}
});

看一下create方法:

save: function(key, val, options) {
var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.extend({validate: true}, options);//这个代码会导致bug
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;//验证通过了
} // Set temporary attributes if `{wait: true}`.
if (attrs && options.wait) {
this.attributes = _.extend({}, attributes, attrs);
} // After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;//你可以在save方法的时候写成功回调
options.success = function(resp) {//返回成功的回调
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);//将options绑定一个error方法
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');//判断id,如果没有则新建,如果有再判断options.patch,改为更新
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options); // Restore attributes.
if (attrs && options.wait) this.attributes = attributes; return xhr;
}

save方法的大致工作流程是这样的,检测是否model实例需要验证,如果有验证,进行验证。封装成功回调和错误回调穿件xhr对象,调用原型的sync方法调用第三方插件发送请求。这里我们分析下源码中关于验证的一点问题。之前model的验证,我们稍作修改达到要求,但是这个直接就报错了

backbone库学习-Collection

这个例子中我们确实没有为Model添加validate,那系统到底怎么会在没有validate的情况下,尝试去执行validate呢?

问题就在save方法里:

options = _.extend({validate: true}, options);//这个代码会导致bug

这里会莫名其妙的给options加上validate等于true,然后在_validate方法中

if(!this.validate && !options.validate) return true//没有验证,直接通过//这里有修改过

这段代码,会直接通过。可能之前修改Model的时候,源代码是||,这里我们将validate:true这段代码注释掉。则代码不报错了。但是又有一个问题。如果例子1.6.2-1修改为

例子1.6.2-2

var books = new BookList();
// 创建一个模型
books.create({
name : 'Thinking in Java',
price : 395.70
}, {
success : function(model, resp) {
// 添加成功后, 在控制台输出集合中的模型列表
console.dir(books.models);
},
validate: function(data){
if(data.price > 0){
return 'hello world';
}
}
});

代码不会报错,但是终端不会显示hello world,细细一想,其实我们没有自定义那个invalid事件,但是问题来,如此定义,我们无法直接获取到我们想要监听的那个model实例对象,所以你也就无从绑定起。这也是backbone需要优化的地方。实际运作中,大家根据需求自行添加吧,这里方法很多,就不细说了。

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

Backbone中集合提供了数据同步和创建的方法与服务器进行交互,但实际上这可能并不能满足我们的需求,backbone学习的作者给出了一些自定义的批量方法。

1.7.1  createAll

先看例子:

// 定义模型类
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();

看一下,自定义方法绑定在collection原型上,实例可以调用。自定义方法基本都调用Backbone.sync方法,这是封装params的方法。如果客户端数据发生改变时,向服务器发出同步请求(更新,删除,修改)。以此达到同步目的。另外就是,自定义绑定到collection实例上,实例包含了很多个model实例,也就达到了批量的作用。

内容不多,时间刚好,以上是我的一点读码体会,如有错误,请指出,大家共通学习。