Variables declared with the var keyword are function scoped.
var variables can be updated as well as redefined.
Variables created with var for use only in a particular block (for example, if blocks) are leaked outside the block.
let and const variables
Variables declared with let and const are block scoped.
let and const variables cannot be redeclared in the same scope.
let vs const
const does not indicate that a value is immutable. A const value can definitely change. The only thing that's immutable here is the binding. const assigns a value ({}) to a variable name (foo), and guarantees that no rebinding will happen. Using an assignment operator or a unary or postfix operator on a const variable throws a TypeError exception.
With var variables, you can only access them as they are defined. Before they are defined, you cannot access the actual value of them, but you can access the fact that the variable has been created before.
If let and const variables are accessed before they are created, it'll cause an error and break the code. This is the temporal dead zone, where you cannot access a variable before it is defined. This does not mean that they are not hoisted which is proven by this valid piece of code:
functionreadThere(){// there used before it is declared (it is not executed)returnthere;}letthere='dragons';console.log(readThere());// "dragons"
Best practices
Use const by default.
Only use let if rebinding is needed.
var shouldn't be used in ES6.
Do not use const to make an object immutable. Use Object.freeze(obj);.
Arrow functions are less verbose than traditional function expressions:
constnames=['Amitabh','Jaya','Abhishek'];// the old wayconstfullNames=names.map(function(name){return`${name} Bachchan`;});// the es6 wayconstfullNames=names.map(name=>`${name} Bachchan`);
Arrow functions are anonymous, making a stack trace relatively harder.
Parenthesize the body of function to return an object literal expression:
Normal functions in JavaScript bind their own this value, however the this value used in arrow functions is actually fetched lexically from the scope it sits inside. It has no this, so when you use this you’re talking to the outer scope.
constbox=document.querySelector('.box');box.addEventListener('click',function(){// have to use function keyword here so this contains 'box'// if arrow function is used, this will have 'window'console.log(this);this.classList.toggle('opening');setTimeout(()=>{// if function keyword was used here, this would contain 'window'// arrow function fetches this lexically from the outer scope i.e. 'box'console.log(this);this.classList.toggle('open');});});
Default function arguments
// tax and tip will be be 0.13 & 0.15 if nothing is passed infunctioncalculateBill(total,tax=0.13,tip=0.15){returntotal+(total*tax)+(total*tip);}consttotalBill=calculateBill(100);console.log(totalBill);
'use strict';varobj={i: 10,b: ()=>console.log(this.i,this),c: function(){console.log(this.i,this);}}obj.b();// prints undefined, Window {...} (or the global object)obj.c();// prints 10, Object {...}
When you need to add a prototype method:
classCar{constructor(make,colour){this.make=make;this.colour=colour;}}constbeemer=newCar('bmw','blue');constsubie=newCar('Subaru','white');Car.prototype.summarize=()=>{// this.make and this.colour is undefined because we used arrow function herereturn`This car is a ${this.make} in the colour ${this.colour}`;};
When you need arguments object
constorderChildren=()=>{// 'arguments' is not defined for arrow functionsconstchildren=Array.from(arguments);returnchildren.map((child,i)=>{return`${child} was child #${i+1}`;})console.log(arguments);}
constname='Snickers';constage=2;constsentence=`My dog ${name} is ${age*7} years old.`;console.log(sentence);
HTML fragments with template literals
constperson={name: 'Daksh',job: 'Web Developer',city: 'Surat',bio: 'Daksh is a really cool guy who loves designing stuff for the web!'};constmarkup=` <div class="person"> <h2>${person.name} <span class="job">${person.job}</span> </h2> <p class="location">${person.city}</p> <p class="bio">${person.bio}</p> </div>`;document.body.innerHTML=markup;
Tags allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions.
In the end, your function can return your manipulated string (or it can return something completely different).
The name of the function used for the tag can be named whatever you want.
// ability to modify the template before it gets assigned to 'sentence'functionhighlight(strings, ...values){letstr='';strings.forEach((string,i)=>{// example: span tag with class h1 around all valuesstr+=`${string} <span class="h1">${values[i]||''}</span>`;});returnstr;}constname='Snickers';constage=100;// tagged with highlightconstsentence=highlight`My dog ${name} is ${age} years old.`;document.body.innerHTML=sentence;console.log(sentence);
4. New string methods
.startsWith()
The startsWith() method determines whether a string begins with the characters of a specified string, returning true or false as appropriate.
// startswithvarstr='To be, or not to be, that is the question.';console.log(str.startsWith('To be'));// trueconsole.log(str.startsWith('not to be'));// falseconsole.log(str.startsWith('not to be',10));// true
.endsWith()
The endsWith() method determines whether a string ends with the characters of a specified string, returning true or false as appropriate.
// endswithvarstr='To be, or not to be, that is the question.';console.log(str.endsWith('question.'));// trueconsole.log(str.endsWith('to be'));// falseconsole.log(str.endsWith('to be',19));// true
.includes()
The includes() method determines whether one string may be found within another string, returning true or false as appropriate.
// includesvarstr='To be, or not to be, that is the question.';console.log(str.includes('To be'));// trueconsole.log(str.includes('question'));// trueconsole.log(str.includes('nonexistent'));// falseconsole.log(str.includes('To be',1));// falseconsole.log(str.includes('TO BE'));// false
.repeat()
The repeat() method constructs and returns a new string which contains the specified number of copies of the string on which it was called, concatenated together.
// repeat'abc'.repeat(-1);// RangeError'abc'.repeat(0);// '''abc'.repeat(1);// 'abc''abc'.repeat(2);// 'abcabc''abc'.repeat(3.5);// 'abcabcabc' (count will be converted to integer)'abc'.repeat(1/0);// RangeError
.padStart() and .padEnd()
The padStart() method pads the current string with another string (repeated, if needed) so that the resulting string reaches the given length. The padding is applied from the start (left) of the current string.
The padEnd() method pads the current string with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end (right) of the current string.
conststrings=['short','medium size','this is really really long'];constlongestString=strings.sort(str=>str.length).map(str=>str.length)[0];// all strings aligned rightstrings.forEach(str=>console.log(str.padStart(longestString)));
Return an object and destructure it while calling the function. You don't need to care about the order or the number of values you need from the returned object.
functionconvertCurrency(amount){constconverted={USD: amount*0.76,GPB: amount*0.53,AUD: amount*1.01,MEX: amount*13.30};returnconverted;}// old wayconsthundo=convertCurrency(100);// es6 way — return multipleconst{MEX,USD,AUD}=convertCurrency(100);
6. The for-of loop
The for...of statement creates a loop iterating over iterable objects (including Array, Map, Set, String, TypedArray, arguments object and so on), invoking a custom iteration hook with statements to be executed for the value of each distinct property.
constcuts=['Chuck','Brisket','Shank','Short Rib'];// the old wayfor(leti=0;i<cuts.length;i++){console.log(cuts[i]);}// cannot abort or skip loopcuts.forEach((cut)=>{console.log(cut);if(cut==='Brisket'){continue;}});// iterates over everything (prototypes, properties, etc.)for(constindexincuts){console.log(cuts[index]);}// for-of loop (iterates only over items)for(constcutofcuts){if(cut==='Brisket'){continue;}console.log(cut);}
Iterating over array using entries()
constcuts=['Chuck','Brisket','Shank','Short Rib'];for(const[i,cut]ofcuts.entries()){console.log(`${cut} is the ${i+1} item`);}
<p>I'm p 01</p><p>I'm p 02</p><p>I'm p 03</p><p>I'm p 04</p><p>I'm p 05</p><script>constps=document.querySelectorAll('p');for(constparagraphofps){paragraph.addEventListener('click',function(){console.log(this.textContent);});}</script>
Iterating over objects
for-of cannot be used to iterate directly over objects.
Alternative methods such as a for-in loop has to be used.
Object.entries() will be implemented in ES2017 making it possible to iterate over objects just like you would over an array.
The Array.from() method creates a new Array instance from an array-like or iterable object:
<divclass="people"><p>Daksh</p><p>Mark</p><p>Jeff</p></div><script>// people is an array-like NodeList but doesn't have all prototype methods of an arrayconstpeople=document.querySelectorAll('.people p');// peopleArray is a true Array now// Array.from() also takes a map function as an optional argumentconstpeopleArray=Array.from(people,person=>{console.log(person);returnperson.textContent;});</script>
The Array.of() method creates a new Array instance with a variable number of arguments, regardless of number or type of the arguments:
The spread operator allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
Spreading strings
This is the simplest use case for the spread operator:
Using a combination of push, splice, concat, etc. is no longer required to construct a new array from multiple arrays:
constfeatured=['Deep Dish','Peperoni','Hawaiian'];constspeciality=['Meatzza','Spicy Mama','Margherita'];// the old wayletpizzas=[];pizzas=pizzas.concat(featured);pizzas.push('veg');pizzas=pizzas.concat(speciality);console.log(pizzas);// the es6 wayconstpizzas=[...featured,'veg', ...speciality];console.log(pizzas);
Spreading into a function
It is common to use Function.prototype.apply in cases where you want to use the elements of an array as arguments to a function. The spread operater makes that process easier:
constinventors=['Einstein','Newton','Galileo'];constnewInventors=['Musk','Jobs'];// the wrong way --> ['Einstein', 'Newton', 'Galileo', ['Musk', 'Jobs']]inventors.push(newInventors);// the old way --> ['Einstein', 'Newton', 'Galileo', 'Musk', 'Jobs']inventors.push.apply(inventors,newInventors);// the es6 way --> ['Einstein', 'Newton', 'Galileo', 'Musk', 'Jobs']inventors.push(...newInventors);
You can skip writing the property names when the property and variable names are same.
constfirst='oreo';constlast='bow';constage=2;constbreed='Labrador Retriever';// the old wayconstdog={firstName: first,last: last,age: age,breed: breed,pals: ['Hugo','Sunny']};// the es6 wayconstdog={firstName: first,
last,
age,
breed,pals: ['Hugo','Sunny']};
Naming object methods
You can skip writing the function keyword while defining object methods.
// the old wayconstmodal={create: function(selector){},open: function(content){},close: function(goodbye){}};// the es6 wayconstmodal={create(selector){},open(content){},close(goodbye){}};
Computed property names
Property key names can now be computed inside object literals while defining them:
A Promise is an object representing the eventual completion or failure of an asynchronous operation.
Attaching callbacks to promises
Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function:
constusersPromise=fetch('https://reqres.in/api/users');usersPromise// .then runs on successful fetch.then(data=>data.json()).then(data=>{console.log(data)})// .catch runs in case of an error.catch((err)=>{console.error(err);});
Building promises
A Promise object is created using the new keyword and its constructor. This constructor takes as its argument a function, called the "executor function".
resolve is called when the asynchronous task completes successfully and returns the results of the task as a value.
reject is called when the task fails, and returns the reason for failure, which is typically an error object.
// executor function takes two args (resolve, reject)constp=newPromise((resolve,reject)=>{setTimeout(()=>{// resolve('Daksh is cool');reject(Error('Err Daksh isn\'t cool'));},1000);});p// resolve.then(data=>{console.log(data);})// reject.catch(err=>{console.error(err);});
Chaining promises + flow control
A common need is to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step. We accomplish this by creating a promise chain.
Always return promises up, otherwise callbacks won't chain, and errors won't be caught.
doSomething().then(result=>doSomethingElse(result)).then(newResult=>doThirdThing(newResult)).then(finalResult=>{console.log(`Got the final result: ${finalResult}`);}).catch(failureCallback);
Multiple promises with Promise.all()
The Promise.all() method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises.
It rejects with the reason of the first promise that rejects.
New primitive data type added to ES6 in addition to Boolean, Null, Undefined, Number, String.
Values of this type can be used to make object properties that are anonymous. This data type is used as the key for an object property when the property is intended to be private, for the internal use of a class or an object type.
When a symbol value is used as the identifier in a property assignment, the property (like the symbol) is anonymous; and also is non-enumerable.
The property can be accessed by using the original symbol value that created it, or by iterating on the result array of Object.getOwnPropertySymbols().
constclassRoom={[Symbol('Mark')]: {grade: 50,gender: 'Male'},[Symbol('Olivia')]: {grade: 23,gender: 'Female'},// unique Olivia[Symbol('Olivia')]: {grade: 34,gender: 'Female'}};// you get nothingfor(personinclassRoom){console.log(person);}// grab all symbols off of classRoom object (can't see any data though)constsyms=Object.getOwnPropertySymbols(classRoom);console.log(syms);// -> [Symbol(Mark), Symbol(Olivia), Symbol(Olivia)]constdata=syms.map(sym=>classRoom[sym]);console.log(data);// -> [{grade: 50, gender: "Male"}, {grade: 23, gender: "Female"}, {grade: 34, gender: "Female"}]
In JavaScript, any function can be added to an object in the form of a property.
An inherited function acts just as any other property, including property shadowing.
functionDog(name,breed){// associated with the instance (snickers, sunny)this.name=name;this.breed=breed;}// method on the prototype (Dog)Dog.prototype.bark=function(){console.log(`Bark Bark! My name is ${this.name}`)}constsnickers=newDog('Snickers','King Charles');constsunny=newDog('Sunny','Golden Doodle');snickers.bark();// -> Bark Bark! My name is Snickers
Basics of classes
Classes in ES6 are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript.
Class syntax has two components: class expressions and class declarations. Class expressions are
Static methods are called without instantiating their class and cannot be called through a class instance. Static methods are often used to create utility functions for an application.
// class declarationclassDog{constructor(name,breed){this.name=name;this.breed=breed;}bark(){console.log(`Bark Bark! My name is ${this.name}`);}cuddle(){console.log(`I love you owner!`);}// static method -> Dog.info();staticinfo(){console.log('A dog is better than a cat');}// string returned when dogProperty.description is looked upgetdescription(){return`${this.name} is a ${this.breed} type of dog`;}// called when value is assigned to dogProperty.nicknamessetnicknames(value){this.nick=value.trim();}// modified string returned when dogProperty.nicknames is looked upgetnicknames(){returnthis.nick.toUpperCase();}}constsnickers=newDog('Snickers','King Charles');constsunny=newDog('Sunny','Golden Doodle');
Extending classes
The extends keyword is used in class declarations or class expressions to create a class which is a child of another class.
// base classclassAnimal{constructor(name){this.name=name;this.thirst=100;this.belly=[];}drink(){this.thirst-=10;returnthis.thirst;}eat(food){this.belly.push(food);returnthis.belly;}}// Dog extended from AnimalclassDogextendsAnimal{constructor(){// make an Animal firstsuper(name);this.breed=breed;}// additional methodbark(){console.log('Bark bark I\'m a dog');}}constrhino=newAnimal('Rhiney');constsnickers=newDog('Snickers','King Charles');// eat() is still availablesnickers.eat('plastic');
Extending arrays with classes
Arrays in ES6 can be extended/modified to have class-like features such as constructors and methods:
Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.
yield can be considered a temporary return.
A generator which has returned (return) will not yield any more values.
constinventors=[{first: 'Albert',last: 'Einstein',year: 1879},{first: 'Isaac',last: 'Newton',year: 1643},{first: 'Galileo',last: 'Galilei',year: 1564},{first: 'Marie',last: 'Curie',year: 1867},{first: 'Johannes',last: 'Kepler',year: 1571},{first: 'Nicolaus',last: 'Copernicus',year: 1473},{first: 'Max',last: 'Planck',year: 1858},];// define generator functionfunction*loop(arr){for(constitemofarr){yielditem;}}// Generator object returnedconstinventorGen=loop(inventors);// returns first object in array and done statusinventorGen.next();{value: Object,done: false}// returns second object in array and done statusinventorGen.next();{value: Object,done: false}// returns third objectinventorGen.next().value();// {first: 'Galileo', last: 'Galilei', year: 1564}
Using generators for ajax flow control
Generators can be used for managing waterfall ajax requests:
functionajax(url){fetch(url).then(data=>data.json()).then(data=>dataGen.next(data))}function*steps(){constbeers=yieldajax('http://api.react.beer/v2/search?q=hops&type=beer');constdaksh=yieldajax('https://api.github.com/users/dakshshah96');constfatJoe=yieldajax('https://api.discogs.com/artists/51988');}// Generator object createdconstdataGen=steps();// kick it off (starts with beer api)dataGen.next();
Looping generators with for-of
function*lyrics(){yield`On a dark desert highway, cool wind in my hair`;yield`Warm smell of colitas, rising up through the air`;yield`Up ahead in the distance, I saw a shimmering light`;yield`My head grew heavy and my sight grew dim`;yield`I had to stop for the night.`;}constachy=lyrics();// for-of already has next()for(constlineofachy){console.log(line);}
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
constphoneHandler={// clean phone numbers when someone sets itset(target,name,value){target[name]=value.match(/[0-9]/g).join('');},// consistently format phone numbers on getget(target,name){returntarget[name].replace(/(\d{3})(\d{3})(\d{4})/,'($1)-$2-$3');}}// empty target object {}constphoneNumbers=newProxy({},phoneHandler);
Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
Values are not index based and need to accessed via a for-of loop or a generator.
constbrunch=newSet();// people start coming inbrunch.add('Daksh');brunch.add('Mark');brunch.add('Jeff');// ready to openconstline=brunch.values();// start seating peopleconsole.log(line.next().value);console.log(line.next().value);// new people show upbrunch.add('Larry');brunch.add('Elon');// seat peopleconsole.log(line.next().value);console.log(line.next().value);console.log(line.next().value);
WeakSet
The WeakSet object lets you store weakly held objects in a collection.
References to objects in the collection are held weakly. If there is no other reference to an object stored in the WeakSet, they can be garbage collected. That also means that there is no list of current objects stored in the collection.
WeakSets are not enumerable.
letdog1={name: 'Snickers',age: 3};letdog2={name: 'Sunny',age: 1};constweakSauce=newWeakSet([dog1,dog2]);console.log(weakSauce);dog1=null;// dog1 has been removedconsole.log(weakSauce);
The Map object holds key-value pairs. Any value (both objects and primitive values) may be used as either a key or a value.
The keys of an Object are Strings and Symbols, whereas they can be any value for a Map, including functions, objects, and any primitive.
A Map is an iterable and can thus be directly iterated, whereas iterating over an Object requires obtaining its keys in some fashion and iterating over them.
constclickCounts=newMap();// get all buttons from HTMLconstbuttons=document.querySelectorAll('button');buttons.forEach(button=>{// the key here is the actual buttonclickCounts.set(button,0);button.addEventListener('click',function(){// current countconstval=clickCounts.get(this);// update countclickCounts.set(this,val+1);});});
WeakMap
The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced. The keys must be objects and the values can be arbitrary values.
References to key objects in the WeakMap are held weakly. If there is no other reference to the key object stored in the WeakMap, they can be garbage collected. That also means that there is no list of current object keys stored in the collection.
WeakMap keys are not enumerable.
letdog1={name: 'Snickers'};letdog2={name: 'Sunny'};conststrong=newMap();constweak=newWeakMap();strong.set(dog1,'Snickers is the best!');weak.set(dog2,'Sunny is the 2nd best!');// strong still has dog1 in it even though it doesn't existdog1=null;// weak no longer has dog2 in it (garbage collected)dog2=null;
Async/Await is built on top of Promises and is compatible with all existing Promise-based APIs.
async automatically transforms a regular function into a Promise. When called, async functions resolve with whatever is returned in their body. They also enable the use of await.
When placed in front of a Promise call, await forces the rest of the code to wait until that Promise finishes and returns a result. Await works only with Promises, it does not work with callbacks and can only be used inside async functions.
A standard try/catch block is used to handle errors.
varrp=require('request-promise');// Encapsulate the solution in an async functionasyncfunctionsolution(){try{constpromiseResult=awaitPromise.reject('Error');}catch(e){console.log(e);}// Wait for the first HTTP call and print the resultconsole.log(awaitrp('http://example.com/'));// Spawn the HTTP calls without waiting for them - run them concurrentlyconstcall2Promise=rp('http://example.com/');// Does not wait!constcall3Promise=rp('http://example.com/');// Does not wait!// After they are both spawned - wait for both of themconstresponse2=awaitcall2Promise;constresponse3=awaitcall3Promise;console.log(response2);console.log(response3);}// Call the async functionsolution().then(()=>console.log('Finished'));
Waiting on multiple promises
asyncfunctiongetData(names){// fetch each user and get all promisesconstpromises=names.map(name=>fetch(`https://api.github.com/users/${name}`)).then(r=>r.json());// awaiting all promises to resolveconstpeople=awaitPromise.all(promises);console.log(people);}// pass array of users to fetchgetData(['dakshshah96','addyosmani']);
Async Await vs Promises
Async/Await does not make Promises obsolete. When working with Async/Await we are still using Promises under the hood. Using a single promise is straightforward. However, when we need to program complicated asynchronous logic, we may end up combining a few promises. Writing all the then clauses and anonymous callbacks can easily get out of hand.
Async/Await doesn't always cut it. For example, when we need to make multiple independent asynchronous calls and wait for all of them to finish. To send all requests at the same time a Promise.all() is required. This will make sure we still have all the results before continuing, but the asynchronous calls will be firing in parallel, not one after another.
The Object.values() method returns an array of a given object's own enumerable property values, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
The Object.entries() method returns an array of a given object's own enumerable property [key, value] pairs, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
constinventory={backpacks: 10,jeans: 23,hoodies: 4,shoes: 11};// using Object.keys for navconstnavKeys=Object.keys(inventory);// ["backpacks", "jeans", "hoodies", "shoes"]constnav=navKeys.map(item=>`<li>${item}</li>`).join('');console.log(nav);// using the new Object.values() methodconsttotalInventoryVals=Object.values(inventory);// [10, 23, 4, 11]consttotalInventory=totalInventoryVals.reduce((a,b)=>a+b);console.log(totalInventory);// 48// using the new Object.entries() methodconstinventoryEntries=Object.entries(inventory);// [["backpacks", 10], ["jeans", 23], ["hoodies", 4], ["shoes", 11]]inventoryEntries.forEach(([key,val])=>{console.log(key,val);});