Hello var
, old friend
In days of yore, using var
was the only way to declare a variable. Those were interesting times, when developers dealt with hoisting all the time, leading to hair-pulling bugs and unexpected behavior. To better understand what hoisting is, think of the following example:
a = 5;
var a;
console.log(a);
This would actually print 5
instead of undefined
, since the compiler hoist the variable declaration within the associated scope and translate the snippet above into
var a;
....
a = 5;
console.log(a)
Same principle applies to functions so the following snippet
foo();
function foo() {
// magic here
}
where the function declaration will get hoisted within the scope but please note this isn’t the case of function expressions, which means that the following example
foo();
var foo = function () {};
would throw a TypeError
such as Uncaught TypeError: foo is not a function
ES2015: let
and const
are introduced
Fast forward a few years, the let
and const
keywords got introduced which, along with var
, are more ways to bind declarations to their associated scope. By using let
we’re able to declare bindings that can be reassigned, such as
let a = 5;
...
a = i + b + n;
and so on. This isn’t the case with const
though, since creates bindings that can only be assigned once, meaning that a block like the following
const a = 5;
a = 12;
would throw a TypeError
such as Uncaught TypeError: Assignment to constant variable
and so would assignment operators do like a += 10
, a++
or even bitwise operators like a <<= 1
.
Now, a common misconception that people make (myself included in the past), is that const
creates immutable variables which is not the case. const
variables are perfectly mutable as shown in the following (working) example:
const obj = { a: 1, b: 2 };
obj.c = 3;
console.log(obj);
// Prints {a: 1, b: 2, c: 3}
In order to actually make an object immutable, one should use the Object.freeze()
method which on strict mode would raise an error, or fail silently otherwise. Please note though, this only performs a shallow freeze, which means we’re still able to mutate the properties of the nested obj.o
object as on the following (working) example:
const obj = Object.freeze({
a: 1,
o: { name: "G.I. Joe", cobra: false },
});
obj.o.cobra = true;
console.log(obj);
// Prints { a: 1, o: { name: "G.I. Joe", cobra: true } }
To make matters a bit more complex, JavaScript provides the Object.seal()
method, which prevents new properties from being added to an object and marks all existing properties as non-configurable, but also Object.preventExtensions()
which only prevents new properties from ever being added to an object.
If we’d like to compare the three, we’d summarize as follows:
Object.freeze()
makes an object’s properties immutable, while nested objects’ properties are still mutable. Example in non-strict mode:const frozen = Object.freeze({ a: 1, b: 2 }); frozen.a = 4; delete frozen.b; // returns false console.log(frozen); // Prints the object in its original shape { a: 1, b: 2 }
Object.seal()
allows properties to be changed, prevents them from being added or deleted. Example in non-strict mode:const sealed = Object.seal({ a: 1, b: 2 }); sealed.a = 5; // property "a" now has a new value sealed.c = 12; delete sealed.b; // returns false console.log(sealed); // Prints the object without the new properties added but with property "a" mutated {a: 5, b: 2}
Object.preventExtensions()
allows properties to be changed and deleted, prevents new properties from being added. Example in non-strict mode:const prev = Object.preventExtensions({ a: 1, b: 2 }); prev.a = 5; delete prev.b; // returns true console.log(prev); // Prints the object with a new value for property "a", without the deleted property "b" {a: 5}
Credits
- “ES2015 const is not about immutability” by Mathias Bynens
- “Hoisting” on You Dont Know JS
- “Block Scoping Revisited” on You Dont Know JS
- Object methods on MDN