Orden de evaluación y declaración de funciones de JavaScript

Resuelto We Are All Monica asked hace 14 años • 4 respuestas

¿Por qué el primero de estos ejemplos no funciona, pero todos los demás sí?

// 1 - does not work
(function() {
setTimeout(someFunction1, 10);
var someFunction1 = function() { alert('here1'); };
})();

// 2
(function() {
setTimeout(someFunction2, 10);
function someFunction2() { alert('here2'); }
})();

// 3
(function() {
setTimeout(function() { someFunction3(); }, 10);
var someFunction3 = function() { alert('here3'); };
})();

// 4
(function() {
setTimeout(function() { someFunction4(); }, 10);
function someFunction4() { alert('here4'); }
})();
We Are All Monica avatar Oct 08 '10 10:10 We Are All Monica
Aceptado

Este no es un problema de alcance ni de cierre. El problema está en el entendimiento entre declaraciones y expresiones .

El código JavaScript, desde la primera versión de JavaScript de Netscape y la primera copia 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 función es:

function name (arguments) {code}

Los argumentos son, por supuesto, opcionales (el código también es opcional, pero ¿cuál es el punto de eso?).

Pero JavaScript también te permite crear funciones usando expresiones . La sintaxis de las expresiones de funciones es similar a la de las declaraciones de funciones, excepto que están escritas en el contexto de la expresión. Y las expresiones son:

  1. Cualquier cosa a la derecha de un =signo (o :en literales de objeto).
  2. Todo lo que esté entre paréntesis ().
  3. 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 el orden de las expresiones importa.

Entonces, para aclarar:


// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();

Fase 1: compilación. El compilador ve que la variable someFunctionestá definida y la crea. Por defecto todas las variables creadas tienen el valor indefinido. Tenga en cuenta que el compilador no puede asignar valores todavía en este punto porque es posible que los valores necesiten que el intérprete ejecute algún código para devolver un valor para asignar. Y en esta etapa todavía no estamos ejecutando código.

Fase 2: ejecución. El intérprete ve que desea pasar la variable someFunctiona setTimeout. Y así es. Lamentablemente, el valor actual de someFunctionno está definido.


// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();

Fase 1: compilación. El compilador ve que estás declarando una función con el nombre algunaFunción y la crea.

Fase 2: El intérprete ve que desea pasar someFunctional setTimeout. Y así es. El valor actual de someFunctiones su declaración de función compilada.


// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();

Fase 1: compilación. El compilador ve que has declarado una variable someFunctiony 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 someFunctiony crea un cierre para la variable. En este punto, el valor de someFunctionaún no está definido. Luego te ve asignando una función a someFunction. En este punto, el valor de someFunctionya no está indefinido. 1/100 de segundo después, setTimeout se activa y se llama a someFunction. Como su valor ya no está indefinido, funciona.


El caso 4 es en realidad otra versión del caso 2 con un poco del caso 3. En el momento en que someFunctionse pasa a setTimeout, ya existe debido a que fue 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 que se pasa a setTimeout. La respuesta a esto 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. Por lo tanto, setTimeout en realidad no obtiene la variable que se le pasa algunaFunción (lo que habría significado la creación de un cierre), sino que solo obtiene el objeto al que se refiere algunaFunción (que en este caso es una función). Este es el mecanismo más utilizado en JavaScript para romper cierres (por ejemplo, en bucles) antes de la invención de la letpalabra clave.

slebetman avatar Oct 08 '2010 03:10 slebetman

El alcance de Javascript se basa en funciones, no en un ámbito estrictamente léxico. Eso significa que

  • Algunafunció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" hacia arriba.

  • en el tercer ejemplo, la variable existe cuando se define el cierre interno anónimo, pero no se usa hasta 10 segundos después, cuando se ha asignado el valor.

  • El cuarto ejemplo tiene tanto la segunda como la tercera razón para funcionar.

Javier avatar Oct 08 '2010 03:10 Javier

Porque someFunction1aún no ha sido asignado en el momento en que setTimeout()se ejecuta la llamada.

someFunction3 puede parecer un caso similar, pero dado que en este caso está pasando una función que se ajusta someFunction3()a setTimeout(), la llamada a someFunction3()no se evalúa hasta más tarde.

matt b avatar Oct 08 '2010 03:10 matt b

Esto suena como 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 simplemente 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 los valores válidos de las 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 algo así 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 comienzo del código pueden resolver la mayoría de estos tipos de problemas, pero aún puede ser necesaria alguna limpieza del código.

Nota adicional:
realicé 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 por qué ser declarado antes de la función A.

Entonces, declara todas tus funciones primero, luego tus variables globales y luego pon el resto del código al final. Siga estas reglas generales y no se equivocará. Incluso podría ser mejor colocar sus declaraciones en el encabezado de la página web y el resto del código en el cuerpo para garantizar el cumplimiento de estas reglas.

Terry Prothero avatar Jul 24 '2012 23:07 Terry Prothero