趁热打铁,将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的更多相关文章

  1. 【原创】backbone1.1.0源码解析之View

    作为MVC框架,M(odel)  V(iew)  C(ontroler)之间的联系是必不可少的,今天要说的就是View(视图) 通常我们在写逻辑代码也好或者是在ui组件也好,都需要跟dom打交道,我们 ...

  2. 【原创】backbone1.1.0源码解析之Collection

    晚上躺在床上,继续完成对Backbone.Collection的源码解析. 首先讲讲它用来干嘛? Backbone.Collection的实例表示一个集合,是很多model组成的,如果用model比喻 ...

  3. 【原创】backbone1.1.0源码解析之Events

    最近在看些node的源代码,发现backbone的应用还是挺广泛的,但是之前的学习忘得一干二净了,后悔当时没做笔记啊. 所以,无奈想用的更好,就是得把源代码看楚,所以还是把源代码的注释笔记留下来,供自 ...

  4. solr&lucene3.6.0源码解析(四)

    本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...

  5. solr&lucene3.6.0源码解析(三)

    solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...

  6. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

  7. solr&lucene3.6.0源码解析(二)

    上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...

  8. solr&lucene3.6.0源码解析(一)

      本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建   首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...

  9. apache mina2.0源码解析(一)

    apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...

随机推荐

  1. WebService技术,服务端and客户端JDK-wsimport工具(一)

    使用webservice服务,需要了解几个名词:soap 简单对象协议.http+xml . WSDL 先看下代码结构: 服务端代码与客户端代码分别处于两不同的包中 一.服务端内容 服务端: @Web ...

  2. c语言数字图像处理(三):仿射变换

    仿射变换及坐标变换公式 几何变换改进图像中像素间的空间关系.这些变换通常称为橡皮模变换,因为它们可看成是在一块橡皮模上印刷一幅图像,然后根据预定的一组规则拉伸该薄膜.在数字图像处理中,几何变换由两个基 ...

  3. 前端常见算法面试题之 - 二维数组中的查找[JavaScript解法]

    --------------------- 作者:吴潇雄 来源:CSDN 原文:https://blog.csdn.net/weixin_43439741/article/details/835118 ...

  4. Hyperledger Fabric网络节点架构

    Fabric区块链网络的组成  区块链网络结构图 区块链网络组成 组成区块链网络相关的节点 节点是区块链的通信主体,和区块链网络相关的节点有多种类型:客户端(应用).Peer节点.排序服务(Orde ...

  5. PAT甲题题解-1072. Gas Station (30)-dijkstra最短路

    题意:从m个加油站里面选取1个站点,使得其离住宅的最近距离mindis尽可能地远,并且离所有住宅的距离都在服务范围ds之内.如果有很多相同mindis的加油站,输出距所有住宅平均距离最小的那个.如果平 ...

  6. javascript 数组对象及其方法

    数组声明:通过let arr = new Array(); 或者 let arr = []; 数组对象可调用的方法: 1)find方法,使用情况是对数组进行筛选遍历,find方法要求某个函数(A)作为 ...

  7. 老李的blog使用日记(3)

    匆匆忙忙.碌碌无为,这是下一个作业,VS,多么神圣高大上,即使这样,有多少人喜欢你就有多少人烦你,依然逃不了被推销的命运,这抑或是它喜欢接受的,但是作为被迫接受者,能做的的也只有接受,而已. 既来之则 ...

  8. Alpha冲刺第8天

    Alpha第8天 1.团队成员 郑西坤 031602542 (队长) 陈俊杰 031602504 陈顺兴 031602505 张胜男 031602540 廖钰萍 031602323 雷光游 03160 ...

  9. Linux命令(六) 查看文件 cat tac more less tail

    如果要查看文件,使用 cat  less  tac   tail  和 more 中的任意一个即可. 1.cat 使用 cat 命令查看文件时会显示整个文件的内容,注意cat只能查看文本文件的内容,如 ...

  10. [转帖]Edge投降Chromium!微软王牌浏览器是如何跪倒的

    Edge投降Chromium!微软王牌浏览器是如何跪倒的   https://tech.sina.com.cn/n/k/2018-12-17/doc-ihmutuec9824604.shtml   谷 ...