JavaScript:了解闭包和提升

时间:2017-08-02 13:12:17

标签: javascript closures 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函数声明移到了作用域的顶部(无论如何它都会被提升,对吧?),然后我宣布{{1它下面的变量。这一切仍然有效,我不知道为什么。当txtlog未被提升时,txt如何保留对let变量的引用?是否将分析范围分析为整体?我可以在这里逐步清楚地了解JavaScript引擎的工作原理。谢谢你的土地!

4 个答案:

答案 0 :(得分:4)

离开test1功能后,它是范围的一部分。如果它在此时与varletconst一起使用并不重要。由于已对整个身体进行了评估,因此该变量存在于范围内。

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

编辑:从技术上讲,使用letconst声明的变量属于范围,但如果您尝试访问它们,则会将它们整合在一起会导致错误。只有在你得到声明它们被初始化并且你可以访问它们之前。所以它们总是在范围内,只有在评估声明之前才可用。

答案 1 :(得分:3)

“封闭范围是否整体分析?” - 是的闭包保留了您(词汇上)离开时的范围。在您的示例中,当您到达txt中的结束}时,test1确实存在,因此它位于范围内,log访问它时没有任何问题。

注意上面的“词法”:绑定是在运行时之前完成的,只有重要的是你的块结构。所以即使这样也行不通,但它不应该从“动态”的角度出发:

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

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

答案 2 :(得分:0)

场景2 中,你正在做:

  1. let txt = 'this is a test message'表示txt将成为test1()范围的一部分。
  2. 同时宣布可以访问的log()。{li> 其父test1()的范围。
  3. 那么在运行时会发生什么?我们会对test1()进行评估,因此log()可以访问test1()的范围。这意味着txt可以log()立即使用。

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

    修改:您还可以考虑在log()内,txt未定义,因此其值应该是未定义的......对吗? console.log(txt)工作输出this is a test message的事实是由于上面对范围界定的解释。将变量声明在函数作用域的顶部,并将函数声明在作用域的底部,因为它们将首先进行评估,这总是很好的做法。在这种情况下考虑人为因素,最佳实践也可以意味着:让您/任何人通过阅读它来了解代码的作用。

答案 3 :(得分:0)

这是一个时间/执行顺序的事情。想想它

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不存储对变量本身的引用,而是存储整个周围的执行上下文,并且此上下文存储变量。