Perché posso usare una funzione prima che sia definita in JavaScript?


168

Questo codice funziona sempre, anche in diversi browser:

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();

Tuttavia, non sono riuscito a trovare un solo riferimento al perché dovrebbe funzionare. L'ho visto per la prima volta nella nota di presentazione di John Resig, ma è stato menzionato solo. Non ci sono spiegazioni lì o ovunque per quella materia.

Qualcuno potrebbe illuminarmi per favore?


3
Nelle versioni più recenti di firefox, questo non funziona se il codice è in modalità try / catch. Vedi questo violino: jsfiddle.net/qzzc1evt
Joshua Smith

Risposte:


217

La functiondichiarazione è magica e fa sì che il suo identificatore sia associato prima che venga eseguito qualsiasi cosa nel suo blocco di codice *.

Ciò differisce da un compito con functionun'espressione, che viene valutata in ordine normale dall'alto verso il basso.

Se hai cambiato l'esempio per dire:

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

smetterebbe di funzionare.

La dichiarazione della funzione è sintatticamente abbastanza separata dall'espressione della funzione, anche se sembrano quasi identiche e in alcuni casi possono essere ambigue.

Questo è documentato nello standard ECMAScript , sezione 10.1.3 . Sfortunatamente l'ECMA-262 non è un documento molto leggibile neanche per gli standard!

*: la funzione, il blocco, il modulo o lo script contenenti.


Immagino che non sia davvero leggibile. Ho appena letto la sezione che hai indicato 10.1.3 e non ho capito perché le disposizioni lì avrebbero causato questo comportamento. Grazie per l'informazione.
Edu Felipe,

2
@bobince Okay, ho iniziato a dubitare di me stesso quando non sono riuscito a trovare una sola menzione del termine "sollevamento" in questa pagina. Spero che questi commenti abbiano abbastanza Google Juice ™ per sistemare le cose :)
Mathias Bynens il

2
Questa è una combo domanda / risposta popolare. Prendi in considerazione l'aggiornamento con un collegamento / estratto della specifica annotata ES5. (Che è un po 'più accessibile.)

1
Questo articolo contiene alcuni esempi: JavaScript-Scoping-and-Hoisting
Lombas,

Ho scoperto che alcune librerie usano la funzione prima della definizione, persino un linguaggio lo consente ufficialmente, ad es. Haskell. Ad essere onesti, questa potrebbe non essere una brutta cosa, dal momento che in alcuni casi puoi scrivere un po 'più espressivo.
windmaomao

28

Si chiama HOISTING - Richiamare (chiamare) una funzione prima che sia stata definita.

Due diversi tipi di funzioni di cui voglio scrivere sono:

Funzioni di espressione e funzioni di dichiarazione

  1. Funzioni di espressione:

    Le espressioni di funzione possono essere archiviate in una variabile in modo che non necessitino di nomi di funzioni. Saranno anche nominati come una funzione anonima (una funzione senza un nome).

    Per invocare (chiamare) queste funzioni hanno sempre bisogno di un nome di variabile . Questo tipo di funzione non funzionerà se viene chiamata prima che sia stata definita, il che significa che il sollevamento non sta avvenendo qui. Dobbiamo sempre definire prima la funzione di espressione e quindi invocarla.

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

    Ecco come puoi scriverlo in ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. Funzioni di dichiarazione:

    Le funzioni dichiarate con la sintassi seguente non vengono eseguite immediatamente. Sono "salvati per un uso successivo" e verranno eseguiti in seguito, quando vengono invocati (chiamati). Questo tipo di funzione funziona se la si chiama PRIMA o DOPO dove è stata definita. Se si chiama una funzione di dichiarazione prima che sia stata definita, il sollevamento funziona correttamente.

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

    Esempio di sollevamento:

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

let fun = theFunction; fun(); function theFunction() {}funzionerà anche (Nodo e browser)
fider

14

Il browser legge il codice HTML dall'inizio alla fine e può eseguirlo mentre viene letto e analizzato in blocchi eseguibili (dichiarazioni di variabili, definizioni di funzioni, ecc.) Ma in qualsiasi momento può utilizzare solo ciò che è stato definito nello script prima di quel punto.

Questo è diverso dagli altri contesti di programmazione che elaborano (compilano) tutto il codice sorgente, forse lo collegano con tutte le librerie necessarie per risolvere i riferimenti e costruiscono un modulo eseguibile, a quel punto inizia l'esecuzione.

Il codice può fare riferimento a oggetti denominati (variabili, altre funzioni, ecc.) Che sono definiti più avanti, ma non è possibile eseguire il codice di riferimento fino a quando tutti i pezzi non sono disponibili.

Man mano che acquisisci familiarità con JavaScript, diventerai intimamente consapevole della tua necessità di scrivere le cose nella sequenza corretta.

Revisione: per confermare la risposta accettata (sopra), utilizzare Firebug per passare alla sezione degli script di una pagina Web. Lo vedrai saltare da una funzione all'altra, visitando solo la prima riga, prima che esegua effettivamente qualsiasi codice.


3

Alcune lingue richiedono che gli identificatori debbano essere definiti prima dell'uso. Un motivo è che il compilatore utilizza un singolo passaggio sul codice sorgente.

Ma se ci sono più passaggi (o alcuni controlli vengono posticipati) puoi vivere perfettamente senza quel requisito. In questo caso, il codice viene probabilmente prima letto (e interpretato) e quindi vengono impostati i collegamenti.


2

Ho usato JavaScript solo un po '. Non sono sicuro che questo possa aiutare, ma sembra molto simile a quello di cui stai parlando e potrebbe fornire alcune informazioni:

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


Il link non è più morto.
mwclarke,

1
Il link è morto.
Jerome Indefenzo,

Ecco un'istantanea da archive.org. Sembra che l'autore abbia rimosso l'intero sito Web per avere contenuti obsoleti, non che questo post del blog rientri in quella categoria.
jkmartindale,

1

Il corpo della funzione "internalFoo" deve andare da qualche parte al momento dell'analisi, quindi quando il codice viene letto (aka analisi) dall'interprete JS, viene creata la struttura dei dati per la funzione e viene assegnato il nome.

Solo più tardi, quindi viene eseguito il codice, JavaScript effettivamente cerca di scoprire se esiste "internalFoo" e che cos'è e se può essere chiamato, ecc.


-4

Per lo stesso motivo, quanto segue inserirà sempre foonello spazio dei nomi globale:

if (test condition) {
    var foo;
}

8
In realtà, è per ragioni molto diverse. Il ifblocco non crea un ambito, mentre un function()blocco ne crea sempre uno. Il vero motivo era che la definizione di nomi javascript globali avviene in fase di compilazione, in modo che anche se il codice non viene eseguito, il nome viene definito. (Mi dispiace aver impiegato così tanto tempo per commentare)
Edu Felipe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.