Pourquoi le premier de ces exemples ne fonctionne-t-il pas, mais tous les autres fonctionnent?
// 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'); }
})();
Ce n'est ni un problème de portée ni un problème de fermeture. Le problème est de comprendre entre déclarations et expressions.
Le code JavaScript, puisque même la première version de Netscape de JavaScript et la première copie de Microsoft, est traité en deux phases:
Phase 1: compilation - dans cette phase, le code est compilé dans un arbre de syntaxe (et bytecode ou binaire selon le moteur).
Phase 2: exécution - le code analysé est alors interprété.
La syntaxe de la fonction déclaration est:
function name (arguments) {code}
Les arguments sont bien sûr optionnels (le code est également optionnel mais à quoi ça sert?).
Mais JavaScript vous permet également de créer des fonctions en utilisant expressions. La syntaxe des expressions de fonction est similaire aux déclarations de fonction, sauf qu'elles sont écrites dans le contexte de l'expression. Et les expressions sont:
=
(Ou :
Sur les littéraux d'objet).()
.Expressions contrairement à déclarations sont traités dans la phase d'exécution plutôt que dans la phase de compilation. Et à cause de cela, l'ordre des expressions est important.
Donc, pour clarifier:
// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();
Phase 1: compilation. Le compilateur voit que la variable someFunction
est définie et la crée. Par défaut, toutes les variables créées ont la valeur indéfinie. Notez que le compilateur ne peut pas encore attribuer de valeurs à ce stade car les valeurs peuvent nécessiter que l'interpréteur exécute du code pour renvoyer une valeur à affecter. Et à ce stade, nous n'exécutons pas encore de code.
Phase 2: exécution. L'interpréteur voit que vous voulez passer la variable someFunction
à setTimeout. Et il en est ainsi. Malheureusement, la valeur actuelle de someFunction
n'est pas définie.
// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
Phase 1: compilation. Le compilateur voit que vous déclarez une fonction avec le nom someFunction et donc il la crée.
Phase 2: L'interpréteur voit que vous voulez passer someFunction
à setTimeout. Et il en est ainsi. La valeur actuelle de someFunction
est sa déclaration de fonction compilée.
// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
Phase 1: compilation. Le compilateur voit que vous avez déclaré une variable someFunction
et la crée. Comme précédemment, sa valeur n'est pas définie.
Phase 2: exécution. L'interpréteur transmet une fonction anonyme à setTimeout pour être exécuté plus tard. Dans cette fonction, il voit que vous utilisez la variable someFunction
afin de créer une fermeture de la variable. À ce stade, la valeur de someFunction
n'est toujours pas définie. Ensuite, il vous voit attribuer une fonction à someFunction
. À ce stade, la valeur de someFunction
n'est plus indéfinie. 1/100e de seconde plus tard, le setTimeout se déclenche et la fonction est appelée. Puisque sa valeur n'est plus indéfinie, cela fonctionne.
Le cas 4 est vraiment une autre version du cas 2 avec un peu de cas 3. Au point someFunction
est passé à setTimeout, il existe déjà en raison de sa déclaration.
Précision supplémentaire:
Vous vous demandez peut-être pourquoi setTimeout(someFunction, 10)
ne crée pas de fermeture entre la copie locale de someFunction et celle passée à setTimeout. La réponse à cela est que les arguments de fonction en JavaScript sont toujours, toujours passés par valeur s'ils sont des nombres ou des chaînes ou par référence pour tout le reste. Ainsi, setTimeout ne reçoit pas réellement la variable someFunction (ce qui aurait signifié la création d'une fermeture) mais obtient uniquement l'objet auquel someFunction fait référence (qui dans ce cas est une fonction). C'est le mécanisme le plus utilisé en JavaScript pour rompre les fermetures (par exemple dans les boucles).
La portée de Javascript est basée sur la fonction et non sur la portée strictement lexicale. cela signifie que
Somefunction1 est défini depuis le début de la fonction englobante, mais son contenu n'est pas défini jusqu'à ce qu'il soit attribué.
dans le deuxième exemple, l'affectation fait partie de la déclaration, elle se déplace donc vers le haut.
dans le troisième exemple, la variable existe lorsque la fermeture intérieure anonyme est définie, mais elle n'est utilisée que 10 secondes plus tard, la valeur étant alors attribuée.
quatrième exemple a à la fois les deuxième et troisième raisons de travailler
Cela ressemble à un cas basique de suivre une bonne procédure pour éviter les ennuis. Déclarez les variables et les fonctions avant de les utiliser et déclarez des fonctions comme ceci:
function name (arguments) {code}
Évitez de les déclarer avec var. C'est tout simplement bâclé et conduit à des problèmes. Si vous prenez l'habitude de tout déclarer avant de l'utiliser, la plupart de vos problèmes disparaîtront rapidement. Lors de la déclaration de variables, je les initialisais avec une valeur valide immédiatement pour m'assurer qu'aucune d'entre elles n'était indéfinie. J'ai également tendance à inclure du code qui vérifie les valeurs valides des variables globales avant qu'une fonction les utilise. Il s'agit d'une protection supplémentaire contre les erreurs.
Les détails techniques de la façon dont tout cela fonctionne sont un peu comme la physique du fonctionnement d'une grenade à main lorsque vous jouez avec. Mon conseil simple est de ne pas jouer avec des grenades à main en premier lieu.
Certaines déclarations simples au début du code peuvent résoudre la plupart de ces types de problèmes, mais un nettoyage du code peut être nécessaire.
Remarque additionnelle:
J'ai effectué quelques expériences et il semble que si vous déclarez toutes vos fonctions de la manière décrite ici, peu importe leur ordre. Si la fonction A utilise la fonction B, la fonction B ne le fait pas doivent être déclarés avant la fonction A.
Donc, déclarez d'abord toutes vos fonctions, vos variables globales ensuite, puis mettez votre autre code en dernier. Suivez ces règles générales et vous ne pouvez pas vous tromper. Il pourrait même être préférable de mettre vos déclarations en tête de la page Web et votre autre code dans le corps pour assurer l'application de ces règles.
Parce que someFunction1
N'a pas encore été affecté au moment où l'appel à setTimeout()
est exécuté.
someFunction3 peut ressembler à un cas similaire, mais comme vous passez une fonction enveloppant someFunction3()
à setTimeout()
dans ce cas, l'appel à someFunction3()
n'est évalué que plus tard. .