Learning ES2015 : let, const and var


Lions at the zoo

Zoos allow for safely viewing dangerous wild animals like lions. Lions are caged in their enclosures and can’t escape its boundaries (if they did, it’d be chaos eh?). Handlers, however, can get into cages and interact with them.

Like cages, you can think of variable scoping rules as establishing the boundaries and walls in which certain entities can live. In the zoo scenario, the zoo can be the global or function scope, the cage is the block scope and the lions are the variables. Handlers declare lions in their cages and lions at the zoo typically don’t exist outside their cages. This is how most block-scoped  zoos languages behave (e.g. C#, Java etc).

The JavaScript zoo is different in many ways. One striking issue is that lions handled by its var handlers are free to roam around in the entire zoo. Tales of mauled unsuspecting developers are a dime a dozen; walk too close to a ‘cage’ and you can fall into a trap.

Be careful of var lions – they might not be limited to their cages.

Fortunately, the  JS zoo authorities (aka TC39) have introduced two new lion handlers. They are the let and const. Now lions stay in their cages and we can all go visit the JS zoo without nasty surprises.

Technically this is not totally correct but who doesn’t like a good story? In block-scoped languages, variables are limited to enclosing scopes (mostly {} ); however JavaScript had function-scoped variables which were limited to the function body. let and const fix that for us.

How can function scope be dangerous?

Let’s go back to the zoo again…

function varHandler(){
    var visitor = 'going to the zoo!';
    var hasCages = true;

    if(hasCages){
        //create lion in cage scope with var
        var lion = 'in cage';
    }
    //check if lion exists outside its 'if cage'
    if(lion != null){
        visitor = 'Aaarrgh, the lion attacks!';
    }
    console.log(visitor);
    console.log(lion);
}
varHandler();
//Aaarrgh, the lion attacks!
//in cage

See! Visitors are not protected from encaged lions. Now, lets see what the new let handler

function letHandler(){
    let visitor = 'going to the zoo!';
    let hasCages= true;

    if(hasCages){
        //create lion in cage scope with let
        let lion = 'in cage';
    }

    //check if lion exists outside its 'if cage'
    if(lion != null){
       visitor = 'Aaarrgh, the lion attacks! :(';
    }

    console.log(visitor);
    console.log(lion);
}
letHandler();
//ReferenceError: lion is not defined

This code fails because we attempt to access the lion outside the if block cage. Even typeof wouldn’t work so the lion can’t be accessed outside its cage. To put it plainly, the lion can’t hurt the visitor.

Let – the solution to lion vars?

Let is mostly like var. The main advantage is the introduction of block (lexical) scoping. You can expect let variables to only exist in their containing blocks (could be a function, statement, expression or block).

The const

Const is a somewhat restricted let. Everything that applies to let applies to consts (e.g. scoping, hoisting etc) but consts have two more rules:

  • Consts must be initialized with a value
  • They cannot have another value assigned to them after initialization
const lion;
//SyntaxError: missing initializer...

const lion = 'hiya';
lion = 'hello';
//TypeError: Assignment to const...

Const objects?

While const objects can’t be reassigned to different values; you can still modify the contents of the object (just like in C# or Java finals).

const zoo = {};
zoo.hasLions = true;
console.log(zoo.hasLions);
//true

zoo = { a: 3};
//TypeError: Identifier 'zoo'...

Why? Think of a house address – homeowners can change the contents of their house (e.g. add more computers) but can’t just go slap their address on a totally different building.

If you want further restriction, try looking at Object.freeze but that is also shallow.

Let + Const vs Var

1. Hoisting

Surprise, surprise. Yes, they are hoisted. According to the ES2015 spec:

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

The access restriction is explained in the temporal dead zone section. This can have performance implications for JavaScript engines since the engine has to check for variable initialization. Yes, let can be slower than var.

2. Binding in loops

We already know that Let/const are block-scoped while var is not. But how does binding in loops work?

for(var i=0; i<=5; i++){
    setTimeout(function(){
        console.log(i);
    });
}
//5 5 5 5 5

Why is the output 5? Well the setTimeout functions are evaluated after the loop exits (remember JavaScript has one execution thread). Thus they are all bound to the value of which is 5 then.

The fix? Well, some tricks like using IIFEs to bind values exist. But let provides a better way out.

for(let i=0; i<=5; i++){
    setTimeout(function(){
        console.log(i);
    });
}
//1 2 3 4 5

This works because the let value is bound on each loop evaluation and not to the last value of i. This same trick allows usage of let/const in for..of loops since the evaluation context is new on every pass.

Should loop counters be consts? Yes since loop counters are usually sentinel values that shouldn’t change. Changing them can cause difficult bugs especially in nested multi-loop scenarios.

3. Overwriting Global values via bindings

let/const can not overwrite global values unlike var. This prevents unintentional clobbering of language constructs.

var alert = 'alert'; //clobbers global alert function
window.alert(2);
//error - alert is not a function

let alert = 'alert'; //shadows global alert function
window.alert(2);

The downside of this is that some legitimate usages e.g. for modules require vars.

4. Variable Redeclaration

Redeclaring a variable that already exists with let/const will throw a SyntaxError. However redeclaring a var with another var works.

var x = 1;
let x = 2;
//error

const y = 0;
let y = 3;
//error

let x = 3;
var x = 4;
//SyntaxError

5. Shadowing

let/const allow for variable shadowing.

function test() {
    let x = 1;
    if(true) {
        let x = 3;
        console.log(x);
    }
    console.log(x);
}
test();
//3
//1

You can think of blocks as anything enclosed between {} in most languages. Do remember that the switch block is one big block.

The Temporal Dead Zone

What happens if you try to access a hoisted variable before it is initialized? With var, the value is undefined however with  let(or const), this throws an error.

The engine assumes you shouldn’t know about or attempt to use a variable that has been declared but not initialized. This state is called the temporal dead zone.

let tdz;
console.log(tdz);
//Error

tdx = 'Temporal dead zone';
console.log(tdz);
//Temporal dead zone

Should I switch to let?

Yay for safe zoos! Well, converting all the var handlers into let/const handlers comes with a hitch. There is a high probability that the zoo breaks down. A better approach involves doing slow gradual conversions over a long period.

The issue is that some var usages help with some tricky areas (e.g. globals for modular namespacing). Converting such to lets will break the application. Others might be workarounds for some scenarios so go at it slowly. For example, when working in a file, you can change all the vars in that function/file to let and run tests to ensure nothing is broken.

Let support across browsers

Safari, iOS and Opera provide no let support so you might have to use a transpiler or polyfill. Here is the matrix on caniuse for let and const.

Is var ever going to be deprecated?

The var behaviour is probably here to stay. Once quirks are out in the wild, they become very difficult to fix since innumerable applications are built on these. Future improvements now have to support these for backward compatibility. The other alternative would be to declare an entirely new language (e.g. Python3 vs Python2) but that approach led to the schism discussed in the last post.

Conlusion

Let and const take away most of the issues associated with Javascript’s function scoping and make it more familiar to programmers coming from other languages. You probably should switch to it too and start using it more often.

Advertisements

One thought on “Learning ES2015 : let, const and var

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s