Backbone在流行的前端框架中是最轻量级的一个,所有代码实现一共只有1831行1。从前端的入门再到Titanium,我虽然几回和Backbone打交道可是却对它的结构知之甚少,也促成了我想读它的代码的原始动力。这个系列的文章主要目的是分享Backbone框架中能够用于平常开发的实践参考,力求可以简明扼要的展示Backbone Modal, Controller和Sync这些核心内容,但愿可以对你们学习和使用Backbone有一些帮助。前端
这个系列的文章将包括如下3篇内容:后端
本文是它的第一篇《Backbone源码解读之Events实现》数组
Backbone的Event是整个框架运转的齿轮。它的优美之处在于它是Backbone的一个基础方法,经过_.extend的方法Mixin到Backbone的每个模块中。前端框架
//Model _.extend(Model.prototype, Events, { changed: null, //other Model prototype methods //... }
事件的管理是一个绑定在Events这个命名空间中的_events对象来实现的。
它的结构是name: [callback functions]的key-callback_array键值对。架构
this._events = { change: [callback_on_change1, callback_on_change2, ....], .... }
当事件发生的时候,event从这个对象中根据事件的名称取得回调函数数组,而后循环执行每一个回调函数,也就说明了为何屡次绑定会重复触发屡次事件。app
Event包括on, off, trigger三个基础方法,其他的全部方法均是对它们的扩展。框架
on接受3个参数,包括事件的名称,回调函数和回调函数执行的上下文环境。其中context是可选参数,若是你不是很熟悉JS的执行上下文环境能够暂时不用管它。异步
抛开全部Backbone的花哨的检查,执行on的操做本质就是向_events中name对应的回调函数数组[callback functions]中Push新的函数。
简单来讲代码实现就是这个样子:函数
Events.on = function(name, callback, context) { if (callback) { var handlers = events[name] || (events[name] = []); handlers.push({callback: callback, context: context, ctx: context || this}); } return this; };
至于你在看源代码的时候会长不少,那是由于一方面Backbone要处理关于_events以及_events[name]未初始化的两种特殊状况。另外一方面eventsApi,onApi这些方法是为了处理on时候你传入的不是一个string类型的名称和一个callback函数所作的条件处理。
例以下面两种方法都是合法的:学习
//传入一个名称,回调函数的对象 model.on({ "change": on_change_callback, "remove": on_remove_callback }); //使用空格分割的多个事件名称绑定到同一个回调函数上 model.on("change remove", common_callback);
可是核心其实都是同一个绑定函数。
值得注意的一点是因为Backbone接受all做为name的参数,而且将回调函数保存在_events.all中,关于它的执行详细能够参考trigger。
与on不一样,off的3个参数都是可选的。
若是没有任何参数的时候,off至关于把对应的_events对象总体清空。
if (!name && !callback && !context) { this._events = void 0; return this; }
若是有name参数可是没有具体清除哪一个callback的时候,则把_events[name]对应的内容所有清空。
if (!callback && !context) { delete this._events[name]; continue; }
若是还有进一步详细的callback和context的状况下,则进入[callback functions]中进行检查,移除具体一个回调函数的条件很是严苛,必需要求上下文和函数与原来彻底一致,也就是说若是传入的callback或者context不是原有on对象的引用,而是复制的话,这里的off是无效的。
var remaining = []; if( callback && callback !== handler.callback && callback !== handler.callback._callback || context && context !== handler.context ){ //保留回调函数在数组中 }
trigger取出name对应的_events[name]以及_event.all中的callback函数。
须要注意的一点是,触发对应名称的callback和all的callback使用了不同的参数,all的参数中还包含了当前事件的名称。
//当绑定3个如下回调函数的时候Backbone会作以下优化处理,听说这样是能够提升执行效率的。 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); return; } };
从使用上来说,对一个对象进行事件的on绑定。而后在同一个对象的其余函数执行过程当中,或者其余的对象中,触发该对象的trigger方法和对应的事件名称来执行其上绑定的callback函数。
接下来再看看Event中进一步定义了哪些其余辅助函数
效果相同与on,不过对应的callback函数仅执行一次。固然,你一样能够在once绑定的回调函数执行前手动经过off将其移除。
来看看Once的实现,因为添加了执行过程当中的移除方法,once在实际实行on的时候使用了以下匿名函数:
var once = _.once(function() {function(){ self.off(name, once); callback.apply(this, arguments); }); return this.on(name, once, context);
可是细心的你必定发现,保存在_event数组中的函数是once这个匿名函数了。可是用户并不知道Backbone的这些操做,在取消绑定时仍然会使用原来的回调函数来试图解除绑定。上面咱们也提到,必须使用彻底一致的函数才可以取消绑定,那么为何还可以成功呢?
这里Backbone作了一个小小的操做,不知道你有没有注意到上面off函数中有这样一行内容?
callback !== handler.callback._callback
既然callback是咱们传入的回调函数,那么哪里来的_callback这个属性呢?答案就在once里面。
var once = _.once(function() {...取消绑定,执行callback); once._callback = callback;
也就是Backbone在返回以前悄悄的为once这个函数添加了一个_callback的属性,用来保存原来的回调函数,这样用户在传入原来的回调函数取消绑定的时候,off会检查函数时候有_callback这个属性和用户传入的函数匹配,一样能够取消绑定。
除了将对象自己expose给另外一个对象,让另外一个对象执行trigger方法触发该对象上绑定的event之外。Event还进一步提供了listenTo系列的方法,执行逻辑正好与on相反。
例若有以下要求,当B对象上发生事件b的时候,触发A对象的callbackOnBEvent函数。
// 使用on的状况下 B.on(“b”, A.callbackOnBEvent) // 使用listenTo的状况下 A.listenTo(B, “b”, callbackOnEvent);
从实现上看,它门的区别就在于谁负责管理这个事件。第一个模型中,B就像是整个系统的master,负责事件到达的时候的分发,让不一样的对象(如A)执行对应的方法。第二个模型中,B更像是一个信息栈,A监听B上发生的事件,而且在对应事件到达的时候触发自身相应的回调函数。二者并没有好坏之分,可是从系统架构上来讲由于自己回调函数的上下文环境就是A,因此listenTo的方式可能会来的更加天然,并且由A本身来控制何时移除回调的执行,也可让代码的解耦程度更高。
使用Event方法来处理异步请求让代码的可读性大大增长。若是你的单页面应用刚好使用了Backbone做为前端框架,将Event经过Backbone.Events这个变量暴露出来,你可使用相似Model扩展的方法
//Your object _.extend(your_object.prototype, Backbone.Events, { //other prototype methods //... }
这样你的Object也就具备了彼此绑定事件、触发事件的能力。
即使你的前端并无使用Backbone,因为Events并不依赖Backbone的其余部分实现,你彻底能够将它放到本身的代码lib中,做为一个基础方法来使用。
相似的方式你也能够常常在Node的后端看到
var util = require("util"); var events = require("events"); function MyStream() { events.EventEmitter.call(this); } util.inherits(MyStream, events.EventEmitter);
总之,我我的是很是推荐多多使用Event来替代层级的Callback结构。
根据2015年4月 稳定版本Backbone.js 1.1.2的注释版本。Master上的代码和注释版本稍有出入,哪位大神知道为何吗?? ↩