> Since every d3.timer invoked for a particular transition uses exactly the
> same tick() closure as described above, I was expecting that there would
> only be a single timer added to the queue for a transition and it would be
> used by all timers from that same transition.
The thing that you are missing is that each closure is a separate
Function object, so they are all registered as separate timers. So, if
I have a global (static) function, and I register it twice, then the
second registration replaces the first:
function globalCallback() {}
d3.timer(globalCallback); // #1
d3.timer(globalCallback); // #2 replaces #1
However, if you use a closure in a new context, then you have a new
function, so they are registered separately:
function registerTimer() {
function localCallback() {}
d3.timer(localCallback);
}
registerTimer(); // #1
registerTimer(); // #2 does not replace #1
Another way to see this:
function createClosure() {
return function() {};
}
var a = createClosure();
var b = createClosure();
console.log(a === b); // false
Another important detail to note is that functions are hoisted, so
each iteration of a loop would not normally create a new instance of a
function. However, the code here uses d3_selection_each (akin to
array.forEach) to iterate over the elements in the selection; since
the closure is defined within the single execution of the "each"
callback function, each element gets a separate closure which causes
separate timers.
> But this doesn't happen so my transition of 5000 elements ends
> up with 5000 entries in the timer queue and the loop runs about
> 12,000,000 times (n(n+1)/2).
I suspect it would be trivial to optimize this check for the common
case where we're adding a new function to the timer queue, so that you
only have to scan the queue in the rare case where you're replacing an
existing timer.
Mike