JavaScript Variable Declarations: Var, Const, & Let

As a Front End Developer, I try my best to understand and avoid any strange behavior JavaScript may exhibit. Let's be real, it's pretty much unavoidable. It's in JavaScript's nature to be little eccentric. However, it would be an excuse to blame it on the language when there is the possibility to understand why.

Using 'var'

We all know var has been the original variable declaration. And in JavaScript, you can declare a variable anywhere you want! It seems great at first, but here's the illusion: they still are declared at the top. And this is exactly why your variables are having an identity crisis.

It's called declaration hoisting. When you execute the code, behind the scenes JavaScript is throwing what you declared at the top anyway.

// where you want value to be
function returnValue () {
    if (condition) {
        var value = 1;
        return value;
    } else {
        return null;
    }
}

// where js will decide it will be
function returnValue () {
    var value = 1;
    if (condition) {
        return value;
    } else {
        return null;
    }
}

"Why didn't anyone warn me!" But then thought, "Well, if JavaScript secretly wants me to do this then I should listen."

Why You Should Use 'const' and 'let'

So the key difference for the new declarations is to avoid hoisting. The intention was to keep our declarations to their given context and nowhere else unless said otherwise. This is why const and let are known as block-level declarations in block-scopes. Actually, as a warning it really means lexical-scopes. I will explain this later in The Temporal Dead Zone

function returnValue(){
    let condition = true;
    if (condition) {
        let value = 1;
    } else {
        let value = 2;
    }
    return value; // reference error
}

returnValue();

The above example will work the same if using const. When either of these declarations are used, it will only remain within the boundaries of its surrounding brackets { }. So what happens when a something is declared outside a block before a child block reassigns a new value to a variable with the same name then? This is where let is similar to var and will alter what is outside.

let value = 2;
if (value === 2) {
    value = 1; // "sure"
}

> 1 // it has changed!

However, using const in this situation will cause an error. The intention for const is its value to remain constant.

// this will throw an error
const value = 2;
if (value === 2) {
    value = 1; // "not okay!"
}

Pitfall with 'const' and Objects

But here we go with a "Why didn't anyone warn me!" scenario. If we target the entire object and rewrite it it will of course cause an error to occur. However, using dot notation anything inside is still susceptible to change. This is because const prevents modification of the binding, but not of the value.

const obj = {
  name: "Jake"
}

// this will throw an error
obj = {
    name: "Jack"
}

// will not throw an error, obj.name becomes this string
obj.name = "Jack";

console.log(obj.name);
> "Jack" // oops!

Solution: Freeze the Object

This where Object.freeze() is useful if we want to prevent modifying the object completely.

const obj = {
  name: "Jake"
}

Object.freeze(obj);

// will not throw an error, but useless
obj.name = "Jack";

console.log(obj.name);
> "Jake"

The "Temporal Dead Zone"

This is another behavior that is interesting, but can be avoided. If you are within the same block and use a variable in anyway before declared an error will be thrown. So understand that for const & let it's not just about the block itself, it's also about the placement within the block (lexical).

if (condition) {
    console.log(typeof value); // error, temporal dead zone!
    let value = 1;
}

Solution: Remember Proper Order

This hasn't happened to me yet because of my experience with hoisting as mentioned above. I learned right away to practice declaring at the top of a block or scope. However, if a variable is used or evaluated outside of the block where it is declared then no reference errors will occur and you will get back the expected undefined.

if (condition) {
    let value = 1; // this should be declared first
    console.log(typeof value); // then this
}

// but if you're going for undefined, have this outside the block
console.log(typeof value);

if (condition) {
    let value = 1;
}

> undefined

Just be mindful!

The Trouble with 'var' in Loops

You might have come across some code that uses for-loops and for each one, the counter's variable name keeps changing (i, j, q, etc...). This is because while using var, the counter is still held on after the loop is finished.

var arr = [1,1,1,1,1,1,1,1,1,1]; // counting to 10, bc why not

for (var i = 0; i < arr.length; i++) {
    arr[i] += i;
}

console.log(i);
> 10 // not good

Solution: 'let' is More Suitable

let arr = [1,1,1,1,1,1,1,1,1,1];

for (let i = 0; i < arr.length; i++) {
    arr[i] += i;
}

console.log(i); // reference error, i is recyclable!

However, if you were to declare i this way with const an error is thrown.

for (const i = 0; i < arr.length; i++ ) { // ERROR
    arr[i] += i;
}

However, another "Why didn't anyone warn me!" You can write both const and let in for-loops, if you do a for-in. And in my opinion, it's more readable to write this way.

const arr = [1,2,3,4];

for (let i in arr) { // using let, okay fine
  console.log(i);
}

for (const i in arr) { // using const? sure, why not
  console.log(i);
}

Global Scoping: 'var' vs 'let' and 'const'

We know that when var is used globally, there's always the possibility the variable will be overwritten. When const & let variables are declared globally in the browser, they cannot be overwritten but only shadowed.

const name = "Jake";

console.log(name);
> "Jake"
console.log(name in window);
> false
Copyright © 2020. Jake Birkes