Let There Be Let In Javascript

Let there be ‘let’ in JavaScript

Let there be ‘var’ in JavaScript. This problem, which is source of many other problems, is dated two decades old. It all started when JavaScript was created back then in matter of 10 days by Brendan Eich. And it exists even today which is why it’s super important to know more about it.

This is third post of our blogger month at Xebia. Every day one of the Xebian post a new blog. You can follow the full series at http://blog.xebia.in/pages/blogger-month-2015/.

-1995-

  • Let there be keyword ‘var’ to declare variables/references in JavaScript.
  • Let ‘var’ be optional for JavaScript to be forgiving language for the beginners.
  • Let variables declared outside of a function be global i.e. accessible from anywhere and those declared inside of a function be local i.e. accessible only inside of function holding them. This is called scope.
  • Let variables be declared anywhere but get hoisted from top of enclosing scope.
  • Moreover, let there be ‘no’ block scope. Only function can give rise to new scope which can even be nested to form closures.
  • And one more thing, let there be lexical scoping to access variables of outer scopes from inner scopes for closures to work.

Back in 1995, some of these design decisions were hasty while creating a brand new programming language that too in mere 10 days. Brendan Eich is a genius. But even genius can make mistakes when asked to rush.

Let’s see the problems related to ‘var’ one at a time in action.

Problem #0: Let there be 'var':


var myVariable = 'I am global';
console.log(myVariable); //Logs 'I am global'

function myFunction() {
    var myVariable = 'I am local';
    console.log(myVariable); //Logs 'I am local'
}

myFunction();

console.log(myVariable); //Logs 'I am global'

This is self explanatory with no visible problem at all so far. But, hidden problems related to ‘var’ creeped in right from the beginning.

You can run below snippets in browser’s console to check these problems:

Problem #1: 'var' is optional:


myVariable = 'I am global';
console.log(myVariable); //Logs 'I am global'

function myFunction() {
    myVariable = 'I am local';
    console.log(myVariable); //Logs 'I am local'
}

myFunction();

console.log(myVariable); //Logs 'I am local' instead.

Because ‘var’ is optional, myVariable inside myFunction appears to be declared/assigned as a local variable inside myFunction. But it is not the case. It is global and the change in its value is visible even outside of myFunction.


For folks coming from other languages, not using ‘var’ doesn’t lead to any error unless you use ‘use strict’ mode.


Problem #2: Let variables be hoisted at top of enclosing scope:


var myVariable = 'I am global';
console.log(myVariable); //Logs 'I am global'

function myFunction() {
    //TDZone starts
    console.log(myVariable); //Logs ???
    //TDZone ends
    var myVariable = 'I am local';
    console.log(myVariable); //Logs 'I am local'
}

myFunction();

console.log(myVariable); //Logs 'I am global'

What will first log statement inside myFunction give? If you think the global myVariable is lurking around for you to use before the local version is declared, you are wrong. It gives ‘undefined’ as the local myVariable gets hoisted at the top of defining scope which is myFunction in this case. And as the local myVariable is yet to be assigned with a value, any attempt to use it in that TDZone in between will give rise to undefined value. That’s why this zone is called Temporal Dead Zone.

Problem #3: Let there be 'no' block scope:


var i = 'I am global';
console.log(i); //Logs 'I am global'

for(var i = 0; i < 5; i++) {
    //do whatever you want...
}

console.log(i); //Logs 5 instead.

Yes, it logs 5 instead of ‘I am global’ because there is no block scope. Variable ‘i’ declared inside for loop is visible even outside of it. Same will be the case if we had declared it inside while, do while, for and else blocks. Let’s call this as Accidentally Unblocked Problem.

Now, let’s see the most hidden and notorious problem that provides one of the most powerful feature of creating closures in JavaScript when applied knowingly and correctly:

Problem #4: Let closure access variables from outer scope:


var messages = ['I', 'will', 'be', 'back'];

for(var i = 0; i < messages.length; i++) {
    setTimeout(function () {
        console.log(messages[i]); //Logs ???
    }, 1000);
}

This won’t log back the popular message we know of. It will log ‘undefined’ 4 times. Yes, undefined again. Why? Think about it.

Variable ‘i’ keeps running from 0, 1, 2, 3 and 4 whereas the calls to anonymous function (marked bold) given to setTimeout function in each iteration are left aside at least for 1 second. (This anonymous function is a closure and has access to variable ‘i’ (declared outside of it) with help of lexical scoping). And by the time their turn come, variable ‘i’ has reached 4 which is ‘undefined’ in messages array. Let’s call this as Runaway Problem as ‘i’ is running away and not holding its binding to each iteration. Clear enough but hidden enough to understand it first time.

If you give one more look to these problems, all of them are hidden due to those design decisions related to ‘var’. We can’t change these rules now as they will break the web due to the existing JavaScript code written based on these rules. There is no turning back in time to rectify this.

So, let’s look forward. Enter 2015. Let there be ‘let’ in JavaScript.

-2015-

  • Let there be ‘let’ in JavaScript to declare variables/references in a new way.
  • Using ‘let’ variables before their declaration will lead to ReferenceError. This will kill the Temporal Dead Zone itself (described in problem #2 above).
  • Let ‘let’ variables have block scope. The scope of variables declared using ‘let’ is its enclosing block and not the whole enclosing function. This avoids Accidentally Unblocked Problem (described in problem #3 above).
  • Let ‘let’ variable declared inside loops have fresh binding in each iteration so that each iteration remembers its value. This will avoid the Runaway Problem (described in problem #4 above).

ES2015 is coming! Let’s use let.

You can run below snippets in babel and see ‘let’ solving these problems:

Problem #2 of Temporary Dead Zone solved with let:**


let myVariable = 'I am global';
console.log(myVariable); //Logs 'I am global'

function myFunction() {
    //TDZone starts
    console.log(myVariable); //Gives not defined/ReferenceError
    //TDZone ends
    let myVariable = 'I am local';
    console.log(myVariable); //Logs 'I am local'
}

myFunction();

console.log(myVariable); //Logs 'I am global'

Problem #3 of no block scope solved with let:


let i = 'I am global';
console.log(i); //Logs 'I am global'

for(let i = 0; i < 5; i++) {
    //do whatever you want...
}

console.log(i); //Logs 'I am global'

Problem #4 (Runaway Problem) solved using let:


let messages = ['I', 'will', 'be', 'back'];

for(let i = 0; i < messages.length; i++) {
    setTimeout(function () {
        console.log(messages[i]); //Logs the popular message
    }, 1000);
}

‘let’ was introduced in 2005 by Brendan Eich as solution to these problems. Though it took ten years to reach ES2015 specs, it is finally coming.

ES2015 (also called ES6), specs for next version of JavaScript were released in June 2015, some time close to 20th birthday of JavaScript. It’s the best birthday gift JavaScript ever had. Be assured, more are coming soon that too every year from now on. That’s why they smartly put year in its new name.


-Conclusion-

Let ‘let’ be the new ‘var’ here on! This doesn’t mean you can just find and replace all ‘var’ by ‘let’ as it may break something based on how the design flaws were accidentally put in use in the code. Best bet is to use ‘let’ whenever possible in the new ES2015 code you write.


Leave a Reply

Your email address will not be published. Required fields are marked *