Pourquoi puis-je utiliser une fonction avant qu'elle ne soit définie dans JavaScript?

168

Ce code fonctionne toujours, même dans différents navigateurs:

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

Je n'ai cependant pas trouvé une seule référence pour expliquer pourquoi cela devrait fonctionner. J'ai vu cela pour la première fois dans la note de présentation de John Resig, mais cela n'a été que mentionné. Il n'y a aucune explication là-bas ni nulle part d'ailleurs.

Quelqu'un pourrait-il m'éclairer.

Edu Felipe
la source
3
Dans les versions plus récentes de Firefox, cela ne fonctionne pas si le code est dans un try / catch. Voir ce violon: jsfiddle.net/qzzc1evt
Joshua Smith

Réponses:

217

La functiondéclaration est magique et oblige son identifiant à être lié avant que quoi que ce soit dans son bloc de code * soit exécuté.

Cela diffère d'une affectation avec une functionexpression, qui est évaluée dans l'ordre descendant normal.

Si vous avez changé l'exemple pour dire:

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

cela cesserait de fonctionner.

La déclaration de fonction est syntaxiquement assez distincte de l'expression de fonction, même si elles semblent presque identiques et peuvent être ambiguës dans certains cas.

Ceci est documenté dans la norme ECMAScript , section 10.1.3 . Malheureusement, ECMA-262 n'est pas un document très lisible, même selon les standards-standards!

*: la fonction, le bloc, le module ou le script contenant.

bobince
la source
Je suppose que ce n'est vraiment pas lisible. Je viens de lire la section que vous avez mentionnée sur 10.1.3 et je n'ai pas compris pourquoi les dispositions qui s'y trouvent causeraient ce comportement. Merci pour l'information.
Edu Felipe
2
@bobince D'accord, j'ai commencé à douter de moi quand je n'ai pas trouvé une seule mention du terme «levage» sur cette page. Espérons que ces commentaires contiennent suffisamment de Google Juice ™ pour régler les choses :)
Mathias Bynens
2
Il s'agit d'un combo question / réponse populaire. Pensez à mettre à jour avec un lien / extrait vers la spécification annotée ES5. (Ce qui est un peu plus accessible.)
1
Cet article a quelques exemples: JavaScript-Scoping-and-
Hoisting
J'ai trouvé pas mal de bibliothèques utilisant la fonction avant la définition, même certaines langues l'autorisent officiellement, ex. Haskell. Pour être honnête, ce n'est peut-être pas une mauvaise chose, car vous pouvez écrire un peu plus expressif dans certains cas.
windmaomao
28

Cela s'appelle HOISTING - Invoquer (appeler) une fonction avant qu'elle n'ait été définie.

Deux types de fonctions différents sur lesquels je souhaite écrire sont:

Fonctions d'expression et fonctions de déclaration

  1. Fonctions d'expression:

    Les expressions de fonction peuvent être stockées dans une variable afin qu'elles n'aient pas besoin de noms de fonction. Ils seront également nommés comme une fonction anonyme (une fonction sans nom).

    Pour invoquer (appeler) ces fonctions, elles ont toujours besoin d'un nom de variable . Ce type de fonction ne fonctionnera pas si elle est appelée avant d'avoir été définie, ce qui signifie que le levage ne se produit pas ici. Nous devons toujours définir la fonction d'expression d'abord, puis l'invoquer.

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

    Voici comment vous pouvez l'écrire dans ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. Fonctions de déclaration:

    Les fonctions déclarées avec la syntaxe suivante ne sont pas exécutées immédiatement. Ils sont "enregistrés pour une utilisation ultérieure" et seront exécutés plus tard, lorsqu'ils seront appelés (appelés). Ce type de fonction fonctionne si vous l'appelez AVANT ou APRÈS où est défini. Si vous appelez une fonction de déclaration avant qu'elle n'ait été définie, le levage fonctionne correctement.

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

    Exemple de levage:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }
Negin
la source
let fun = theFunction; fun(); function theFunction() {}fonctionnera également (Nœud et navigateurs)
fider
14

Le navigateur lit votre HTML du début à la fin et peut l'exécuter au fur et à mesure qu'il est lu et analysé en morceaux exécutables (déclarations de variables, définitions de fonctions, etc.) Mais à tout moment, il ne peut utiliser que ce qui a été défini dans le script avant ce point.

Ceci est différent des autres contextes de programmation qui traitent (compilent) tout votre code source, peut-être le lient avec toutes les bibliothèques dont vous avez besoin pour résoudre les références, et construisent un module exécutable, à quel point l'exécution commence.

Votre code peut faire référence à des objets nommés (variables, autres fonctions, etc.) qui sont définis plus loin, mais vous ne pouvez pas exécuter de code de référence tant que toutes les pièces ne sont pas disponibles.

Au fur et à mesure que vous vous familiariserez avec JavaScript, vous deviendrez intimement conscient de votre besoin d'écrire les choses dans le bon ordre.

Révision: pour confirmer la réponse acceptée (ci-dessus), utilisez Firebug pour parcourir la section de script d'une page Web. Vous le verrez passer d'une fonction à l'autre, ne visitant que la première ligne, avant d'exécuter réellement un code.

dkretz
la source
3

Certaines langues exigent que les identifiants soient définis avant utilisation. Une raison à cela est que le compilateur utilise un seul passage sur le code source.

Mais s'il y a plusieurs passes (ou si certains contrôles sont reportés), vous pouvez parfaitement vivre sans cette exigence. Dans ce cas, le code est probablement d'abord lu (et interprété), puis les liens sont définis.

Toon Krijthe
la source
2

Je n'ai utilisé que peu de JavaScript. Je ne sais pas si cela vous aidera, mais cela ressemble beaucoup à ce dont vous parlez et peut donner un aperçu:

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

RailsSon
la source
Le lien n'est plus mort.
mwclarke
1
Le lien est mort.
Jerome Indefenzo
Voici un aperçu de archive.org. On dirait que l'auteur a supprimé tout son site Web pour avoir un contenu obsolète, non pas que ce billet de blog entre dans cette catégorie.
jkmartindale
1

Le corps de la fonction "internalFoo" doit aller quelque part au moment de l'analyse, donc lorsque le code est lu (aka parsing) par l'interpréteur JS, la structure de données de la fonction est créée et le nom est attribué.

Ce n'est que plus tard, alors que le code est exécuté, JavaScript essaie réellement de savoir si "internalFoo" existe et de quoi il s'agit et s'il peut être appelé, etc.

Aaron Digulla
la source
-4

Pour la même raison, les éléments suivants seront toujours placés foodans l'espace de noms global:

if (test condition) {
    var foo;
}
Andrew Hedges
la source
8
En fait, c'est pour des raisons très différentes. Le ifbloc ne crée pas de portée, tandis qu'un function()bloc en crée toujours une. La vraie raison était que la définition des noms javascript globaux se produit lors de la phase de compilation, de sorte que même si le code ne s'exécute pas, le nom est défini. (Désolé, il a fallu si longtemps pour commenter)
Edu Felipe