发布于 2016-04-12 11:40:48 | 185 次阅读 | 评论: 0 | 来源: 分享
这里有新鲜出炉的Backbone.JS中文API手册,程序狗速度看过来!
Backbone.js JavaScript的MVC 应用框架
Backbone 为复杂Javascript应用程序提供模型(models)、集合(collections)、视图(views)的结构。其中模型用于绑定键值数据和自定义事件;集合附有可枚举函数的丰富API; 视图可以声明事件处理函数,并通过RESTful JSON接口连接到应用程序。
在传统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的构造函数主要做了以下几件事:
接下来是一个重要的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方法做的几件事:
接下来是与后端打交道的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的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方法做的事情:
与验证相关的_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;
}