Skip to content

Instantly share code, notes, and snippets.

@mfdj
Last active February 5, 2016 01:58
Show Gist options
  • Save mfdj/e831f0354ba818d81eec to your computer and use it in GitHub Desktop.
Save mfdj/e831f0354ba818d81eec to your computer and use it in GitHub Desktop.
A very subtle detail of Javascript closures is understanding *when* a closed over symbol's value is read for use inside the closure body. Surprisingly the value isn't captured when the closure is defined, so the scope of the symbol can mutate the symbol at any point before the closure is called.
function makeProblems(count) {
var problems = [];
for (var i = 0; i < count; i++) {
var problematic = function(runId) {
console.log(runId + ' executed with i as: ' + i); // <-- `i` is bound to makeProblems scope
};
problems.push(problematic);
problematic('immediate #' + i);
}
i = 666; // obvious evil: since `i` scoped to makeProblems all closures will see this value
return problems;
}
function makeSolutions(count) {
var solutions = [];
for (var i = 0; i < count; i++) {
var solved = (function(i) { // <-- `i` is bound to the Immediately Invoked Function Expressions's scope (not makeSolutions scope)
return function(runId) {
console.log(runId + ' executed with i as: ' + i); // <-- also bound to the IIFE's scope
};
})(i); // <-- current value of `i` is passed into IIFE scope
solutions.push(solved);
solved('immediate #' + i);
}
i = 0.666; // not so evil!
return solutions;
}
function runAll(run) {
for (var i = 0, len = run.length; i < len; i++) {
run[i]('later #' + i);
}
}
console.log("\n~~~~~~ problems ~~~~~~");
runAll(makeProblems(3));
console.log("\n~~~~~~ solutions ~~~~~~");
runAll(makeSolutions(3));
~~~~~~ problems ~~~~~~
immediate #0 executed with i as: 0
immediate #1 executed with i as: 1
immediate #2 executed with i as: 2
later #0 executed with i as: 666
later #1 executed with i as: 666
later #2 executed with i as: 666
~~~~~~ solutions ~~~~~~
immediate #0 executed with i as: 0
immediate #1 executed with i as: 1
immediate #2 executed with i as: 2
later #0 executed with i as: 0
later #1 executed with i as: 1
later #2 executed with i as: 2
@mattwellss
Copy link

I used to run into this all the time when I had a lot of event handlers to add to a NodeList. Then I learned about jQuery and promptly forgot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment