How to use the current value of a variable in a more internal function?

The following code "prints" 50 times the text "message 50" in a textarea.

for (var i = 0; i < 50; i++) {
    setTimeout(function() {
        textArea.value += 'Mensagem ' + i + '\n';
        textArea.scrollTop = textArea.scrollHeight;
    }, 100 * i);
}

See the example in jsfiddle.

I understand this is because the value of i is updated in the external function, and at the time of execution of the internal function, the reference points to the updated value.

How do I print the value of the variable i corresponding to the iteration number? That is, the value of i at the time the function Internal was created in the call of setTimeout, resulting in texts from " Message 0 "to"message 49"

Author: bfavaretto, 2013-12-27

3 answers

Note:

In the modern version of JavaScript (from ES6) this problem no longer happens if we use let in for (let i = 0; ...

More about let, cont and var here .


Answer to question code:

The problem in the question code is that the loop for is ready before the function setTimeout() runs, so the value of i is already 50 before the code textArea.value += 'Mensagem ' + i + '\n'; is run.

Calling the setTimeout() via extena function to the loop loop, then the the value of i that is passed as a parameter is the value of each iteration.

You can use this:

var textArea = document.getElementById('a');
var tempo = function (pTempo) {
    setTimeout(function () {
        textArea.value += 'Mensagem ' + pTempo+ '\n';
        textArea.scrollTop = textArea.scrollHeight;
    }, 100 * pTempo);

}
for (var i = 0; i < 50; i++) {
    tempo(i);
}

Example


Another option, similar, but instead of having an external function, using a function that self-executes within the for loop, and which captures the value of i:

var textArea = document.getElementById('a');
for (var i = 0; i < 50; i++) {
    (function () {
        var iScoped = i;
        setTimeout(function () {
            textArea.value += 'Mensagem ' + iScoped + '\n';
            textArea.scrollTop = textArea.scrollHeight;
        }, 100 * iScoped );
    })()
} 

Example


There is yet another way, since the function setTimeout() accepts a third parameter for within the function.

Var timeoutID = window.setTimeout (func, delay, [param1, param2,...]);

example: setTimeout (function (parameter ) {/*Code */}, time, parameter );

Source: MDN English

var textArea = document.getElementById('a');
for (var i = 0; i < 50; i++) {
    setTimeout(function(i) {
        textArea.value += 'Mensagem ' + i + '\n';
        textArea.scrollTop = textArea.scrollHeight;
    }, 100 * i, i);
}

Example

 31
Author: Sergio, 2020-06-11 14:45:34

The name of the concept that is causing you confusion is closure ("clausura" in English, but this term is rarely used), and refers to the way functions defined within a "lexical context" (i.e. the body of a function, a block, a source file) access variables defined in that context.

In JavaScript, only functions define a new lexical context (other languages have different rules - some even support the concept of closure):

var a = 10; // Mesmo "a" para script1.js, script2.js, etc (efetivamente, uma global)
function f() {
    var b = 20; // Um "b" diferente para cada invocação de f
    if ( x ) {
        var c = 30; // Mesmo "c" dentro e fora do if (i.e. o contexto é "f", não o bloco if)

And each new context created inside ( inner ) of an existing context has access to all variables defined in the "outside" (outer):

function x(a1) {          // "x" tem acesso a "a"
    var a2;
    function y(b1) {      // "y" tem acesso a "a" e "b"
        var b2;
        function z(c1) {  // "z" tem acesso a "a", "b", e "c"
            var c2;

It is important to note that it does not matter when the inner function will execute, nor what value the outer variables had at the time the object function was created (in contrast to the function definition, which is at compile/interpret time). What matters is that they both share the same variable, and written on one side will reflect on the readings of the other and vice versa.

In your case, you are creating a new (anonymous) function within the lexical context of the external code (another function? script body?), and it shares the variable i (and not the value of i). By the time this function is executed, the loop for has already changed its value several times, bringing it to its maximum value (50) and this is what the internal function will access. If the external code modified or reused i for other purposes, this would also be reflected in the internal function (and similarly, if one of the function-objects changed that i it would interfere with the others as well).

There are several ways to modify the code to achieve the desired behavior (i.e. a different i for each function-object) - as already pointed out by @Sergio-but my favorite is the one that makes the nature of the more explicit closure (although it seems visually "strange" to those unfamiliar with the concept):

for (var i = 0; i < 50; i++) {
    (function(i) {
        setTimeout(function() {
            textArea.value += 'Mensagem ' + i + '\n';
            textArea.scrollTop = textArea.scrollHeight;
        }, 100 * i);
    })(i);
}

Note that the i argument of the anonymous function is not the same i passed as a parameter to the same - since they are in different lexical contexts. It should also be noted that the variable textArea is still coming from the external context, and depending on the case it may be interesting to include it in closure also:

    (function(i, textArea) { ... })(i, textArea);

This guarantees that-even if this variable has its value changed (i.e. points to a different element) - the internal function still has access to the value it had at the time the loop was executed.

 12
Author: mgibsonbr, 2013-12-27 19:48:02

With modern JavaScript (ES-2015+)

The other answers are still valid, but nowadays we have a standard solution for this with modern JavaScript, using block-scoped variables declared with let. You should use this solution if:

  • is running the code in Node.js; or
  • is using transpiled JavaScript (with Babel, probably); or
  • are satisfied with the current compatibility of browsers

With block-scoped variables, each iteration of the loop will create a new variable, captured by the closure passed to the asynchronous function. The question code comes to work simply by replacing var with let:

const textArea = document.querySelector('textarea');

for (let i = 0; i < 50; i++) {
    setTimeout(function() {
        textArea.value += 'Mensagem ' + i + '\n';
        textArea.scrollTop = textArea.scrollHeight;
    }, 100 * i);
}
<textarea></textarea>
 6
Author: bfavaretto, 2020-01-05 02:29:21