No se trata de un problema de alcance ni de cierre. El problema está en la comprensión entre declaraciones y expresiones .
El código JavaScript, ya que incluso la primera versión de JavaScript de Netscape y la primera copia de él de Microsoft, se procesa en dos fases:
Fase 1: compilación: en esta fase, el código se compila en un árbol de sintaxis (y código de bytes o binario según el motor).
Fase 2: ejecución - luego se interpreta el código analizado.
La sintaxis para la declaración de funciones es:
function name (arguments) {code}
Los argumentos son, por supuesto, opcionales (el código también es opcional, pero ¿qué sentido tiene eso?).
Pero JavaScript también te permite crear funciones usando expresiones . La sintaxis de las expresiones de función es similar a las declaraciones de función excepto que están escritas en el contexto de la expresión. Y las expresiones son:
- Cualquier cosa a la derecha de un
=
signo (o :
en objetos literales).
- Cualquier cosa entre paréntesis
()
.
- Parámetros de funciones (esto en realidad ya está cubierto por 2).
Las expresiones a diferencia de las declaraciones se procesan en la fase de ejecución en lugar de en la fase de compilación. Y por eso importa el orden de las expresiones.
Entonces, para aclarar:
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();
Fase 1: compilación. El compilador ve que la variable someFunction
está definida y la crea. Por defecto, todas las variables creadas tienen el valor indefinido. Tenga en cuenta que el compilador aún no puede asignar valores en este punto porque los valores pueden necesitar que el intérprete ejecute algún código para devolver un valor para asignar. Y en esta etapa aún no estamos ejecutando código.
Fase 2: ejecución. El intérprete ve que desea pasar la variable someFunction
a setTimeout. Y así es. Desafortunadamente, el valor actual de someFunction
no está definido.
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
Fase 1: compilación. El compilador ve que está declarando una función con el nombre someFunction y así la crea.
Fase 2: El intérprete ve que desea pasar someFunction
al setTimeout. Y así es. El valor actual de someFunction
es su declaración de función compilada.
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
Fase 1: compilación. El compilador ve que ha declarado una variable someFunction
y la crea. Como antes, su valor no está definido.
Fase 2: ejecución. El intérprete pasa una función anónima a setTimeout para que se ejecute más tarde. En esta función, ve que estás usando la variable, someFunction
por lo que crea un cierre a la variable. En este punto, el valor de someFunction
todavía no está definido. Luego ve que le asignas una función someFunction
. En este punto, el valor de someFunction
ya no está indefinido. 1/100 de segundo después, setTimeout se dispara y se llama a someFunction. Dado que su valor ya no está indefinido, funciona.
El caso 4 es realmente otra versión del caso 2 con un poco del caso 3 someFunction
incluido . En el punto que se pasa a setTimeout, ya existe debido a que se ha declarado.
Aclaración adicional:
Quizás se pregunte por qué setTimeout(someFunction, 10)
no crea un cierre entre la copia local de someFunction y la pasada a setTimeout. La respuesta a eso es que los argumentos de función en JavaScript siempre, siempre se pasan por valor si son números o cadenas o por referencia para todo lo demás. Entonces, setTimeout no obtiene la variable someFunction que se le pasa (lo que habría significado la creación de un cierre), sino que solo obtiene el objeto al que se refiere someFunction (que en este caso es una función). Este es el mecanismo más utilizado en JavaScript para romper cierres (por ejemplo, en bucles).