【问题标题】:How to structure javascript callback so that function scope is maintained properly如何构造javascript回调以便正确维护函数范围
【发布时间】:2010-05-24 22:44:13
【问题描述】:

我正在使用 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); 代码仅打印来自for 循环的test 的最后一个值。如何使它在调用函数getFileContents 期间打印test 的值? (我想在不更改getFileContents 的情况下执行此操作,因为它是一个非常通用的辅助函数,我不想通过将test 之类的特定变量传递给它来使其具体化。

【问题讨论】:

标签: javascript function callback scope


【解决方案1】:

使用您提供的代码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`
}

【讨论】:

  • 如果第一个代码块在另一个 .js 文件中,使用
  • 是的。 “封闭函数范围”意味着您的回调函数从它声明的函数(具有test 变量)扩展其范围。如果变量没有在任何函数内部声明,它们将在“全局范围”内。
  • 最后一个问题:如果testfor each 循环内改变值,为什么test 的最后一个值是alert(test); 代码打印的唯一值?以及如何修复它以便打印在调用getFileContents 期间设置的test 的值?
  • 有关上述问题的更多说明,请参阅问题编辑。
  • @Chetan - 更新了关于使用闭包解决循环问题的说明。
【解决方案2】:

JavaScript 使用lexical scoping,这基本上意味着您的第二个代码示例将按照您的预期工作。

考虑以下示例,借用自 David Flanagan 的 Definitive Guide1

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 页。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 我该如何调用函数getFileContents
    【解决方案4】:

    我遇到了类似的问题。我的代码如下所示:

    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”指的是循环的最后一个实例。如果这个最终请求在其他请求之前完成,那么只要它们的就绪状态发生变化(即使它们的就绪状态

    解决方案是将“xmlHttp”替换为“this”,这样每次调用回调时,内部函数都会引用请求对象的正确实例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-28
      • 1970-01-01
      • 1970-01-01
      • 2014-03-08
      • 2021-11-12
      • 1970-01-01
      • 1970-01-01
      • 2014-10-08
      相关资源
      最近更新 更多