var vs. let vs. const

JavaScript provides in total three keywords—var, let, const —to be able to declare variables. This post covers difference between three keywords in detail.

Cheat Sheet

var let const
scope global scope/
function scope
block scope block scope
beginning of a ... statement declaration declaration
declaration w/o value initialization 🟢
(default undefined)
🟢
(default undefined)

Syntax Error
re-declaration 🟢
re-assignment 🟢 🟢
hoisting 🟢
access before declaration undefined Reference Error Reference Error

Scope

Variables declared using var are either function-scoped or global-scoped. If it's created outside of any function, it's added as a  property to global object.

var a = 1
let b = 2
const c = 3

console.log(a) // 1
console.log(b) // 2
console.log(c) // 3

console.log(window.a) // 1
console.log(window.b) // undefined
console.log(window.c) // undefined

console.log(window.a === a) // true

If var is inside a function, the variable it created is accessible anywhere within the function it was created. On the other hand, let and const are block-scoped, meaning that they are only accessible within the nearest set of curly braces.

function () {
  if (true) {     // block scope
    var a = 1
    let b = 2
    const c = 3
  }
	
  console.log(a); // 1
  console.log(b); // ReferenceError: b is not defined
  console.log(c); // ReferenceError: c is not defined
}

In the above code, when a variable is declared within a function using var, it is accessible outisde if block, as long as it is in the function scope it was created. On the other hand, reference to b and c throws Reference Error since let and const are only valid within block scope of if statement.

Statement vs. Declaration

var keyword designates beginning of statement, whereas  let and const launches declaration. This means that var keyword may appear in some part of code where let and const may not.

For example, most control flows such as if ... else block only accept statement in body. Therefore, these code snippets here throws Syntax Error,

if (true) let a = 0; 
// SyntaxError: Lexical declaration cannot appear in a single-statement
if (true) const a = 1
// Uncaught SyntaxError: Unexpected token 'const'

while this doesn't:

if (true) var i = 0;

According to MDN, declarations are those that "bind[] identifiers to values while statements "carr[y] out actions." In this regard, it seems odd that var are thought to start a statement while let and const declaration. In light of what MDN goes on to say, we can see this as reflecting var not following normal lexical scoping rules and creating side effects such as "creating global variables, mutating existing (...) variables, and defining variables that are visible outside its block."

Declaration Without Value Initialization

When declaring variable with var or let, initialization of value may be skipped. If no value is specified, it is initialized with default value undefined. However, when declaring constant variable with const, initializer is required and value must be specified as it is declared, since constants, by definition, do not allow changes to value after declaration.

var a
console.log(a) // undefined

let b
console.log(b) // undefined

const c // Uncaught SyntaxError: Missing initializer in const declaration

Re-Declaration

Re-declaring a variable with the same name as existing var-declared variable doesn't throw error. The one declared later overrides the value of previous one. The same is true with use strict.

var a = 'oldVal'
var b = 'newVal'
console.log(b) // newVal

Re-Assignment

var and let both allows re-assigning value to existing variable while const does not.

var a = 10
let b = 20
const c = 30

a = 'a'
b = 'b'
c = 'c' // Uncaught TypeError: Assignment to constant variable.

On the other hand, there can't be more than one let- or const- declared variable with same name within a scope.

let x = 10
let x = 15 // Uncaught SyntaxError: Identifier 'x' has already been declared
const y = 10
const y = 15 // Uncaught SyntaxError: Identifier 'y' has already been declared

let and const also prevents var keyword from declaring variable with same identifier

let x = 10
var x = 10000 // Uncaught SyntaxError: Identifier 'x' has already been declared

Hoisting

If we define hoisting as "being able to reference a variable in its scope before the line it is declared, without throwing a Reference Error (MDN)," var-declared variables are hoisted, while variables declared with let and const are not.

console.log(a) // undefined
var a = 'abc'
console.log(a) // 'abc'

console.log(b); // ReferenceError: can't access lexical declaration 'b' before initialization
let b = 'def'

console.log(c); // ReferenceError: can't access lexical declaration 'c' before initialization
const c = 'ghr'

Execution context, in creation phase, works as if it hoists declaration (but not initialization) of var-declared variable to the top of scope before executing code. As a result, a logged undefined (but not the initialized value abc) before its declaration, instead of throwing SyntaxError.

let and const keywords are considered non-hoisting and any attempt to access them before initialization throws ReferenceError. Those variables are said to be in a "Temporal Dime Zone (TDZ)," which stretches from the beginning of a scope to the line where variables are declared and initialized with let or const.

               // beginning of TDZ
console.log(a) //       inside TDZ → ReferenceError

let a = 2      //       end of TDZ
💡
The term "temporal" designates that the zone is created depending on the temporal order of code execution, not on spatial order the code is written. For example, in the following code, calling function logA doesn't throw Syntax Error because even though logA is declared before a and thus seems to access a before declaration, invocation of logA happens outside TDZ.
                                  // beginning of TDZ
const logA = () => console.log(a)

const a = 2                       // end of TDZ
logA()
⚠️
Are let and const really non-hoisting?️

It is noteworthy that MDN says that "hoisting is not a term normatively defined in the ECMAScript specification("The spec does define a group of declarations as " HoistableDeclaration," but this only includes function, function*, async function, and async function*, as opposed to us often relating the concept with var keyword) "and outlines how the colloquial usage of the term might apply.

Value Hoisting: Being able to use a variable's value in its scope before the line it is declared (function, function, async function, and async function*)

Declaration Hoisting: Being able to reference a variable in its scope before the line it is declared, without throwing a ReferenceError). (var)

The declaration of the variable causing behavior changes in its scope before the line in which it is declared (let, const, class declarations; also collectively called lexical declarations)

According to the third, broader definition of hoisting, variables declared with let or const keyword also behaves in a way as if they are hoisted.

Let's take a look at an example.

While the following code logs 1,

const x = 1;

if (true) {
  console.log(x); // 1
}

the following throws ReferenceError.

const x = 1;
{
  console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization
  const x = 2;
}

Those who do not agree that let and const are non-hoisting argue that

"If the const x = 2 declaration is not hoisted at all (as in, it only comes into effect when it's executed), then the console.log(x) statement should be able to read the x value from the upper scope. However, because the const declaration sill "taints" the entire scope it's defined in, the console.log(x) statement reads the x from the const x = 2 declaration instead (...), and throws a ReferenceError."

While the claim deserves remark, MDN adds to it that it would be still more useful to regard lexical declarations like let and const non-hoisting, sayint that "the hoisting of these declarations doesn't bring any meaningful features."

For more informations on hoisting, see also: Hoisting.

References

Subscribe to go-kahlo-lee

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe