Posted on

Scoping in Javascript With ‘Let’

In my post yesterday about multiple-values I used the keyword ‘let’ in some of the examples. This is another feature of the Mozilla Javascript platform. I assume it is named after ‘let’ from Lisp, because it performs the same task: letting you bind values in block scope.

As you know, the scope of variables in Javascript is the entire function in which they appear. So take a trivial function like this.

function foo() {
    var f = 10;
    print(f);

    {
        var f = 20;
        print(f);
    }

    print(f);
}

When we call the function the output will be

    10
    20
    20

Our rebinding of ‘f’ inside the inner block changes ‘f’ on the outside. So unlike a language like Perl, we cannot use arbitrary blocks to control scope. At least not without ‘let’. We can rewrite the above like so:

function foo() {
    var f = 10;
    print(f);

    {
        let f = 20;
        print(f);
    }

    print(f);
}

When we run this we get

    10
    20
    10

The binding we create with ‘let’ is only in effect for the lexical block in which it appears. Once that block ends, the previous value is restored. There are many apt adjectives for describing this behavior. I opt for ‘delicious’.

My example above uses what is called a ‘let definition’. That is when you use ‘let’ inside of a block to define a variable in the same way you would ‘var’. There are two other uses of ‘let’. First there is the ‘let statement’, which looks like this:

function foo() {
    var f = 10;
    print(f);

    let (f = 20) {
        print(f);
    }

    print(f);
}

This has the same effect as the previous example. The difference between let statements and let definitions is stylistic. Let statements look better (in my opinion) when the only function of the block is to introduce some temporary bindings. If you need to do to for an existing block, then let definitions are more appropriate. For example, consider the difference between

// A let statement

if (foo === 20) {
    let (bar = foo + 10) {
        ...
    }
}

and

// A let definition

if (foo === 20) {
    let bar = foo + 10;
    ...
}

We save on some indentation.

The third type of ‘let’ is the ‘let expression’. It is used to introduce bindings that are scoped to a single expression. Like so:

var x = 10;
var y = 20;

print( let (x = 5, y = 5) x + y );    // Prints 10
print( x + y );                       // Prints 30

The right side of a ‘let’ references the scope outside of the ‘let’, which means if we are rebinding an existing variable, we can use its current binding to do so. As an example:

var foo = 10;

let (foo = foo + 500, bar = foo) {
    print(foo)    // Prints 510
    print(bar)    // Prints 10
}

The ‘foo’ on the right side of both bindings refers to the ‘foo’ from the outer scope.

In all cases, ‘let’ creates a fresh binding when it is encountered. This is very important when creating closures over values. Consider this loop that creates a series of <li> elements, each with an onclick event:

var list = document.getElementById("list");

for (var i = 1; i <= 5; i++) {
    var item = document.createElement("li");

    let j = i;

    item.onclick = function (event) {
        alert("Item " + j + " was clicked");
    };

    list.appendChild(item);
}

Each onclick value closes over ‘j’, so that they will print out ‘Item 1’, ‘Item 2’, and so on when clicked. Because ‘let’ always creates a fresh reference, we get the expected behavior. If we wrote var j = i instead then all five elements would print out ‘Item 5 was clicked’. This is because they would all point to the same ‘i’, whose value is changed by the for-loop. We would have this same problem if we also used ‘i’ from inside the ‘onclick’ function. When we create a closure over a variable with a changing value, using ‘let’ is the only way to create a snapshot of the current value at that time.

So the next time you want to get your lexical scope on, don’t forget that you have ‘let’ available.