This post is part 2 of the series on ES6. If you have skipped part 1, you can view it here. While the topics covered in this post are not related to the part 1, reading part 1 first is recommended. In this post, we will discuss more ES6 features that are really useful. If you’ve mastered these, you can jump into Angular 2+ and React Native development. Let’s start!
Fat Arrow Functions
If you are already using fat arrow functions in ES6, you know how easy and amazing they are. First, let’s take a look at a simple example using ES5:
Anonymous function: a function that does not have a name. For example:
setTimeout(function() { console.log('I am here after 3 light-years ;)'); }, 3000);
Let’s give this anonymous function a name:
function myFunction() { console.log('I am your function'); } setTimeout(myFunction, 3000);
The above function has a name, myFunction
. Notice the argument of the setTimeout
function. We are passing a function as an argument, because JavaScript functions are first-class functions. This means, a function can take another function as its argument.
ES6 introduced fat arrow functions, which is a slightly shorter way of writing anonymous functions.
“An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.” ~ mozilla docs
Since arrow functions do not have their own this, the output is not similar as with function expressions. Example:
var compareObj= { age: 26, ageArrow: () => console.log(this.age, this), ageExpression: function() { console.log(this.age, this); } } > compareObj.ageArrow(); // undefined Window > compareObj.ageExpression(); // 26 {age: 26, ageArrow: Æ’, ageExpression: Æ’}
The above setTimeout
anonymous function could also be written using fat arrow function:
Example 1:
setTimeout(() => { console.log("I am here after 3 light-years ;)"); }, 3000);
Example 2: Simple sum
function legacy declaration and fat arrow representation.
const sumFun = function(arg1, arg2, arg3) { return arg1 + arg2 + arg3; };
Fat arrow representation:
const sumFun = ( arg1, arg2, arg3 ) => arg1 + arg2 + arg3;
Or: in case of multiline statements inside function: you are required to return explicitly by using the return
keyword, like in the following example. If it is one line statement, it is automatically returned (as stated in above example).
const sumFun = ( arg1, arg2, arg3 ) => { return arg1 + arg2 + arg3; }
Destructuring
Destructuring is a way to extract the values from an array and an object directly into variables. Let’s understand by taking an example:
const modusEmployee = { name: ‘Nikhil’, country: ‘India’, city: ‘New Delhi’, nativeLanguage: ‘Hindi’ };
In ES5, we could do the object value extracting as:
const empName = modusEmployee.name; // Nikhil const empCity = modusEmployee.city; // New Delhi
In ES6, it is improved:
const { name, city } = modusEmployee; console.log(name); // Nikhil console.log(city); // New Delhi
Here we are using alias for property name
const { name: empName, city: empCity } = modusEmployee; console.log(empName); // Nikhil console.log(empCity); // New Delhi
Destructuring can be used in the following ways:
Object destructuring
Array destructuring
Function parameter destructuring
We have already covered the object destructuring, let’s see how Arrays destructuring works.
Array destructuring is quite similar to object destructuring. The difference is that it extracts the values based on index.
const myCoins = [ 1,2,5,10 ]; const [ coin1, coin2 ] = myCoins; console.log(coin1) // 1 console.log(coin2); // 2
Bonus points!
If you just want the third value:
const [ , , coin3 ] = myCoins; console.log(coin3); // 5;
If you want all values in one variable:
const [ ...all ] = myCoins; console.log(all); // [1,2,5,10]
If you want to get rest of the values:
const myCoins = [ 1,2,5,10 ]; const [ coin1, ...otherCoins ] = myCoins; console.log(coin1); // 1 console.log(otherCoins); // [ 2, 5, 10 ]
Handling conditions when no values are available for destructuring: Default Values. You can provide default values when the property you are destructuring is not defined:
const [ random = ‘not found’ ] = [ ‘Available’ ]; console.log(random); // Available const [ random = ‘not found’ ] = []; console.log(random); // not found
Object Orientation: Class
JavaScript is a prototype-based programming language and in ES5 there was no class
keyword available to use. It does not mean that we couldn’t code using object oriented programming in ES5. We can mimic object oriented programming in ES5 by creating an object with prototype methods. Inheritance can also be achieved by using these objects. This is how we have had to create a “class” with ES5:
var Automobile = function (engineSound) { this.engineSound = engineSound; } Automobile.prototype.engineSoundFun = function() { console.log(this.engineSound) }; var enfield = new Automobile(‘dhuk dhuk’); enfield.engineSoundFun(); Output: // dhuk dhuk
If you are familiar with C++ or Java then you might be thinking, “Where is object orientation in the above example since no class
keyword is used?” The syntax became easier to understand in ES6 as the classical object oriented pattern in other languages. ES6 introduces the class
keyword that makes it much easier to understand. ES6 Classes support prototype-based inheritance, super calls, instance and static methods, and constructors.
Let’s do the above example in ES6 way:
class Automobile { constructor(engineSound) { this.engineSound = engineSound; } engineSoundFun() { console.log(this.engineSound); } } const enfield = new Automobile(‘Dhuk Dhuk’); enfield.engineSoundFun() Output: // Dhuk Dhuk
Notice the code is much cleaner, easier and familiar in ES6. However, the prototype pattern is still available to use in ES6.
Template Literals
Remember, debugging in code using console.log
and variable concatenation:
var name = ' Welcome! ' + first + ' ' + last + '.'; console.log(‘Hi ‘ + name + ‘ your username is ‘ + username):
Luckily, in ES6 we can use a new syntax ${first}
inside of the back-ticked string in order to clean it up:
const name = ` Welcome! ${first} ${last}.`; console.log(`Hi ${name} your username is: ${username}`);
Another issue before was using multiline strings, the new line statements used were printing using special characters like \n
:
var message = ‘Individuals in semler project \n are not just developers or testers, they are \n good friends’;
Using a template literal with the back-tick now works just as you would expect it to:
const message = `Individuals in semler project are not just developers or testers, they are good friends`;
Spread Operator
If you are aware of the concept of deep copy and shallow copy in JavaScript (take a glimpse below in the bonus section of this post), you can understand that sometimes it becomes hard to analyze what is happening between objects copying and their usage.
Code speaks more, let’s understand the scenario by example (without spread operator):
const myCurrencyNotes = [1, 5, 10, 1000]; const piratedNotes = myCurrencyNotes; console.log(piratedNotes); Output: [1, 5, 10, 1000] > piratedNotes.push(50); console.log(piratedNotes); Output: [1, 5, 10, 1000, 50] Now, when you log: > console.log(myCurrencyNotes); Output: [1, 5, 10, 1000, 50]
We know that when working with objects in JavaScript (arrays are a type of object) we assign by reference, not by value.
Same case with the usage of spread operator:
const myCurrencyNotes = [1, 5, 10, 1000]; const piratedNotes = [ ...myCurrencyNotes ]; console.log(piratedNotes); Output: [1, 5, 10, 1000] > piratedNotes.push[50,100] > console.log(myCurrencyNotes); Output: [1, 5, 10, 1000]
Spot the difference here, when we push elements in piratedNotes
array, the original array myCurrencyNotes
is not affected.
Another use case of spread would be:
var newCurrencyNotes = [ 50,100 ]; var myAllCurrencyNotes = [ 1, 5, 10, newCurrencyNotes, 1000 ]; console.log(myAllCurrencyNotes); Output: [1, 5, 10, Array(2), 1000]. 0:1 1:5 2:10 3:(2) [50, 100] 4:1000
You may not be expecting the 3rd step in output because it becomes a nested array, to eliminate such problem we can utilize spread operator:
const myAllCurrencyNotes = [1, 5, 10, ...newCurrencyNotes, 1000] console.log(myAllCurrencyNotes); Output: [1, 5, 10, 50, 100, 1000]
Spread in function:
function checkFakeCurrencyNote(...notesVariations) { if (notesVariations.includes(1000)) { return ‘Contains an invalid currency denomination’; } else{ return ‘Aha! nice currency’; } } const currency = [ 1, 10, 1000 ]; > checkFakeCurrencyNote(...currency); Output: "Contains an invalid currency denomination"
Promise
Objects are everywhere in JavaScript. A promise is also an object that returns its value in the future. Using a promise is useful when you have an asynchronous operation to perform. A promise can either be resolved or rejected.
A promise in JavaScript is similar to our real-life promise. Suppose you invited your friend to a party (request sent) and he told you he may organize the party (resolve
) or he may not (rejected
). If a promise is called resolved and you can process it further using .then()
. If the promise is rejected and it can be handled in a .catch()
or as the second argument in a .then()
.
You can make a promise by using new Promise
. This Promise constructor takes in a function that contains two arguments — resolve
and reject
. The basic skeleton for creating a promise is:
new Promise((resolve, reject) => { /* Do something here */ });
The promise is created with name promise, as we have already discussed above, the case of .then()
and error
:
promise.then((resolvedValue) => { console.log(resolvedValue); }, (error) => { console.log(error); });
Let’s understand by taking a complete snippet:
const yourAge = window.prompt(‘Enter your age’, 13); console.log(`Entered age is ${yourAge}`);
The variable yourAge
is declared and ready to store the user’s random input value in a browser prompt. We will use this random value later in the program:
new Promise((resolve, reject) => { If ( yourAge < 99 ) { resolve("Congrats!! you are still young Carlos"); } else { reject(new Error(‘Now at this stage, you should start eating tons of veggies’)); } });
Here, I have initialized a promise using new Promise and passed two arguments. The nested logic compares the variable yourAge
value. If the value is less than 99, the promise returns resolve
, else it rejects and outputs with an error.
If we simply run the above snippet with positive case value i.e value < 99
, we will get nothing as output and if we provide negative case value i.e value > 99
, we will get an Uncaught
error:
Uncaught (in promise) Error: Now at this stage, you should start eating tons of veggies at Promise (:5:13) at new Promise () at :1:17
Note: Though you can remove new Error
from reject
and simply return a string like resolve
, it is preferred to reject with an error but is not required.
reject(‘Now at this stage, you should start eating tons of veggies’);
But if your input is a negative case value, you will still get:
Uncaught (in promise) Now at this stage, you should start eating tons of veggies
These responses are expected. If you see the previous example above, we have to use .then
and .catch
to get the expected responses.
promise.then((resolvedValue) => { console.log(resolvedValue); }, (error) => { console.log(error); });
Suppose you provided 89 as an input value, the output will be:
Congrats!! you are still young Carlos Promise {: undefined} __proto__: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: undefined
And for negative case input, suppose you provide 120:
Now at this stage, you should start eating tons of veggies Promise {: undefined} __proto__:Promise [[PromiseStatus]]:"resolved" [[PromiseValue]]:undefined
Bonus: Shallow and Deep Copy in JavaScript
Shallow copy means the memory location of both objects will be the same. So if you change the property value of the copy object, the property value of the original object will also change at deep level of object. A shallow copy will duplicate the top-level properties, but the nested object is shared between the original (source) and the copy (target). But in deep copy, the memory locations will be different/separated. Changing the property values of copies object will not change the original one.
Shallow copy example: The Object.assign()
method is used to copy the values of properties from one or more source objects to a target object:
let oldObj = { id:1 } > oldObj {id: 1} let newObj = { ...oldObj }; > newObj {id: 1} > newObj.id = 9; > newObj {id: 9} > oldObj {id: 1}
Everything is fine until here, but as per the statement “A shallow copy will duplicate the top-level properties.” Let’s understand by an example:
let oldObj = { a: 1, b: { c: 2, }, } let newObj = Object.assign({}, oldObj ); >newObj { a: 1, b: { c: 2} } oldObj.a = 10; > oldObj // { a: 10, b: { c: 2} } >newObj // { a: 1, b: { c: 2} } newObj.a = 20; >oldObj // { a: 10, b: { c: 2} } > newObj // { a: 20, b: { c: 2} } newObj.b.c = 30; > oldObj // { a: 10, b: { c: 30} } > newObj // { a: 20, b: { c: 30} }
To solve this problem, we have deep copy, check below. Thanks to my colleague, Mitchell @LikelyMitch, for simplifying this for me. Deep copy example:
const oldObj = { id: 1, foo: { bar: 8 } }; > const newObj = JSON.parse(JSON.stringify(oldObj)); > newObj.foo.bar = 10 // 10 > oldObj.foo.bar; // 8
What’s Next
I will update the link here of my upcoming post. There we will learn how users can access your application even when they have low network connectivity or even no network. I will share how your web application can load within 3 seconds (which is a performance benchmark criterion for a web application). I will also discuss how your web application can be used on a mobile device, like a hybrid mobile app with an app icon. So stay tuned, there’s more to come.
To gain a good understanding of ES6, I would recommend reading the first part of this topic.
I hope this was a good read. Please share your feedback & suggestions in the comments section.
Happy Coding!
Nikhil Kumar
Related Posts
-
Quick Practical Guide for ES6
In this post we are going to examine what new features arrived in ES6 and…
-
ES6 Import Statement Without Relative Paths Using Webpack
The ES2015 module system is probably familiar to you by now. If you’ve used it,…