diff --git a/backbone.js b/backbone.js index 8e0df5b1..74f2a774 100644 --- a/backbone.js +++ b/backbone.js @@ -15,6 +15,9 @@ // Save the previous value of the `Backbone` variable. var previousBackbone = root.Backbone; + // Create a local reference to slice. + var slice = Array.prototype.slice; + // The top-level namespace. All public Backbone classes and modules will // be attached to this. Exported for both CommonJS and the browser. var Backbone; @@ -70,8 +73,9 @@ // Passing `"all"` will bind the callback to all events fired. bind : function(ev, callback, context) { var calls = this._callbacks || (this._callbacks = {}); - var list = calls[ev] || (calls[ev] = []); - list.push([callback, context]); + var list = calls[ev] || (calls[ev] = {}); + var tail = list.tail || list; + list.tail = tail.next = {callback: callback, context: context}; return this; }, @@ -79,20 +83,19 @@ // callbacks for the event. If `ev` is null, removes all bound callbacks // for all events. unbind : function(ev, callback) { - var calls; + var calls, list, node, prev; if (!ev) { this._callbacks = {}; } else if (calls = this._callbacks) { if (!callback) { - calls[ev] = []; - } else { - var list = calls[ev]; - if (!list) return this; - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] && callback === list[i][0]) { - list[i] = null; - break; - } + calls[ev] = {}; + } else if (list = node = calls[ev]) { + while (prev = node, node = node.next) { + if (node.callback !== callback) continue; + prev.next = node.next; + if (list.tail === node) list.tail = prev; + node.context = node.callback = null; + break; } } } @@ -103,21 +106,12 @@ // same arguments as `trigger` is, apart from the event name. // Listening for `"all"` passes the true event name as the first argument. trigger : function(eventName) { - var list, calls, ev, callback, args; - var both = 2; + var node, calls, callback, args, ev, events = ['all', eventName]; if (!(calls = this._callbacks)) return this; - while (both--) { - ev = both ? eventName : 'all'; - if (list = calls[ev]) { - for (var i = 0, l = list.length; i < l; i++) { - if (!(callback = list[i])) { - list.splice(i, 1); i--; l--; - } else { - args = both ? Array.prototype.slice.call(arguments, 1) : arguments; - callback[0].apply(callback[1] || this, args); - } - } - } + while (ev = events.pop()) { + if (!(node = calls[ev])) continue; + args = ev == 'all' ? arguments : slice.call(arguments, 1); + while (node = node.next) if (callback = node.callback) callback.apply(node.context || this, args); } return this; } diff --git a/test/events.js b/test/events.js index 3b413107..a8235ef2 100644 --- a/test/events.js +++ b/test/events.js @@ -66,21 +66,33 @@ $(document).ready(function() { equals(obj.counterA, 1, 'counterA should have only been incremented once.'); equals(obj.counterB, 1, 'counterB should have only been incremented once.'); }); - + test("Events: bind a callback with a supplied context", function () { expect(1); - + var TestClass = function () { return this; } TestClass.prototype.assertTrue = function () { ok(true, '`this` was bound to the callback') }; - + var obj = _.extend({},Backbone.Events); - + obj.bind('event', function () { this.assertTrue(); }, (new TestClass)); - + obj.trigger('event'); - + }); -}); \ No newline at end of file + test("Events: nested trigger with unbind", function () { + expect(1); + var obj = { counter: 0 }; + _.extend(obj, Backbone.Events); + var incr1 = function(){ obj.counter += 1; obj.unbind('event', incr1); obj.trigger('event'); }; + var incr2 = function(){ obj.counter += 1; }; + obj.bind('event', incr1); + obj.bind('event', incr2); + obj.trigger('event'); + equals(obj.counter, 3, 'counter should have been incremented three times'); + }); + +});