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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2900839/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:24:12  来源:igfitidea点击:

How to structure javascript callback so that function scope is maintained properly

javascriptfunctioncallbackscope

提问by Chetan

I'm using XMLHttpRequest, and I want to access a local variable in the success callback function.

我正在使用 XMLHttpRequest,并且我想访问成功回调函数中的局部变量。

Here is the 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();
}

And I want to call it like this:

我想这样称呼它:

var test = "lol";

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

Here, testwould be out of the scope of the callback function, since only the enclosing function's variables are accessible inside the callback function. What is the best way to pass testto the callback function so the alert(test);will display testcorrectly?

在这里,test将超出回调函数的范围,因为在回调函数内部只能访问封闭函数的变量。传递test给回调函数以便正确alert(test);显示的最佳方法是什么test

Edit:

编辑:

Now, if I have the following code calling the function defined above:

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

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

The alert(test);code only prints the last value of testfrom the forloop. How do I make it so that it prints the value of testduring the time at which the function getFileContentswas called? (I would like to do this without changing getFileContentsbecause it's a very general helper function and I don't want to make it specific by passing a specific variable like testto it.

alert(test);代码只打印的最后一个值testfor环。如何使它testgetFileContents调用函数期间打印 的值?(我想在不更改的情况下执行此操作,getFileContents因为它是一个非常通用的辅助函数,我不想通过传递像test它这样的特定变量来使其特定。

回答by gnarf

With the code you have provided testwill still be in scope inside the callback. xhrwill not be, other than xhr.responseTextbeing passed in as data.

您提供的代码test仍然在回调范围内。 xhr不会,除了xhr.responseText作为data.

Updated from comment:

从评论更新

Assuming your code looks something like this:

假设您的代码如下所示:

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

As this script runs, testwill be assigned the values of the keys in testers- getFileContentsis called each time, which starts a request in the background. As the request finishes, it calls the callback. testis going to contain the FINAL VALUEfrom the loop, as that loop has already finished executing.

当这个脚本运行时,test将被分配键的值testers-getFileContents每次被调用,这会在后台启动一个请求。当请求完成时,它调用回调。 test将包含循环中的最终值,因为该循环已经完成执行。

There is a technique you can use called a closure that will fix this sort of problem. You can create a function that returns your callback function, creating a new scope you can hold onto your variables with:

您可以使用一种称为闭包的技术来解决此类问题。您可以创建一个返回回调函数的函数,创建一个可以保留变量的新范围:

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
  );
}

This will basically create a new scope (along with our new function) that will "hold on" to the value of test.

这将基本上创建一个新的作用域(连同我们的新函数),它将“保持” test.

Another way of writing the same sort of thing:

另一种写同样的东西的方式:

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

回答by Daniel Vassallo

JavaScript uses lexical scoping, which basically means that your second code example will work just like how you intend it to work.

JavaScript 使用词法范围,这基本上意味着您的第二个代码示例将按照您希望的方式工作。

Consider the following example, borrowed from David Flanagan's Definitive Guide1:

考虑以下示例,借自 David Flanagan 的Definitive Guide 1

var x = "global";

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

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

Also keep in mind that unlike C, C++ and Java, JavaScript does not have block-level scope.

还要记住,与 C、C++ 和 Java 不同,JavaScript 没有块级作用域。

In addition, you may also be interested in checking out the following article, which I highly recommend:

此外,您可能还有兴趣查看我强烈推荐的以下文章:



1David Flanagan: JavaScript - The Definitive Guide, Fourth Edition, Page 48.

1David Flanagan:JavaScript - 权威指南,第四版,第 48 页。

回答by Igor Zevaka

In this scenario, test will be resolved as you'd expect it, but the value of thismight be different. Normally, to preserve the scope, you would make it a parameter to the asynchronous function like so:

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

回答by Bruce

I ran into a similar problem. My code looked like 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));
}

The output was often the response text of the last object (but not always). In fact it was kind of random and even the number of responses varied (for a constant input). It turns out that the function should have been written:

输出通常是最后一个对象的响应文本(但并非总是如此)。事实上,它有点随机,甚至响应的数量也有所不同(对于恒定输入)。原来函数应该是这样写的:

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

Basically, in the first version, the "xmlHttp" object gets set to the version in the current stage of the loop. Most of the time the loop would have finished before any of the AJAX requests finished, so in this case "xmlHttp" referred to the last instance of the loop. If this final request finished before the other requests then they would all print the response from the last request whenever their ready state changed (even if their ready states were < 4).

基本上,在第一个版本中,“xmlHttp”对象被设置为循环当前阶段的版本。大多数情况下,循环会在任何 AJAX 请求完成之前完成,因此在这种情况下,“xmlHttp”指的是循环的最后一个实例。如果这个最终请求在其他请求之前完成,那么它们都会在它们的就绪状态改变时打印最后一个请求的响应(即使它们的就绪状态 < 4)。

The solution is to replace "xmlHttp" with "this" so the inner function is referring to the correct instance of the request object every time the callback is called.

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