JavaScript:理解闭包和提升

JavaScript: Understanding closures and hoisting

我知道函数声明被提升到其作用域的顶部。这允许我们在 JavaScript:

中实际声明之前使用这些函数
sayHello(); // It works!

function sayHello() {
  console.log('Hello');
}

我还了解到闭包使函数能够保留对在同一范围内声明的变量的引用:

function outerFxn() {
  let num = 0;

  function innerFxn() {
    console.log(num);
    num++;
  }

  return innerFxn;
}

const logNum = outerFxn();
logNum(); // 0
logNum(); // 1
logNum(); // 2

到目前为止一切顺利。但这里有一些奇怪的地方,我希望有人能准确解释发生了什么......

场景 1:可以理解的闭包

function zero(cb) {
  return setTimeout(cb, 0);
}

function test1() {
  let txt = 'this is a test message';

  function log() {
    console.log(txt);
  }

  zero(log);
}

在上面的示例中,log 函数保留了对其创建范围的引用,持有 txt 变量。然后,稍后在 setTimeout 中执行时,它成功记录了 txt 变量的值。伟大的。然后就是这个...

场景 2:发生了什么?

function zero(cb) {
  return setTimeout(cb, 0);
}

function test1() {
  function log() {
    console.log(txt);
  }

  let txt = 'this is a test message';

  zero(log);
}

我已将 log 函数声明移动到范围的顶部(无论如何它都会被提升到那里,对吧?),然后 我是在它下面声明 txt 变量。这一切仍然有效,我不确定为什么。当 letconst 未被提升时,log 如何保留对 txt 变量的引用?是否将闭包作用域 作为一个整体 进行了分析?我可以稍微清楚地了解 JavaScript 引擎在此处逐步执行的操作。谢谢大地!

它是您离开 test1 函数后作用域的一部分。此时它是否与 varletconst 一起使用并不重要。由于对整个 body 进行了评估,因此该变量存在于作用域中。

如果您在评估 let 声明之前尝试使用 log,则会出现错误。

Edit:从技术上讲,用 letconst 声明的变量在范围内,但它们被单元化,如果您尝试访问它们。只有在您到达声明时它们才会被初始化并且您可以访问它们。所以它们总是在范围内,只是在评估声明之前不可用。

"Are closure scopes analyzed as-a-whole?" - 是的。闭包保留范围,因为它是在你(词法)离开它的那一刻。在您的示例中,当您到达 test1 中的结尾 } 时,txt 确实存在,因此它在范围内,并且 log 可以毫无问题地访问它。

注意 "lexically" 上面:绑定是在运行时之前完成的,此时唯一重要的是您的块结构。所以即使这样也行得通,尽管从 "dynamic" 的角度来看不应该:

function test1() {
    function log() {
        console.log(txt);
    }

    zero(log);
    let txt = 'this is a test message';
}

场景 2 中,您正在做:

  1. let txt = 'this is a test message' 这意味着 txt 将成为 test1() 范围的一部分。
  2. 同时您声明 log() 可以访问 其父级 test1() 的范围。

那么 运行 时间会发生什么? test1() 将被评估,因此 log() 将有权访问 test1() 的范围。这意味着 txt 将可供 log() 立即使用。

提示:调试它,放一些断点,看看会发生什么。

编辑: 您还可以考虑在 log() 内,txt 未定义,因此其值应该未定义...对吧? console.log(txt) 工作输出 this is a test message 的事实是由于上述关于范围界定的解释。在函数作用域的顶部声明你的变量,在作用域的底部声明你的函数总是一个好习惯,因为它们将首先被评估。考虑到这种情况下的人为因素,最佳实践还可以意味着:you/anyone 仅通过阅读就可以理解代码的作用。

这是 timing/execution 订单。想想就好

function test1(){
    var context = { }; 

    function log(){
        if(context.hasOwnProperty("txt")){
            console.log(context.txt); 
        }else{
            throw new Error("there is no value 'txt' declared in this context");
        }
    }

    context.txt = 'this is a test message';
    log();
}

在您的代码中与未提升变量 txt 相同。在执行 log 时,let txt 将在适当的函数上下文中声明。即使不吊装也可用。函数 log 不存储对变量本身的引用,而是对整个周围执行上下文的引用,并且此上下文存储变量。