2010-10-08 12 views
73

¿Por qué el primero de estos ejemplos no funciona, pero todos los demás sí?Declaración de función y orden de evaluación de JavaScript

// 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'); } 
})(); 

Respuesta

168

Esto no es ni un problema alcance ni tampoco es un problema de cierre. El problema está en entender entre declaraciones y expresiones.

código JavaScript, ya que incluso la primera versión de Netscape de JavaScript y Microsoft primera copia de la misma, se procesa en dos fases:

Fase 1: Elaboración - en esta fase el código se compila en un árbol sintáctico (y código de bytes o binario dependiendo del motor).

Fase 2: ejecución: se interpreta el código analizado.

La sintaxis para la declaración de la función es:

function name (arguments) {code} 

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

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

  1. Cualquier cosa a la derecha de un signo = (o : de objetos literales).
  2. Cualquier cosa entre paréntesis ().
  3. Parámetros para las funciones (esto ya está cubierto por 2).

Expresiones a diferencia de declaraciones se procesan en la fase de ejecución en lugar de la fase de compilación. Y debido a esto, el orden de las expresiones es importante.

Por lo tanto, para aclarar:


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

Fase 1: compilación. El compilador ve que la variable someFunction se define para que la cree. Por defecto, todas las variables creadas tienen el valor de indefinido. Tenga en cuenta que el compilador no puede asignar valores aún 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 este momento todavía no estamos ejecutando el código.

Fase 2: ejecución. El intérprete ve que quiere pasar la variable someFunction a setTimeout. Y así es. Lamentablemente, el valor actual de someFunction no 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 así la crea.

Fase 2: El intérprete ve que quiere pasar someFunction al setTimeout. Y así es. El valor actual de someFunction es 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 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á usando la variable someFunction, por lo que crea un cierre a la variable. En este punto, el valor de someFunction aún no está definido. Luego ve que asigna una función al someFunction. En este punto, el valor de someFunction ya no está indefinido. 1/100 de segundo después, se activan los setTimeout y se llama a algunaFunción. Como su valor ya no está indefinido, funciona.


Caso 4 es realmente otra versión del caso 2 con un poco de caso 3 tirado. En el punto someFunction se pasa a SetTimeOut ya existe debido a que se declaró.


aclaración adicional:

Usted puede preguntarse por qué setTimeout(someFunction, 10) no crea un cierre entre la copia local de algunaFuncion y la aprobada para setTimeout. La respuesta a esto es que los argumentos de función en JavaScript siempre son siempre pasados ​​por valor si son números o cadenas o como referencia para todo lo demás. Así que setTimeout en realidad no obtiene la variable que alguna función le pasó (lo que significaría que se está creando un cierre) sino que solo obtiene el objeto al que se refiere alguna funció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).

+6

Esa fue una gran respuesta. –

+0

Esto probablemente sea una falta de comprensión de los cierres, pero siempre lo consideré como el acceso a un alcance, no como la creación de algo entre un alcance y otro. También pensé que estaba en el nivel de alcance, no en el nivel variable. ¿Le importaría elaborar un poco más sobre eso, o señalarme en la dirección de algo que pueda leer? De nuevo, gran respuesta, ojalá pudiera votarlo dos veces. –

+0

Esta respuesta me hace desear poder votar varias veces en la misma respuesta. Verdaderamente una gran respuesta. Gracias – ArtBIT

1

Debido someFunction1 aún no se le ha asignado en el momento en que se ejecuta la llamada a setTimeout().

someFunction3 puede verse como un caso similar, pero ya que está de paso en función envolver someFunction3() a setTimeout() en este caso, la llamada a someFunction3() no se evalúa hasta más tarde.

+0

Pero 'someFunction2' aún no se ha asignado cuando se ejecuta la llamada a' setTimeout() '...? – jnylen

+1

@jnylen: declarar una función con la palabra clave 'function' no es precisamente equivalente a asignar una función anónima a una variable. Las funciones declaradas como 'función foo()' son "elevadas" al comienzo del alcance actual, mientras que las asignaciones de variables ocurren en el punto donde están escritas. – Chuck

+0

+1 para funciones especiales. Sin embargo, solo porque pueda funcionar no significa que deba hacerse. Declarar siempre antes de usar. – mway

2

El alcance de Javascript se basa en funciones, no estrictamente en el alcance léxico. eso significa que

  • Somefunction1 se define desde el inicio de la función de cerramiento, pero su contenido es indefinido hasta su cesión.

  • 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 el valor ha sido asignado.

  • cuarto ejemplo tiene ambas de la segunda y tercera razones para trabajar

+0

En su primer punto, ¿quiere decir 'someFunction1'? – jnylen

+0

tks, arreglado ..... – Javier

1

Esto suena como un caso básico de seguir un buen procedimiento para evitar problemas. Declarar variables y funciones antes de usarlos y declarar funciones de esta manera:

function name (arguments) {code} 

evitarlos declarar con var. Esto es simplemente descuidado y lleva a problemas. Si adquiere el hábito de declarar todo antes de usarlo, la mayoría de sus problemas desaparecerán a toda prisa. Al declarar variables, las inicializaría con un valor válido de inmediato para asegurar 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 algo así como la física de cómo funciona una granada de mano cuando juegas con ella. Mi consejo simple es no jugar con granadas de mano en primer lugar.

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 cierta limpieza del código.

Nota adicional:
me encontré con un par de experimentos y parece que si se declara todas sus funciones en la forma descrita aquí, que en realidad no importa en qué orden están en función de si utiliza una función. B, la función B no tiene que ser declarada antes de la función A.

Por lo tanto, declare todas sus funciones primero, sus variables globales a continuación, y luego ponga su otro código al final. Sigue estas reglas generales y no puedes equivocarte. 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.

Cuestiones relacionadas