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 的例子
// 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; }; });