Backbone源码分析(二)
在传统MVC框架模式中,Model承担业务逻辑的任务。Backbone作为一个mvc框架,主要的业务逻辑交由Model与Collection来实现。Model代表领域对象,今天主要学一下Model源码中几个重要的函数。
我们先看一下Model的构造函数做了哪些事情:
// Create a new model with the specified attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) {
//对参数的处理
var attrs = attributes || {};
options || (options = {}); this.cid = _.uniqueId(this.cidPrefix);//利用underscore生成一个客户端的唯一标识符cid
this.attributes = {};//this.attributes是backbone中存放所有数据属性的对象
//collection在获取model对应的后端url时使用,在model上设置collection并不会自动将model加入collection
if (options.collection) this.collection = options.collection;
//调用parse方法解析数据
if (options.parse) attrs = this.parse(attrs, options) || {};
//处理defaults默认数据,用attrs覆盖defaults
var defaults = _.result(this, 'defaults');
attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
this.set(attrs, options);//接收attrs将数据处理后放入this.attributes
this.changed = {};//changed属性用来保存修改过的属性数据,第一次set,不需要changed数据
this.initialize.apply(this, arguments);//调用initialize初始化model,这个方法需要子类来覆盖
};
Model的构造函数主要做了以下几件事:
- 处理参数
- 处理model的属性:cid、attributes、collection
- 解析数据、处理属性的默认值
- set方法接收处理参数
- 调用initialize做初始化操作
接下来是一个重要的set函数,这个函数是Model最核心的一个方法
// Set a hash of model attributes on the object, firing `"change"`. This is
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
set: function(key, val, options) {
if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments.
var attrs;
if (typeof key === 'object') {//{key: value}形式
attrs = key;
options = val;
} else {// key, value, options形式
(attrs = {})[key] = val;
} options || (options = {});//设置options参数 // Run validation.
//首先验证参数,这里没有直接调用validate方法,而是调用_validate这个私有方法,该方法内部调用validate方法
if (!this._validate(attrs, options)) return false; // Extract attributes and options.
var unset = options.unset;
var silent = options.silent;
var changes = [];//用来存放所有有变动的key
var changing = this._changing;//this._chaning用来标识set方法是否在处理中,我猜这里的设置跟webworker多线程有关
this._changing = true;//这里代表属性的变动更新开始
// this.changed = {};//不能放在这里,如果val没改变,所有changed都被清空掉了 if (!changing) {//使用_previousAttributes来保留最近一次的attributes
this._previousAttributes = _.clone(this.attributes);
this.changed = {};//每次set时,changed都会被重置的{},这表示仅保留最近一次的变化
} var current = this.attributes;
var changed = this.changed;
var prev = this._previousAttributes; // For each `set` attribute, update or delete the current value.
for (var attr in attrs) {//遍历attrs
val = attrs[attr];
//对于单线程环境,current与_previousAttributes是一样的,这里的处理也应当是应对多线程
if (!_.isEqual(current[attr], val)) changes.push(attr); //changes是本次变化的keys
if (!_.isEqual(prev[attr], val)) {
changed[attr] = val; //存储变化量
} else {
delete changed[attr];
}
//这里根据unset的设置,如果unset为true移除,否则设置attributes中的对应属性
unset ? delete current[attr] : current[attr] = val;
} // Update the `id`.
//idAttribute的目的是跟后端数据库记录的id保持一致
if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); // Trigger all relevant attribute changes.
// 在所有赋值结束后,发送事件通知
if (!silent) {
if (changes.length) this._pending = options;
for (var i = 0; i < changes.length; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
} // You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing) return this; //这里我觉得也是跟多线程有关,如果多个线程同时更新model,最终只发出一个整体的change事件
if (!silent) {
while (this._pending) {//很奇怪的设置
options = this._pending;
this._pending = false;
this.trigger('change', this, options);//触发事件
}
}
this._pending = false;
this._changing = false;
return this;
}
来整理一下set方法做的几件事:
- 根据api的参数声明来处理参数
- 声明几个与属性变化相关的变量
- 设置_previousAttributes与changed来保存上次属性和这次的变化数据
- 更新属性,保存本次变化数据和对应的key
- 将发生变化的属性广播出去,change:key形式
- 在model层次上发出change事件
接下来是与后端打交道的save方法:
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
// save方法保持客户端与数据库内记录同步,前后端数据可能出现不一致情况,
// 如果options中wait参数为true的话,会用后端返回的数据来更新前端数据
save: function(key, val, options) {
// Handle both `"key", value` and `{key: value}` -style arguments.
var attrs;
if (key == null || typeof key === 'object') {//`{key: value}`
attrs = key;
options = val;
} else {//`"key", value`
(attrs = {})[key] = val;
} //在方法默认开启验证和解析
options = _.extend({validate: true, parse: true}, options);
var wait = options.wait; // 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.
// wait为false的话,首先在前端更新model,set中调用验证方法
if (attrs && !wait) {
if (!this.set(attrs, options)) return false;
} else if (!this._validate(attrs, options)) {//否则利用_validate进行验证
return false;
} // After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;//保存this关键字
var success = options.success;
var attributes = this.attributes;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;//这里先确保数据与为同步时保持一致
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
// wait属性为true,利用后端数据更新model的属性
if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
if (serverAttrs && !model.set(serverAttrs, options)) return false;
// success带表更新成功后的回调函数。
// save方法,将模型数据的同步完全封装起来,开发者只需专注于自身业务逻辑即可!
if (success) success.call(options.context, model, resp, options);
// 触发sync事件
model.trigger('sync', model, resp, options);
};
wrapError(this, options); // Set temporary attributes if `{wait: true}` to properly find new ids.
//wait 为true,临时更新attributes,目的是下文中将model更新到数据库内
if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); //根据model是否拥有idAttribute属性,决定是创建还是更新
var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch' && !options.attrs) options.attrs = attrs;
var xhr = this.sync(method, this, options); // Restore attributes.
this.attributes = attributes;//恢复数据,等到success后利用后端数据结果更新属性 return xhr;
},
其中用到的wrapError方法,源码如下:
// Wrap an optional error callback with a fallback error event.
//将options中的error回调函数,变成一个能够触发error事件的回调函数
var wrapError = function(model, options) {
var error = options.error;
options.error = function(resp) {
if (error) error.call(options.context, model, resp, options);
model.trigger('error', model, resp, options);
};
}
save方法做的几件事:
- 处理参数
- 如果以客户端为准,则首先跟新model,否则验证需保存的属性
- 声明局部变量,替换options中的success回调函数和error回调
- 如果以后端返回数据为准,则先直接将attributes属性暂时更改,方便sync方法同步model,而后将attributes恢复,等待succes毁掉中利用后端返回结果更新
接下来是销毁model的destroy方法:
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
//destroy方法用来销毁model,当wait属性为true时,等待后台销毁成功时做实际销毁工作
destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
var wait = options.wait; var destroy = function() {
model.stopListening();//移除其他代码中监听的model事件
// 触发destroy事件
model.trigger('destroy', model, model.collection, options);
}; // 后台销毁成功后的success回调
options.success = function(resp) {
if (wait) destroy();//销毁操作
// 回调函数,业务逻辑相关
if (success) success.call(options.context, model, resp, options);
//拥有idAttribute属性,则触发sync事件
if (!model.isNew()) model.trigger('sync', model, resp, options);
}; var xhr = false;
if (this.isNew()) {//数据库中并没有该条记录
_.defer(options.success);//underscore函数,延迟调用function直到当前调用栈清空为止
} else {
wrapError(this, options);//包装错误
xhr = this.sync('delete', this, options);// 与后台数据同步
}
if (!wait) destroy(); //无需后台等待的话,直接做销毁操作
return xhr;
}
destroy方法做的事情:
- 声明局部变量以及做销毁操作的destroy方法
- 替换options中的success方法
- 如果model未存储于数据库中,直接使用underscore的defer延迟执行success,否则向后台发送删除请求
与验证相关的_validate方法如下:
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
_validate: function(attrs, options) {
if (!options.validate || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
//backbone希望在验证失败时候,validate方法返回一个error对象
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;
//触发invalid事件,也就是说如果单独调用validate方法不会触发invalid事件
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
return false;
}
Backbone源码分析(二)的更多相关文章
- Backbone源码分析(三)
Backbone源码分析(一) Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨C ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- Tomcat源码分析二:先看看Tomcat的整体架构
Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- Backbone源码分析-Backbone架构+流程图
作者:nuysoft/高云/nuysoft@gmail.com 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. Backbone0.9.1源码分析分析系列 jQuery1.6.1源码分析系 ...
随机推荐
- 多个jar包合并成一个jar包的办法
步骤: 1.将多个JAR包使用压缩软件打开,并将全包名的类拷贝到一个临时目录地下. 2.cmd命令到该临时目录下,此时会有很多.class文件,其中需要带完整包路径 3.执行 jar -cvfM te ...
- MySQL 数据类型 详解
MySQL 数据类型 详解 MySQL 的数值数据类型可以大致划分为两个类别,一个是整数,另一个是浮点数或小数.许多不同的子类型对这些类别中的每一个都是可用的,每个子类型支持不同大小的数据,并且 My ...
- 安装SVN客户端重启电脑之后,右键未出现SVN选项的原因
今天安装SVN客户端明明安装成功,电脑也重启过了,但是就是在右键里找不到SVN,百度了很久,删注册表.建用户组之类的方法都试过了,但是都没有效果. 后来才找到真正的原因,我的安装包是32位的,系统是6 ...
- Linux学习笔记(12)-进程间通信|匿名管道
Linux的进程间通信有几种方式,包括,管道,信号,信号灯,共享内存,消息队列和套接字等-- 现在一个个的开始学习! ----------------------------------------- ...
- mongoose数据库连接和操作
var mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27017/hometown'); var db = ...
- 用Fiddler的自动响应模拟系统集成
1. 下载最新版本的Fiddler Fiddler 官网 2, 安装并启动Fiddler 3, 勾选自动响应 见上图 4, 添加自动响应规则 见上图 5, 添加自动响应内容文件 添加响应文件到Fidd ...
- XmlRpc.net 入参结构体嵌套的转义操作
项目使用C#开发,需要使用XmlRpc和Linux服务器端交互,用的是XmlRpc.net. 普通的程序调用入参和出差都没有问题,今天遇到入参结构体嵌套,结果 args 入参在服务器端不能解析.抓包数 ...
- 织梦cms更新新建的栏目提示:DedeTag Engine Create File False:C:/phpStudy/WWW/
这个问题怎么解决呢?只是更换了一下栏目名称.增加了新的栏目,结果就不行了. 问题原因: 新建的栏目没有"列表命名规则",导致cms解析的时候,出现解析错误. 解决办法 在下图填入: ...
- [深度学习大讲堂]从NNVM看2016年深度学习框架发展趋势
本文为微信公众号[深度学习大讲堂]特约稿,转载请注明出处 虚拟框架杀入 从发现问题到解决问题 半年前的这时候,暑假,我在SIAT MMLAB实习. 看着同事一会儿跑Torch,一会儿跑MXNet,一会 ...
- windows 10 开始菜单和cortana无法工作的问题
过了个周末,到了实验室一开机发现报了个关键错误:开始菜单和cortana无法工作. 经过一番google ,发现问题,原来是360禁用了一个服务导致,这个服务是UserManager. 我直接去开启发 ...