• 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);
  • 相关阅读:
    Javascript 操作select控件大全(新增、修改、删除、选中、清空、判断存在等) 转载
    FCKeditor 2.6 + Asp.Net 设置
    19个常用.net webservice服务集合
    ASP.NET Session无法使用或易丢失
    javascript实现缩略图
    Asp.net Excel导入或导出
    smarty截取中文乱码问题解决办法转载
    Git学习
    Android活动(Activity)的基本介绍
    Android活动(Activity)Toast和Menu
  • 原文地址:https://www.cnblogs.com/fengyuqing/p/backbone_events_code_analysis.html
Copyright © 2020-2023  润新知