There have been many updates to the modern JavaScript language over the last few years, and you must understand some of the new features if you’re trying to learn React.
React encourages software developers to use modern JavaScript, so this post gives you an overview of the newest features of modern JavaScript.
So today, let’s look at some of these concepts which every JS dev must be aware of.
Let’s get started and dive into the things you need to know about JS.

ES5 and ES6
The oversight of the JavaScript language specification is in the hands of ECMA, a non-profit entity, which has standardized this language as ECMAScript.
The terms “ES5” and “ES6” are often heard in reference to the 5th and 6th editions of the ECMAScript standard, unveiled in 2009 and 2015 respectively. ES5 serves as a foundational implementation with extensive support across various platforms, while ES6 heralds substantial enhancements over ES5, retaining backward compatibility. Following the launch of ES6, ECMA has embarked on yearly updates to the standard, progressively modernizing the language. In numerous settings, the label “ES6” broadly covers all advancements made post-ES5, not solely the ES6 specification.
Web browsers face a challenge in keeping up with such a swiftly evolving language and, in reality, they fall short. Features introduced in ES6 and subsequent updates aren’t universally supported across all browsers. To sidestep the risk of code failure due to unimplemented language features, modern JavaScript frameworks utilize a method known as transpiling. This technique transforms modern JavaScript code into its ES5 equivalent, ensuring functional consistency across all platforms. Thanks to transpiling, JavaScript developers are alleviated from the concern over varying levels of browser support for different JavaScript language features.
Modules (Import/Export)
Modern JavaScript allows developers to work with modules, making code organization and reuse much easier.
The import
and export
statements are used to handle modularization.
In the era of traditional JavaScript applications tailored for browsers, the concept of “importing” functions or objects from other modules might have seemed foreign. The practice was straightforward – you just embedded <script>
tags to load the required dependencies, which then became available in the global scope, accessible to any JavaScript code operating within the current page’s context.
Transitioning to the modern landscape, contemporary JavaScript front-end frameworks have ushered in a more organized and rational dependency model, thanks to the integration of advanced tooling. This modern model thrives on the principles of imports and exports.
Now, let’s expand on this:
Import/Export in Modern JavaScript:
The import/export syntax is a hallmark of modern JavaScript, facilitating a modular programming approach. Here are some additional insights:
Modular Code: The import/export syntax enables developers to organize code into manageable modules, each with a specific purpose. This modularity promotes code reuse, maintainability, and testing.
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from './math.js';
console.log(add(2, 3)); // Output: 5
Named and Default Exports: JavaScript modules can have named exports (several per module) and a default export (one per module).
// utils.js
export function multiply(a, b) {
return a * b;
}
export default function square(x) {
return x * x;
}
// app.js
import square, { multiply } from './utils.js';
console.log(square(4)); // Output: 16
console.log(multiply(2, 3)); // Output: 6
Scope Management: Unlike the old method where dependencies were loaded into the global scope, the import/export syntax encapsulates scope within modules, preventing global namespace pollution.
Static Analysis and Tree Shaking: With import/export, tools can perform static analysis to determine which parts of modules are used. This enables tree shaking, a process that removes dead code, optimizing the application’s size.
Dynamic Imports: Besides static imports, modern JavaScript supports dynamic imports, allowing developers to load modules on demand, further optimizing performance.
// Dynamic import
import('./module.js')
.then(module => {
module.function();
});
Variables and Constants
In older versions of JavaScript, declaring variables with the var
keyword was a bit messy due to scoping issues. However, starting from ES6, Modern JavaScript introduced the let
and const
keywords making variable declaration much clearer and more predictable. The let
keyword is now used for variables that may change over time, while const
is used for variables that remain constant. Although var
was common in older code, it’s better to use let
now because it helps avoid some of the scoping problems that var
has.
The let
and const
keywords provide block-scoped variable declarations, making code more readable and maintainable.
Prior to ES6, JavaScript relied on the var
keyword, which only allowed for function and global scope. There was no scope at the block level. Modern JavaScript added block scoping with the addition of let
and const
.
To define a variable, simply prefix it with the let
keyword or const
keyword:
let a;
const a = someValue;
You can also declare a variable and give it an initial value simultaneously in Modern JavaScript.
Here’s how you can do it using let
, and const
:
let a = 10;
const a = 10;
If no initial value is specified, the variable is given a value as undefined
.
Undefined Initialization: When you declare a variable without assigning a value to it, JavaScript initializes it with the special value undefined
.
let a;
console.log(a); // Output: undefined
In modern JavaScript, using const
is a good practice when you have a variable whose value should not change over time. It helps make your code more readable and predictable, as developers can easily see which variables are meant to be constant.
const MAX_USERS = 100;
console.log(MAX_USERS); // Output: 100
MAX_USERS = 101; // Error: Assignment to constant variable.
In this example:
- We declare a constant named
MAX_USERS
and initialize it with a value of100
. - We then log the value of
MAX_USERS
to the console, which outputs100
. - Finally, we attempt to reassign a new value
101
toMAX_USERS
, which results in an error becauseMAX_USERS
is a constant and cannot be reassigned.
But, if you assign a mutable object (like an array or an object) to a constant, you can still modify the content of the object, but you cannot reassign a new object to the constant.
Here’s an example:
const userProfiles = [];
console.log(userProfiles); // Output: []
// You can push items into the array
userProfiles.push({ name: 'John Doe', age: 25 });
console.log(userProfiles); // Output: [{ name: 'John Doe', age: 25 }]
// You can also modify the objects within the array
userProfiles[0].age = 26;
console.log(userProfiles); // Output: [{ name: 'John Doe', age: 26 }]
// However, you cannot reassign a new array or object to the constant
userProfiles = [{ name: 'Jane Doe', age: 24 }]; // Error: Assignment to constant variable.
In this example:
- A constant
userProfiles
is initialized as an empty array. - We then push an object into the
userProfiles
array, and modify the object’s property within the array. - Finally, we attempt to reassign a new array to
userProfiles
, which results in an error sinceuserProfiles
is a constant and cannot be reassigned.
When you create a constant with const
, what you’re really doing is creating a constant reference to a value, not a constant value itself. This means that the constant userProfiles
will always refer to the same object, but the object itself can change.
It may be confusing but let me try me explain this in more simple layman’s words.
Here’s a simplified analogy:
Imagine userProfiles
as a box and const
as a label with a name on it. Once you stick the label on the box, the label’s name (in this case, userProfiles
) always points to that box and can’t be moved to another box. However, you can still open the box and change what’s inside of it. So, in JavaScript, you can change the content of the object that userProfiles
refers to (like adding or modifying properties), but you can’t change which object userProfiles
refers to.
In technical terms, objects and arrays in JavaScript are mutable, and when you perform actions like pushing items to an array or modifying object properties, you’re not changing the reference to the object or array—you’re changing the content of the object or array itself. Hence, it’s allowed even when the array or object is assigned to a constant.
Wrap up of let and const
- The keywords
let
andconst
introduce block scoping in JavaScript. - Declaring a variable with
let
prevents us from re-defining or re-declaring anotherlet
variable with the same name within the same scope (function or block scope), though it allows us to re-assign a new value to it. - Declaring a variable with
const
disallows re-defining or re-declaring anotherconst
variable with the same name within the same scope (function or block scope). However, if the variable is of a reference type like an array or object, we can modify the values stored in that variable.
Explore more about let
and const
in the JavaScript reference documentation. It offers detailed information that will enhance your understanding and coding skills.
Equality and Inequality Comparisons
Modern JavaScript have introduced new comparison operators ===
and !==
, providing a more predictable way to compare values.
In JavaScript, there are two primary types of comparisons: equality and inequality. These comparisons can be either strict or type-converting.
1. Equality Comparisons:
- Strict Equality (===): This checks whether two values are equal in type and value without attempting to convert the types.
3 === 3; // true
'3' === 3; // false
- Loose Equality (==): This checks whether two values are equal in value, and it attempts to convert the types if they are not.
3 == 3; // true
'3' == 3; // true
2. Inequality Comparisons:
- Strict Inequality (!==): This checks whether two values are not equal in type or value without attempting to convert the types.
3 !== 3; // false
'3' !== 3; // true
Loose Inequality (!=): This checks whether two values are not equal in value, and it attempts to convert the types if they are not.
3 != 3; // false
'3' != 3; // false
It’s advisable to use the newer operators ===
and !==
for all equality and inequality comparisons. This practice ensures clearer and more reliable comparison outcomes, making your code easier to read and debug.
Additional Considerations:
- Besides these, Modern JavaScript also supports relational comparisons (<, >, <=, >=) which are useful for comparing numeric values.
- When comparing objects, arrays, or functions, JavaScript compares the references, not the actual content.
Recap:
Understanding the nuances between strict and loose comparisons, and when to use each, is crucial for writing accurate and bug-free code. By adhering to best practices, like preferring strict comparisons, you set a solid foundation for robust JavaScript code.
Refer to the Strict equality and Strict inequality operators section in the JavaScript reference documentation for further insights.
Arrow Functions
Arrow functions offer a shorter syntax for writing function expressions and automatically bind this
to the surrounding code’s context.
const myFunction = (a, b) => a + b;
The expression const myFunction = (a, b) => a + b;
is a concise way to define a function in JavaScript using the arrow function syntax introduced in ES6.
Let’s break down the expression:
const myFunction
:- This part declares a constant named
myFunction
. In JavaScript, functions act as first-class objects, allowing assignment to variables, passage as arguments, and return from other functions.
- This part declares a constant named
= (a, b) =>
:- This part is the arrow function syntax. The parameters
a
andb
are enclosed in parentheses, followed by the arrow=>
. This syntax is a shorter alternative to the traditional function expression.
- This part is the arrow function syntax. The parameters
a + b
:- This is the body of the arrow function. In this concise form, the expression
a + b
is automatically returned. This is equivalent to{ return a + b; }
in a traditional function expression.
- This is the body of the arrow function. In this concise form, the expression
Putting it together, const myFunction = (a, b) => a + b;
declares a constant named myFunction
that references an arrow function. This function accepts two parameters, a
and b
, and returns the sum of their values.
How you can use this function:
You can now use myFunction
to add two numbers together.
const result = myFunction(5, 3);
console.log(result); // Output: 8
Benefits of Arrow Functions:
- Conciseness: Arrow functions are more concise than traditional function expressions.
- Lexical
this
: Arrow functions capture thethis
value of the enclosing context, which can be beneficial in certain scenarios.
Okay, before going ahead let’s do a comparison with traditional function expression.
For comparison, here’s how the same function would be written using a traditional function expression:
const myFunction = function(a, b) {
return a + b;
};
Arrow functions provide a modern and concise way to define functions in JavaScript, making your code more readable and maintainable.
So, should I always use Arrow Functions?
While arrow functions are a great addition to modern JavaScript, they are not always the best choice. Let’s see some of the issues which we can face while using them.
No Named Functions:
- Arrow functions are anonymous, which can make debugging more difficult. Named functions provide better stack traces in error messages.
function add(a, b) { // Named function
return a + b;
}
No this
Binding:
- Arrow functions capture the
this
value from their surrounding context, which can be problematic in object-oriented programming or when working with event handlers
const obj = {
value: 10,
getValue: function() { // Traditional function to access object's properties
return this.value;
}
};
const obj = {
value: 'hello',
createArrowFunction: function() {
return () => console.log(this.value);
},
createRegularFunction: function() {
return function() { console.log(this.value); };
}
};
const arrowFunc = obj.createArrowFunction();
arrowFunc(); // Output: 'hello'
const regularFunc = obj.createRegularFunction();
regularFunc(); // Output: undefined (or throws a TypeError in strict mode)
Not Suitable for Object Methods:
- Arrow functions are not ideal for methods on objects since they don’t have their own
this
context.
const obj = {
value: 10,
getValue() { // Short method syntax
return this.value;
}
};
Less Readable in Complex Scenarios:
- In complex scenarios, or when the function body has multiple statements, arrow functions can make code less readable.
const complexFunction = (a, b) => { // Might be less readable
const c = a * b;
const d = c + a;
return d + b;
};
Not Suitable for Constructors:
- Arrow functions cannot be used as constructors. Traditional functions can be used to create new objects using the
new
keyword.
const ArrowFunction = () => {};
// Attempting to use ArrowFunction as a constructor
const instance = new ArrowFunction(); // TypeError: ArrowFunction is not a constructor
No Access to new.target
:
- The
new.target
meta-property helps identify if a constructor was called using thenew
keyword. Arrow functions do not have access tonew.target
since they can’t be used as constructors.
function RegularFunction() {
console.log(new.target);
}
const arrowFunction = () => console.log(new.target);
new RegularFunction(); // Output: function RegularFunction()
arrowFunction(); // Output: undefined
No Generator Functions:
- Arrow functions cannot act as generator functions; hence, they can’t use the
yield
keyword within their body. Generator functions are defined using thefunction*
syntax and are used to work with iterators and generators.
// Attempting to create a generator function using arrow syntax
const genFunc = *() => {
yield 1;
}; // SyntaxError: Unexpected token '*'
// Correct way to create a generator function
function* correctGenFunc() {
yield 1;
}
These examples demonstrate the different behaviours and limitations of arrow functions compared to traditional function expressions, particularly in the context of this
binding, constructor usage, accessing new.target
, and creating generator functions.
Understanding these nuances is crucial for writing robust and bug-free JavaScript code. It’s always about choosing the right tool for the job.
Refer to the Arrow function expressions section in the JavaScript reference documentation for further insights.
Template Literals
Template literals allow for easier string interpolation and multi-line strings.
Often, there’s a need to construct a string that incorporates both fixed text and variables.
ES6 uses template literals for this:
const name = 'Twitter';
console.log(`Hello, ${name}!`); // Outputs: Hello, Twitter
Template literals are a new kind of string literals that allow for string formatting and multi-line strings.
They are enclosed by backticks (“) instead of single or double quotes.
const greeting = `Hello, World!`;
Multi-Line Strings:
- You can create multi-line strings without needing to use the newline character (
\n
) or string concatenation.
const poem = `Roses are red,
Violets are blue,
Sugar is sweet,
And so are you.`;
Expression Interpolation:
- You can embed expressions within template literals using
${expression}
syntax.
const name = 'Ankur';
const greeting = `Hello, ${name}!`;
console.log(greeting); // Output: Hello, Ankur!
Tagged Templates:
- Template literals can be tagged, allowing you to parse template literals with a function.
function highlight(strings, ...values) {
// Some processing...
}
const age = 25;
const highlighted = highlight`I am ${age} years old.`;
String Interpolation:
String interpolation is a feature provided by template literals, allowing you to insert values into strings in a more readable and concise manner.
const age = 36;
const text = `I am ${age} years old.`;
console.log(text); // Output: I am 36 years old.
In this example, the value of the variable age
is inserted into the string at the location of ${age}
.
Advantages:
- Readability: Template literals and string interpolation make the code more readable and easier to understand.
- Ease of Use: They simplify the syntax for complex string manipulations and formatting.
- Flexibility: Tagged templates provide a way to customize string parsing and processing.
These features significantly enhance the ease and efficiency of string handling in JavaScript, making code more clean and maintainable.
Refer to the Template literals (Template strings) section in the JavaScript reference documentation for further insights.
Ready to continue? Start reading A Complete Guide on How to Create a Project with Vite, Component Lifecycles, Props, Styling & More
If you like this blog and want to read more about ReactJS and JavaScript then start reading some of recent articles.
- Why Learn React
- The Good Parts of React
- Common JavaScript Questions I Used to Ask in Interview
- A Simple and Effective way to Learn & Practice JavaScript.
- How to Learn ReactJS in 2024
- A Complete In-Depth Look at Hooks, Context API and Redux: React State Management

Jack of all trades, master of my life.