为什么我不能从onreadystatechange处理程序内部访问xhr对象?

时间:2018-11-23 13:59:00

标签: javascript scope xmlhttprequest

编辑:我已经删除了原始代码,并用通用代码代替了该代码,任何人都可以自行运行来重现我的问题:

var StripeCheckout = {
    configure: function(obj){
        obj.token();
    }
};

StripeCheckout.configure({
    token: function(token) {
        var request = new XMLHttpRequest();
        request.open('POST', 'http://localhost:3000/process-order');
        request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
        request.onreadystatechange = function() {
            debugger; // right here is the problem
        };
        request.send();
    }
});

对我来说,在debugger语句所在的匿名函数中,request未被神秘地定义,尝试访问它的是ReferenceError。这真的使我感到迷惑。

这是一个很奇怪的部分。如果我创建一个在顶层定义的一次性变量,并在创建throwaway = request;对象后立即设置request,则在onreadystatechange处理程序throwaway内已定义,一切正常,即使request未定义。

我刚刚测试了它,也可以按预期在调试器语句的位置以this的身份对其进行访问。是什么赋予了?为什么request没有定义?

2 个答案:

答案 0 :(得分:1)

request的引用是对内部函数周围的执行上下文的引用,该内部函数是实现闭包一部分的堆分配结构,并且需要进行垃圾回收。

here所述,如果没有引用,则V8 JavaScript引擎(Chrome和Node.JS等应用程序使用该引擎来实现JavaScript)不会创建闭包。 (这是一种优化。)因此,当您到达调试器时,对request的引用已经丢失,这就是为什么您得到ReferenceError而不是undefined的原因。请注意,Safari 11中的行为有所不同,它使用WebKit(而不是V8)中的JavaScriptCore来实现JavaScript。

如果您在内部函数中添加对request的引用,则闭包将由V8创建,您将可以在调试器内部访问它。

示例

在Chrome中运行此代码(或者在我也使用V8的Opera中运行该代码),您会在Scope链中看到一个Closure。

var StripeCheckout = {
  configure: function(obj) {
    return obj.token();
  }
};

StripeCheckout.configure({
  token: function(token) {
    var request = new XMLHttpRequest();
    request.open('POST', 'https://jsonplaceholder.typicode.com/posts');
    request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
    request.onreadystatechange = function() {
      // include the console.log statement 
      // and everything works as expected.
      console.log('req.rs:', request.readyState);
      debugger; // right here is the problem
    };
    request.send();
  }
});

Chrome debugger showing closure

注释掉console.log语句,该语句是对闭包的唯一引用,并且闭包本身消失(使用V8)。

Closure not present with V8

但是,当您在Safari中运行相同的代码(没有引用)时,它使用的是WebKit而不是V8的JavaScriptCore,并且仍以request定义闭包存在,调试器将按预期工作。

Closure present in Safari

答案 1 :(得分:0)

JavaScript函数是闭包。它们捕获了工作所需的周围范围中的变量,但仅此而已。由于您没有在函数的实际代码中引用request,因此JavaScript不会捕获它,因为您的函数似乎不需要它。如果您包含引用request的代码,则会对其进行定义:

var request = new XMLHttpRequest();
request.open('POST', 'http://localhost:3000/process-order');
request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
request.onreadystatechange = function() {
    console.log(request.toString());
    debugger;
};
request.send();

如果在浏览器的开发人员工具窗口打开的情况下运行此代码段,它将停止在debugger语句中,并且将定义request

enter image description here

另一方面,您的原始代码段可能会失败,因为该函数中未引用request,因此闭包不包括它。在您尝试查看request的值时,它可能已被清除。

发生这种情况的原因是为了提高性能:如果JavaScript函数仅捕获了作用域中的每个变量,则将永远不会释放许多变量,并且您的网络浏览器甚至会使用更多内存并占用由于必须长时间跟踪所有这些变量,因此性能受到了影响。