Backbone事件实现浅析

Backbone中Events的中只有3个方法,分别是on, off, trigger,十分清晰,也没有其他依赖,下面我们来分析一下。

 

1. 绑定方法:on

    // Bind an event, specified by a string name, `ev`, to a `callback`
    // function. Passing `"all"` will bind the callback to all events fired.
    on: function(events, callback, context) {
      var ev;
      events = events.split(/\s+/);
      var calls = this._callbacks || (this._callbacks = {});
      while (ev = events.shift()) {
        // Create an immutable callback list, allowing traversal during
        // modification.  The tail is an empty object that will always be used
        // as the next node.
        var list  = calls[ev] || (calls[ev] = {});
        var tail = list.tail || (list.tail = list.next = {});
        tail.callback = callback;
        tail.context = context;
        list.tail = tail.next = {};
      }
      return this;
    },

初看这段代码可能在没有用笔做一些推导的情况下没法理出结构,没关系,我们来实践一下推出它生成的对象结构。

 

 

实验代码:

var obj = {};
// 此处为了看清结构,所以用字符串代替function传入
Backbone.Events.on.call(obj , 'happy', 'happy1');
Backbone.Events.on.call(obj , 'happy', 'happy2');
Backbone.Events.on.call(obj , 'sad', 'sad1');
Backbone.Events.on.call(obj , 'sad', 'sad2');

console.log(obj);

 

 

得到下面结果:

注意这里tail的作用:永远指向最后一级,这样要添加新的callback的时候就不用遍历到最后一层。

 

 

 

 

2. 解绑方法:off

    // 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 `ev` is null, removes all bound callbacks for all events.
    off: function(events, callback, context) {
      var ev, calls, node;
      if (!events) {
        delete this._callbacks;
      } else if (calls = this._callbacks) {
        events = events.split(/\s+/);
        while (ev = events.shift()) {
          node = calls[ev];
          delete calls[ev];
          if (!callback || !node) continue;
          // Create a new list, omitting the indicated event/context pairs.
          while ((node = node.next) && node.next) {
            if (node.callback === callback &&
              (!context || node.context === context)) continue;
            this.on(ev, node.callback, node.context);
          }
        }
      }
      return this;
    },

 根据代码来看,off的原理并不是删除某个匹配到的相再衔接到上一层。

首先注意,外层while过程中把内容做了转移,并删除了事件变量,这是为了之后建立新的绑定列表做准备。

接着是遍历了一遍这个转移后事件对象的链表,碰到非指定移除的目标则绑定它,碰到指定移除的目标就忽略。

 

 

 

 

3. 触发方法:trigger

    // Trigger an event, firing all bound callbacks. Callbacks are passed the
    // same arguments as `trigger` is, apart from the event name.
    // Listening for `"all"` passes the true event name as the first argument.
    trigger: function(events) {
      var event, node, calls, tail, args, all, rest;
      if (!(calls = this._callbacks)) return this;
      all = calls['all'];
      (events = events.split(/\s+/)).push(null);
      // Save references to the current heads & tails.
      while (event = events.shift()) {
        if (all) events.push({next: all.next, tail: all.tail, event: event});
        if (!(node = calls[event])) continue;
        events.push({next: node.next, tail: node.tail});
      }
      // Traverse each list, stopping when the saved tail is reached.
      rest = slice.call(arguments, 1);
      while (node = events.pop()) {
        tail = node.tail;
        args = node.event ? [node.event].concat(rest) : rest;
        while ((node = node.next) !== tail) {
          node.callback.apply(node.context || this, args);
        }
      }
      return this;
    }

触发比较简单,基本就是遍历再遍历。

注意有个all属性,如果定义在all属性里的callback,那任何事件被触发,all里面的callback都会执行。

 

 

至于为什么Backbone要实现这样的不易修改的链表式数据结构来存贮绑定信息,个人猜想可能是防止开发者随意改动绑定内容。

posted @ 2012-04-16 11:35  谁抢了我的刺猬  阅读(1298)  评论(1编辑  收藏  举报