Backbone事件实现深度分析

backbone的Event之前是链表实现,表头是绑定事件的对象,
然后分裂出以不同event开始的事件函数链表(老版本),
1.1.2版本就变成以event为名的事件函数数组了,
省去了很多链表操作,简洁不少

以下是链表实现方法的代码
// 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 = {};//修改tail指针,永远指向下一个
  }
  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);
此处引用: http://www.cnblogs.com/qdwang/archive/2012/04/16/2451399.html   的例子

173803_9ifn_551872.png

// 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];
      //javascript的delete删除的对象如果是一个引用类型,
      //那它删除的不是引用的对象,而是指向该引用对象的指针。
      //这里斩断链条,后面重新构建
      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;
},


// 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()) {//结束条件就是之前push的null
    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;
}


关于为什么新版本添加stopListening方法:

简单的说就是方便在view消失的时候能迅速解除对于所有关联obj的引用

防止僵尸obj(由于一直被已经消失的view引用而无法删除),

观察listenTo(对原on方法的包装),

用一个“私有对象”保存所有事件对象的引用,

然后在stopListening(对原off方法的包装)中对此“私有对象”进行批量操作,

只需调用一次就可达到解除所有对象以及事件的绑定,而以前需要手动off一个个解绑(容易漏掉)


具体详情参考国外一篇精彩文章:

http://lostechies.com/derickbailey/2013/02/06/managing-events-as-relationships-not-just-references/

关于model-view关系分析的很有道理,最后用on还是listenTo还得在建立app之前建立清晰的工作流模型然后下结论,尤其注意僵尸对象形成原因

数组实现的Events

  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) {
      if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;//eventsApi参考后面分析
      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;
      var once = _.once(function() {//返回once,只执行一次,具体参考underscore的once方法
        self.off(name, once);
        callback.apply(this, arguments);
      });
      once._callback = callback;//
      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) {
      if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;

      // Remove all callbacks for all events.
      if (!name && !callback && !context) {//都不传则赋值空
        this._events = void 0;
        return this;
      }

      var names = name ? [name] : _.keys(this._events);
      for (var i = 0, length = names.length; i < length; i++) {
        name = names[i];

        // Bail out if there are no events stored.
        var events = this._events[name];
        if (!events) continue;
              
        // Remove all callbacks for this event.
        if (!callback && !context) {//对应链表实现的斩断链条
          delete this._events[name];
          continue;
        }

        // Find any remaining events.
        var remaining = [];
        
        //这里遍历的是不同事件的函数队列,
        //剔除与callback名称相同的,
        //如果context存在但与传入的上下文不相同也保留,
        //还有个_callback条件请参考once方法
        
        for (var j = 0, k = events.length; j < k; j++) {
          var event = events[j];
          if (
            callback && callback !== event.callback &&
            callback !== event.callback._callback || 
            context && context !== event.context
          ) {
            remaining.push(event);
          }
        }

        // Replace events if there are any remaining.  Otherwise, clean up.
        if (remaining.length) {
          this._events[name] = remaining;
        } else {
          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;
      var args = slice.call(arguments, 1);
      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;
      if (!callback && typeof name === 'object') callback = this;
      if (obj) (listeningTo = {})[obj._listenId] = obj;
      for (var id in listeningTo) {
        obj = listeningTo[id];
        obj.off(name, callback, this);
        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.
  
    //无非是解决了事件的不同类型传递"change blur"还是{change: action},
  //然后拆分成单个事件名传给on/off/trigger方法,
  //算是统一的适配器吧,就算新增值类型,只需修改此方法即可
  
  var eventsApi = function(obj, action, name, rest) {  

    if (!name) return true;

    // Handle event maps.
    if (typeof name === 'object') {
      for (var key in name) {
        obj[action].apply(obj, [key, name[key]].concat(rest));
      }
      return false;
    }

    // Handle space separated event names.
    if (eventSplitter.test(name)) {
      var names = name.split(eventSplitter);
      for (var i = 0, length = names.length; i < length; 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).
    //据说是call方法快于apply方法,而传入的参数多半不超过3个,算是冗余换性能吧
  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.
  _.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;
    };
  });


转载于:https://my.oschina.net/wizardpisces/blog/368686

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值