Backbone Collection 源码简谈
一切由一个例子引发:
var Man=Backbone.Model.extend({
initilize:function(){
this.bind('change:name',function(){
console.log('改名了');
});
},
validate:function(){
if(this.attributes.name=='jack'){
console.log('不同意');
}
}
});
var man=new Man();
man.set({'name':'jack','sex':'man','age':'12'},{validate:false}); var Mans=Backbone.Collection.extend({
model:man
});
var tom=new Man({name:'tom'});
var mary=new Man({name:'mary'});
var mans=new Mans();
mans.add(tom);
mans.add(mary);
mans.each(function(m){
console.log(m.get('name'));// tom mary
});
Collection的实例化声明很简单,初级使用只用了model属性,然后涉及到了 add方法和each遍历方法。
Collection 工厂
// Create a new **Collection**, perhaps to contain a specific type of `model`.
// If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
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();
this.initialize.apply(this, arguments);
if (models) this.reset(models, _.extend({silent: true}, options));
};
以上是collection的源码
collection的参数:
models: array 由model-object组成的数组。
options: object 配置型信息,包括以下几个属性:
model: function
comparator: string
silent: bool 是否禁止触发事件
sort: bool 是否进行排序
at:number 用于确定添加models时从何位置添加
add:
merge:
remove:
工厂函数执行步骤如下:
1.进行参数的调整和初始化分配
2.对collection-object进行重置,重置操作代码如下:
// Private method to reset all internal state. Called when the collection
// is first initialized or reset.
_reset: function() {
this.length = 0;
this.models = [];
this._byId = {};
},
3.执行collection-object的初始化操作,initialize函数默认为空函数
4.关键的最后一步,使用reset方法对collection-object附加属性
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any granular `add` or `remove` events. Fires `reset` when finished.
// Useful for bulk operations and optimizations.
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();
models = this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return models;
},
工厂函数分解完毕。下面我们就来看看关键的 reset方法都做了什么?
reset(models,options)
reset函数的两个参数均来自工厂函数
reset执行步骤如下:
1.首先还是参数的准备
2.遍历models并对models进行_removeReference处理
// Internal method to sever a model's ties to a collection.
_removeReference: function(model) {
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
通过查看源码,我们发现,处理后的model-object没有了collection属性,因为该属性保存了它原来隶属于的collection。 同时暂时关闭了model-object上的all方法。
3.再次调用_reset()对collection对象进行重置
4.调用add方法,其实add就是set方法,下面我们讲到add的时候就直接说set方法:
// Add a model, or list of models to the set.
add: function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
},
5.执行 reset 事件,如有则执行。
6.返回参数1
reset分解结束。
上一单元我们提到了reset中调用了add,而add的实质其实就是set方法,那么接下来就八一八set
set(models, options)
其参数来自于工厂函数。不同的是,add对options中的merge字段做了强制处理。导致set接收的options变了味儿,其中就附加了
var addOptions = {add: true, remove: false};
和{merge:false}
以下是set源码:
// Update a collection by `set`-ing a new list of models, adding new ones,
// removing models that are no longer present, and merging models that
// already exist in the collection, as necessary. Similar to **Model#set**,
// the core operation for updating the data contained by the collection.
set: function(models, options) {
options = _.defaults({}, options, setOptions);
if (options.parse) models = this.parse(models, options);
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : _.clone(models);
var i, l, id, model, attrs, existing, sort;
var at = options.at;
var targetModel = this.model;
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;
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++) {
attrs = models[i];
if (attrs instanceof Model) {
id = model = attrs;
} else {
id = attrs[targetModel.prototype.idAttribute];
} // If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(id)) {
if (remove) modelMap[existing.cid] = true;
if (merge) {
attrs = attrs === model ? model.attributes : attrs;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
}
models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
model = models[i] = this._prepareModel(attrs, options);
if (!model) continue;
toAdd.push(model); // Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
if (order) order.push(existing || model);
} // Remove nonexistent models if appropriate.
if (remove) {
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;
if (at != null) {
for (i = 0, l = toAdd.length; i < l; i++) {
this.models.splice(at + i, 0, toAdd[i]);
}
} else {
if (order) this.models.length = 0;
var orderedModels = order || toAdd;
for (i = 0, l = orderedModels.length; i < l; i++) {
this.models.push(orderedModels[i]);
}
}
} // Silently sort the collection if appropriate.
if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort events.
if (!options.silent) {
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
}
if (sort || (order && order.length)) this.trigger('sort', this, options);
} // Return the added (or merged) model (or models).
return singular ? models[0] : models;
},
1.老一套,参数准备,把models强制变成数组
2.局部变量的准备,为运行打好基础。
3.遍历models这个数组,根据不同情况取id,第一种id其实就是model-object.
4.在遍历的过程中判断这个model-object是否已经存在于collection-object中(即检测_byId对象,get也是用于检测_byId),如果存在:
options.remove是否为true,如果是则整理modelMap
options.merge 如果为true, 将已有的model-object和当前的model-object合并
将处理完的 existing model-object放入models数组的当前位置中
5.如果不存在且options.add为true:
将当前model存入toAdd数组
开启该model-object的all事件(在工厂函数的时候关过一次)
将该model-object存入_byId对象
6.如果order存在且是数组,则将existing||model push进去 循环到此结束
7.如果options.remove存在
遍历循环this,也就是collection-class={length:model-object个数,_byId:{cid:model-object},models:[model-object]}
还记得上面那个循环维护的modelMap吗?这时就要派上用场,modelMap里面记录的都是已经存在的model-object.cid:true,根据上述判断,对于不满足条件的model-object将放入toRemove数组
遍历完毕后检查toRemove数组,然后执行remove操作,remove操作详细看分割线下之下第一个函数
8.然后检测是否存在toAdd或者order数组:
如果不需要排序且指定了at 位置,则按照位置插入model-object
否则将this.models置空,将order或toAdd存入其中
9.如果存在排序,则调用this.sort,sort方法看分割线下第二个函数
10.进行事件处理,分别根据情况选择触发add 和 sort函数
11.根据传入参数是否单一决定返回model-object 或 models
至此set函数分析完毕
结论:建立一个collection-object,依次会调用: 工厂函数->reset函数->set函数
工厂函数的一条线分析完毕,综上分析collection的核心其实就是models数组和_byId对象
collection的相关操作
at(index):获取该索引下的model
// Get the model at the given index.
at: function(index) {
return this.models[index];
},
其实就是取出collection-object.models[index]
-----------------------------------------------collection 内部函数----------------------------------
1.remove(models, options)
参数: models:model-object 的集合
options: object 配置对象
二者均来自collection 工厂函数
作用: 从collection中删除单个model-object 或 多个model-object
原理: 1.还是进行参数的保障性处理和局部变量的准备
2.遍历这个即将要被删除掉的数组
3.在遍历过程中针对_byId进行删除工作
4.在遍历过程中针对collection-object.models属性进行删除操作
5.对collection-object.length属性进行操作
6.在遍历过程中options.silent没有声明,则触发remove事件,如果有all也会触发
7.关闭当前models[i]的all事件,遍历结束
8.返回经过处理后的第一个参数
源码:
// Remove a model, or a list of models from the set.
remove: function(models, options) {
var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models);
options || (options = {});
var i, l, index, model;
for (i = 0, l = models.length; i < l; i++) {
model = models[i] = this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byId[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
this._removeReference(model);
}
return singular ? models[0] : models;
},
2.sort(options)
参数:options object 配置对象
作用:用于collection中model-object的排序,一般我们不会用到它,使用sort的前提是collection-object中定义comparator属性
原理:1.首先检测是否包含comparator属性,没有则抛出 Cannot sort a set without a comparator 错误
2.然后检测 comparator是否为字符串或长度是否为1
通过检测则调用sortBy函数进行排序
否则使用underscore.bind方法排序
3.根据options.silent决定是否触发sort事件
4.返回this collection-object
源码:
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
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));
} if (!options.silent) this.trigger('sort', this, options);
return this;
},
看到这里大家有些奇怪。这个sortBy和_.bind究竟是啥东西呢?咱们接着往下扒:
sortBy集成于这种机制: 它的兄弟还有 countBy 和 groupBy
// Underscore methods that take a property name as an argument.
var attributeMethods = ['groupBy', 'countBy', 'sortBy']; // Use attributes instead of properties.
_.each(attributeMethods, function(method) {
Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
return _[method](this.models, iterator, context);
};
});
这下我们就知道 this.comparator其实是一个字符串,然后这个sortBy方法走的是underscore的sortBy方法。
// Sort the object's values by a criterion produced by an iteratee.
_.sortBy = function(obj, iteratee, context) {
iteratee = _.iteratee(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iteratee(value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
而underscore的sortBy利用了原生js数组的sort方法,通过前后两个对象抽出相同属性比较可以为对象数组排序的特性进行排序,只不过实现的方式有些绕弯,有兴趣的同学可以自己探索一下。原理就是:
var arr=[{name:'jack',age:6},{name:'jim',age:4},{name:'tom',age:7}];
var o=arr.sort(function(left,right){
var a=left.age;
var b=right.age;
return a-b;
});
console.log(o);
// {name:'jim',age:4} ,{name:'jack',age:6},{name:'tom',age:7}
3.at(index)
参数: number 索引值
作用: 获取该索引下的model-object
原理: 操作collection-object数组
源码:
// Get the model at the given index.
at: function(index) {
return this.models[index];
},
4.继承underscore的函数:
继承的时候,backbone做了一些小小的修改,当执行方法时,underscore中方法所操作的第一个参数obj就是collection-object.models ,第二个参数是我们在backbone调用时传的参数
// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
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);
};
});
5.clone()
作用:将一个collection-object进行深度克隆
原理:new 一个collection-object出来,需要传入当前this对象的models
源码:
// Create a new collection with an identical list of models as this one.
clone: function() {
return new this.constructor(this.models);
},
6.get(obj)
参数: string/object 可以是字符串或者{id:''} {cid:''} 组成的对象
作用: 通过id获取model-object
原理:通过匹配_byId对象,查找相应的模型对象
源码:
// Get a model from the set by id.
get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
},
7.where(attrs,first)
参数:attrs object 需要查找的 key-value组合,first bool 是否得到第一个满足条件的该对象
作用: 根据key-value组合查找到相对应的model-object
原理:如果first存在,则使用继承自underscore的 find方法遍历寻找目标对象,否则调用filter遍历寻找该目标
源码:
// Return models with matching attributes. Useful for simple cases of
// `filter`.
where: function(attrs, first) {
if (_.isEmpty(attrs)) return first ? void 0 : [];
return this[first ? 'find' : 'filter'](function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
Backbone Collection 源码简谈的更多相关文章
- Backbone Model 源码简谈 (版本:1.1.0 基础部分完毕)
Model工厂 作为model的主要函数,其实只有12行,特别的简练 var Model = Backbone.Model = function(attributes, options) { va ...
- backbone.Collection源码笔记
Backbone.Collection backbone的Collection(集合),用来存储多个model,并且可以多这些model进行数组一样的操作,比如添加,修改,删除,排序,插入,根据索引取 ...
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...
- django-jwt token校验源码简析
一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...
- Backbone.js源码分析(珍藏版)
源码分析珍藏,方便下次阅读! // Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Backbone ...
- Backbone Events 源码笔记
用了backbone一段时间了,做一些笔记和总结,看的源码是1.12 backbone有events,model,collection,histoty,router,view这些模块,其中events ...
- Backbone.js源码浅介
终于看到一个只有一千多行的js框架了,于是抱着一定可以看懂他的逻辑的心态,查看了他的整个源码,进去之后才发现看明白怎么用容易,看懂怎么写的就难了,于是乎有了这篇博客的标题:浅介,只能粗浅的介绍下Bac ...
- OpenStack之Glance源码简析
Glance简介 OpenStack镜像服务器是一套虚拟机镜像发现.注册.检索. glance架构图: Glance源码结构: glance/api:主要负责接收响应镜像管理命令的Restful请求, ...
随机推荐
- 树莓派的WIFI配置
参考网址: http://www.cnblogs.com/iusmile/archive/2013/03/30/2991139.html http://my.oschina.net/pikeman/b ...
- NLP系列-中文分词(基于统计)
上文已经介绍了基于词典的中文分词,现在让我们来看一下基于统计的中文分词. 统计分词: 统计分词的主要思想是把每个词看做是由字组成的,如果相连的字在不同文本中出现的次数越多,就证明这段相连的字很有可能就 ...
- Android之Audio和Video
The Android platform offers built-in encoding/decoding for a variety of common media types, so that ...
- Hyperledger02
docker 思想 模块化: 集装箱 标准化: 运输标准化, 存储方式标准化,API接口的标准化 安全性: 隔离 docker解决什么问题 devops 我这程序程序没问题啊 系统好卡.哪个程序死循环 ...
- 利用jquery操作dom时,用event.target优化dom操作
html: <ul id="contents"> <li data-link="#part1">Part 1</li> &l ...
- arm单板上移植gdb
虚拟机 : vmware 12 image: ubuntukylin 14.04.1 系统版本:Linux dancy 3.13.0-32-generic #57-Ubuntu SMP Tue Jul ...
- Mac上利用Aria2加速百度网盘下载
百度网盘下载东西的速度那叫一个慢,特别是大文件,看着所需时间几个小时以上,让人很不舒服,本文记录自己在mac上利用工具Aria2加速的教程,windows下思路也是一样! 科普(可以不看) 这里顺带科 ...
- lintcode-84-落单的数 III
84-落单的数 III 给出2*n + 2个的数字,除其中两个数字之外其他每个数字均出现两次,找到这两个数字. 样例 给出 [1,2,2,3,4,4,5,3],返回 1和5 挑战 O(n)时间复杂度, ...
- ASP.NET MVC如何使用输出缓存
通过这篇文章你将学习到在MVC中如何使用输出缓存,业务逻辑我就不多介绍了,主要是Outputcache的基本使用.至于数据缓存还是等我的下一篇文章吧,一步一步来不急的. 输出缓存的使用方法是在Co ...
- servletContext的定义