Javascript scoping makes my head hurt
Written by Piers Cawley on , updated
Who came up with the javascript scoping rules? What were they smoking. Here’s some Noddy Perl that demonstrates what I’m on about: my @subs; for my $i (0..4) { push @subs, sub { $i } } print $subs[0]->(); # => 0; Here’s the equivalent (or what I thought should be the equivalent) in Javscript: var subs = []; for (var i in [0,1,2,3,4]) { subs[i] = function () { return i; } } alert subs0 // => 4 What’s going on?
Who came up with the javascript scoping rules? What were they smoking. Here’s some Noddy Perl that demonstrates what I’m on about:
my @subs;
for my $i (0..4) {
push @subs, sub { $i }
}
print $subs[0]->(); # => 0;
Here's the equivalent (or what I thought should be the equivalent) in Javscript:
var subs = [];
for (var i in [0,1,2,3,4]) {
subs[i] = function () {
return i;
}
}
alert subs[0]() // => 4
What's going on? In Perl, $i
is scoped to the `for` block. Essentially, each time through the loop, a new variable is created, so the generated closures all refer to different $i
s. In Javascript, `i` is scoped to the `for` loop's containing function. Each of the generated closures refer to the same `i`. Which means that, to get the same effect as the perl code, you must write:
var subs = [];
for (var shared_i in [0,1,2,3,4]) {
(function (i) {
subs[i] = function () {
return i;
};
})(shared_i);
}
subs[0]() // => 0
h3. Dodgy Ruby scopingI had initially planned to write the example “How it should work” code in Ruby, but it turns out that Ruby’s for
has the same problem:
subs = [];
for i in 0..4
subs << lambda { i }
end
subs[0].call # => 4
Which is one reason why sensible Ruby programmers don't use `for`. If I were writing the snippet in 'real' Ruby, I'd write:
subs = (0..4).collect { |i|
lambda { i }
}
subs[0].call # => 0
h3. My conclusionJavascript is weird. Okay, so you already know this. In so many ways it’s a lovely language, but it does have some annoyingly odd corners.