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的验证,我们稍作修改达到要求,但是这个直接就报错了
这个例子中我们确实没有为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实例,也就达到了批量的作用。
内容不多,时间刚好,以上是我的一点读码体会,如有错误,请指出,大家共通学习。