implement callbacks as linked list

This commit is contained in:
Brad Dunbar
2011-10-31 07:15:03 -04:00
parent 2156b9e28f
commit de2430554e
2 changed files with 39 additions and 33 deletions

View File

@@ -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;
}

View File

@@ -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');
});
});
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');
});
});