New syntax
In order to bring ES2015 syntax to your project, let’s first familiarize yourself with a new way of declaring variables.
Two new keywords were introduced with that specification: let and const.
You might wonder why to indroduce new ways of declaring variables, if we have been doing just fine with var keyword.
Well, those two new keywords differ from var, because they behave differently in a block scope.
Using them actually will make your code less error prone. Let's see var vs let comparison first (as const behaves the same in this context).
Let vs var
Scope
Global scope (if they are not declared within a function)
Unlike var, let statement does NOT pollute global object
//browser
var a = 1;
let b = 2;
console.log(window.a); //it will print 1
console.log(window.b); //it will print undefined
//node
var a = 1;
let b = 2;
console.log(global.a); //it will print 1
console.log(global.b); //it will print undefined
Let bindings apply to the current scope which might give some problems in switch statement like this:
let x = 1;
switch(x){
case 0:
let foo;
break;
case 1:
let foo; //SyntaxError
break;
}
//but you can fix it by enclosing each case with curly brackets:
switch(x){
case 0: {
let foo;
break;
}
case 1: {
let foo; //no problem
break;
}
}
Var vs Let in block scope
var foo = 1;
var bar = 2;
if(true){
var foo = 100; //this will change the value of global variable foo
let bar = 200; // this is block scoped and not affect globally initialized bar
console.log(foo); // 100
console.log(bar); // 200
}
console.log(foo); // 100
console.log(bar); // 2
Function scope
Both bindings behave similarly within a function scope except for variable hoisting.
/* both foo and bar are not available here and will throw ReferenceError */
function test(){
/* foo is undefined here */
/* bar will throw ReferenceError */
var foo = 1;
let bar = 2;
/* foo and bar available here*/
}
/* both foo and bar are not available here and will throw ReferenceError */
Redeclaration
var foo = 1;
var foo = 2; // no error is thrown, instead value of foo is 2 now
let bar = 1;
let bar = 2; // SyntaxError is thrown because variable within the same scope has been already declared
Workarounds with closures
In ES5 closures was a workaround to allow memoization of counter within for loop. That is no longer a case with let binding as its block scoped and declared separately for each loop iteration.
//Problem: createDynamicMenu function dynamically
//create 4 li elements and append it to unordered list
//however when you click on each button you will see in your console
//'Item 5 clicked' instead of 'Item i clicked' for each iteration
var list = document.getElementById('list'); //get ul element with id list
function createDynamicMenu(){
for(var i = 1; i < 5; i++){
var element = document.createElement('li');
element.append(document.createTextNode('Button '+i));
element.onclick = function(){
console.log('Item '+i+' clicked');
//when createDynamicMenu() has been invoked and 'i' will equal 5,
// hence each onclick listener will print 'Item 5 clicked'
}
list.append(element);
}
}
createDynamicMenu();
//Solution with closures
function createDynamicMenu(){
for(var i = 1; i < 5; i++){
var element = document.createElement('li');
element.append(document.createTextNode('Button '+i));
element.onclick = (function(i){
return function(){
console.log('Item '+i+' clicked');
}
})(i);
//closure 'remembers' 'i' counter at the time it was defined
//and for each 'i' onclick listener will
//print out correctly 'Item i clicked'
list.append(element);
}
}
//Solution with let binding
function createDynamicMenu(){
for(let i = 1; i < 5; i++){
var element = document.createElement('li');
element.append(document.createTextNode('Button '+i));
element.onclick = function(){ console.log('Item '+i+' clicked'); }
// let 'i' is block scoped and created separetely for each iteration,
// hence each onclick listener will print out correct value
list.append(element);
}
}
Hoisting
Var bindings are subject to variable hoisting which means they are moved to the top of the current execution context. This rule does not apply to let bindings, which means that they cannot be used before it's initialization
console.log(zebra); // undefined - does not throw an error even if its
// above the initialization statement
console.log(letItBe); // throws Reference errors as let is
// not subject to variable hoisting
var zebra = 'test';
let letItBe = 'test';
Const
Const behaves the same like let in the examples above.
The main difference is that value of a constant cannot be redeclared and variable identifier cannot be re-assigned. Const keyword create a read-only reference to a value.
const ZEBRA; //will throw SyntaxError as ZEBRA has not been initialized
const NUMBER_OF_WHEELS = 4; // perfectly valid initialization
const NUMBER_OF_DOORS = 4, NUMBER_OF_WINGS = 2; // perfectly valid initializations
NUMBER_OF_DOORS = 3; // reassignment will throw an error
const NUMBER_OF_DOORS = 6; // redeclaration will throw an error
Immutability? Not really...
The value types re-assignments works as intended (throws an error), however when it comes reference types like an array or object they can be modified without throwing an error (not immutable)
const ITEMS = [1,2,3];
const ITEMS = [4,5,6]; //it will throw SyntaxError
ITEMS = [7,8,9]; // it will throw TypeError
//however...
ITEMS.push(4); //works ok - ITEMS equals [1,2,3,4]
ITEMS.pop(); //works ok - ITEMS equals [1,2,3]
ITEMS[3] = '100'; // works ok - ITEMS equals [1,2,3,100]
const USER = { name: 'John', age: 22 };
const USER = { name: 'Tom', age: 32}; //re-declaration will throw SyntaxError
USER = {name: 'Tom', age: 32} // // re-assignment will throw TypeError
//but...
USER.name = 'Tom'// works ok - USER equals { name: 'Tom', age: 22 };
delete USER.name //works ok - USER equals { age: 22 };
Solution for immutability
Use external libraries such as immutable.js or Object.freeze() (natively supported in ES5)
//Object.freeze() usage
const ITEMS = [1,2,3];
Object.freeze(ITEMS); // ITEMS are now immutable
ITEMS.push(4); //it will throw TypeError
ITEMS[3] = '100'; // it does not mutate the original array but neither throw an error
//but it doesn't work with nested object, so you must apply this recursively
const USER = {
name: 'Thomas',
address: {
street: 'Akacjowa 4',
city: 'Warszawa'
}
}
Object.freeze(USER);
USER.name = 'John'; // fails silently and value is not changed
USER.address.city = 'London'; // value has been changed and now equals 'London'
//solution:
function deepFreeze(obj){
var props = Object.getOwnPropertyNames(obj);
props.forEach(function(name){
var prop = obj[name];
if(prop !== null && typeof prop === 'object'){
deepFreeze(prop); //apply freezing recursively
}
});
return Object.freeze(obj);
}
deepFreeze(USER);
USER.address.city = 'Rzeszow'; // fails silently and value is not changed
Usage
//use camel case syntax for lets
let myVar = 'foo';
let itMightChange = 3;
//use upper case for const
const DEFAULT_NUMBER = 1;
const NUMBER_OF_WHEELS = 4;
Var vs Let vs Const
If you are using ES2015 (ES6) use const by default.
If you think that current variable might be re-assigned in the future use let instead.
You should forget about var, and in most cases all your var keywords could be replaced with let straight away!
- https://developer.mozilla.org
- icons for main picture: https://dribbble.com/jucha