Search code examples
javascriptnode.jsecmascript-6scopinglexical

Lexical scoping in ES6 / Node


I'm attempting to understand ES6 lexical scoping (using the node runtime). Consider the following:

'use strict';
let x = 10;
function f() {
   console.log(x);
   console.log(y); // This should crash
}

let y = 5;
f();

Paraphrasing from the O'Reilly book "Learning Javascript":

Lexical scoping means whatever variables are in scope where you define a function from (as opposed to when you call it) are in scope in the function.

However, when I run this program (via node), it outputs: 10 5

Isn't the call to console.log(y) breaking the lexical scoping rules here? If not, why not?

Edit: For future reference, it appears that the author of the textbook (Learning Javascript 3rd Edition O'Reilly) has recently listed this example as an error in the "Confirmed Errata". on http://www.oreilly.com/catalog/errata.csp?isbn=0636920035534


Solution

  • As mentioned by Benjamin Gruenbaum, let and const don't hoist at all.

    As a matter of fact, there are new rules that apply to let and const, such as the…

    temporal dead zone

    Now, if these were var declarations, everything would be clear. But with let and const, ES6 introduces a new concept of the temporal dead zone. This includes a new, subtle dynamic.

    Let's have a look at two examples:

    Hoisting classically would work in an example like this:

    'use strict';
    var x = 10;
    
    console.log(x);
    console.log(y); // This should NOT crash
    
    var y = 5;
    

    But if we were to replace the var declarations with let declarations, it would crash:

    'use strict';
    let x = 10;
    
    console.log(x);
    console.log(y); // This crashes: ReferenceError: can't access lexical declaration `y' before initialization
    
    let y = 5;
    

    Why does this crash?

    Because unlike var assignments, accessing variables defined using let before the actual let statement is invalid (they are in the temporal dead zone).

    2. Temporal Dead Zone in this case

    In this case however, the temporal dead zone is not an issue. Why?

    Because while we define the function with the console.log(y) statement beforehand, the actual function call and thus variable access only happens towards the end of the code. So the variable bindings are only evaluated at this point (thanks again, @BG):

    'use strict';
    let x = 10;
    function f() {
       console.log(x);
       console.log(y); // This should not yet crash
    }
    
    let y = 5;
    f(); // console.log(y) is only called here
    

    If you were to reverse the order of let y = 5; and f();, your code would crash.