Ordre des fonctions JavaScript: pourquoi est-ce important?

106

Question originale:

JSHint se plaint lorsque mon JavaScript appelle une fonction qui est définie plus bas dans la page que l'appel. Cependant, ma page est pour un jeu, et aucune fonction n'est appelée tant que tout n'a pas été téléchargé. Alors, pourquoi les fonctions de commande apparaissent-elles dans mon code?

EDIT: Je pense que j'ai peut-être trouvé la réponse.

http://www.orrectatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

Je gémis à l'intérieur. On dirait que j'ai besoin de passer UNE AUTRE journée à réorganiser six mille lignes de code. La courbe d'apprentissage avec javascript n'est pas du tout raide, mais elle est très longue.

Chris Tolworthy
la source
+1 pour l'excellente référence dans la mise à jour. Et j'espère que cela vous convaincra que vous n'avez pas vraiment besoin de réorganiser votre code. :)
awm

Réponses:

294

tl; dr Si vous n'appelez rien tant que tout n'est pas chargé, ça devrait aller.


Edit: Pour un aperçu qui couvre également certaines déclarations ES6 ( let, const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

Ce comportement étrange dépend de

  1. Comment vous définissez les fonctions et
  2. Lorsque vous les appelez.

Voici quelques exemples.

bar(); //This won't throw an error
function bar() {}

foo(); //This will throw an error
var foo = function() {}
bar();
function bar() {
    foo(); //This will throw an error
}
var foo = function() {}
bar();
function bar() {
    foo(); //This _won't_ throw an error
}
function foo() {}
function bar() {
    foo(); //no error
}
var foo = function() {}
bar();

C'est à cause de quelque chose qui s'appelle le levage !

Il existe deux façons de définir des fonctions: la déclaration de fonction et l' expression de fonction . La différence est ennuyeuse et infime, alors disons simplement cette chose légèrement fausse: si vous l'écrivez comme function name() {}, c'est une déclaration , et quand vous l'écrivez comme var name = function() {}(ou une fonction anonyme affectée à un retour, des choses comme ça), c'est une expression de fonction .

Voyons d'abord comment les variables sont gérées:

var foo = 42;

//the interpreter turns it into this:
var foo;
foo = 42;

Maintenant, comment les déclarations de fonction sont gérées:

var foo = 42;
function bar() {}

//turns into
var foo; //Insanity! It's now at the top
function bar() {}
foo = 42;

Les varinstructions "jettent" la création de footout en haut, mais ne lui attribuent pas encore de valeur. La déclaration de fonction vient ensuite sur la ligne, et finalement une valeur est affectée à foo.

Et qu'en est-il de ça?

bar();
var foo = 42;
function bar() {}
//=>
var foo;
function bar() {}
bar();
foo = 42;

Seule la déclaration de fooest déplacée vers le haut. L'affectation intervient uniquement après l'appel à bar, là où elle se trouvait avant que tout le levage ne se produise.

Et enfin, par concision:

bar();
function bar() {}
//turns to
function bar() {}
bar();

Maintenant, qu'en est-il des expressions de fonction ?

var foo = function() {}
foo();
//=>
var foo;
foo = function() {}
foo();

Tout comme les variables régulières, la première fooest déclarée au point le plus élevé de la portée, puis une valeur lui est attribuée.

Voyons pourquoi le deuxième exemple génère une erreur.

bar();
function bar() {
    foo();
}
var foo = function() {}
//=>
var foo;
function bar() {
    foo();
}
bar();
foo = function() {}

Comme nous l'avons vu précédemment, seule la création de fooest hissée, l'affectation vient là où elle apparaissait dans le code «original» (non hissé). Quand barest appelé, fooune valeur lui est attribuée avant foo === undefined. Maintenant, dans le corps de fonction de bar, c'est comme si vous faisiez undefined(), ce qui génère une erreur.

Zirak
la source
Désolé de creuser cela, mais les surcharges comme Array.prototype.someMethod = function () {} sont-elles levées? Il semble que je reçoive des erreurs si ces types de choses sont à la fin de mon script.
Edge
Comment vous assurez-vous que tout se charge ? Existe-t-il une pratique courante?
zwcloud
6

La raison principale est probablement que JSLint n'effectue qu'un seul passage sur le fichier, il ne sait donc pas que vous allez définir une telle fonction.

Si vous avez utilisé la syntaxe des instructions de fonctions

function foo(){ ... }

Il n'y a en fait aucune différence là où vous déclarez la fonction (elle se comporte toujours comme si la déclaration était au début).

D'un autre côté, si votre fonction a été définie comme une variable régulière

var foo = function() { ... };

Vous devez garantir que vous ne l'appellerez pas avant l'initialisation (cela peut en fait être une source de bogues).


Puisque réorganiser des tonnes de code est compliqué et peut être une source de bogues en soi, je vous suggère de rechercher une solution de contournement. Je suis presque sûr que vous pouvez dire à JSLint le nom des variables globales à l'avance afin qu'il ne se plaint pas de choses non déclarées.

Mettre un commentaire sur le début du fichier

/*globals foo1 foo2 foo3*/

Ou vous pouvez utiliser une zone de texte pour cela. (Je pense également que vous pouvez le transmettre dans les arguments à la fonction jslint interne si vous pouvez vous en mêler.)

hugomg
la source
Merci. Donc, la ligne / * globals * / fonctionnera? Bon - n'importe quoi pour que JsHint m'apprécie. Je suis encore nouveau dans JavaScript et j'obtiens des pauses inexplicables lorsque je rafraîchis une page, mais aucun bogue n'a été signalé. J'ai donc pensé que la solution était de respecter toutes les règles et de voir si cela se produisait toujours.
Chris Tolworthy
4

Il y a beaucoup trop de gens qui poussent des règles arbitraires sur la façon dont JavaScript doit être écrit. La plupart des règles sont des conneries.

Le levage de fonctions est une fonctionnalité de JavaScript car c'est une bonne idée.

Lorsque vous avez une fonction interne qui est souvent l'utilité des fonctions internes, l'ajouter au début de la fonction externe est un style acceptable d'écriture de code, mais cela présente l'inconvénient de devoir lire les détails pour arriver à quoi la fonction extérieure le fait.

Vous devez vous en tenir à un principe tout au long de votre base de code, soit mettre les fonctions privées en premier ou en dernier dans votre module ou fonction. JSHint est bon pour renforcer la cohérence, mais vous devez ABSOLUMENT ajuster le .jshintrc en fonction de vos besoins, PAS ajuster votre code source aux concepts de codage farfelus d'autres peuples.

Un style de codage que vous pourriez voir dans la nature, vous devriez éviter car il ne vous donne aucun avantage et seulement une douleur de refactoring possible:

function bigProcess() {
    var step1,step2;
    step1();
    step2();

    step1 = function() {...};
    step2 = function() {...};
}

C'est exactement ce que le levage de fonction est là pour éviter. Apprenez simplement la langue et exploitez ses atouts.

Henrik Vendelbo
la source
2

Seule la déclaration de fonction est levée et non l'expression de fonction (affectation).

Abhay Srivastav
la source