female developer in front of computer

October 2020 marked the 14th anniversary of the introduction of “let” and "const" to Javascript. Yes, it was in 2006 that this feature was first introduced, and we’re still having conversations about it. So why, you ask, are we still talking about this? It seems that a lot of confusion remains, not to mention a lot of old code being passed around on StackOverflow with “var” all over the place. Here I’ll discuss the difference between these three declarations, and come to a conclusion about best practices

A Brief History

JavaScript was introduced in 1995 by Brendan Eich and is, along with HTML and CSS, a core web technology. It is so thoroughly integrated in the World Wide Web that backwards compatibility at a language level is absolutely essential. That’s why when attempting to solve prevalent problems with the use of “var” to declare variables (heretofore the only choice for doing so), Brendan Eich did not change its implementation. He had no choice but to introduce a new way to declare variables. The first version of “let” was implemented in Firefox by Brendan Eich in 2006 as a solution. Browsers have gradually adopted these changes over the years, with the usual laggers. ES6, or ECMAScript 2015, became standard in 2015, at which point all major browsers supported the spec.

The Problems with “var”

When making use of a variable, it’s necessary to consider the scope in which it is declared. For instance, a variable declared inside a function is inaccessible to code outside of that function. Scope is the primary difference between “var” and its alternatives, “let” and “const”.

Variables declared with “var” are scoped, not to the closure in which they are declared, but rather to the nearest function block, or to Window if not inside a function. That means that, rather unintuitively, “var” variables scope bleeds outside of the block in which the variable is declared. This unintuitive scoping leads to bugs that are very difficult to trace.

What would you expect the console log to output when you run myOuterFunc?

function myOuterFunc() {
  var date = new Date();
  function myInnerFunc() {
    console.log("The current date is " + date)
    if (true) {
      // Whoops, we accidentally reused this variable name!
      var date = "a string"
    }
  }
  myInnerFunc()
}

Through a baroque sequence of hoisting and scope bleeding, the variable date is neither a Date object nor “a string.” It’s undefined! This is what’s happening:

  • When myOuterFunc is called, date is assigned to new Date(). No problem here.
  • Before myInnerFunc is executed, javascript hoists variable and function definitions to the top of the nearest closure.
  • Since the var declaration in our “if” statement bleeds outside its block, the declaration gets hoisted to the top of the scope. This is key. The declaration gets hoisted, but the assignment to “a string” only occurs in the “if” block.

This is effectively how our code is being run:

function myOuterFunc() {
  var date = new Date();
  function myInnerFunc() {
    var date = undefined;
    console.log("The current date is " + date)
    if (true) {
      // Whoops, we accidentally reused this variable name!
      date = "a string"
    }
  }
  myInnerFunc()
}

This is a nasty bug, and difficult to track down. Imagine that instead of a simple new Date(), our initial assignment is a value derived from any number of other variables. How far down that path would we go before looking at code that runs after our variable declaration?

You might be saying, “But I know how to how to use ‘var’ properly  —  ‘var’ to me is like clay in the hands of a master potter!” After all, how often is something that somebody calls a “flaw,” in fact, a powerful tool in the hands of an expert? To those people I say:

As long as you and anyone else who will ever touch your code never makes any mistakes in variable declaration, you should feel free to continue using “var” to declare your variables.

For everybody else, there is “let” and “const.”

“let” and “const”

“let” and “const” declare variables, as in let myVariable = 10. But unlike “var,” they come with a few more rules.

A variable declared with “let:”

  • is scoped to the block in which it is declared
  • throws an error if referenced before it is defined
  • throws an error if redeclared with “let” in the same scope

These rules reduce bugs and make our code more declarative. Would a developer be more free if they could declare a variable anywhere and reference it anywhere? Sure  —  but that’s like saying you ought to be able to fire a gun before it’s been loaded. Sure sounds fraught to me!

You can run the code examples above directly in your browser console. You’ll see that “date” is undefined. Now change the two “var” declarations to “let” et voila!

“const” has all the same rules as “let,” except that “const:” * cannot be reassigned * cannot be declared without a value, eg const startsUndefined is a SyntaxError

When to Use “let,” “const,” and “var”

Here’s a quick breakdown:

  • “let” should be used to declare a variable when you expect to reassign its value at some point
  • “const” should be used to indicate that a variable’s value should not be reassigned (and indeed, prevent such)
  • “var” should not be used

I always recommend being as restrictive as possible. In practice, this means aiming to use “const” for every variable declaration, only changing it to“let” if I must reassign its value later.

Here it’s worth noting the difference between reassigning a variable declared as a primitive, versus updating the value of a variable declared as an Object. I think the easiest way to understand this distinction is to understand variables as pointers to locations in memory. You may not mutate the value of a primitive declared as a “const.” Declaring const myNum = 1 creates a primitive integer, 1, at a particular location in memory. Any attempt to update the value is a reassignment. On the other hand, const myArray = [] creates a new array at a particular location in memory. So myArray.push['new value'] succeeds. We haven’t pointed myArray to a new location in memory, we’ve simply mutated the existing array.

A Note on Conventions

There is a difference between a variable declared with “const,” and what we would call a “constant.” By convention, genuine constants in javascript should be declared as such: const MY_UPPER_SNAKE_CASE_CONSTANT = "constant value"

This allows for a distinction between a constant whose value is meant to represent something unchanging but arbitrary, and a “const” whose value may be derived programmatically, but should not be altered henceforth, eg: const currentTime = new Date()

Moving Forward

I can’t say I’d recommend going back to your website from 2002 and doing a find-and-replace on “var.” But moving forward, there’s really no good reason to keep using “var” because “let” and “const” were introduced specifically to address problems with the use of “var.” Using these new variable declaration statements will lead to fewer nasty bugs and more declarative code.

New Call-to-action
blog comments powered by Disqus
Times
Check

Success!

Times

You're already subscribed

Times