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. 3. Python 简介

    3. Python 简介 下面的例子中,输入和输出分别由大于号和句号提示符 ( >>> 和 ... ) 标注:如果想重现这些例子,就要在解释器的提示符后,输入 (提示符后面的) 那些 ...

  2. [转]ArcIMS 中地图坐标参考设置(ArcGIS Unknown Spatial Reference)

    "ArcGIS Unknown Spatial Reference"问题: shp文件在Arcgis打开后经常因为原有坐标系无法识别而丢失信息,出现以下提示信息: "Un ...

  3. Permutations

    Permutations Given a collection of distinct numbers, return all possible permutations. For example,[ ...

  4. gradle项目中profile的实现

    gradle中并没有直接类似maven中的profile支持,只能变通的用其它方法来处理,在打包不同环境的应用时,通常会遇到二类问题: 一.不同的环境依赖的jar包不同 拿web开发来说,生产环境一般 ...

  5. oracle的decode函数在mysql的实现

    oracle中的decode函数很好用,换成mysql中可以用类似下面的方法实现: SELECT IF(TRUE, '真值', '假值'); 如果想再弄复杂点,可以多个IF嵌套,不过嵌套的层次多了,代 ...

  6. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  7. Computer vision labs

    积累记录一些视觉实验室,方便查找 1.  多伦多大学计算机科学系 2.  普林斯顿大学计算机视觉和机器人实验室 3.  牛津大学Torr Vision Group 4.  伯克利视觉和学习中心 Pro ...

  8. IT职场人的“存在主义”

      人生在世,最重要的一条就是:找准你的位置.也就是,你的定位问题. 就在前两天,参加一次社交活动,一知名培训师回顾过往,感慨地说:一个好的培训师,一定要定位好自己的客户群,根据他们的需求做好自己的定 ...

  9. canvas弹动效果

    弹动效果,用物体与目标的距离乘上系数再累加至速度上,让物体呈加速度运动,再让速度乘与摩擦力系数,让物体最终停止运动 代码如下所示 var canvas = document.getElementByI ...

  10. maven 打包

    使用命令行形式打包 1.配置maven环境变量,在变量path中加入maven路径. 2.在要打包的项目目录下使用:Ctrl+shift+鼠标右键点击,点击 在此处打开命令行窗口. 在打开的命令行窗口 ...