XMLHttpRequestを使用していて、successコールバック関数のローカル変数にアクセスしたい。
これがコードです:
_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();
}
_
そして、私はそれをこのように呼びたいです:
_var test = "lol";
getFileContents("hello.js", function(data) {
alert(test);
});
_
ここで、test
はコールバック関数のスコープ外になります。なぜなら、コールバック関数内でアクセスできるのは、囲んでいる関数の変数のみだからです。 alert(test);
がtest
を正しく表示するようにtest
をコールバック関数に渡すための最良の方法は何ですか?
編集:
ここで、上で定義した関数を呼び出す次のコードがあるとします。
_for (var test in testers) {
getFileContents("hello.js", function(data) {
alert(test);
});
}
_
alert(test);
コードは、test
ループからのfor
の最後の値のみを出力します。関数test
が呼び出されたときにgetFileContents
の値を出力するようにするにはどうすればよいですか? (getFileContents
を変更せずにこれを実行したいと思います。これは非常に一般的なヘルパー関数であり、test
のような特定の変数を渡して特定にしたくないためです。
あなたが提供したコードでtest
はまだコールバック内のスコープにあります。 xhr
は、data
として渡されるxhr.responseText
以外は使用されません。
コメントから更新:
コードが次のようになっていると仮定します。
for (var test in testers)
getFileContents("hello"+test+".js", function(data) {
alert(test);
});
}
このスクリプトを実行すると、test
にtesters
のキーの値が割り当てられます-getFileContents
が呼び出されるたびに、バックグラウンドでリクエストが開始されます。リクエストが完了すると、コールバックが呼び出されます。 test
には、ループの実行がすでに終了しているため、ループからのFINAL VALUEが含まれます。
この種の問題を修正するクロージャーと呼ばれる使用可能な手法があります。コールバック関数を返す関数を作成し、次のようにして変数に保持できる新しいスコープを作成できます。
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
);
}
これにより、test
の値を「保持」する新しいスコープが(新しい関数と共に)基本的に作成されます。
同じようなことを書く別の方法:
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`
}
JavaScriptは 字句スコープ を使用します。これは基本的に、2番目のコード例が意図したとおりに機能することを意味します。
David Flanaganの Definitive Guide から借りた次の例を考えてみましょう。1:
var x = "global";
function f() {
var x = "local";
function g() { alert(x); }
g();
}
f(); // Calling this function displays "local"
また、C、C++、Javaとは異なり、JavaScriptにはブロックレベルのスコープがないことに注意してください。
また、次の記事も参考にしてください。
1 David Flanagan: JavaScript-The Definitive Guide 、第4版、48ページ。
このシナリオでは、期待どおりにテストが解決されますが、this
の値は異なる場合があります。通常、スコープを保持するには、次のように非同期関数のパラメーターにします。
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);
私は同様の問題に遭遇しました。私のコードは次のようになりました:
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));
}
多くの場合、出力は最後のオブジェクトの応答テキストでした(ただし、常にではありません)。実際、それは一種のランダムであり、応答の数さえも変化しました(一定の入力に対して)。関数が作成されている必要があることがわかります。
xmlHttp.onreadystatechange=function()
{
if(this.readyState==4)
{
var responseText = this.responseText;
Debug(responseText);
}
}
基本的に、最初のバージョンでは、「xmlHttp」オブジェクトはループの現在のステージのバージョンに設定されます。ほとんどの場合、ループはAJAX要求のいずれかが完了する前に完了していたため、この場合、「xmlHttp」はループの最後のインスタンスを参照しました。この最後の要求が他の前に完了した場合次に、準備状態が変化したとき(準備状態が<4であっても)は、最後の要求からの応答をすべて出力します。
解決策は、「xmlHttp」を「this」に置き換えることです。これにより、コールバックが呼び出されるたびに、内部関数がリクエストオブジェクトの正しいインスタンスを参照します。