¿Por qué puedo usar una función antes de que esté definida en JavaScript?

168

Este código siempre funciona, incluso en diferentes navegadores:

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

Sin embargo, no pude encontrar una sola referencia de por qué debería funcionar. Vi esto por primera vez en la nota de presentación de John Resig, pero solo se mencionó. No hay explicación allí o en ningún lado para el caso.

¿Puede alguien alumbrarme?

Edu Felipe
fuente
3
En las versiones más recientes de Firefox, esto no funciona si el código está en un intento / captura. Vea este violín: jsfiddle.net/qzzc1evt
Joshua Smith

Respuestas:

217

La functiondeclaración es mágica y hace que su identificador se vincule antes de que se ejecute algo en su bloque de código *.

Esto difiere de una asignación con una functionexpresión, que se evalúa en orden normal de arriba hacia abajo.

Si cambiaste el ejemplo para decir:

var internalFoo = function() { return true; };

dejaría de funcionar.

La declaración de la función está sintácticamente bastante separada de la expresión de la función, a pesar de que parecen casi idénticas y pueden ser ambiguas en algunos casos.

Esto está documentado en el estándar ECMAScript , sección 10.1.3 . ¡Desafortunadamente, ECMA-262 no es un documento muy legible incluso para los estándares estándares!

*: la función que contiene, bloque, módulo o script.

bobince
fuente
Supongo que realmente no es legible. Acabo de leer la sección que señaló 10.1.3 y no entendí por qué las disposiciones allí causarían este comportamiento. Gracias por la información.
Edu Felipe el
2
@bobince Bien, comencé a dudar de mí mismo cuando no pude encontrar una sola mención del término "elevación" en esta página. Esperemos que estos comentarios tengan suficiente Google Juice ™ para
arreglar las
2
Este es un combo popular de preguntas / respuestas. Considere actualizar con un enlace / extracto a la especificación anotada ES5. (Que es un poco más accesible.)
1
Este artículo tiene algunos ejemplos: JavaScript-Scoping-and-Hoisting
Lombas
Encontré que bastantes bibliotecas usan la función antes de la definición, incluso algunos idiomas lo permiten oficialmente, ej. Haskell Para ser sincero, esto podría no ser algo malo, ya que puede escribir un poco más expresivo en algunos casos.
windmaomao
28

Se llama LEVANTAMIENTO - Invocar (llamar) una función antes de que se haya definido.

Dos tipos diferentes de funciones sobre las que quiero escribir son:

Funciones de expresión y funciones de declaración

  1. Funciones de expresión:

    Las expresiones de funciones se pueden almacenar en una variable para que no necesiten nombres de funciones. También se nombrarán como una función anónima (una función sin nombre).

    Para invocar (llamar) estas funciones siempre necesitan un nombre de variable . Este tipo de función no funcionará si se llama antes de que se haya definido, lo que significa que el levantamiento no está sucediendo aquí. Siempre debemos definir primero la función de expresión y luego invocarla.

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");

    Así es como puedes escribirlo en ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. Funciones de declaración:

    Las funciones declaradas con la siguiente sintaxis no se ejecutan inmediatamente. Se "guardan para su uso posterior" y se ejecutarán más tarde, cuando se invoquen (se invoquen). Este tipo de función funciona si lo llama ANTES o DESPUÉS de donde se ha definido. Si llama a una función de declaración antes de que se haya definido, la elevación funciona correctamente.

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");

    Ejemplo de elevación:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }
Negin
fuente
let fun = theFunction; fun(); function theFunction() {}también funcionará (Node y navegadores)
fider
14

El navegador lee su HTML de principio a fin y puede ejecutarlo tal como se lee y analiza en fragmentos ejecutables (declaraciones de variables, definiciones de funciones, etc.) Pero en cualquier momento solo puede usar lo que se ha definido en el script antes de ese punto.

Esto es diferente de otros contextos de programación que procesan (compilan) todo su código fuente, quizás lo vinculen con cualquier biblioteca que necesite para resolver referencias y construyan un módulo ejecutable, en cuyo punto comienza la ejecución.

Su código puede hacer referencia a objetos con nombre (variables, otras funciones, etc.) que se definen más adelante, pero no puede ejecutar el código de referencia hasta que todas las piezas estén disponibles.

A medida que se familiarice con JavaScript, se dará cuenta de su necesidad de escribir las cosas en la secuencia adecuada.

Revisión: para confirmar la respuesta aceptada (arriba), use Firebug para recorrer la sección de secuencia de comandos de una página web. Verá que salta de una función a otra, visitando solo la primera línea, antes de que realmente ejecute cualquier código.

dkretz
fuente
3

Algunos idiomas requieren que los identificadores se definan antes de su uso. Una razón para esto es que el compilador usa una sola pasada en el código fuente.

Pero si hay varios pases (o se aplazan algunos controles), puede vivir perfectamente sin ese requisito. En este caso, el código probablemente se lea primero (y se interprete) y luego se establecerán los enlaces.

Toon Krijthe
fuente
2

Solo he usado JavaScript un poco. No estoy seguro de si esto ayudará, pero se parece mucho a lo que está hablando y puede dar alguna idea:

http://www.dustindiaz.com/javascript-function-declaration-ambiguity/

CarrilesHijo
fuente
El enlace ya no está muerto.
mwclarke
1
El enlace está muerto.
Jerome Indefenzo
Aquí hay una instantánea de archive.org. Parece que el autor eliminó todo su sitio web por tener contenido desactualizado, no es que esta publicación de blog caiga en esa categoría.
jkmartindale
1

El cuerpo de la función "internalFoo" debe ir a algún lugar en el momento del análisis, de modo que cuando el intérprete JS lee el código (también conocido como análisis), se crea la estructura de datos para la función y se asigna el nombre.

Solo más tarde, cuando se ejecuta el código, JavaScript realmente intenta averiguar si existe "internalFoo" y qué es y si se puede llamar, etc.

Aaron Digulla
fuente
-4

Por la misma razón, lo siguiente siempre se colocará fooen el espacio de nombres global:

if (test condition) {
    var foo;
}
Andrew Hedges
fuente
8
En realidad, es por razones muy diferentes. El ifbloque no crea un ámbito, mientras que un function()bloque siempre crea uno. La verdadera razón fue que la definición de los nombres globales de JavaScript ocurre en la fase de compilación, de modo que incluso si el código no se ejecuta, el nombre está definido. (Lo siento, tardó tanto en comentar)
Edu Felipe