.

Tags:

Until today, JavaScript comes with a function-level scope for variables and functions. This quirk often trips beginners who are already familiar with other curly braces language. With ECMAScript 6, the situation will change with the availability of the well-understood block scope.

Function-level scope leads to a situation called hoisting. For example, for this code:

function f() {
  doSomething();
  var a = 1;
}

what really happens is something like:

function f() {
  var a;
  doSomething();
  a = 1;
}

Often, the knowledge about hoisting (or lexical environment in general) is used in a quiz or an interview question. In the following fragment, those who don’t possess the understanding will be left puzzled:

console.log('foo' in window); // true
var foo;

For programmers familiar with C/C++/Java, the use of var in a wrong place can trigger many pitfalls, among others variable leaking. Whether this is intentional or not is often not cleared from the code itself. Ambiguity like that may lead to a bug and other hard-to-trace annoyances:

for (var i = 0; i < 3; i++) {
  var j = i * i;
  console.log(j);
}
console.log(j); //  4

With the upcoming ECMAScript 6, we can solve the issue by using Let and Const Declarations, see Section 12.2.1 of the latest specification draft. If we rewrite the above example to use let, it will look like the following:

for (var i = 0; i < 3; i++) {
  let j = i * i;
  console.log(j);
}
console.log(j); // will throw

then running it will give an error instead:

ReferenceError: j is not defined

This is the typical programmer’s expectation, j is confined to that particular curly-braced block.

Its partner-in-crime, const, behaves pretty much the same except it must be initialized and only once. This is perfect to store an immutable object. With both let and const, an optimistic static code analyzer can work much smarter to detect patterns which may cause problems and warn the user ahead of time.

When can we can start using let and const? Fortunately, we can already use it today. Firefox already has implemented some supports for ECMAScript 6, including this block scope. With Chrome, you can enable V8 experimental features by toggling the switch via chrome://flags.

Note: with V8, at least for the time being, you can use let with strict mode only, otherwise it will complain SyntaxError: Illegal let declaration outside extended mode.

What about other browsers? Until they start supporting this block scope feature, you need to fall back to the solution of converting the code (also known as transpiling) into some construct which can be executed by today’s browsers. Using a generic transpiler such as Google Traceur is often the recommended way.

An alternative solution is by using defs.js from Olov Lassus. The idea is to transform block scoped declarations into normal variable statements, obviously taking into account the scope of each declaration. Given the code, defs.js will use Esprima to parse the code, walk the syntax tree, and apply the transformation whenever necessary. As an example, this code fragment:

function f() {
  let j = data.length;
  console.log(j, 'items');
  for (let i = 0; i < j; ++i) {
    let j = data[i] * data[i];
    console.log(j); // squares
  }
}

will be transformed into:

function f() {
  var j = data.length;
  console.log(j, 'items');
  for (var i = 0; i < j; ++i) {
    var j$0 = data[i] * data[i];
    console.log(j$0); // squares
  }
}

Look how defs.js recognizes the right scope for j and therefore masquerade the innermost j with another name, j$0. The transformation itself is non-destructive, defs.js does not bother with anything other than let and const declaration. You can see how a comment is left untouched and the coding style is still exactly the same.

Obviously, there’s much more to defs.js than my simple example above, refer to Olov’s blog post for more details.

How do you plan to use let and const?

  • Ivan

    Ariya when I read your blog in my head I read it out in your voice and accent :)

    • http://ariya.ofilabs.com/ Ariya Hidayat

      Thank you, comrade!

  • Dmitry Soshnikov

    Good overview,

    Small typo:

    console.log(foo in window); // true
    var foo;

    should be:

    console.log(‘foo’ in window); // true
    var foo;

    Since in your example the _value_ of `foo` (which is undefined) is taken and is coerced to string, which became “undefined” and which is also in window, but I think you meant that ‘foo’ is in window.

    Also, notice, that existed for years `let` in SpiderMonkey, has different semantics in respect of for-loops. In SpiderMonkey unspecified implementation it’s still bound to outer scope, while in ES6 spec, I believe it should be bound to inner for-loop scope:

    for (var k …) {
    let x = k; // this is how it’s now in SM
    }

    for (let x …) // this is how it will be in ES6

    • http://ariya.ofilabs.com/ Ariya Hidayat

      Good catch on ‘foo’! I’ve now corrected it, thank you very much.

  • http://getify.me/ getify

    Another option is my (once an April Fool’s joke, now a real thing I intend to keep working on) “BlockScoper”, which transpiles `let` statements back into ES3 compatible code, still block scoped, using the little known fact that the `catch` clause has ES3 block scope. https://github.com/getify/BlockScoper.js