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).
El alcance de Javascript se basa en funciones, no en un alcance estrictamente léxico. Eso significa que
Alguna función1 se define desde el inicio de la función adjunta, pero su contenido no está definido hasta que se asigna.
en el segundo ejemplo, la asignación es parte de la declaración, por lo que se 'mueve' a la parte superior.
en el tercer ejemplo, la variable existe cuando se define el cierre interno anónimo, pero no se usa hasta 10 segundos después, para entonces se ha asignado el valor.
El cuarto ejemplo tiene tanto la segunda como la tercera razones para funcionar.
fuente
Porque
someFunction1
aún no se ha asignado en el momento en quesetTimeout()
se ejecuta la llamada a .someFunction3 puede parecer un caso similar, pero como está pasando una función que se ajusta
someFunction3()
asetTimeout()
en este caso, la llamada asomeFunction3()
no se evalúa hasta más tarde.fuente
someFunction2
tampoco se ha asignado aún cuandosetTimeout()
se ejecuta la llamada a ...?function
palabra clave no es exactamente equivalente a asignar una función anónima a una variable. Las funciones declaradas comofunction foo()
son "elevadas" al comienzo del alcance actual, mientras que las asignaciones de variables ocurren en el punto donde están escritas.Esto parece un caso básico de seguir un buen procedimiento para no meterse en problemas. Declare variables y funciones antes de usarlas y declare funciones como esta:
function name (arguments) {code}
Evite declararlos con var. Esto es descuidado y genera problemas. Si adquiere el hábito de declarar todo antes de usarlo, la mayoría de sus problemas desaparecerán rápidamente. Al declarar variables, las inicializaría con un valor válido de inmediato para asegurarme de que ninguna de ellas esté indefinida. También tiendo a incluir código que verifica valores válidos de variables globales antes de que una función los use. Esta es una protección adicional contra errores.
Los detalles técnicos de cómo funciona todo esto son como la física de cómo funciona una granada de mano cuando juegas con ella. Mi simple consejo es, en primer lugar, no jugar con granadas de mano.
Algunas declaraciones simples al principio del código podrían resolver la mayoría de estos tipos de problemas, pero aún podría ser necesaria una limpieza del código.
Nota adicional:
Ejecuté algunos experimentos y parece que si declaras todas tus funciones de la manera descrita aquí, realmente no importa en qué orden estén. Si la función A usa la función B, la función B no tiene que hacerlo. declararse antes de la función A.
Entonces, declare todas sus funciones primero, luego sus variables globales, y luego coloque su otro código al final. Siga estas reglas generales y no se equivocará. Incluso podría ser mejor poner sus declaraciones en el encabezado de la página web y su otro código en el cuerpo para garantizar el cumplimiento de estas reglas.
fuente