Janishar Ali
•
29 May 2025
JavaScript has evolved significantly over the years. ES6 (2015) was a major milestone which introduced important features that transformed the way we write code in JavaScript. Since then, many successive releases have continued to add new features. In this article I will share the features which I use most frequently in both my frontend and backend projects.
It has replaced the string concatenation using + operator. It is also very convenient for composing dynamic text as well as defining multiline strings.
`some text ${variable1} ${variable2} some more text`
Example:
const user = {
firstName: 'Janishar',
lastName: 'Ali',
}
Without String Template
const message = 'Thank you ' + user.firstName + ' ' + user.lastName + ' for reading this article';
console.log(message);
// console log
// Thank you Janishar Ali for reading this article
With String Template
const message = `Thank you ${user.firstName} ${user.lastName} for reading this article`;
console.log(message);
// console log
// Thank you Janishar Ali for reading this article
Multiline Text
const message = `// source code
function fn(value){
return value++;
}
`;
console.log(message);
/*
console log
// source code
function fn(value){
return value++;
}
*/
var
was the original method to declare variables but it had some side effects.
[1] var
could be reassigned
var message = "Hello";
var message = "World!";
console.log(message);
// console log
// World!
[2] var
was not block scoped but function scoped
function fn() {
for (var i = 0; i < 2; i++) {
var counter = i;
console.log(i);
}
console.log("counter", counter);
}
fn();
// console log
// 0
// 1
// counter 1
Here counter still exists after the for loop
Now, we can declare variables using `let` keyword.
[1] let
will allow the values to be assigned but not redeclare.
let message = "Hello";
let message = "World!";
console.log(message);
// console error
// Uncaught SyntaxError: Identifier 'message' has already been declared
let message = "Hello";
message = "World!"; // allows reassignment
console.log(message); // World!
[2] let
is block scoped and is not accessible outside the block, unlike var
.
function fn() {
for (let i = 0; i < 2; i++) {
let counter = i;
console.log(i);
}
console.log("counter", counter);
}
fn();
// console error
// Uncaught ReferenceError: counter is not defined
// at fn (<anonymous>:6:26)
// at <anonymous>:9:1
Now, we also have const
keyword for declaring variables which once declared cannot be reassigned. Similar to let
it is also block scoped. It is perhaps the most used keyword for me. The modern JS development favors immutability - meaning we don't change the values but rather create new ones.
const message = "Hello";
message = "World!";
console.log(message);
// console error
// Uncaught TypeError: Assignment to constant variable.
[1] Arrays
We can also assign the values of an array into variables in a single statement.
const names = ["Janishar", "Ali"];
const [janishar, ali] = names;
console.log(janishar, ali);
// console log
// Janishar Ali
It is very useful in React applications.
const [expanded, setExpanded] = useState(true);
[2] Objects
We can create the variables from the object keys in a single statement. The variable names must match the keys from the object.
const user = {
firstName: 'Janishar',
lastName: 'Ali',
address: 'fifocode',
}
const {firstName, lastName} = user;
console.log(firstName, lastName);
// console log
// Janishar Ali
This is also very useful in React applications.
const { isLoggedIn, isForcedLogout, askLogin, user } = state.auth;
[3] Spread object with rest
We can also get few properties of an object and combine rest into another object.
const user = {
firstName: "Janishar",
lastName: "Ali",
address: "fifocode",
};
const { firstName, ...rest } = user;
console.log(rest);
// console log
// {lastName: 'Ali', address: 'fifocode'}
[4] Object in a function call
We can also destructure an object in a function's parameter.
const user = {
firstName: 'Janishar',
lastName: 'Ali',
address: 'fifocode',
}
function fn({firstName, lastName}){
console.log(firstName, lastName);
}
fn(user);
// console log
// Janishar Ali
This is one of the most used features in React applications to receive props in a component.
export default function ArticlePage({ article, slug }) {
...
}
We can specify the default values to function parameters. These default values will be used when arguments are not passed.
function fn(name = "Name", rollNum = 0) {
console.log(name, rollNum);
}
fn();
// console log
// Name 0
fn('Janishar Ali');
// console log
// Janishar Ali 0
fn('Janishar Ali', 100);
// console log
// Janishar Ali 100
We can pass a list of arguments to a function in a very convenient way.
function fn(type, ...payload){
console.log(type);
console.log(payload);
}
fn('Message', 'Janishar', 'Ali')
// console log
// Message
// ['Janishar', 'Ali']
[1] Arrays
We have a very convenient way to copy all the array elements into another array or create a new array with values from existing arrays. This is very useful for writing immutable code, especially when updating React state.
const fruits = ['Apple', 'Mango', 'Orange'];
const vegetables = ['Potato', 'Spinach'];
const basket = [...fruits, ...vegetables];
console.log(basket);
// console log
// ['Apple', 'Mango', 'Orange', 'Potato', 'Spinach']
[2] Object
Similar to an array, objects can also spread into another object. This is a very important feature to copy an object and alter only the selected fields.
const state = {
fullscreen: false,
isLoggedIn: false,
}
const newState = {
...state,
isLoggedIn: true,
}
console.log(newState);
// console log
// {fullscreen: false, isLoggedIn: true}
This feature is extremely important in React applications for mutating a component state.
const defaultFormState = {
email: "",
password: "",
isEmailError: false,
emailError: "",
isPasswordError: false,
passwordError: "",
};
const [credentials, setCredentials] = useState(defaultFormState);
const handleCredentialChange = (name) => (e) => {
e.preventDefault();
setCredentials({
...credentials,
isEmailError: false,
emailError: "",
isPasswordError: false,
passwordError: "",
[name]: e.target.value,
});
};
We can use =>
operator to define a function as an alternative to function keyword
. It's quite convenient for a functional programming.
[1] Normal Function vs Arrow Function
// normal function
function fn(type, message) {
console.log(type, message);
}
fn("transaction", "successful");
// console.log
// transaction successful
// arrow function
const fn = (type, message) => {
console.log(type, message)
}
fn('transaction', 'successful');
// console.log
// transaction successful
[2] Callback Functions
function callback(message) {
console.log(message);
}
// normal function
function fn(payload, callback) {
callback(`${payload} received`);
}
fn("payment", callback);
// console log
// payment received
function fn(payload, callback) {
callback(`${payload} received`);
}
// callback as an arrow function
fn("payment", (message) => {
console.log(message);
});
// console log
// payment received
We can conveniently use of
operator for an array iteration in place of index based iteration.
Normal Iteration
const fruits = ['Apple', 'Mango', 'Orange'];
for(let i = 0; i < fruits.length; i++){
console.log(fruits[i]);
}
// console log
// Apple
// Mango
// Orange
Iteration with of operator
const fruits = ['Apple', 'Mango', 'Orange'];
for(const fruit of fruits){
console.log(fruit);
}
// console log
// Apple
// Mango
// Orange
We have many utility functions associated with an Array.
[1] map
Iterates over an array and transform the items using a callback function. It produces a new array with transformed values. This is one of the most used features in React application.
const items = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
];
const names = items.map((item) => item.name);
console.log(names);
// ['one', 'two']
[2] find
Iterates over an array and returns the item matches using a callback function or undefined
if no matching item is found.
const items = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
];
const found = items.find((item) => item.id == 1);
console.log(found);
// {id: 1, name: 'one'}
const notfound = items.find((item) => item.id == 3);
console.log(notfound);
// undefined
[3] filter
Iterates over an array and returns only the items which satisfy the conditions specified by the callback function.
const items = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" },
];
const filtered = items.filter((item) => item.id > 1);
console.log(filtered);
// (2) [{…}, {…}]
// 0: {id: 2, name: 'two'}
// 1: {id: 3, name: 'three'}
// length: 2
It is a special type of function that can pause execution and resume later, allowing us to generate values on the fly. It reduces the boiler plate to achieve a given task where state needs to be maintained.
Pagination Example
function* paginate(array, size) {
for (let i = 0; i < array.length; i += size) {
yield array.slice(i, i + size);
}
}
const pages = paginate([1, 2, 3, 4, 5, 6, 7], 3);
console.log(pages.next().value); // [1, 2, 3]
console.log(pages.next().value); // [4, 5, 6]
console.log(pages.next().value); // [7]
[1] Class and Object
We can define an object's structure using class
keyword. New object is created using new
keyword, which calls the constructor function of the class. constructor
initialises the member variables. We can access the reference to the current object using this
keyword in any member functions. The class can also define methods, which are functions bound with the object. In this Example we create an Animal
class and then create a cat
and a dog
object from that class.
class Animal {
// constructor is a special function which is called when the new keyword is used
constructor(name, sound) {
// this is a special keyword which refers to the current object
this.name = name;
this.sound = sound;
}
// member function is called methods
speak() {
// methods can access the current object using this keyword
console.log(this.name, this.sound);
}
}
const cat = new Animal("Cat", "Meow!");
cat.speak(); // Cat Meow!
const dog = new Animal("Dog", "Barks!");
dog.speak(); // Dog Barks!
[2] Inheritance
Often, we want to add properties on top of an existing class. We can extend a class
using extends
keyword. When we do this, we need to call the parent class's constructor using super
function. We can also add more member variables to the child class. The methods of the super class is also accessible to the child class.
In the example below, Cat
class inherits Animal
class.
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
console.log(this.name, this.sound);
}
}
class Cat extends Animal {
constructor(breed) {
// super is a special function to call the parent's constructor
super("Cat", "Meow!");
// the child class can add its own properties
this.breed = breed;
}
showBreed() {
console.log(this.breed);
}
}
const cat = new Cat("Persian");
cat.speak(); // Cat Meow!
cat.showBreed(); // Persian
[3] Static Function and variables
The JS does not support static member variables directly but we can check its existence and only set it once in the constructor. We can define static members to a class which can be accessed using class name rather than an instance.
class Animal {
constructor(name, sound) {
// sets the static variable
if (Animal.count == undefined) Animal.count = 0;
Animal.count++;
this.name = name;
this.sound = sound;
}
speak() {
console.log(this.name, this.sound);
}
// defines a static function which is defined at the class level
static population() {
console.log(Animal.count);
}
}
const cat = new Animal("Cat", "Meow!");
Animal.population(); // 1
const dog = new Animal("Dog", "Barks!");
Animal.population(); // 2
We can export variables and functions from one file and import them inside another using export
and import
keywords.
Note: This is only supported withtype="module"
in scripts inside a html page. In NodeJs we can enable module using"type": "module"
inpackage.json
// module1.js
export const module1 = {
name: "Module 1",
};
// module2.js
import { module1 } from "./module1";
We can define a collection with unique elements using Set
.
const s = new Set();
s.add("Apple");
s.add("Mango");
s.add("Apple"); // duplicate
console.log(s);
// console log
// Set(2) {'Apple', 'Mango'}
We can define a key-value pairs using Map
and access the value by key.
const m = new Map();
m.set('key1', 'value1');
m.set('key2', 'value2');
console.log(m.get('key1'));
// console log
// value1
We can access the members properties only when it is defined using ?.
operator. It helps avoid many if-else statements.
Without chaining
const state = {};
console.log(state.auth.token);
// Uncaught TypeError: Cannot read properties of undefined (reading 'token')
With Chaining
const state = {}
console.log(state.auth?.token);
// undefined
Returns a default value if the access variable is undefined
.
const state = {};
const token = state.auth?.token ?? "Not Present";
console.log(token);
// Not Present
const state = {
auth: {
token: "access-token",
},
};
const token = state.auth?.token ?? "Not Present";
console.log(token);
// access-token
Promise is a powerful way to handle asynchronous operations. Let's see how to create a promise object. A Promise object takes a callback function with two special parameters: resolve
and reject
.
Note: We use async/wait in place of Promises now. But let's first discuss the original implementation before going to async/await.
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received!");
}, 3000);
});
}
fetchData().then((data) => console.log(data));
// Promise {<pending>}
// Data received!
In this above example the promise was suspended for 3 sec. After 3 sec the timer ticked and the resolve
function is called. When the resolve
function is called then the then
function defined callback is called, which logs the data
A promise has 3 states:
resolve()
is calledreject()
is calledWhen we call reject then an error is thrown. We can catch this error is a catch
function
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 3000);
});
}
fetchData().then(data => console.log(data));
// Uncaught (in promise) Error
Let's catch this error
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 3000);
});
}
fetchData()
.then(data => console.log(data))
.catch(err => console.log(err));
// Error, but the program did not crash
Finally block run after the promise is fulfilled
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 3000);
});
}
fetchData()
.then(data => console.log(data))
.finally(() => console.log('Done!'))
// Done!
// Uncaught (in promise) Error
We can now save ourselves from writing callbacks for resolve
and reject
promises manually. Also, we no longer required to use then
block to receive the results.
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchData() {
await wait(3000);
return "Data received!";
}
const data = await fetchData();
console.log(data);
// Data received!
We define a function as asynchronous using async
keyword. and we wait for the async
function to return by calling await
on the function call.
Here, you must be wondering what iswait
function and we still usingPromise
object there. This wait is a utility function that helps to suspend the execution on the lineawait
inside the fetchData function, so that we can demonstrate that the execution is indeed suspended when we callawait fetchData()
Catch Error from an async function
We have to use try/catch block to catch the error from the fetchData function.
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchData() {
await wait(3000);
throw new Error();
}
try {
const data = await fetchData();
console.log(data);
} catch (err) {
console.log(err);
}
// Error
// at fetchData (<anonymous>:7:9)
// at async <anonymous>:11:14
async/await is very powerful in handling IO operations, like network calls.
These were the most important JavaScript modern features that I use frequently in my code base. I hope this article provided a good overview. I will write on the use-cases where I use them in my future articles.