Si le callback est défini dans la même portée que la boucle (ce qui est souvent le cas), alors le callback aura accès à la variable index. Laissant de côté les particularités de NodeJS pour un moment, considérons cette fonction :
function doSomething(callback) {
callback();
}
Cette fonction accepte une référence de fonction de rappel et tout ce qu'elle fait, c'est l'appeler. Pas très excitant :-)
Maintenant, utilisons ça dans une boucle :
var index;
for (index = 0; index < 3; ++index) {
doSomething(function() {
console.log("index = " + index);
});
}
(Dans le code à forte intensité de calcul - comme un processus serveur - il vaut mieux ne pas faire littéralement ce qui précède dans le code de production, nous y reviendrons dans un moment).
Maintenant, quand on exécute ça, on voit le résultat attendu :
index = 0
index = 1
index = 2
Notre callback a pu accéder à index
car le callback est un fermeture sur les données dans la portée où il est défini. (Ne vous inquiétez pas du terme "fermeture". les fermetures ne sont pas compliquées .)
La raison pour laquelle j'ai dit qu'il est probablement préférable de ne pas faire littéralement ce qui précède dans le code de production à forte intensité de calcul est que le code crée une fonction sur le fichier chaque itération (sauf optimisation fantaisiste dans le compilateur, et V8 est très intelligent, mais l'optimisation de la création de ces fonctions n'est pas triviale). Voici donc un exemple légèrement retravaillé :
var index;
for (index = 0; index < 3; ++index) {
doSomething(doSomethingCallback);
}
function doSomethingCallback() {
console.log("index = " + index);
}
Cela peut paraître un peu surprenant, mais cela fonctionne toujours de la même manière, et a toujours le même résultat, car doSomethingCallback
est toujours une fermeture sur index
donc il voit toujours la valeur de index
à partir du moment où il est appelé. Mais maintenant, il n'y a qu'un seul doSomethingCallback
plutôt que d'en créer une nouvelle à chaque boucle.
Maintenant, prenons un exemple négatif, quelque chose qui n'a pas travail :
foo();
function foo() {
var index;
for (index = 0; index < 3; ++index) {
doSomething(myCallback);
}
}
function myCallback() {
console.log("index = " + index); // <== Error
}
Cela échoue, car myCallback
n'est pas défini dans la même portée (ou une portée imbriquée) que index
est défini dans, et donc index
est indéfinie dans myCallback
.
Enfin, examinons la mise en place de gestionnaires d'événements dans une boucle, car il faut faire attention à cela. Ici, nous allons nous plonger un peu dans NodeJS :
var spawn = require('child_process').spawn;
var commands = [
{cmd: 'ls', args: ['-lh', '/etc' ]},
{cmd: 'ls', args: ['-lh', '/usr' ]},
{cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;
for (index = 0; index < commands.length; ++index) {
command = commands[index];
child = spawn(command.cmd, command.args);
child.on('exit', function() {
console.log("Process index " + index + " exited"); // <== WRONG
});
}
Il semble comme ci-dessus devrait fonctionner de la même manière que nos boucles précédentes, mais il y a une différence cruciale. Dans nos boucles précédentes, le callback était appelé immédiatement, et donc il voyait la bonne valeur de index
parce que index
n'avait pas encore eu l'occasion de passer à autre chose. Dans l'exemple ci-dessus, cependant, nous allons faire tourner la boucle avant que le callback ne soit appelé. Le résultat ? Nous voyons
Process index 3 exited
Process index 3 exited
Process index 3 exited
Il s'agit d'un point crucial. Une fermeture n'a pas de copie des données qu'il referme, il a une référence en direct à elle. Donc au moment où le exit
sur chacun de ces processus est exécuté, la boucle sera déjà terminée, de sorte que les trois appels verront le même résultat. index
(sa valeur à partir du fin de la boucle).
Nous pouvons corriger ce problème en faisant en sorte que le callback utilise un fichier différents variable qui ne changera pas, comme ceci :
var spawn = require('child_process').spawn;
var commands = [
{cmd: 'ls', args: ['-lh', '/etc' ]},
{cmd: 'ls', args: ['-lh', '/usr' ]},
{cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;
for (index = 0; index < commands.length; ++index) {
command = commands[index];
child = spawn(command.cmd, command.args);
child.on('exit', makeExitCallback(index));
}
function makeExitCallback(i) {
return function() {
console.log("Process index " + i + " exited");
};
}
Maintenant nous sortons les valeurs correctes (dans l'ordre où les processus sortent) :
Process index 1 exited
Process index 2 exited
Process index 0 exited
La façon dont cela fonctionne est que le callback que nous assignons à la fonction exit
se ferme sur l'événement i
dans l'appel que nous faisons à makeExitCallback
. Le premier callback qui makeExitCallback
crée et renvoie des fermetures sur le i
pour cet appel à makeExitCallback
le deuxième callback qu'il crée ferme le dossier de l'utilisateur. i
valeur pour que Appel à makeExitCallback
(qui est différente de la i
pour l'appel précédent), etc.
Si vous donnez l'article lié ci-dessus Après lecture, un certain nombre de choses devraient être plus claires. La terminologie de l'article est un peu dépassée (ECMAScript 5 utilise une terminologie actualisée), mais les concepts n'ont pas changé.