2010-10-08 13 views
73

Pourquoi le premier de ces exemples ne fonctionne-t-il pas, mais tous les autres le font?Déclaration de fonction JavaScript et ordre d'évaluation

// 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'); } 
})(); 

Répondre

168

Ce n'est ni un problème de portée ni un problème de fermeture. Le problème est dans la compréhension entre déclarations et expressions.

code JavaScript, car même première version de Netscape de première copie de JavaScript et Microsoft de celui-ci, est traité en deux phases:

Phase 1: compilation - dans cette phase, le code est compilé dans un arbre de syntaxe (et bytecode ou binaire en fonction du moteur).

Phase 2: exécution - le code analysé est ensuite interprété.

La syntaxe de la fonction déclaration est:

function name (arguments) {code} 

Les arguments sont bien sûr en option (le code est facultatif aussi bien, mais quel est le point de cela?).

Mais JavaScript vous permet également de créer des fonctions en utilisant les expressions . La syntaxe des expressions de fonction est similaire aux déclarations de fonction, sauf qu'elles sont écrites dans le contexte d'expression. Et les expressions sont:

  1. Tout à droite d'un signe = (ou : sur les littéraux d'objet).
  2. Tout entre parenthèses ().
  3. Paramètres des fonctions (ceci est déjà couvert par 2).sont traités dans la phase d'exécution

expressions contrairement __gVirt_NP_NNS_NNPS<__ déclarations plutôt que la phase de compilation. Et à cause de cela, l'ordre des expressions compte.

Donc, pour préciser:


// 1 
(function() { 
setTimeout(someFunction, 10); 
var someFunction = function() { alert('here1'); }; 
})(); 

Phase 1: compilation. Le compilateur voit que la variable someFunction est définie pour la créer. Par défaut, toutes les variables créées ont la valeur non définie. Notez que le compilateur ne peut pas encore affecter 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 c'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 le crée. Phase 2: L'interpréteur voit que vous voulez passer someFunction à setTimeout. Et c'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 est indéfinie.

Phase 2: exécution. L'interpréteur passe une fonction anonyme à setTimeout pour être exécuté plus tard. Dans cette fonction, vous voyez que vous utilisez la variable someFunction pour créer une fermeture de la variable. À ce stade, la valeur de someFunction est toujours indéfinie. Ensuite, il vous voit attribuer une fonction à someFunction. À ce stade, la valeur de someFunction n'est plus indéfinie. 1/100ème de seconde plus tard les déclencheurs setTimeout et la fonction someFunction est appelée. Puisque sa valeur n'est plus indéfinie, cela fonctionne.


Cas n ° 4 est vraiment une autre version de cas 2 avec un peu de cas 3 jeté. Au point someFunction est passé à SetTimeOut il existe déjà parce qu'elle est déclarée.


précisions supplémentaires:

On peut se demander pourquoi setTimeout(someFunction, 10) ne crée pas de fermeture entre la copie locale de someFunction et celui passé à 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 n'obtient pas réellement la variable someFunction qui lui est transmise (ce qui aurait signifié la création d'une fermeture) mais seulement l'objet auquel fait référence someFunction (qui dans ce cas est une fonction).C'est le mécanisme le plus largement utilisé en JavaScript pour casser les fermetures (par exemple dans les boucles).

+6

C'était une très bonne réponse. –

+0

Il s'agit probablement d'un manque de compréhension des fermetures, mais j'ai toujours pensé qu'il s'agissait d'un accès à une portée, et non pas de créer quelque chose entre une portée et une autre. Je pensais aussi que c'était au niveau de la portée, pas au niveau variable. Cela vous dérangerait-il d'en dire un peu plus à ce sujet ou de m'indiquer ce que je peux lire? Encore une fois, bonne réponse, j'aimerais pouvoir voter deux fois. –

+0

Cette réponse me donne envie de voter plusieurs fois pour la même réponse. Vraiment une bonne réponse. Merci – ArtBIT

1

Parce que someFunction1 n'a pas encore été attribué au moment de l'appel à setTimeout() est exécuté. SomeFunction3 peut ressembler à un cas similaire, mais comme vous passez une fonction someFunction3() à setTimeout() dans ce cas, l'appel à someFunction3() n'est pas évalué plus tard.

+0

Mais 'someFunction2' n'a pas encore été affecté lorsque l'appel de' setTimeout() 'est exécuté soit ...? – jnylen

+1

@jnylen: déclarer une fonction avec le mot-clé 'function' n'est pas précisément équivalent à l'assignation d'une fonction anonyme à une variable. Les fonctions déclarées comme 'fonction foo()' sont "hissées" au début de la portée courante, tandis que les affectations de variables se produisent au moment où elles sont écrites. – Chuck

+0

+1 pour les fonctions spéciales. Cependant, juste parce que cela peut fonctionner ne signifie pas que cela devrait être fait. Toujours déclarer avant d'utiliser. – mway

2

La portée de Javascript est basée sur la fonction, pas sur la portée strictement lexicale. cela signifie que

  • Somefunction1 est défini à partir du début de la fonction d'enceinte, mais il est contenu est indéfini jusqu'à affecté. Dans le deuxième exemple, l'assignation fait partie de la déclaration, donc elle «se déplace» vers le haut. Dans le troisième exemple, la variable existe lorsque la fermeture interne anonyme est définie, mais elle n'est utilisée que 10 secondes plus tard. La valeur a été affectée.

  • quatrième exemple, a deux raisons deuxième et troisième à travailler

+0

Dans votre premier point, vous voulez dire 'someFunction1'? – jnylen

+0

tks, fixe ..... – Javier

1

Cela ressemble à un cas de base de suivre une bonne procédure pour éviter les ennuis. Déclarer des variables et fonctions avant de les utiliser et déclarer des fonctions comme ceci:

function name (arguments) {code} 

éviter de les déclarer avec var. Ceci est juste 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. Lorsque je déclare des variables, je les initialise immédiatement avec une valeur valide pour s'assurer qu'aucune d'elles n'est indéfinie. J'ai également tendance à inclure du code qui vérifie les valeurs valides des variables globales avant qu'une fonction les utilise. C'est une garantie 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 pourraient résoudre la plupart de ces types de problèmes, mais un nettoyage du code pourrait être nécessaire.

Note complémentaire:
J'ai couru quelques expériences et il semble que si vous déclarez toutes vos fonctions de la manière décrite ici, il ne compte pas vraiment quel ordre ils sont en fonction Si A utilise la fonction. B, la fonction B ne doit pas être déclarée avant la fonction A.

Donc, déclarez toutes vos fonctions en premier, vos variables globales ensuite, puis placez votre autre code en dernier. Suivez ces règles empiriques et vous ne pouvez pas vous tromper. Il pourrait même être préférable de mettre vos déclarations dans la tête de la page Web et votre autre code dans le corps pour assurer l'application de ces règles.

Questions connexes