对 Javascript 中使用 AJAX 的闭包的澄清

Clarification on closures in Javascript with AJAX

提问人:Merc 提问时间:10/30/2011 更新时间:1/15/2016 访问量:2680

问:

我应该在 Javascript 中正确理解 Clores,但我显然没有......

目前,我正在阅读的文本具有以下函数来抽象 AJAX 调用:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = (function(myxhr){
    return function(){
      callback(myxhr);
    }
  })(xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

这是我的实际问题:我的大脑拒绝理解为什么这个不起作用:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = callback(xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

我的意思是,以“我”的方式,我想象会发生的事情是我调用 say request('http://...', a_callback)。在 request() 中,创建了一个新的 xhr 对象,并将其分配给回调...难道行不通吗?(令人讨厌的)副作用是什么?根据我的(有限的)理解,例如,在一个循环中,你最终可能会引用函数变量的最新值,你需要闭包。但在这里......不是“var xhr=...”确保每次都创建一个新对象?

请解释一下,好像我的智商是 30(这可能是真的:D)

默克。

JavaScript AJAX 闭包

评论

1赞 vzwick 10/30/2011
请解释一下,就好像我的智商为 30 一样 - 你的问题听起来更像是“AJAX,Y U NO WORK,PLZ HELP,紧急!!!!1111".
1赞 Merc 10/31/2011
:-)好吧,当我写问题时,我什至弄错了“我的”例子,所以我想我用英语写作会获得额外的分数,但仅此而已!

答:

6赞 SLaks 10/30/2011 #1

当你写 时,你会立即调用并将其结果分配给 。xhr.onreadystatechange = callback(xhr)callbackonreadystatechange

1赞 Dave Newton 10/30/2011 #2

在代码中,您显示您实际上是在调用,因为末尾。callback()

9赞 Pointy 10/30/2011 #3

在第一个示例中,您不需要额外的闭包。这样就可以了:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function(){
      callback(xhr);
  };
  xhr.open('GET', url, true);
  xhr.send('');
}

在现代浏览器(或使用 polyfill 修补的浏览器)中,您也可以这样做:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = callback.bind(null, xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

编辑 — 还要记下@Raynos提供的答案。实际上,您并不需要将 XHR 对象作为参数传递。

再次编辑 — 在回应合法评论时,您问为什么您最初的想法行不通:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = callback(xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

问题出在这里的行上:

  xhr.onreadystatechange = callback(xhr);

这将立即分配给从调用回调函数返回的值。换句话说,不是在状态更改时建立要调用的内容,而是立即进行调用。这种错误非常普遍,因为很容易读错。但是,每当 JavaScript 看到函数引用后跟括号中的参数列表时,这都会被解释为执行函数调用的请求,而不是对未来函数调用的函数包装器的请求。onreadystatechange

评论

0赞 Merc 10/31/2011
所以我可以写这个,对吧?函数 request(url, callback){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = 回调; xhr.open('GET', url, true); xhr.send(''); }
0赞 Pointy 10/31/2011
是的,你也可以这样做!唯一的问题是浏览器不会将 XHR 对象作为参数传递,但它会将其设置为您的值。this
1赞 Merc 10/31/2011
嘿,尖尖的......所以事实证明我是对的!顺便说一句,我从 Stoyan Stefanov 的“面向对象的 Javascript”中得到了闭包示例。一本很棒的书,但第 258 页的示例,以 xhr 为结尾,真的让我大吃一惊......我当时想:“好吧,我不会在这里使用封盖,他做到了,所以我会塞满......但为什么要关闭?!?”。我把头撞了大约 2 个小时,试图弄清楚为什么你可能需要闭合......事实证明你没有:D
0赞 MJB 1/15/2016
投了反对票,因为它实际上并没有解释为什么它在他的例子中不起作用。
1赞 Pointy 1/15/2016
我认为@MJB解决了。
1赞 Dan 10/30/2011 #4

问得好。函数调用和指向函数的指针之间有一个重要的区别 - 您希望为他们提供指向函数的指针(不是真的,它是一个闭包,但这就是我所说的),以便他们以后可以调用它。如果你曾经做过函数式编程,或者玩过Lisp家族中的任何内容,这一点非常重要。

1赞 Raynos 10/30/2011 #5

您无需传递回调。xhr

你可以做xhr.onreadystatechange = callback;

然后用于引用回调中的对象。thisxhr

例:

xhr.onreadystatechange = function () {
  console.log(this.readystate);
};

免責聲明:对于跨浏览器支持的某些价值 >_>

评论

0赞 Pointy 10/30/2011
呃......?哦,对了;因为浏览器以 XHR 对象作为接收方调用 XHR 回调。(对吧?this
0赞 Raynos 10/30/2011
@Pointy,因为这就是调用事件侦听器的工作方式。见 4.6。XHR 是 EventTarget,是 XHR 对象。target
1赞 Yola 10/30/2011 #6

在 C 语言和函数返回后的大多数其他常用语言中,由于堆栈帧被销毁,所有局部变量都不再可访问。

在 JavaScript 中,如果在另一个函数中声明一个函数,则局部变量在从调用的函数返回后仍可访问。

0赞 Merc 10/31/2011 #7

我正在回答我自己的问题,因为编辑会混淆一切 - 如果这不是这样做的方法,请原谅我。

所以。。。在我的例子中,我真正想写的是:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function(){ callback(xhr); }
  xhr.open('GET', url, true);
  xhr.send('');
}

或者,因为我不需要 xhr(我可以使用“this”):

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = callback;
  xhr.open('GET', url, true);
  xhr.send('');
}

所以,最后,我是对的......使用闭包(如原始问题所示)完全是浪费时间,对吧?

如果有一个循环,那会很重要,我必须设置几个回调。但在这种情况下,由于每次调用“请求”时都会创建一个新的 xhr,并且它设置了正确的回调,因此实际上不需要关闭。右?

默克。