事件模块Backbone.Events在Backbone中占有十分重要的位置,其他模块Model,Collection,View所有事件模块都依赖它。通过继承Events的方法来实现事件的管理,可以说,它是Backbone的核心组成部分。

此外,事件模块的所有方法都挂在了全局的Backbone上,如果你的代码中需要用到自定义事件(实现观察者模式),可以直接使用它。

所以很有必要一起来研究下Backbone.Events的源码,一来学习Backbone事件模块优秀的写法和思想,二来可以更好的灵活使用Backbone事件模块。

// Backbone.Events
// --------------- // A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
var Events = Backbone.Events = { // Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
on: function(name, callback, context) {
//两种情况:
//1. 一次添加多个事件时,通过eventsApi一个一个添加,所以eventsApi返回false,那么直接return
//2. 回调函数没定义,没有意义,直接return
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
//因此这里往下事件是一个一个添加的,即name是一个事件名(如:click | custom)
//初始化私有变量,用于存储事件的信息
this._events || (this._events = {});
var events = this._events[name] || (this._events[name] = []);
events.push({callback: callback, context: context, ctx: context || this});
return this;
}, // Bind an event to only be triggered a single time. After the first time
// the callback is invoked, it will be removed.
once: function(name, callback, context) {
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
var self = this;
//将callback进行包装,返回的新函数newCallback内部会调用calllback
//不同的是,newCallback只会调用callback一次,之后只会返回callback执行的结果
//也就是说once实质上并没有去除掉事件监听函数,而是控制了callback只会执行一次
var once = _.once(function() {
self.off(name, once);
callback.apply(this, arguments);
});
//保留原callback,用于off操作
once._callback = callback;
//实质调用.on()方法注册事件
return this.on(name, once, context);
}, // Remove one or many callbacks. If `context` is null, removes all
// callbacks with that function. If `callback` is null, removes all
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
//两种情况:
//1. 根本没注册过事件,何谈删除事件,直接return
//2. 像上述所说支持多事件删除
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
//如果是obj.off()这样调用,那么删除该对象obj上所有的事件监听
//也就是是将_events清空了
if (!name && !callback && !context) {
this._events = void 0;
return this;
}
//如果name为空,像obj.off(undefined, cb1, ctx1)
//那么name就为所有注册过的事件(即_.keys(this._events))
names = name ? [name] : _.keys(this._events);
//根据name遍历events
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
if (events = this._events[name]) {
this._events[name] = retain = [];
//如果callback或者context有一个有值
//那么接下来将它们作为条件进行接下来事件的off操作
//实质其实是先清空_events,将不满足条件删除条件的事件监听器重新填入_events中
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
//这里对指定了callback或者context的情况,做了条件判断
//这里的_callback是因为.once方法会对原callback进行包装,这里的evn.callback就是包装后的,原callback保存在_callback中
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
retain.push(ev);
}
}
}
//发现该事件的事件监听器被删光了,那么做了清理工作,删除_events对应的key
if (!retain.length) delete this._events[name];
}
} return this;
}, // Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(name) {
if (!this._events) return this;
//分离出传给callback的参数
var args = slice.call(arguments, 1);
//同样支持多事件同时trigger
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
var allEvents = this._events.all;
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, arguments);
return this;
}, // Tell this object to stop listening to either specific events ... or
// to every object it's currently listening to.
stopListening: function(obj, name, callback) {
var listeningTo = this._listeningTo;
if (!listeningTo) return this;
var remove = !name && !callback;
//这里是兼容(obj, {click: cb1, change: cb2})这种形式
//保证第三个参数是作为context传入,这里是this
if (!callback && typeof name === 'object') callback = this;
//如果有指定obj,那么解除对象只针对obj
//如果没有指定,则是解除监听的所有对象的事件绑定
if (obj) (listeningTo = {})[obj._listenId] = obj;
for (var id in listeningTo) {
obj = listeningTo[id];
obj.off(name, callback, this);
//两种情况下做清理工作
//1. 已经表明清除对obj的的所有事件监听(即name和callback都为空)
//2. obj对象自身都没有被绑定事件了,哪来的事件让你监听呢?
if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
}
return this;
} }; // Regular expression used to split event strings.
var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event
// names `"change blur"` and jQuery-style event maps `{change: action}`
// in terms of the existing API.
var eventsApi = function(obj, action, name, rest) {
if (!name) return true; // Handle event maps.
//支持映射关系
//如:(obj, 'on', {'click': function x () {}, 'blur': function xx () {}}, context)
if (typeof name === 'object') {
for (var key in name) {
//反复调用action(on | off | once), 每次添加一个事件监听,从而达到添加多个。
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
} // Handle space separated event names.
//支持空格分割事件(即多事件共享同一个函数)
//如:(obj, 'on', 'click blur', function () {}, context)
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;
}; // A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
//这里做了个优化,就是如果arg参数在3个之类的话,用call进行调用
//因为call要比apply的效率高(http://jsperf.com/function-versus-function-call-versus-function-apply)
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;
}
}; var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; // Inversion-of-control versions of `on` and `once`. Tell *this* object to
// listen to an event in another object ... keeping track of what it's
// listening to.
//添加listenTo和listernToOnce方法
//实质是:
//1. 给需要监听的对象obj赋予一个_listenId的随机id
//2. 再给监听者(调用对象)添加一个map就是listeningTo属性,添加上述的id和obj
//3. 给obj绑定被监听的事件,将this指向调用者
//这里实质就是调用obj的on或者once方法来添加事件监听,
//那么单独列出这样的一个方法的好处在于方便监听者,可以随时监听和解除监听,上述的1,2两不操作是为了以后解除监听做准备
_.each(listenMethods, function(implementation, method) {
Events[method] = function(obj, name, callback) {
var listeningTo = this._listeningTo || (this._listeningTo = {});
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
listeningTo[id] = obj;
if (!callback && typeof name === 'object') callback = this;
obj[implementation](name, callback, this);
return this;
};
}); // Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off; // Allow the `Backbone` object to serve as a global event bus, for folks who
// want global "pubsub" in a convenient place.
_.extend(Backbone, Events);

Backbone事件模块源码分析的更多相关文章

  1. Zepto事件模块源码分析

    Zepto事件模块源码分析 一.保存事件数据的handlers 我们知道js原生api中要移除事件,需要传入绑定时的回调函数.而Zepto则可以不传入回调函数,直接移除对应类型的所有事件.原因就在于Z ...

  2. Spark Scheduler模块源码分析之TaskScheduler和SchedulerBackend

    本文是Scheduler模块源码分析的第二篇,第一篇Spark Scheduler模块源码分析之DAGScheduler主要分析了DAGScheduler.本文接下来结合Spark-1.6.0的源码继 ...

  3. Spark Scheduler模块源码分析之DAGScheduler

    本文主要结合Spark-1.6.0的源码,对Spark中任务调度模块的执行过程进行分析.Spark Application在遇到Action操作时才会真正的提交任务并进行计算.这时Spark会根据Ac ...

  4. nginx健康检查模块源码分析

    nginx健康检查模块 本文所说的nginx健康检查模块是指nginx_upstream_check_module模块.nginx_upstream_check_module模块是Taobao定制的用 ...

  5. ApplicationEvent事件机制源码分析

    <spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后做些事情> <服务网关zu ...

  6. Django(51)drf渲染模块源码分析

    前言 渲染模块的原理和解析模块是一样,drf默认的渲染有2种方式,一种是json格式,另一种是模板方式. 渲染模块源码入口 入口:APIView类中dispatch方法中的:self.response ...

  7. Fabric2.2中的Raft共识模块源码分析

    引言 Hyperledger Fabric是当前比较流行的一种联盟链系统,它隶属于Linux基金会在2015年创建的超级账本项目且是这个项目最重要的一个子项目.目前,与Hyperledger的另外几个 ...

  8. Django(48)drf请求模块源码分析

    前言 APIView中的dispatch是整个请求生命过程的核心方法,包含了请求模块,权限验证,异常模块和响应模块,我们先来介绍请求模块 请求模块:request对象 源码入口 APIView类中di ...

  9. Django(49)drf解析模块源码分析

    前言 上一篇分析了请求模块的源码,如下: def initialize_request(self, request, *args, **kwargs): """ Retu ...

随机推荐

  1. BZOJ1853 [Scoi2010]幸运数字

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...

  2. css选择器([class*=" icon-"], [class^=icon-] 的区别)

    官方解释: [attribute^=value],a[src^="https"],选择其 src 属性值以 "https" 开头的每个 <a> 元素 ...

  3. css3中-moz、-ms、-webkit,-o分别代表的意思,以及微信浏览器内核分析

    这种方式在业界上统称:识别码.前缀 //-ms代表[ie]内核识别码 //-moz代表火狐[firefox]内核识别码 //-webkit代表谷歌[chrome]/苹果[safari]内核识别码 // ...

  4. genymotion访问电脑的localhost

    用来进行android测试时使用genymotion,genymotion是运行在virtualbox中的,virtualbox为两者建立了连接,在linux下通过ifconfig可以看到有一个叫做v ...

  5. PostgreSQL Reading Ad Writing Files、Execution System Instructions Vul

    catalog . postgresql简介 . 文件读取/写入 . 命令执行 . 影响范围 . 恶意代码分析 . 缓解方案 1. postgresql简介 PostgreSQL 是一个自由的对象-关 ...

  6. 在浏览器中直接生成 PDF

    well, 如果有人在准备开发一个浏览器端的 PDFCreator,那么,你需要知道的是,已经有人在这么做了.不过,他的做法显然更靠谱一些——先开发一个可以将基本元素放入 PDF 的库.太简单?no! ...

  7. POJ3258 River Hopscotch

    地址 别人的代码,自己边界总是控制不好,还不知道哪里错了!思维!这种问题代码越简洁反而越不容易错吧.. #include<stdio.h> #include<algorithm> ...

  8. C++ 之 const references

    extraction from The C++ Programming Language 4th. ed., Section 7.7 References, Bjarne Stroustrup To ...

  9. AngularJs $templateCache 和 $templateRequest 模板缓存

    $templateCache 第一次使用模板,它被加载到模板缓存中,以便快速检索.你可以直接将模板标签加载到缓存中,或者通过$templateCache服务. 通过script标签: <scri ...

  10. POJ 1330 Nearest Common Ancestors(Targin求LCA)

    传送门 Nearest Common Ancestors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 26612   Ac ...