Backbone源码分析(一)

Backbone源码分析(二)

Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨Collection的源码。

让我们先来看一下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;//model对象指定collection管理的Model类型
//comparator 排序使用
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();// 重置集合
this.initialize.apply(this, arguments);//调用初始化方法
//如果参数中传递model数组,则利用models数组重置集合
if (models) this.reset(models, _.extend({silent: true}, options));
};

接下来是在set, add, remove 中经常用到的splice函数:

  // Splices `insert` into `array` at index `at`.
//将insert数组在at位置拼接到array中
var splice = function(array, insert, at) {
//防止at超出数组长度
at = Math.min(Math.max(at, 0), array.length);
//先创建一个空数组
var tail = Array(array.length - at);
var length = insert.length;
var i;
//将at之后的数组元素暂存到tail中
for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
//用insert替换array中at之后的元素
for (i = 0; i < length; i++) array[i + at] = insert[i];
//将原at之后的array元素放到insert元素之后
for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
};

下面我们来讲解set函数,set函数是Collection中非常重要的一个函数,集增删改查与一身,处理了大量的核心业务逻辑:

    // 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) {
if (models == null) return;
// var setOptions = {add: true, remove: true, merge: true};
options = _.extend({}, setOptions, options);//setOptions是预设参数
// 如果models为原生对象,会利用Collection中model属性来转化成Model实例
if (options.parse && !this._isModel(models)) {
models = this.parse(models, options) || [];
} var singular = !_.isArray(models);
models = singular ? [models] : models.slice(); // 处理at,确保at为合理的数字
var at = options.at;
if (at != null) at = +at; //转化为数字
if (at > this.length) at = this.length;
if (at < 0) at += this.length + 1; var set = [];// set表示经过本次处理后应当存在于this.models中的model
var toAdd = [];// 本次操作增加的model数组
var toMerge = [];// 本次操后修改的model数组
var toRemove = [];// 本次操作删除掉的models
var modelMap = {};//modelMap是本次变化后的应该存在于Collection中的models的key集合 var add = options.add;
var merge = options.merge;
var remove = options.remove; var sort = false;
//有comparator属性,没设置at,sort为true
//如果对collection做了插入的话,需要自己手动排序
var sortable = this.comparator && at == null && options.sort !== false;
//comparator 是model中的属性
var sortAttr = _.isString(this.comparator) ? this.comparator : null; // Turn bare objects into model references, and prevent invalid models
// from being added.
var model, i;
//先过滤一遍,找出存在于collection中的和不存在于当前collection中的
for (i = 0; i < models.length; i++) {//处理add和existing
model = models[i]; // If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
// get根据idAttribute || cid 来查找
var existing = this.get(model);
if (existing) {
if (merge && model !== existing) {
var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);//在这之前应该验证一下
// 使用当前属性替换model中已存在属性
existing.set(attrs, options);
toMerge.push(existing);
// 查看排序字段是否有更改
if (sortable && !sort) sort = existing.hasChanged(sortAttr);
}
// 将更的model id存到modelMap中
if (!modelMap[existing.cid]) {
modelMap[existing.cid] = true;
set.push(existing);
}
models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
// _prepareModel将原始对象转化为Model实例
model = models[i] = this._prepareModel(model, options);
if (model) {
toAdd.push(model);
// _addReference 将model加入到Collection的_byId中,并绑定model的所有事件
this._addReference(model, options);
modelMap[model.cid] = true;
set.push(model);
}
}
} // Remove stale models.
if (remove) {
for (i = 0; i < this.length; i++) {
model = this.models[i];
// 在this.models中但不在本次set中的model,都要删除
if (!modelMap[model.cid]) toRemove.push(model);
}
//移除model,this.models、this._byId;移除model的绑定事件
if (toRemove.length) this._removeModels(toRemove, options);
} // See if sorting is needed, update `length` and splice in new models.
var orderChanged = false;
var replace = !sortable && add && remove;
if (set.length && replace) {//如果同时有加减操作,便将models放到this.models中
//如果this.models中set的数据不一致,则认为order有变化。
orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
return m !== set[index];
});//没有启用排序,但是this.models与set不一致时,仍会触发sort事件 //处理完remove后,该删除的都删除掉;用set替换this.models
this.models.length = 0;
splice(this.models, set, 0);
this.length = this.models.length;
} else if (toAdd.length) {//如果仅仅是增加model,则将toAdd插入到指定位置去
if (sortable) sort = true;
splice(this.models, toAdd, at == null ? this.length : at);
this.length = this.models.length;
} // Silently sort the collection if appropriate.
//这里排序一下,但不要触发事件,在下文统一处理
if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort/update events.
//collection跟新完毕后,再发送事件
// remove在上文删除时已触发
if (!options.silent) {
for (i = 0; i < toAdd.length; i++) {
if (at != null) options.index = at + i;
model = toAdd[i];
model.trigger('add', model, this, options);
}
if (sort || orderChanged) this.trigger('sort', this, options);
if (toAdd.length || toRemove.length || toMerge.length) {
options.changes = {
added: toAdd,
removed: toRemove,
merged: toMerge
};
this.trigger('update', this, options);
}
} // Return the added (or merged) model (or models).
return singular ? models[0] : models;
},

Collection中存储model的属性有两个:this.models数组和_byId键值对,所有的增删改查都需要对这两个属性进行更新。

set函数中主要做了以下几件事情:

  • 将models参数处理成Model的实例数组
  • 处理options中的at参数,将其变成一个合理的数字
  • 声明变量set, toAdd, toMerge, toRemove, modelMap
  • 遍历models参数,找出其中应当更改或者加入到Collection中的model并添加到上文的变量中
  • 从Collection的this.models中删除不存在本次set中的model
  • 统一更改Collection中的this.models数组
  • 排序,但不要触发sort事件
  • 统一处理事件: add、sort、update事件

add函数内部就是利用set函数来处理的:

// Add a model, or list of models to the set. `models` may be Backbone
// Models or raw JavaScript objects to be converted to Models, or any
// combination of the two.
add: function(models, options) {//没有的会加进去,已存在的会根据options决定是否merge
return this.set(models, _.extend({merge: false}, options, addOptions));
},

add之后,另一个重要的操作就是remove,Collection中的remove逻辑由remove, _removeModels, _removeReference 三个函数完成

remove: function(models, options) {
//处理参数,models处理成数组
options = _.extend({}, options);
var singular = !_.isArray(models);
models = singular ? [models] : models.slice();
//删除掉models,并触发removed事件
var removed = this._removeModels(models, options);
//从Collection层面上触发update事件
if (!options.silent && removed.length) {
options.changes = {added: [], merged: [], removed: removed};
this.trigger('update', this, options);//触发update事件,注意options里面的change,这样可以方便很多事
}
return singular ? removed[0] : removed;//注意api的返回值和事件参数的设置
},
    // Internal method called by both remove and set.
_removeModels: function(models, options) {
var removed = [];
for (var i = 0; i < models.length; i++) {
var model = this.get(models[i]);
if (!model) continue; // 首先从this.models数组中删除model
var index = this.indexOf(model);
this.models.splice(index, 1);
this.length--; // Remove references before triggering 'remove' event to prevent an
// infinite loop. #3693
// 从_byId中删除model的引用
delete this._byId[model.cid];
var id = this.modelId(model.attributes);
if (id != null) delete this._byId[id]; // 触发Collection的remove事件
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options); //被删除的model也触发remove事件
} // 删除model的引用和移除model的绑定事件
removed.push(model);
this._removeReference(model, options);
}
return removed;
},
    // Internal method to sever a model's ties to a collection.
_removeReference: function(model, options) {
//断开model与collection的关联
delete this._byId[model.cid];
var id = this.modelId(model.attributes);
if (id != null) delete this._byId[id];
if (this === model.collection) delete model.collection;
// 移除所有的绑定事件
model.off('all', this._onModelEvent, this);
},

remove整体逻辑如下:

  • 处理models参数
  • this.models_byId中删除model,触发Collection的remove事件
  • 断开model与Collection的关联,移除model的绑定事件
  • 触发Collection的update事件

_removeReference对应的是_addReference,它的作用于_removeRenence相反:

// Internal method to create a model's ties to a collection.
_addReference: function(model, options) {
//将model与collection关联起来,绑定model的各种事件
this._byId[model.cid] = model;
var id = this.modelId(model.attributes);
if (id != null) this._byId[id] = model;
model.on('all', this._onModelEvent, this);
},

最后要提一下的是reset函数:

// 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) {//reset中不会触发add和remove事件
options = options ? _.clone(options) : {};
for (var i = 0; i < this.models.length; i++) {
//断开与Collection的链接,和移除model的事件
this._removeReference(this.models[i], options);
}
options.previousModels = this.models;//多看设计
this._reset();//将Collection置空,length、this.models、this._byId
models = this.add(models, _.extend({silent: true}, options));
// 触发事件
if (!options.silent) this.trigger('reset', this, options);
return models;
},

Backbone源码分析(三)的更多相关文章

  1. tomcat源码分析(三)一次http请求的旅行-从Socket说起

    p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...

  2. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  3. Backbone源码分析-Backbone架构+流程图

    作者:nuysoft/高云/nuysoft@gmail.com 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. Backbone0.9.1源码分析分析系列 jQuery1.6.1源码分析系 ...

  4. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

  5. ABP源码分析三十一:ABP.AutoMapper

    这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...

  6. ABP源码分析三十三:ABP.Web

    ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事一,在A ...

  7. ABP源码分析三十四:ABP.Web.Mvc

    ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...

  8. ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  9. Duilib源码分析(三)XML解析器—CMarkup

    上一节介绍了控件构造器CDialogBuilder,接下来将分析其XML解析器CMarkup: CMarkup:xml解析器,目前内置支持三种编码格式:UTF8.UNICODE.ASNI,默认为UTF ...

随机推荐

  1. Microsoft Visual Studio PDB文件相关事宜

    Microsoft Visual Studio PDB:调试的符号文件,程序数据库 (PDB) 文件保存着调试和项目状态信息,使用这些信息可以对程序的调试配置: 当以 /ZI 或 /Zi(用于 C/C ...

  2. Microsoft Visual Studio 工程属性表props/vsprops创建与使用

    props/vsprops:工程属性表文件(project property sheet) 后者为vs2008的,前者为vs2010及以后版本的,其主要包含工程属性配置相关,可以单独提取出来供不同工程 ...

  3. [Unity3D]自制UnityForAndroid二维码扫描插件

    一周左右终于将二维码生成和扫描功能给实现了,终于能舒缓一口气了,从一开始的疑惑为啥不同的扫码客户端为啥扫出来的效果不同?通用的扫描器扫出来就是一个下载APK,自制的扫描器扫出来是想要的有效信息,然后分 ...

  4. 【CentOS】LAMP相关2

    ////////////////配置Apache//////////////////////////// 拿一个discuz来演示 LFS是什么,然后去掌握以下吧,对我们的提升很大?????听说广州的 ...

  5. PTAM 编译安装 on MAC(mavericks)

    最近有需要研究PTAM,所以需要在我的MAC上编译安装,整个过程让人非常崩溃各种问题陷阱.现在整理一下编译过程.我已经成功在MAC上编译.那么接下来我会详细讲解整个过程: 注意: 部分链接可能需要FQ ...

  6. GAMBIT、ICEM、HYPERMESH耦合面的处理方法

    前两天在论坛里碰到有朋友问关于使用fluent仿真流固耦合,使用hypermesh作为前处理时的耦合面的方法,刚好今天有点时间,借此机会总结一下GAMBIT.ICEM和HYPERMESH这三款软件作为 ...

  7. 基于dubbo的分布式项目实例应用

    本文主要学习dubbo服务的启动检查.集群容错.服务均衡.线程模型.直连提供者.只定阅.只注册等知识点,希望通过实例演示进一步理解和掌握这些知识点. 启动检查 Dubbo缺省会在启动消费者时检查依赖的 ...

  8. sqlite like 通配符 ,匹配区分大小写(默认不区分大小写)

    在查询前先执行这个语句 , 1 时区分大小写,0时不区分 PRAGMA case_sensitive_like =0; select prod_name,PROD_PRICEfrom products ...

  9. 获取终端ip地址

    网上找的,记录下 import java.io.*; import java.net.*; import java.util.*; //import org.apache.http.conn.util ...

  10. 单片机与控制实验(2)——LED点阵显示屏

    一.实验目的和要求 了解LED点阵显示的基本原理和实现方法.掌握点阵汉字库的编码和从标准字库中提取汉字编码的方法. 二.实验设备 单片机测控实验系统 LED点阵显示器实验模块 Keil开发环境 STC ...