JavaScript中的词法范围,变量生存期和承诺

Lexical scope, variable lifetime and promises in javascript

本文关键字:变量 生存期 承诺 范围 词法 JavaScript      更新时间:2023-09-26

我已经阅读了SO中的答案数量,但是我无法在脑海中对javascript中的可变生命周期做出明确的解释。一般来说,答案是关于hoisting/shadowing而这里的情况并非如此。例如,我们经常与jQuery一起编写这些类型的脚本;

function getSomeData() {
  var $container = $('#someContainer');
  $.get('/url', function(data) {
    $container.html(data);
  });
}

现在,我的主要问题是$container变量如何保持对匿名回调函数可用?我知道,当执行到$.get时,get立即返回,getSomeData最终返回,尽管get将来随时返回。因此,我被迫认为getSomeData实际上保留在函数调用堆栈中,以便为回调函数提供$container变量,因为据我所知,如果在使用它的作用域中找不到变量,解释器会在父作用域(到全局作用域(中查找它。以下是附带问题:

  • 这种模式是否会导致性能下降?
  • 如果我将匿名函数更改为声明的函数并使用类似;

    var callback = function(data) { $container.html(data); } $.get('/url', callback);

    这种用法有什么优点(调试目的和可读性除外(?

  • 如果这里没有涉及函数调用堆栈,那么在 promise 解析之前$container存储在哪里?Plase注意,这种嵌套也是可能的:

    function getSomeData() { var $container = $('#someContainer'); var replaceHtml = function(data) { $container.html(data); } $.get('/url', function(data) { replaceHtml(data); }); }

请对此给出明确的解释,谢谢!

现在,我的主要问题是$container变量如何可用于匿名回调函数?

。因此,我被迫认为getSomeData实际上保留在函数调用堆栈中,以便为回调函数提供$container变量,因为据我所知,如果在使用它的作用域中找不到变量,解释器会在父作用域(到全局作用域(中查找它。

你真的接近理解这一点。只需进行一些更正和澄清即可到达那里。

它与函数调用堆栈无关。您说得很对,getSomeData在运行回调之前返回。这意味着它像往常一样从堆栈中脱落。但是,它的局部变量等继续存在,即使函数已返回。为什么?因为回调对它们有间接引用。

当调用 JavaScript 函数时,会创建一个对象(理论上(,该对象是该调用的执行上下文。该上下文包含各种内容,包括一个名为变量环境的对象,该对象包含参数和变量以及与调用相关的参数和变量。在上下文中创建的任何函数都会保留对该上下文的变量环境对象的引用,因此即使函数已返回,也可以访问它们。这些函数称为闭包,因为它们"关闭"创建它们的上下文。

因此,当回调引用$container变量时,JavaScript 引擎会查看回调自己的上下文及其变量环境,并且在那里找不到$container,而是查看下一个包含环境。它在那里找到$container并使用它。

让我们将其分解为几个步骤。请注意,这里有挥手,规范包含详细信息。

  1. 有些东西叫getSomeData
  2. 引擎将返回地址推送到堆栈上
  3. 引擎为调用创建执行上下文
  4. 引擎为上下文创建一个变量环境,用参数、局部变量等填充它。
  5. 引擎运行函数中的代码
    • 其中一部分是创建回调函数,该函数被赋予对变量环境对象的引用
    • 另一部分是将该函数引用交给$.get,这会保留一段时间
  6. 代码执行"掉落"了函数的末尾,因此引擎从堆栈中弹出返回地址并继续
  7. 一段时间后,$.get通过它保留的对它的引用来调用回调
  8. 为调用回调创建新的上下文和变量环境
  9. 回调代码运行,在其自己的变量环境中查找内容以及创建它的环境(用于$container(
  10. 代码执行"掉落"了函数的末尾,引擎弹出堆栈,$.get删除了对函数的引用
  11. 由于该函数没有任何引用,因此它有资格进行垃圾回收
  12. 在某些时候,引擎的 GC 算法会释放该函数,因此该函数不再引用调用 getSomeData 的变量环境对象,因此该对象符合 GC 的条件。
  13. 在某些时候,引擎会释放可变环境对象
  14. 现在,除了副作用(例如实际使用来自$.get的信息(之外,所有内容都已清理完毕

这种模式是否会导致性能下降?

本身不是,不是。如果您创建一个函数并保留它,那么由于它保留了创建它的变量环境,这可能会导致内存影响。但是一旦你释放了函数(在这种情况下,一旦$.get调用了它并删除了对它的引用(,那么该函数就可以被GC'd,变量环境(及其内容(可以被GC'd。

在上面,我谈到了很多对象和垃圾回收,但当然JavaScript引擎可以并且围绕这一点进行了很多优化。这是现代发动机比旧发动机快得多的重要原因。(只是一部分,但一部分。

如果我将匿名函数更改为声明的函数并使用类似;

var callback = function(data) {
  $container.html(data);
}
$.get('/url', callback);

这种用法有什么优点(调试目的和可读性除外(?

不。

如果这里没有涉及函数调用堆栈,那么在 promise 解析之前,$container存储在哪里?

见上文。