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源码分析系 ...
随机推荐
- C 标准库系列之locale.h
locale.h 区域设置相关,主要针对时间日期.货币格式.字符控制.数字格式等以满足某区域的设置需要. locale设置类别主要包括以下几个宏定义的类别: LC_ALL:设置所有的类别: LC_CO ...
- RxJava 学习笔记(一)
最近Android6.0的权限问题,要把之前的APP进行改造,用到了RxPermission框架!之前了解过RXJAVA,但是由于之前项目一直没有使用这个框架,所以也就一直搁置了.正好Rxpermis ...
- Power服务器中KVM克隆新虚拟机
查看当前所有虚拟机:virsh list --all 克隆新虚拟机:virt-clone -o guest01 -n guest02 -f /var/lib/libvirt/images/guest ...
- Knockout.js随手记(7)
数组元素的新增/移除事件 前两篇博客已经很清楚的知道knockout.js通过observableArray()数组元素增减,可以实时的反映在UI上.当然我们想在数组增加或移除元素时加上自定义逻辑就好 ...
- 使用git grep进行git搜索
1.git grep foo 会自动map所有包含foo的文件 2.git grep -n foo 显示行号 3.git grep --name-only foo 只显示文件名 4.git grep ...
- C#改善程序的50种方法
作者: suyan010203 来源: 博客园 发布时间: 2011-07-09 14:47 阅读: 11976 次 推荐: 8 原文链接 [收藏] 从去年找工作以来,都没什么时间写博 ...
- Python学习日志(一)
Python的安装 访问http://www.python.org 点击downloads,选择Windows 我在这里选择了Latest Python 3 Release - Python 3.5. ...
- Json格式示意图
json视图工具:http://www.bejson.com/jsonviewernew/ 一.Json格式化,(看到数组里面又有数组一下子疑问不是合格json):尾门地址查询: =>=> ...
- Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)
线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...
- 如何发布付费WP8应用
如何发布付费应用,听起来蛮简单的,事实上也确实如此,但是发布付费WP8应用前的资料填写却让我郁闷不已. 我打开仪表板,点击进入账户--账户摘要,填写税务资料和付款账户,发现付款账户保存成功了,税务资料 ...