The Joys of Block Scoping with ES6

By Kyle Pennell

This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.

Brendan Eich invented JavaScript in 10 days around May 6-15 in 1995. The language, originally called Mocha, started as a simple client-side scripting language. O’Reilly’s history on the language details the context at the time the language was invented:

Eich eventually decided that a loosely-typed scripting language suited the environment and audience, namely the few thousand web designers and developers who needed to be able to tie into page elements (such as forms, or frames, or images) without a bytecode compiler or knowledge of object-oriented software design.


JavaScript was born in a time when the web was very different but would eventually become one of the most ubiquitous programming languages in use. Few could have predicted how popular it would become. Naturally, then, JavaScript has some design flaws that have frustrated and confused developers since its inception.

One of the common complaints has been Javascript’s lack of block scope. Unlike other popular languages like C or Java, blocks ({...}) in JavaScript (pre-ES6) do not have scope. Variables in JavaScript are scoped to their nearest parent function or globally if there is no function present.

When asked why JavaScript has no block scopes, Brendan Eich’s answer is pretty straightforward: there wasn’t enough time to add block scopes.

Brendan Eich series of tweets

Thankfully, JavaScript descended from C/C++/Java, and was future-proofed for adding block scoping later as Eich explains in this series of tweets.

And now, finally, ECMAScript 6 (ES6) is here and among many things, it gives us block scoping via the two new ES6 variable keywords, let and const. Support for let/const is still limited to Edge, Chrome, and Firefox but more browsers will likely support it soon.

This post will explain why const and let are helpful and how they are used.

Make Love Not Var
source: http://www.cs.uni.edu/~wallingf/blog/archives/monthly/2012-10.html

The Challenges of Using var

Before introducing const and let, it’s worth discussing why they are helpful or necessary in the first place. Var has been the only keyword for variables in JavaScript up until now but it has several drawbacks.

Var scoping is confusing for devs from other languages

The scope of var is confusing for developers coming from other languages. It’s quite easy for them to unintentionally cause bugs in code that uses if blocks or for loops. Variable declaration in ES5 and below doesn’t work in the way they would expect. Given JavaScript’s popularity, developers from other languages sometimes have to write JavaScript, and therefore variable scoping, which is easier to understand, would be helpful for them.

Global vs. local confusion with var

When writing JS using var, it’s difficult to immediately discern which variables are scoped locally vs. globally. It’s very easy to accidentally create a variable on the global object in JavaScript. This generally doesn’t affect simple demo apps but can cause problems for enterprise level applications as team members accidentally obliterate each other’s variables.

Confusing workaround patterns

The lack of clear global vs. local scope differentiation in JavaScript has forced developers to come up with patterns like the IIFE (Immediately Invoked Function Expression). This is an awkward workaround to the lack of block scope. It’s also a way to avoid attaching var-declared variables to the global object.


(function(){
  // code here
}());

If you’re not a seasoned Javascript developer, this pattern makes little sense. What is going on here? What are all those parentheses? Why doesn’t that function have a name? Block scoping should lessen the need for workaround design patterns like this.

Misconceptions about hoisting

Another challenge of var is that it doesn’t work in the way most developers think it does. The JavaScript interpreter make two passes on a section of JavaScript code. The first pass processes variables and function declarations and lifts them to the top (the ‘hoisting’). The second pass processes the function expressions and undeclared variables. This makes for some confusing code if the developer is not keenly aware of how hoisting works. Take this example from developer Ben Cherry’s blog:

Developer Ben Cherry's example js

Screenshots in this post are from Visual Studio Code in Mac

code: http://codepen.io/DevelopIntelligenceBoulder/pen/obZYRY?editors=101

In the above example, var foo = 10; is hoisted up to the nearest parent function. That’s why it alerts 10 even though you might think you would get a “ReferenceError: foo is not defined”. The example refactored with let is more intuitive.

Developer Ben Cherry's example js

code: http://codepen.io/DevelopIntelligenceBoulder/pen/VvVNpg?editors=101

Using let

The new ES6 keyword let allows developers to scope variables at the block level (the nearest curly brackets). If you’d like to see which browsers support let (and const) follow the Microsoft Edge page for tracking support across ES6 features. You can also check here if your current browser supports let and const.

Here are some examples of let (vs. var) in different types of blocks:

if block example

if block example

code: http://codepen.io/DevelopIntelligenceBoulder/pen/BoGEoa?editors=101

In the above example, the variable is scoped to the IIFE function when it’s declared with var. Within the if block, a separate fruit variable is declared with let and scoped to the if block.

for loop block example

for loop block example

code: http://codepen.io/DevelopIntelligenceBoulder/pen/KdrYdg?editors=101

The for loop example above is a bit more interesting than the if block. By using let in the initialization expression, the i variable is scoped only to the block. By using var, the i will be scoped to the nearest function. The variable will equal 10 outside of the for loop block. This can have consequences such as creating an accidental closure as in examples like this:

for loop block example

code: http://codepen.io/DevelopIntelligenceBoulder/pen/XmyQXe?editors=101

In this example, the code is meant to register an event listener on a simple list and alert which number was clicked. Instead, it will alert Clicked on Number: 5 for each list item. Wrapping the event listener part in a function would be a common workaround for avoiding the accidental closure.

But instead, refactoring this with let (once it’s fully supported) creates a block scope in the for loop and avoids the accidental closure. It allows the expected iterator to be used within the event listener callback function.

for loop block example

code: http://codepen.io/DevelopIntelligenceBoulder/pen/JYeVGv?editors=101

One caveat with let is that it doesn’t hoist in the same way var does. If you try to use it before it’s been declared, you will get a reference error. This has been termed the Temporal Dead Zone by one developer (the term has gained popularity since).

Using const

Like constants in other languages, const will often be used for values that won’t need to be reassigned in a program’s execution. Strings like API keys or numbers like CANVAS_HEIGHT would be uses cases of const variables that don’t need to be reassigned. Variables declared with const are often written in all caps, but this is a matter of preference.

Const is the other new ES6 keyword for declaring variables. Const works like a constant in other languages in many ways but there are some caveats. Const stands for ‘constant reference’ to a value. The values that const references are not immutable (their properties can be changed). This can be explained by borrowing a metaphor from Eloquent JavaScript (a great beginner’s JS book).

Eloquent Javascript’s author Marijn Haverbeke says that it’s better to think of variables as being tentacles rather than boxes.

They do not contain values; they grasp them — two variables can refer to the same value. A program can access only the values that it still has a hold on. When you need to remember something, you grow a tentacle to hold on to it or you reattach one of your existing tentacles to it.

So with const, you can actually mutate the properties of an object being referenced by the variable. You just can’t change the reference itself. Explained via the above metaphor, the tentacle won’t move or change but what it’s holding onto can. Here are some code examples of const in action:

	
const PI_VALUE = 3.141592;
 
const APIKEY = 'aekljefj3442313kalnawef';

const NAMES = [];
NAMES.push("Jim");
console.log(NAMES.length === 1); // true
NAMES = ["Steve", "John"]; // error

If you want a constant to be completely immutable, use object.freeze to make the properties immutable.

When to Use const vs. let

There’s still some debate in the JavaScript community as to when to use const vs. let. Some originally recommended using let in the place of var. Others now call for defaulting to const instead of let.

Reginald Braithwaite's ES6 conventions tweet

JavaScript expert Reginald Braithwaite’s tweet above has been echoed by many. Developer Eric Elliot expounds upon the argument for using const before let:

If I don’t need to reassign, const is my default choice over let because I want the usage to be as clear as possible in the code.

const is a signal that the variable won’t be reassigned.

let is a signal that the variable may be reassigned, such as a counter in a loop, or a value swap in an algorithm. It also signals that the variable will be used only in the block it’s defined in, which is not always the entire containing function.

var is now the weakest signal available.

Kyle Simpson (author of You Don’t Know JS) wrote some counterarguments to the enthusiasm around “let is the new var,” suggesting that the implicit nature of let declarations can cause problems in JS code. Furthermore, there are cases when var is still useful:

let improves scoping options in JS, not replaces. var is still a useful signal for variables that are used throughout the function. Having both, and using both, means scoping intent is clearer to understand and maintain and enforce. That’s a big win!

Simpson believes that let is the companion to var and that it shouldn’t be used to replace all var statements.

It’s still to be determined how this debate will shake out.

Summary

Once they are supported by more browsers, const and let will allow for block scoping variables in JavaScript, rendering patterns like IIFE less necessary. Developers from other languages will likely have an easier time understanding JavaScript scoping. Developer Aaron Frost put it, cheekily, “Using LET and CONST instead of VAR will have an odd side-effect, where your code will execute at runtime just as it appears at development time.”

More Hands-on with Web Development

This article is part of the web development series from Microsoft and DevelopIntelligence on practical JavaScript learning, open source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine. DevelopIntelligence offers instructor-led JavaScript Training, AngularJS Training and other Web Development Training for technical teams and organizations.

We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.microsoftedge.com:

More in-depth learning from our engineers and evangelists:

Our community open source projects:

More free tools and back-end web dev stuff:

  • Tommy Graves

    I don’t think the example in the “Misconceptions about hoisting” example is helpful at all. One would expect, if foo = 1 were not declared outside the function, that it would alert ’10’. Instead, it throws an error, because let is block scoped and the if statement is a block, so foo never gets declared for use outside of the scope.

    • Kyle Pennell

      Thanks for pointing this out. I’ll work to get these changed and the code examples changed in the post. Much appreciated.

  • Siderite

    I dig the new features, but… did it have to be called “let”?! So many bad memories with that keyword :D I think it would have been more appropriate to have type defining keywords instead. “int i=10”, instead of “let it_be_whatever_it_wants_to_be=10”

  • brianm101

    Can’t really understand why Kyle Simpson thinks that there is much of a place for var over let! Perhaps for signifying global variables, but that’s about it.

    The Aaron Frost comment about says it all:

    Aaron Frost put it, cheekily, “Using LET and CONST instead of VAR will have an odd side-effect, where your code will execute at runtime just as it appears at development time.”
    Brilliant!

  • Kyle Pennell

    Thanks for pointing this out. I’ll work to get these changed and the code examples changed in the post.

  • Kyle Pennell

    Thanks for pointing this out. I’ll work to get these changed and the code examples changed in the post. Thanks again.

  • https://www.codepunker.com/ Daniel Gheorghe

    To avoid even more confusion…

    “var” is not bad… it’s just one way JavaScript works.

    You should use “var” at the beginning of a function if you plan to use that variable in the function and in child “block scopes”.

    If you plan to use a variable only inside a block scope then you can use let because it’s not hoisted and cleaned immediately after exiting the scope.

  • Brook Monroe

    “The scope of var is confusing for developers coming from other languages.”

    T’isn’t. I’ve discovered a really cool technique for sorting this out, and it’s called “learning.” Seriously, the same issue exists with C/C++/C#/Java programmers transitioning into Python (or vice versa) but I don’t see any motion to add bracing to Python or remove it from the other languages. Likewise, all my C# developers trying to sneak into C++ territory write memory leaks, and that’s also remedied by learning. (And skill. Never underestimate the power of skill.)

    That little tirade aside, I’m not arguing against local/block scoping–it’s necessary and long overdue. (So is const.) I agree with Siderite–“let” makes me shudder uncomfortably, but since backwards compatibility had to be assured, it wasn’t possible to just localize ‘var’ without breaking a lot of existing code. The only thing I like less than ‘let’ is ‘def,’ so I’m convincing myself that it could have been worse.

  • devopsftrou

    If you want a better example of the dangers of hoisting, try the following:

    var test = 10;

    function someFunc(){

    alert(test);

    var test = 50;
    }

    someFunc();

    In this example, people unfamiliar with hoisting will assume that 10 will be alerted, and instead test is undefined. This is because the declaration of the variable is hoisted but the initialization is left in place. var test; is the same as saying var test = undefined;, so that’s what gets alerted.

    Of course if var had been omitted within the function, there would be nothing to hoist and 10 would be alerted as expected.

    This is one of the big reasons why it is encouraged for all var statements to be placed at the top of the enclosing function scope—to avoid such confusion entirely.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.