Backbone.js是前端的MVC框架,它通过提供模型Models、集合Collection、视图Veiew赋予了Web应用程序分层结构。从源码中可以知道,Backbone主要分了以下几个模块:

(function(root) {
Backbone.Events //自定义事件机制
Backbone.Model //模型
Backbone.Collection //模型集合
Backbone.Router //路由配置器
Backbone.View //视图
Backbone.sync //向服务器同步数据方法
})(this)

自己主要阅读了Events、Model、Collection、sync这几个模块,所以对这几个模块进行介绍。

Events模块

//Backbone的事件对象
var Events = Backbone.Events = { //事件订阅函数
//name:事件名
//callback:事件回调函数对象
//context:事件上下文
on: function(name, callback, context) {
//eventsApi的作用请看下方eventsApi方法的注释
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
//_events对象用于存储各个事件的回调函数对象列表
//_events对象中的属性名为事件名称,而属性值则为一个保护函数对象的对象数组
this._events || (this._events = {});
var events = this._events[name] || (this._events[name] = []);
//将包含回调函数的对象添加到指定事件的回调函数列表,即注册事件
events.push({callback: callback, context: context, ctx: context || this});
return this;
}, //取消事件订阅
off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
//当name,callback,context都没指定时,取消订阅所有事件
if (!name && !callback && !context) {
this._events = void 0;
return this;
} //未指定name时,则取所有的事件name
names = name ? [name] : _.keys(this._events);
//对每个包含回调函数的对象进行筛选,不符合指定参数条件的进行保留
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
if (events = this._events[name]) {
this._events[name] = retain = [];
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
//保留
retain.push(ev);
}
}
}
if (!retain.length) delete this._events[name];
}
} return this;
}, //触发事件
//name: 触发的事件名
trigger: function(name) {
if (!this._events) return this;
//参数列表,不包含name
var args = slice.call(arguments, 1);
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
var allEvents = this._events.all;
//触发事件,即调用相应的回调函数
if (events) triggerEvents(events, args);
//任何事件触发时,all事件都会被触发
if (allEvents) triggerEvents(allEvents, arguments);
return this;
} }; //该函数的主要作用:
//当指定事件名为object对象时,将object对象中key作为事件名
//将obejct中的value作为回调函数对象,然后递归调用on、off、trigger
//当指定的事件名为string,但包含空格时,将string按空格切割,再依次递归调用
//该函数需要对应的看它是如何被调用的,直接看是比较难明白的
//当时我就看了好久没明白,函数名取为eventsApi对我一点帮助也没用。。
var eventsApi = function(obj, action, name, rest) {
if (!name) return true; //当指定的事件名为object时
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
} // 当指定的事件名包含空格时
//eventSplitter = /\s+/;
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
} return true;
}; //调用事件回调函数的函数
//可能是为了性能问题,才使用了switch,而不是直接使用default中的代码
//但我不太明白,这样为什么会提高效率,希望高人解答
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
}
};

上面的代码就为Events的核心部分,我们可以从中得知:

在Events中维护了一个_events对象,而_events对象中的属性名就代表了一个事件,属性值为一个数组,数组中的元素为包含了注册的回调函数的对象。所以当我们调用on('click', callback, ctx)时,实际上是做了这样的操作:this._events['click'].push({ callback: callback, context: ctx });
Events模块还提供了once、listenTo、stopListening、listenToOnce等有用的方法,但它们都是基于on、off方法实现的,所以这边就不多说了。
另外多说几句:因为javascript的函数也为对象,是一等公民,所以可以方便的通过维护函数对象列表来实现事件机制而不是通过设计模式里的观察者模式来实现,c#中的事件机制也是如此(基于委托)。

Model模块

在Backbone中Model是一个构造函数

var Model = Backbone.Model = function(attributes, options) {
var attrs = attributes || {};
options || (options = {});
this.cid = _.uniqueId('c');
//存储相应model所应具有的属性
this.attributes = {};
if (options.collection) this.collection = options.collection;
//解析attrs,默认直接返回attrs
if (options.parse) attrs = this.parse(attrs, options) || {};
attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
//设置model相应的属性
this.set(attrs, options);
this.changed = {};
//用于初始化model的函数,需要使用者自己指定
this.initialize.apply(this, arguments);
};

随后我们可以看到Model函数的原型扩展,需要注意的是Events对象被拓展到了Model的原型中,这样model对象也就有了事件机制:

//将Events和指定对象扩展至Model的原型中
_.extend(Model.prototype, Events, { //该方法用于向服务端同步数据(增、删、改)
//该方法默认调用的是Backbone.sync方法(ajax)
//我们可以通过替换Backbone.sync来使用我们自己的sync方法,比如mongodb,这样backbone也能
//用于Node.js后端
sync: function() {
return Backbone.sync.apply(this, arguments);
}, //获取model的属性值
get: function(attr) {
return this.attributes[attr];
}, //设置model的属性,当属性值发生变化时,触发'change'事件
//该方法为Model的核心
set: function(key, val, options) {
var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this; //让set方法可以这样调用set({key: value ....}, options);
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
} options || (options = {}); //验证设置的属性是否符合要求,_validate方法内部将会调用validate方法
//validate方法需要model使用者自己指定
//当设置的属性不符合要求时,直接返回false
if (!this._validate(attrs, options)) return false; //表示应当删除属性,而不是设置属性
unset = options.unset;
//当silent为true时,不触发change事件
silent = options.silent;
//变化的属性列表
changes = [];
//表示是否在变化中
//这里我还是有点疑惑
changing = this._changing;
this._changing = true; if (!changing) {
//表示变化前的属性值
this._previousAttributes = _.clone(this.attributes);
//存储改变了的属性和其属性值
this.changed = {};
}
current = this.attributes, prev = this._previousAttributes; if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; //设置model属性值
for (attr in attrs) {
val = attrs[attr];
//当设置的值与当前的model对象的属性值不同时,将要设置的属性的key加入changes列表中
if (!_.isEqual(current[attr], val)) changes.push(attr);
//this.changed存储改变了的属性和其属性值
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
} //触发change事件
if (!silent) {
if (changes.length) this._pending = options;
//触发'change:变更的属性名'事件
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
} if (changing) return this;
if (!silent) {
//触发'change'事件,这里使用while,是因为change事件也有可能会调用set方法
//所以需要递归的调用
while (this._pending) {
options = this._pending;
this._pending = false;
this.trigger('change', this, options);
}
}
this._pending = false;
this._changing = false;
return this;
}
});

在这里我列出来的3个方法:

首先是sync方法,它用于与服务器端同步,model中的create、update、fetch、destory方法都是通过调用它来跟服务端交付,backbone默认实现的sync就是通过ajax与服务端交付,所以我认为,如果我们将sync替换为直接与sqlite、mongodb交付,这样backbone的Model也能用于服务器端了。

第二个是get方法:这个方法很简单,获取model的属性值(model的属性值是存在于model.attributes对象中)

第三个是set方法:该方法是model的核心,它用于设置model的属性值,首先调用this.validate方法(使用者需指定)验证属性值是否符合业务要求,之后对属性值一一设置,对于改变了的属性值触发'change:key'事件(没有指定silent)。最后再触发change事件。

Collection模块

Collection是model对象的有序集合(你可以指定它的comparator属性来进行相应的排序),它内部维护了一个model数组,它提供了集合的增删改查、排序操作 ,也使用了sync方法与服务器端同步。Collection也将Events包含到了自身当中。

Collection中最强大方法就是set,你可以使用它进行增加、删除、修改:

set: function(models, options) {
//other code..... var add = options.add, merge = options.merge, remove = options.remove;
var order = !sortable && add && remove ? [] : false; //迭代参数models,对于其中每个model进行相应的操作
for (i = 0, l = models.length; i < l; i++) {
attrs = models[i] || {};
if (attrs instanceof Model) {
id = model = attrs;
} else {
id = attrs[targetModel.prototype.idAttribute || 'id'];
} //如果集合中已经存在该对象,则是进行删除或者修改操作
if (existing = this.get(id)) {
//进行删除操作,记录下需要删除的对象
if (remove) modelMap[existing.cid] = true;
//进行修改操作
if (merge) {
attrs = attrs === model ? model.attributes : attrs;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
}
models[i] = existing; //否则对集合进行添加操作,记录下应该被添加的对象
} else if (add) {
model = models[i] = this._prepareModel(attrs, options);
if (!model) continue;
toAdd.push(model);
this._addReference(model, options);
}
if (order) order.push(existing || model);
} //根据之前的记录下的应删除的对象,删除集合中相应的对象
if (remove) {
for (i = 0, l = this.length; i < l; ++i) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
}
//删除集合中相应的对象,并触发remove事件
if (toRemove.length) this.remove(toRemove, options);
} //进行添加操作
if (toAdd.length || (order && order.length)) {
if (sortable) sort = true;
this.length += toAdd.length;
//添加到指定位置,默认添加到末尾
if (at != null) {
for (i = 0, l = toAdd.length; i < l; i++) {
this.models.splice(at + i, 0, toAdd[i]);
}
//说实话,我不太明白这段代码
} else {
if (order) this.models.length = 0;
var orderedModels = order || toAdd;
for (i = 0, l = orderedModels.length; i < l; i++) {
this.models.push(orderedModels[i]);
}
}
} //当进行了添加或修改操作,并且可以排序时,则对集合进行排序
if (sort) this.sort({silent: true}); if (!options.silent) {
for (i = 0, l = toAdd.length; i < l; i++) {
//触发add事件
(model = toAdd[i]).trigger('add', model, this, options);
}
//触发排序事件
if (sort || (order && order.length)) this.trigger('sort', this, options);
} return singular ? models[0] : models;
}

我列出了set方法中的大部分代码,它根据指定的参数进行添加 删除 修改操作,并进行排序。而我个人感觉这样不是太好,因为一个方法做了太多的事情,有点array.splice的味道,使得整个方法的代码十分冗长,也变得不易理解。。。我比较菜,看这个看了好久。。

Collection虽然提供了set,但它还是提供了add(内部调用set)、reset(内部调用set)、remove方法。Collection还跟Model一样提供了sync方法,用于和服务端同步数据。

最后,Collection还提供了underscore.js库对集合的操作方法,它们都是调用underscore库实现的方法。

Sync模块

sync是Backbone用于同步服务端数据的方法,它的默认实现:

Backbone.sync = function(method, model, options) {
var type = methodMap[method]; //some init code.... //默认使用json格式
var params = {type: type, dataType: 'json'}; if (!options.url) {
params.url = _.result(model, 'url') || urlError();
} //将请求的数据类型设置为json
//将对象格式化为json数据
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
} //some for old server code....
//and some for ie8 code // ajax请求
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
}

上面就是它的主要代码,我将一部分有关兼容性的代码给移除了。

阅读源码的收获:

因为自己接触js不久,就想去看看一个优秀的js项目是如何写的,所以就选择了backbone这个相对比较轻量级的框架。当然,因为自己水平有限,加上写的js代码也不多,不能很好领悟backbone的设计思想,也不能很好的指出backbone有什么不足的地方,但我还是有一些收获:

1.学到了js中的一些使用技巧,比如使用||操作符 model || model = {},还有如何利用参数在代码中实现类似重载的行为(js函数本身没有重载)

2.对this变量的绑定有了更好的理解

3.相对于c#而言,js是一门弱类型的动态语言,所以对一个对象的扩展要灵活多

4.在c#中,如果我需要去提高模块的可扩展性,我可能要利用接口利用多态去实现,但js则就轻松的多,我只需暴露一个属性接口即可,因为我可以轻松的替换他,就像Backbone.sync一样,但带来的缺点就是如果你的sync方法并不符合设计,你只会在运行时发现错误,而不是编译时

参考资料:https://github.com/jashkenas/backbone/blob/master/backbone.js

Backbone源码阅读手记的更多相关文章

  1. 【 js 基础 】【 源码学习 】backbone 源码阅读(一)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

  2. 【 js 基础 】【 源码学习 】backbone 源码阅读(二)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 ...

  3. 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

  4. 【 js 基础 】【 源码学习 】backbone 源码阅读(三)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

  5. goroutine调度源码阅读笔记

    以下为本人阅读goroutine调度源码随手记的笔记,现在还是一个个知识点的形式,暂时还没整理,先发到这里,一点点更新:   1). runq [256]guintptr P 的runable队列最大 ...

  6. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  7. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  8. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  9. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

随机推荐

  1. 标题栏Menu

    标题栏menu就是指下图中红框里面的菜单按钮. 标题栏上所有的按钮或者其他元素都定义在xml文件里面,这些文件资源称为menu resource.要在标题栏添加按钮,需要在项目的/res/menu/路 ...

  2. This text field does not specify an inputType or a hint

    android开发过程中突然发现的warning,EditText 报出 “This text field does not specify an inputType or a hint”   原因: ...

  3. 使用github之前的技能准备

    Git的导入 介绍 Git属于分散型版本管理系统,是为版本管理而设计的软件.版本管理就是管理更新的历史记录.它为我们提供了一些在软件开发过程中必不可少的功能,例如记录一款软件添加或更改源代码的过程,回 ...

  4. [django]django+datatable简单运用于表格中

    使用datatable首先需要下载datatable文件,文件主要包括三个文件夹css,img,js相关文件,在django中如何配置呢? 首先需要在模板中引入datatable文件,格式如下: &l ...

  5. 读 [The Root of Lisp]

    首先,在对 Lisp 有一丢丢的了解下读这篇文章会大大激发你学下去的欲望.之后可以看作者的著作<ANSI Common Lisp>. 想要体会一下 Lisp 的强大,本文是不二之选. Co ...

  6. BZOJ 1061: [Noi2008]志愿者招募 [单纯形法]【学习笔记】

    1061: [Noi2008]志愿者招募 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 3975  Solved: 2421[Submit][Stat ...

  7. [No00006B]方便的网络下载工具wget 可下载网站目录下的所有文件(可下载整个网站)

    wget是linux下命令行的下载工具,功能很强大,它能完成某些下载软件所不能做的,比如如果你想下载一个网页目录下的所有文件,如何做呢?网络用户有时候会遇到需要下载一批文件的情况,有时甚至需要把整个网 ...

  8. web端功能测试总结(二)

    一.输入框测试 在web测试中,我们经常遇到这种输入框的测试,输入框测试看似简单,实际上包含了很多的测试基本的方法,思考逻辑,下面就是我总结出来的一些注意点: 1)验证输入输出信息的一致性 2)输入框 ...

  9. C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 - 面向全国标准省市县行政数据基础之上的组织机构管理

    由于信息系统庞大.各种业务子系统.各种开发语言开发的业务逻辑.各种年代维护的代码.各种参差不齐的历史遗留信息系统,面向全国的业务系统,面向某个领域的汽运管理信息系统,面向内部的业务系统,面向外部的各种 ...

  10. 图像旋转 OpenCV实现

    经常对一幅图像进行旋转操作,OpenCV中提供了很方便易用的仿射变换函数warpAffine, 通过getRotationMatrix2D可以得到放射变换矩阵(矩阵大小2x3) #include &l ...