¿Por qué los enlaces let y var se comportan de manera diferente usando la función setTimeout? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Este código registra 6, 6 veces:

(function timer() {
  for (var i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

Pero este código...

(function timer() {
  for (let i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

... registra el siguiente resultado:

0
1
2
3
4
5

¿Por qué?

Es porque let se une al interior scope cada elemento de manera diferente y var mantiene el último valor de i?

Author: Badacadabra, 2015-07-08

2 answers

Con var tiene un ámbito de función, y solo un enlace compartido para todas sus iteraciones de bucle, es decir, ien cada devolución de llamada setTimeout significa la misma variableque finalmente es igual a 6 después de que finalice la iteración de bucle.

Con let tiene un ámbito de bloque y cuando se usa en el bucle for obtiene un nuevo enlace para cada iteración , es decir, el i en cada devolución de llamada setTimeout significa una variable diferente, cada una de que tiene un valor diferente: el primero es 0, el siguiente es 1 etc.

Así que esto:

(function timer() {
  for (let i = 0; i <= 5; i++) {
    setTimeout(function clog() { console.log(i); }, i * 1000);
  }
})();

Es equivalente a esto usando solo var:

(function timer() {
  for (var j = 0; j <= 5; j++) {
    (function () {
      var i = j;
      setTimeout(function clog() { console.log(i); }, i * 1000);
    }());
  }
})();

Usando la expresión de función inmediatamente invocada para usar el ámbito de función de una manera similar a como funciona el ámbito de bloque en el ejemplo con let.

Podría escribirse más corto sin usar el nombre j, pero tal vez no sería tan claro:

(function timer() {
  for (var i = 0; i <= 5; i++) {
    (function (i) {
      setTimeout(function clog() { console.log(i); }, i * 1000);
    }(i));
  }
})();

Y aún más corto con flecha funciones:

(() => {
  for (var i = 0; i <= 5; i++) {
    (i => setTimeout(() => console.log(i), i * 1000))(i);
  }
})();

(Pero si puedes usar las funciones de flecha, no hay razón para usar var.)

Así es como Babel.js traduce su ejemplo con let para ejecutarse en entornos donde let no está disponible:

"use strict";

(function timer() {
  var _loop = function (i) {
    setTimeout(function clog() {
      console.log(i);
    }, i * 1000);
  };

  for (var i = 0; i <= 5; i++) {
    _loop(i);
  }
})();

Gracias a Michael Geary por publicar el enlace a Babel.js en los comentarios. Vea el enlace en el comentario para una demostración en vivo donde puede cambiar cualquier cosa en el código y ver la traducción que se lleva a cabo de inmediato. Es interesante ver cómo otras características de ES6 también se traducen.

 29
Author: rsp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-08 08:42:08

Técnicamente es como @rsp explica en su excelente respuesta. Así es como me gusta entender que las cosas funcionan bajo el capó. Para el primer bloque de código usando var

(function timer() {
  for (var i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

Puedes imaginar que el compilador va así dentro del bucle for

 setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec
 setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec

Y así sucesivamente

Desde que i se declara usando var, cuando se llama clog, el compilador encuentra la variable i en el bloque de función más cercano que es timer y ya hemos llegado al final del bucle for , i contiene el valor 6, y ejecuta clog. Eso explica 6 siendo registrado seis veces.

 3
Author: Quannt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-08 07:56:42