Backbone事件模块源码分析

事件模块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);
时间: 2024-10-04 04:02:55

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

shiro登录模块源码分析

接触和使用shiro还是有好大一段时间,可惜并没有搞明白其实的原理和机制.因为工作中使用的框架都封装好了,so......并没有去研究. 原来一直猜想的是shiro可以直接连接数据库,验证用户名和密码.但是又没在实体类中找到有什么特殊的标记注解之类的. 就有点好奇了,于是趁这两天工作不是那么紧张了,就跟了下源码. 如有不对,请留言指正.蟹蟹! 红色部分是主要代码: UsernamePasswordToken token = new UsernamePasswordToken(user.getUs

memcache(二)事件模型源码分析

memcache事件模型 在memcache中,作者为了专注于缓存的设计,使用了libevent来开发事件模型.memcache的时间模型同nginx的类似,拥有一个主进行(master)以及多个工作者线程(woker). 流程图 在memcache中,是先对工作者线程进行初始化并启动,然后才会创建启动主线程. 工作者线程 初始化 memcache对工作者线程进行初始化,参数分别为线程数量以及`main_base`, /* start up worker threads if MT mode *

5.2.4.最简单的模块源码分析3

printk:printk内核态,printf用户态(没什么用) 打印级别内核把级别比命令行低的所有消息显示在终端(console)上.但是所有信息都会记录在printk的"ring buffer"中. cat /proc/sys/kernel/printk(4 4 1 7)(比4小的能显示) echo 4 > /proc/sys/kernel/printk(更改级别) 但是Ubuntu中中不管级别是怎样,都不能显示,必须dmesg日志等级: 头文件: include <l

5.2.2.最简单的模块源码分析1

本节主要讲解模块安装和过程,及其和module_init宏的关联,并且重点讲解了模块安装时的安全性兼容性校验问题. 5.2.2.1.常用的模块操作命令 * lsmod(list module):打印当前内核中已经安装的模块 [email protected]:~$ lsmodModule Size Used byvmhgfs 48609 1snd_ens1371 24547 2snd_ac97_codec 105709 1 snd_ens1371 * modinfo(module info):打

(二)最简单的模块源码分析

一.常用的模块操作命令(1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 (2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息.,用法是modinfo xxx.ko(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用

5.2.3.最简单的模块源码分析2

本节主要讲解了模块的卸载过程.MODULE_LICENSE等信息添加宏和__init.__exit宏. static 声明函数,全局变量,则它们不能被其它文件函数调用 声明静态局部变量,具有记忆功能.它与其他普通局部变量区别:static局部变量只被初始化一次,下一次的运算依据上一次的结果值. __init:一个宏定义,__cold notrace标记内核启动时所用的初始化代码,内核启动完成后就不再使用.__section(.init.text)其修饰的内容放到.init.text段中 defi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

Android ViewGroup触摸屏事件派发机制详解与源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 该篇承接上一篇<Android View触摸屏事件派发机制详解与源码分析>,阅读本篇之前建议先阅读. 1 背景 还记得前一篇<Android View触摸屏事件派发机制详解与源码分析>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事

Android View触摸屏事件派发机制详解与源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 1.背景 最近在简书和微博还有Q群看见很多人说Android自定义控件(View/ViewGroup)如何学习?为啥那么难?其实答案很简单:"基础不牢,地动山摇." 不扯蛋了,进入正题.就算你不自定义控件,你也必须要了解Android控件的触摸屏事件传递机制(之所以说触摸屏是因为该