One of the first things new javascript developers are introduced to is the var
keyword. What is often missing from this introduction is that it uses hoisting under the hood, ready to shoot you in the foot.
What is variable hoisting?
When using the var keyword in javascript, the variable definition (but not it's value!) is hoisted to the top of the current function scope. An example:
console.log(x) // returns "undefined", because the variable definition below is hoisted up but not it's initial value
var x = 10; // defines variable "x". the variable is available above this line, but will have a value of "undefined" until this line is reached
console.log(y) // throws ReferenceError, because the variable y is not declared anywhere
Notice how the variable x is usable before reaching the line it is defined in, but a variable that is not defined at all still causes a ReferenceError exception to tbe thrown. This behaviour is what hoisting means.
So why is var problematic?
Remember when we said that the variable definition is hoisted to the top of the function scope? That's really unintuitive when coming from other programming languages. Usually, a variable is scoped to it's parent block, commonly enclosed by curly brackets { ... }
. In addition to that, redeclaring a variable using var
is allowed and can be easily missed:
var x = 10
for (var i = 0; i < 3; i++){
var x = i * 2;
console.log(`Loop i=${i}, x=${x}`)
}
console.log(`Outer x: ${x}`)
Initially, one would assume that the second time var x
is declared, it declares a local variable contained within the scope of the for
loop. In most other languages this would be the case, and there is no syntax error in the code. But what really happens is that the second var
does not declare a new variable - in fact, it changes the value of the x
variable outside the for loop and quietly ignores the second var keyword entirely.
Enter let
Added in ES6 (Ecmascript2015), the let keyword comes to the rescue for developers getting stung by variable hoisting. By declaring a variable with let instead of var, it will behave more predictable as in other programming languages: A variable declared with let can't be used before the line it was declared in, scopes itself to the parent block and can't be redeclared withing the same block. An example:
let x = 10
for (let i = 0; i < 3; i++){
let x = i * 2;
console.log(`Loop i=${i}, x=${x}`)
}
console.log(`Outer x: ${x}`)
This time, the code works as expected, declaring 2 seperate variables named x
, one outside and one inside the for loop. Using the same variable name in different scopes is allowed, but trying to redeclare an existing variable within the same scope will throw an error:
let x = 10 // initially declare x
let x = 11 // throws SyntaxError: Identifier 'x' has already been declared
x = 11 // assignment still works as expected
What about const
?
The const keyword added alongside let behaves almost exactly the same, but doesn't allow assigning new values once declared. Additionally, you must provide a value when declaring it (as it can't be changed later and constants without values are really pointless):
const x = 10 // normal use
const x // throws SyntaxError: Missing initializer in const declaration
x = 11 // throws TypeError: Assignment to constant variable.
let y // unlike const, let can declare variables without initial values
This looks like constants you know from other programming languages, but it has a slight caveat: Variables declared const
cannot be assigned a new value, but the one assigned to them can change. An example:
const x = {"name": "max"} // declare x as constant object
x.age = 19 // works, x is now { name: 'max', age: 19 }
delete x.name // also works, x is now { age: 19 }
x = {"name": "max"} // throws TypeError: Assignment to constant variable.
As you can see, we cannot directory give x a new value, but as long as we don't replace the reference to the object we initially gave to x entirely, we can change the contents of that object at will, deleting or changing keys that were initially defined or adding new ones. This works for both objects and arrays.
To sum it up, stop using var
entirely. Every time you consider using var
in your javascript code, let
is the better choice. The decision between let
and const
is a less important one as their difference minor and the important behaviour is consistent across both. Real-world use cases for variable hoisting almost unanimously stem from bad code design that will be prone to bugs and hard to maintain in the future.