【原创】backbone1.1.0源码解析之Model
趁热打铁,将Backbone.Model的源代码注释也发出来。
Model是用来干嘛的?写过mvc的同学应该都知道,说白了就是model实例用来存储数据表中的一行数据(row)
Backbone利用model的attributes与数据库的字段一一对应,通过ajax获取数据后,在前端进行存储,又提供了一系列的方法,
在改变model实例的同时,完成关联视图view的更新,和服务器端数据的更新,从而达到mvc的效果。
下面就是Backbone.Model的源码注释了,如果错误了还望指出来,或者不清晰,可以给我留言
// Backbone.Model
// -------------- // Backbone **Models** are the basic data object in the framework --
// frequently representing a row in a table in a database on your server.
// A discrete chunk of data and a bunch of useful, related methods for
// performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`)
// is automatically generated and assigned for you.
// Model构造器,大概在this对象上添加的属性
// {
// cid : 'cxxx',
// attributes : {...},
// changed : {}
// }
// options可选参数(在进行model实例方法调用时还会见到更多的options参数)
// {
// collection : true(false),
// parse : true(false),
// }
var Model = Backbone.Model = function(attributes, options) {
var attrs = attributes || {};
options || (options = {});
// model对象唯一id
this.cid = _.uniqueId('c');
// model的属性集合
this.attributes = {};
// 指定model所属的collection
if (options.collection) this.collection = options.collection;
// 对attrs进行过滤,默认parse函数返回原attrs,继承时可以根据需要进行重写
if (options.parse) attrs = this.parse(attrs, options) || {};
// 将attrs中没有被设置的属性,设置默认值
// 这里的defaults是自定义的,可以是{...},也可以是返回{...}的函数
attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
this.set(attrs, options);
// 因为是构造函数,所以重置上述调用set操作带来的引起changed变化
this.changed = {};
// 执行初始化操作,用户可自定义
this.initialize.apply(this, arguments);
}; // Attach all inheritable methods to the Model prototype.
// Model实例公有方法(添加了事件机制)
_.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ.
// 改变过的属性集合
changed: null, // The value returned during the last failed validation.
// 执行validate方法时,如果验证失败,那么失败的结果会放在该变量里
validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
// 自定义数据库返回的唯一键的名字
idAttribute: 'id', // Initialize is an empty function by default. Override it with your own
// initialization logic.
// 一般会覆盖用于自定义初始化操作
initialize: function(){}, // Return a copy of the model's `attributes` object.
toJSON: function(options) {
return _.clone(this.attributes);
}, // Proxy `Backbone.sync` by default -- but override this if you need
// custom syncing semantics for *this* particular model.
// 调用Backbone.sync,可以通过改写Backbone.sync来实现自己的异步操作
sync: function() {
return Backbone.sync.apply(this, arguments);
}, // Get the value of an attribute.
// 获取属性值
get: function(attr) {
return this.attributes[attr];
}, // Get the HTML-escaped value of an attribute.
// 获取经过html-escaped过的属性值,主要事为了防止xss攻击
escape: function(attr) {
return _.escape(this.get(attr));
}, // Returns `true` if the attribute contains a value that is not null
// or undefined.
// 判断是否含有某个属性值
has: function(attr) {
return this.get(attr) != null;
}, // 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.
// 设置属性值,触发对应属性的的change事件,触发对象的change事件
set: function(key, val, options) {
var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments.
// 处理属性集合,({key1: value1, key2: value2, ...}, [options])
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
// 处理(key1, value1, [options])
(attrs = {})[key] = val;
} options || (options = {}); // Run validation.
// 验证失败了,set也就失败了
if (!this._validate(attrs, options)) return false; // Extract attributes and options.
// unset 用于删除属性
unset = options.unset;
// silent 用于控制是否触发对应属性的change事件
silent = options.silent;
changes = [];
changing = this._changing;
this._changing = true; // 这里判断其实是介于接下来的trigger change事件里面还可能在进行set操作
// 而_previousAttributes和changed依附于this对象实现共享
// 这里是考虑到用户自定义change回调时,循环迭代的问题
if (!changing) {
// 保存修改前的属性
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`.
// 设置id,这里的id是只数据库中的唯一键,不同于cid
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
// 与当前值就行比较,如果不一样,那么记录这个改变
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
// 记录改变的属性
this.changed[attr] = val;
} else {
// 如果在迭代中属性又还原不变了,那么删除掉
delete this.changed[attr];
}
// 删除或者更新属性
unset ? delete current[attr] : current[attr] = val;
} // Trigger all relevant attribute changes.
// 触发属性改变的事件,如:change:attr1
if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; 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.
// 这里返回this,是因为上面进行trigger change:attr1时,用户自定义函数可能会再次进行set操作
// 而接下来的有些操作是针对对象本身的,只需要被执行一次
if (changing) return this;
if (!silent) {
// 如果属性有改变,那么触发对象的change事件
// 这里的循环同样是为了处理change事件的回调函数里面包含set操作而引发change事件
// 因为上述的changing会进行return,那么通过this._pending来进行while判断,来执行多次对象的change事件回调
// 这里依然是循环迭代的问题
while (this._pending) {
this._pending = false;
this.trigger('change', this, options);
}
}
// 恢复状态
this._pending = false;
this._changing = false;
return this;
}, // Remove an attribute from the model, firing `"change"`. `unset` is a noop
// if the attribute doesn't exist.
// unset实质是利用set方法,只需将options.unset设置为true
unset: function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
}, // Clear all attributes on the model, firing `"change"`.
// 清除所有属性
clear: function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;
return this.set(attrs, _.extend({}, options, {unset: true}));
}, // Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged: function(attr) {
// 如果attr为空,那么通过查看this.changed来判断对象是否改变(即是否有某一属性改变)
if (attr == null) return !_.isEmpty(this.changed);
// 返回是否某一个特定属性改变了
return _.has(this.changed, attr);
}, // Return an object containing all the attributes that have changed, or
// false if there are no changed attributes. Useful for determining what
// parts of a view need to be updated and/or what attributes need to be
// persisted to the server. Unset attributes will be set to undefined.
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
// 返回changed属性列表,没有改变则返回false
// 如果有传递diff,那么就进行attributes和diff的比较,筛选出属性值不一样的属性
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
var val, changed = false;
// 还是考虑循环迭代的问题
var old = this._changing ? this._previousAttributes : this.attributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
}, // Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
// 返回修改前的某个属性值
previous: function(attr) {
if (attr == null || !this._previousAttributes) return null;
return this._previousAttributes[attr];
}, // Get all of the attributes of the model at the time of the previous
// `"change"` event.
// 返回修改前的属性集合
previousAttributes: function() {
return _.clone(this._previousAttributes);
}, // Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overridden,
// triggering a `"change"` event.
// 从服务器端获取数据,填充model
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
// 回调success函数
options.success = function(resp) {
// 设置model的值(可能会验证失败),那么返回false
if (!model.set(model.parse(resp, options), options)) return false;
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
// 包装回调error函数
wrapError(this, options);
// 异步获取数据
return this.sync('read', this, options);
}, // 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: 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); // 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.
// options.wait为true用于等待服务器返回结果,再进行属性值的设置(进而view变化)
// 为false的话,先进行数据验证,再设置属性值(改变view, 拥有更好的用户体验),再提交数据,
//(这样的话等待服务器返回结果先还原attributes,进行属性值的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}`.
// 因为接下来的Backbone.async中的再取数据时,会先取options.attrs的值,
// 取不到就会执行model.toJson()来获得所有属性作为数据
// 这里暂时改变this.attributes的值,不用set(因为set还要触发change等等)
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;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
// 还原属性值
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
// 用服务端的修正的数据覆盖并合并attrs
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
// 服务器端返回的数据更新attributes
// 对于wait为true的情况,此时view才刚得以变化
// 对于wait为false的情况,应该再此时再一次对view进行改变或者纠正,
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
// 成功回调函数
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options); // 新数据用create来创建
// 否则用patch表示只提交改变的attrs,或者update表示提交所有的属性
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options); // Restore attributes.
// 还原之前为了提交数据的临时改变
if (attrs && options.wait) this.attributes = attributes; return xhr;
}, // 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.
// 删除服务器端model数据
destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success; var destroy = function() {
model.trigger('destroy', model, model.collection, options);
}; options.success = function(resp) {
// 等待服务器端返回或者新model,执行前端destory
if (options.wait || model.isNew()) destroy();
if (success) success(model, resp, options);
// 不是新model执行远程请求成功的sync事件
if (!model.isNew()) model.trigger('sync', model, resp, options);
}; // 新数据,服务器端不存在,那么只需要进行本地操作就行了
if (this.isNew()) {
options.success();
return false;
}
wrapError(this, options); // 异步删除
var xhr = this.sync('delete', this, options);
// 不等待服务器返回结果,先前端删除
if (!options.wait) destroy();
return xhr;
}, // Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
// 返回当前model实例对应的url
// 默认规则是:[collection.url]/[id]
url: function() {
var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
}, // **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
// 对数据进行解析过滤,用户可根据需求自定义
parse: function(resp, options) {
return resp;
}, // Create a new model with identical attributes to this one.
// 克隆当前对象(即用相同attributes初始化)
clone: function() {
return new this.constructor(this.attributes);
}, // A model is new if it has never been saved to the server, and lacks an id.
// 通过this.id来判断对象数据是否是从服务器取过来的,因为数据库的数据都是有唯一id的嘛
isNew: function() {
return this.id == null;
}, // Check if the model is currently in a valid state.
// 检查当前的attributes是否合法,返回验证结果
isValid: function(options) {
return this._validate({}, _.extend(options || {}, { validate: true }));
}, // Run validation against the next complete set of model attributes,
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
// 对进行set或者save操作时,会对设置的属性数据进行验证
_validate: function(attrs, options) {
// 验证的条件
// 1. options有设置validate参数为true
// 2. 存在自定义的validate函数
if (!options.validate || !this.validate) return true;
// 将需要验证的attrs和对象原有的this.attributes属性合并到一个空对象上
// 目的是,用户用validate时,可能需要参考一些已经设置好的属性值
attrs = _.extend({}, this.attributes, attrs);
// 验证函数验证失败返回错误信息,成功返回空值
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;
// 验证失败,触发invalid事件
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
return false;
} }); // 添加几个用的underscore.js里的方法到model中,用于处理this.attributes,
// 毕竟this.attributes是个对象也是个集合嘛,用underscore会很方便
// Underscore methods that we want to implement on the Model.
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; // Mix in each Underscore method as a proxy to `Model#attributes`.
_.each(modelMethods, function(method) {
Model.prototype[method] = function() {
var args = slice.call(arguments);
// 第一个参数时属性集合
args.unshift(this.attributes);
return _[method].apply(_, args);
};
});
【原创】backbone1.1.0源码解析之Model的更多相关文章
- 【原创】backbone1.1.0源码解析之View
作为MVC框架,M(odel) V(iew) C(ontroler)之间的联系是必不可少的,今天要说的就是View(视图) 通常我们在写逻辑代码也好或者是在ui组件也好,都需要跟dom打交道,我们 ...
- 【原创】backbone1.1.0源码解析之Collection
晚上躺在床上,继续完成对Backbone.Collection的源码解析. 首先讲讲它用来干嘛? Backbone.Collection的实例表示一个集合,是很多model组成的,如果用model比喻 ...
- 【原创】backbone1.1.0源码解析之Events
最近在看些node的源代码,发现backbone的应用还是挺广泛的,但是之前的学习忘得一干二净了,后悔当时没做笔记啊. 所以,无奈想用的更好,就是得把源代码看楚,所以还是把源代码的注释笔记留下来,供自 ...
- solr&lucene3.6.0源码解析(四)
本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...
- solr&lucene3.6.0源码解析(三)
solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...
- Heritrix 3.1.0 源码解析(三十七)
今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...
- solr&lucene3.6.0源码解析(二)
上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...
- solr&lucene3.6.0源码解析(一)
本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建 首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...
- apache mina2.0源码解析(一)
apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...
随机推荐
- WebService技术,服务端and客户端JDK-wsimport工具(一)
使用webservice服务,需要了解几个名词:soap 简单对象协议.http+xml . WSDL 先看下代码结构: 服务端代码与客户端代码分别处于两不同的包中 一.服务端内容 服务端: @Web ...
- c语言数字图像处理(三):仿射变换
仿射变换及坐标变换公式 几何变换改进图像中像素间的空间关系.这些变换通常称为橡皮模变换,因为它们可看成是在一块橡皮模上印刷一幅图像,然后根据预定的一组规则拉伸该薄膜.在数字图像处理中,几何变换由两个基 ...
- 前端常见算法面试题之 - 二维数组中的查找[JavaScript解法]
--------------------- 作者:吴潇雄 来源:CSDN 原文:https://blog.csdn.net/weixin_43439741/article/details/835118 ...
- Hyperledger Fabric网络节点架构
Fabric区块链网络的组成  区块链网络结构图 区块链网络组成 组成区块链网络相关的节点 节点是区块链的通信主体,和区块链网络相关的节点有多种类型:客户端(应用).Peer节点.排序服务(Orde ...
- PAT甲题题解-1072. Gas Station (30)-dijkstra最短路
题意:从m个加油站里面选取1个站点,使得其离住宅的最近距离mindis尽可能地远,并且离所有住宅的距离都在服务范围ds之内.如果有很多相同mindis的加油站,输出距所有住宅平均距离最小的那个.如果平 ...
- javascript 数组对象及其方法
数组声明:通过let arr = new Array(); 或者 let arr = []; 数组对象可调用的方法: 1)find方法,使用情况是对数组进行筛选遍历,find方法要求某个函数(A)作为 ...
- 老李的blog使用日记(3)
匆匆忙忙.碌碌无为,这是下一个作业,VS,多么神圣高大上,即使这样,有多少人喜欢你就有多少人烦你,依然逃不了被推销的命运,这抑或是它喜欢接受的,但是作为被迫接受者,能做的的也只有接受,而已. 既来之则 ...
- Alpha冲刺第8天
Alpha第8天 1.团队成员 郑西坤 031602542 (队长) 陈俊杰 031602504 陈顺兴 031602505 张胜男 031602540 廖钰萍 031602323 雷光游 03160 ...
- Linux命令(六) 查看文件 cat tac more less tail
如果要查看文件,使用 cat less tac tail 和 more 中的任意一个即可. 1.cat 使用 cat 命令查看文件时会显示整个文件的内容,注意cat只能查看文本文件的内容,如 ...
- [转帖]Edge投降Chromium!微软王牌浏览器是如何跪倒的
Edge投降Chromium!微软王牌浏览器是如何跪倒的 https://tech.sina.com.cn/n/k/2018-12-17/doc-ihmutuec9824604.shtml 谷 ...