JavaScript is function-scoped. Every time you create a new function, it:
- Gets a scope object which holds its own declared variables (including argument variables)
- Has a reference to the scope in which it was defined, which is walked at runtime when you attempt to access a variable that isn't present in the function's own scope.
So, looking at a slightly edited version of your code (added var
to the loop so i
isn't global, removed the animateLetters()
wrapper layer):
$(document).ready(function () {
var word = 'abc';
$('#newWordButton').click(function () {
function changeLetter() {
for (var i = 0; i < word.length; i++) {
var currentLetter = word.charAt(i);
$('#wordsDiv').fadeOut(1000, function () {
$('#wordsDiv').replaceWith('<img src = "images/letters/' + currentLetter + '.gif" />');
$('#wordsDiv').fadeIn(1000);
});
}
}
setTimeout(changeLetter, 1000);
});
});
This is what the resulting scopes which are generated look like (with a scope represented by square brackets):
[global] - this is the fallback scope, and how every reference to $ is resolved
| var $
+-[function()] (passed to ready call)
| var word
+-[function()] (passed to click call)
| var changeLetter
+-[function changeLetter()]
| var i = 4
| var currentLetter = 'c'
+-[function()] (passed to 1st fadeOut call)
| |
+-[function()] (passed to 2nd fadeOut call)
| |
+-[function()] (passed to 3rd fadeOut call)
|
When your fadeOut functions are called, they walk up the scope chain to find the currentLetter
variable when it's accessed, as they don't have one in their own scope. Notice that they all have access to the same parent scope, so they're all accessing the same currentLetter
variable which, after the for
loop has exited, will be pointing at 'c'
.
This is what the bottom of the scope diagram looks like when you use @gpojd's solution (but don't forget to add var i
to the loop expression!), after the for
loop has exited:
[function changeLetter()]
| var i = 4
+-[function()] (IIFE in 1st loop iteration)
| | var currentLetter = 'a'
| +-[function()] (passed to 1st fadeOut call)
| |
+-[function()] (IIFE in 2nd loop iteration)
| | var currentLetter = 'b'
| +-[function()] (passed to 2nd fadeOut call)
| |
+-[function()] (IIFE in 3rd loop iteration)
| var currentLetter = 'c'
+-[function()] (passed to 3rd fadeOut call)
|
The solution is using an Immediately-Invoked Function Expression (IIFE) to create an intermediate function scope on each loop iteration, which passes the value of the character at the current value of i
into what becomes a currentLetter
variable in its own scope - now when your fadeOut functions are called, they find and use the currentLetter
variable in the function scope created by the IIFE.