如何构造javascript回调以便正确维护函数范围

时间:2010-05-24 22:44:14

标签: javascript function callback scope

我正在使用XMLHttpRequest,我想在成功回调函数中访问本地变量。

以下是代码:

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超出了回调函数的范围,因为只有封闭函数的变量可以在回调函数中访问。将test传递给回调函数的最佳方法是什么,以便alert(test);正确显示test

编辑:

现在,如果我有以下代码调用上面定义的函数:

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

alert(test);代码仅打印test循环中for的最后一个值。如何使其在调用函数test期间打印getFileContents的值? (我希望在不更改getFileContents的情况下执行此操作,因为它是一个非常通用的帮助函数,我不希望通过将特定变量(如test)传递给它来使其具体化。

4 个答案:

答案 0 :(得分:41)

使用您提供的代码test仍将在回调范围内。除了xhr传递xhr.responseText之外,data不会{。}}。

更新自评论

假设您的代码如下所示:

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`
}

答案 1 :(得分:7)

JavaScript使用lexical scoping,这基本上意味着您的第二个代码示例将像您打算如何工作一样工作。

考虑以下示例,借用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,第四版,第48页。

答案 2 :(得分:2)

在这种情况下,测试将按照您的预期得到解决,但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);

答案 3 :(得分:0)

我遇到了类似的问题。我的代码看起来像这样:

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”,这样每次调用回调时内部函数都会引用请求对象的正确实例。