emkay
emkay

Reputation: 901

Variable scope in nested functions in Javascript

I have looked through countless examples which indicate that this is supposed to work, but it does not. I was wondering if someone could have a look and indicate why. I am trying to access the variable "dia" from within the setTimeout function, but it always returns undefined:

var dialogue = new Array();
dialogue[0] = 'Hi there Mo, I am Mark. I will be guiding through the game for you today';
dialogue[1] = 'Hey there Mark, how you doing?';
dialogue[2] = 'I am doing fine sweetie pie, how about yourself?';
dialogue[3] = 'I am good too, thanks. Are you ready for today, i.e. the big day?';
dialogue[4] = 'I certainly am, Mark';
var dcount;
var loopDelay;
var diatext;
for(dcount = 0; dcount <= dialogue.length; dcount++)   {
    var dia = dialogue[dcount];
    if(dcount == 0) { loopDelay = 0; } else {
        loopDelay = ((dia.length)*1000)/18;
    }
    setTimeout(function() {
        alert(dia);
        diatext = Crafty.e('2D, DOM, Text')
            .text(dia)
            .textFont({ size: '11px', weight: 'bold' })
            .attr({ x: 200, y: 150, w:400, h:300})
            .css();
    }, loopDelay);
}

Upvotes: 2

Views: 11059

Answers (2)

user1549458
user1549458

Reputation: 96

try this

var dialogue = new Array(); 
dialogue[0] = 'Hi there Mo, I am Mark. I will be guiding through the game for you today';
dialogue[1] = 'Hey there Mark, how you doing?';
dialogue[2] = 'I am doing fine sweetie pie, how about yourself?';
dialogue[3] = 'I am good too, thanks. Are you ready for today, i.e. the big day?';
dialogue[4] = 'I certainly am, Mark';
var dcount;
var loopDelay;
var diatext;
for(dcount = 0; dcount < dialogue.length; dcount++)   {
    var dia = dialogue[dcount];
    if(dcount == 0) { loopDelay = 0; } else {
    loopDelay = ((dia.length)*1000)/18;
}
setTimeout(function(count) {
    alert(dialogue[count]);

}, loopDelay,dcount);
}

This solution just pass an argument to the setTimeout function so it can take the array index from there and take the correct item

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074355

There are two problems:

The first is that the function you're passing into setTimeout has an enduring reference to the dia variable, not a copy of dia's value as of when the function was created. So when the functions run, they all see the same value for dia, which is the value it has then, after the loop is complete.

This example may help make this clearer:

var a = 1;
setTimeout(function() {
    alert(a);
}, 0);
a = 2;
setTimeout(function() {
    alert(a);
}, 0);

The code above shows us "2" twice. It does not show us "1" and then "2". Both functions access a as it is when they run.

If you think about it, this is exactly how global variables work. And in fact, there's a reason for that: It's exactly the way global variables work. :-)

More: Closures are not complicated

Now, sometimes, you want to get a copy of dia's value as of when the function was created. In those cases, you usually use a builder function and pass dia to it as an argument. The builder function creates a function that closes over the argument, rather than dia:

for(dcount = 0; dcount <= dialogue.length; dcount++)   { // But see note below about <=
    var dia = dialogue[dcount];
    if(dcount == 0) { loopDelay = 0; } else {
        loopDelay = ((dia.length)*1000)/18;
    }
    setTimeout(buildFunction(dia), loopDelay);
}
function buildFunction(d) {
    return function(d) {
        alert(d);
        diatext = Crafty.e('2D, DOM, Text')
            .text(d)
            .textFont({ size: '11px', weight: 'bold' })
            .attr({ x: 200, y: 150, w:400, h:300})
            .css();
    };
}

Because the function buildFunction returns closes over d, which doesn't change, rather than dia, which does, it gives us the value as of when it was created.

The second problem is that your loop condition is incorrect, which is why you're seeing undefined. Your loop is:

for(dcount = 0; dcount <= dialogue.length; dcount++)   {

There is no element at dialogue[dialogue.length]. The last element is at dialogue[dialogue.length - 1]. You should be exiting your loop with < dialogue.length, not <= dialogue.length. With < dialogue.length, you'd still have a problem: dia would always be the last entry (see above), but at least it wouldn't be undefined.

Upvotes: 11

Related Questions