简体   繁体   English

JavaScript:了解闭包和提升

[英]JavaScript: Understanding closures and hoisting

I understand that function declarations are hoisted to the top of their scope. 我知道函数声明被提升到其范围的顶部。 This allows us to use those functions before they're actually declared in JavaScript: 这允许我们在JavaScript中实际声明之前使用这些函数:

sayHello(); // It works!

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

I also understand that closures enable functions to retain references to variables declared within the same scope: 我也理解闭包使函数能够保留对在同一范围内声明的变量的引用:

function outerFxn() {
  let num = 0;

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

  return innerFxn;
}

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

So far so good. 到现在为止还挺好。 But here's some weirdness that I'm hoping someone can explain exactly what's going on... 但是这里有些奇怪,我希望有人可以准确地解释发生了什么......

Scenario 1: understandable closure 场景1:可理解的闭包

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

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

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

  zero(log);
}

In the example above, the log function retains a reference to the scope in which it was created, holding on the the txt variable. 在上面的示例中, log函数保留对其创建范围的引用,并保留txt变量。 Then, when executed later on in the setTimeout , it successfully log's the txt variable's value. 然后,当稍后在setTimeout执行时,它会成功记录txt变量的值。 Great. 大。 Then there's this... 那就是这个......

Scenario 2: what is happening? 情景2:发生了什么?

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

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

  let txt = 'this is a test message';

  zero(log);
}

I've moved the log function declaration to the top of the scope (it would have been hoisted there anyway, right?), then I'm declaring the txt variable below it. 我已经将log函数声明移到了作用域的顶部(无论如何它都会被提升,对吧?), 然后我在它下面声明了txt变量。 This all still works and I'm not sure why. 这一切仍然有效,我不知道为什么。 How is log retaining a reference to the txt variable when let 's and const 's aren't hoisted up? 如何log保持到基准txt时可变let的和const的不吊上来? Are closure scopes analyzed as-a-whole ? 闭合范围是作为一个整体进行分析的吗? I could use a bit of clarity on what the JavaScript engine is doing step by step here. 我可以在这里逐步清楚地了解JavaScript引擎的工作原理。 Thank you SO land! 谢谢SO土地!

It's part of the scope after you leave the test1 function. 离开test1函数后,它是范围的一部分。 It doesn't matter if it's being used with var , let or const at that point. 如果它在那一点上与varletconst一起使用并不重要。 Since the whole body has been evaluated, the variable exists on the scope. 由于已对整个身体进行了评估,因此该变量存在于范围内。

Had you tried to use log before the let declaration is evaluated, you'd gotten an error. 如果在评估let声明之前尝试使用log ,则会出现错误。

Edit : Technically, the variables declared with let and const are in scope, but they are unitialized which results in an error if you try to access them. 编辑 :从技术上讲,使用letconst声明的变量在范围内,但它们是单元化的,如果您尝试访问它们会导致错误。 It's only until you get to the declarations that they are initialized and you can access them. 只有在你得到声明它们被初始化并且你可以访问它们之前。 So they are always in scope, just not available until declarations are evaluated. 所以它们总是在范围内,只有在评估声明之前才可用。

"Are closure scopes analyzed as-a-whole?" “封闭范围是否整体分析?” - yes. - 是的 Closures retain the scope as it is at the moment you (lexically) leave it. 闭包保留了您(词汇上)离开时的范围。 In your example, txt does exist when you reach the closing } in test1 , so it's in the scope, and log has no problems accessing it. 在您的示例中,当您在test1到达closing }时, txt确实存在,因此它在范围内,并且log访问它时没有问题。

Note "lexically" above: bindings are done prior to runtime, when only thing that matters is your block structure. 注意上面的“词法”:绑定是在运行时之前完成的,只有重要的是你的块结构。 So even this would work, although it shouldn't from the "dynamic" point of view: 所以即使这样也行不通,但它不应该从“动态”的角度出发:

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

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

In Scenario 2 you are doing: 场景2中,您正在做:

  1. let txt = 'this is a test message' which means that txt will be part of the scope of test1() . let txt = 'this is a test message' ,这意味着txt将成为test1()范围的一部分。
  2. At the same time you are declaring log() which will have access to the scope of its parent test1() . 同时,您声明了log() ,它可以访问其父test1()的范围。

So what happens on run-time? 那么在运行时会发生什么? test1() will be evaluated and as such log() will have access to the scope of test1() . 将评估test1() ,因此log()将可以访问test1()的范围。 This means that txt will be available for log() to use immediately. 这意味着txt可用于log()立即使用。

Tip: debug it, put some break points and see what happens. 提示:调试它,放置一些断点,看看会发生什么。

Edit: You can also consider that within log() , txt is not defined and as such its value should be undefined... right? 编辑:你也可以考虑在log() ,没有定义txt ,因此它的值应该是未定义的......对吗? The fact that console.log(txt) works outputs this is a test message is due to the above explanation on scoping. console.log(txt)工作输出的this is a test message是由于上面对范围的解释。 It's always good practice to declare your variables at the top of the function scope, and your functions at the bottom of the scope since they will be evaluated first anyways. 将变量声明在函数作用域的顶部,并将函数声明在作用域的底部,因为它们将首先进行评估,这总是很好的做法。 Consider the human factor in this situation, best practice can also mean: for you/anyone to understand what the code does just by reading it. 在这种情况下考虑人为因素,最佳实践也可以意味着:让您/任何人通过阅读它来了解代码的作用。

this is a timing/execution order thing. 这是一个时间/执行顺序的事情。 Think about it like 想想就好

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();
}

Same in your code with the not hoisted variable txt . 代码与未提升的变量txt At the time, log is executed, let txt will have been declared in the proper function context. 那时,执行loglet txt在适当的函数上下文中声明。 It is available even if it ain't hoisted. 即使没有悬挂也可以使用。 The function log doesn't store a reference to the variable itself, but to the whole surrounding execution context, and this context stores the variables. 函数log不存储对变量本身的引用,而是存储整个周围的执行上下文,并且此上下文存储变量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM