2010-05-24 3 views
31

J'utilise XMLHttpRequest, et je veux accéder à une variable locale dans la fonction callback de succès.Comment structurer le callback javascript pour que la portée de la fonction soit correctement entretenue

Voici le code:

function getFileContents(filePath, callbackFn) { 
    var xhr = new XMLHttpRequest(); 
    xhr.onreadystatechange = function() { 
     if (xhr.readyState == 4) { 
      callbackFn(xhr.responseText); 
     } 
    } 
    xhr.open("GET", chrome.extension.getURL(filePath), true); 
    xhr.send(); 
} 

Et je veux l'appeler comme ceci:

var test = "lol"; 

getFileContents("hello.js", function(data) { 
    alert(test); 
}); 

Ici, test serait hors de la portée de la fonction de rappel, car seule la englobante Les variables de la fonction sont accessibles dans la fonction de rappel. Quel est le meilleur moyen de passer test à la fonction de rappel afin que le alert(test); affiche correctement test?

Edit:

Maintenant, si j'ai le code suivant appelle la fonction définie ci-dessus:

for (var test in testers) { 
    getFileContents("hello.js", function(data) { 
     alert(test); 
    }); 
} 

Le code alert(test); imprime uniquement la dernière valeur de test de la boucle for. Comment faire pour qu'il imprime la valeur de test pendant l'appel de la fonction getFileContents? (Je voudrais le faire sans changer getFileContents parce qu'il est une fonction d'aide très générale et je ne veux pas faire spécifique en passant une variable spécifique comme test à elle.

+0

Voir aussi: http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS

Répondre

39

Avec le code que vous avez fourni test sera toujours visible dans le rappel. xhr ne sera pas, autre que xhr.responseText être passé en tant que data.

Mise à jour de commentaire:

En supposant que votre code ressemble à quelque chose comme ceci:

for (var test in testers) 
    getFileContents("hello"+test+".js", function(data) { 
    alert(test); 
    }); 
} 

Comme ce script est exécuté, test sera attribué les valeurs des clés dans testers-getFileContents est appelée chaque heure, qui lance une requête en arrière-plan. Lorsque la requête se termine, elle appelle le rappel. test va contenir la VALEUR FINALE à partir de la boucle, car cette boucle a déjà fini d'être exécutée.

Il existe une technique que vous pouvez utiliser appelée une fermeture qui va résoudre ce genre de problème. Vous pouvez créer une fonction qui retourne votre fonction de rappel, la création d'un nouveau champ d'application, vous pouvez conserver vos variables avec:

for (var test in testers) { 
    getFileContents("hello"+test+".js", 
    (function(test) { // lets create a function who has a single argument "test" 
     // inside this function test will refer to the functions argument 
     return function(data) { 
     // test still refers to the closure functions argument 
     alert(test); 
     }; 
    })(test) // immediately call the closure with the current value of test 
); 
} 

Ce sera essentiellement créer un nouveau champ d'application (ainsi que notre nouvelle fonction) qui « tenir » à la valeur de test.

Une autre façon d'écrire le même genre de chose:

for (var test in testers) { 
    (function(test) { // lets create a function who has a single argument "test" 
    // inside this function test will refer to the functions argument 
    // not the var test from the loop above 
    getFileContents("hello"+test+".js", function(data) { 
     // test still refers to the closure functions argument 
     alert(test); 
    }); 
    })(test); // immediately call the closure with the value of `test` from `testers` 
} 
+0

Que se passe-t-il si le premier bloc de code se trouve dans un autre fichier .js, inclus avec le tag

7

JavaScript utilise lexical scoping, ce qui signifie essentiellement que votre deuxième exemple de code fonctionnera comme la façon dont vous avez l'intention de travailler

Prenons l'exemple suivant, emprunté à David Flanagan Definitive Guide .

var x = "global"; 

function f() { 
    var x = "local"; 
    function g() { alert(x); } 
    g(); 
} 

f(); // Calling this function displays "local" 

gardez à l'esprit t Contrairement à C, C++ et Java, JavaScript n'a pas de portée au niveau du bloc.

De plus, vous pouvez aussi être intéressé à vérifier l'article suivant, que je recommande fortement:


David Flanagan: JavaScript - The Definitive Guide, quatrième édition , Page 48.

2

Dans ce scénario, le test sera résolu que vous attendez, mais la valeur de this peut être différent. Normalement, pour préserver la portée, vous faire un paramètre à la fonction asynchrone comme ceci:

function getFileContents(filePath, callbackFn, scope) { 
    var xhr = new XMLHttpRequest(); 
    xhr.onreadystatechange = function() { 
     if (xhr.readyState == 4) { 
      callbackFn.call(scope, xhr.responseText); 
     } 
    } 
    xhr.open("GET", chrome.extension.getURL(filePath), true); 
    xhr.send(); 
} 


//then to call it: 
var test = "lol"; 

getFileContents("hello.js", function(data) { 
    alert(test); 
}, this); 
+0

Et comment j'appellerais la fonction 'getFileContents'? – Chetan

0

je suis tombé sur un problème similaire. Mon code ressemblait à ceci:

for (var i=0; i<textFilesObj.length; i++) 
{ 
    var xmlHttp=new XMLHttpRequest(); 
    var name = "compile/" + textFilesObj[i].fileName; 
    var content = textFilesObj[i].content; 

    xmlHttp.onreadystatechange=function() 
    { 
     if(xmlHttp.readyState==4) 
     { 
      var responseText = xmlHttp.responseText; 
      Debug(responseText); 
     } 
    } 

    xmlHttp.open("POST","save1.php",true); 
    xmlHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); 
    xmlHttp.send("filename="+name+"&text="+encodeURIComponent(content)); 
} 

La sortie est souvent le texte de réponse du dernier objet (mais pas toujours). En fait, c'était plutôt aléatoire et même le nombre de réponses variait (pour une entrée constante). Il se trouve que la fonction aurait dû être écrit:

xmlHttp.onreadystatechange=function() 
    { 
     if(this.readyState==4) 
     { 
      var responseText = this.responseText; 
      Debug(responseText); 
     } 
    } 

En fait, dans la première version, l'objet « xmlHttp » se prépare à la version dans la phase actuelle de la boucle. La plupart du temps, la boucle aurait fini avant que toutes les requêtes AJAX soient terminées, donc dans ce cas "xmlHttp" fait référence à la dernière instance de la boucle. Si cette requête finale se terminait avant les autres demandes, elles imprimeraient toutes la réponse à partir de la dernière requête chaque fois que leur état prêt changerait (même si leurs états prêts étaient < 4).

La solution consiste à remplacer "xmlHttp" par "this" afin que la fonction interne se réfère à l'instance correcte de l'objet de requête chaque fois que le rappel est appelé.

Questions connexes