Modern JavaScript (ES6 and beyond) transformed how developers write code for the web. Released in 2015, ES6 introduced cleaner syntax, better ways to handle asynchronous operations, and tools that make applications more maintainable. Today, ES6+ features form the foundation of professional web development. Understanding them is essential for building interactive websites, handling data from APIs, and creating responsive user interfaces.
This guide explains the core concepts of modern JavaScript with a focus on practical use in web development. It covers promises, async/await, closures, and DOM manipulation through clear explanations and step by step examples that beginners can follow and adapt.
How to Get Started with Modern JavaScript Features?
Modern JavaScript begins with a few foundational improvements over older versions of the language. These features appear in almost every contemporary web project.
Start by using let and const instead of var. const declares values that should not be reassigned, while let allows reassignment within a block. This reduces common bugs caused by variable hoisting and scope leakage.
JavaScript
const apiUrl = "https://api.example.com/data";
let userCount = 0;
userCount = 5; // allowed
// apiUrl = "new-url"; // error
Arrow functions provide a shorter way to write functions and automatically bind the surrounding this context, which is especially useful in callbacks and event handlers:
JavaScript
const greet = (name) => `Hello, ${name}!`;
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
Template literals (using backticks) make string construction readable and support multi line text and embedded expressions:
JavaScript
const name = "Alex";
const message = `Welcome, ${name}!
Your account is ready.`;
Destructuring extracts values from arrays or objects in a single line:
JavaScript
const user = { id: 1, name: "Sam", role: "admin" };
const { name, role } = user;
These features improve readability and reduce boilerplate. They appear constantly when working with data, APIs, and the browser environment.

How to Use Promises and Async/Await for Asynchronous Code?
Web applications frequently wait for network requests, timers, or user actions. Promises and async/await are the modern tools for managing these operations without nested callbacks.
A Promise represents a value that may be available now, later, or never. It has three states: pending, fulfilled, or rejected. You create a Promise with the Promise constructor or receive one from methods such as fetch.
JavaScript
const fetchUser = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve({ id: 1, name: "Jordan" });
} else {
reject(new Error("Failed to load user"));
}
}, 1000);
});
};
fetchUser()
.then(user => console.log(user.name))
.catch(error => console.error(error.message));
Async/await builds on Promises and makes asynchronous code look and behave more like synchronous code. Mark a function with async and use await inside it to pause execution until the Promise settles.
JavaScript
async function loadUser() {
try {
const user = await fetchUser();
console.log(user.name);
return user;
} catch (error) {
console.error("Error:", error.message);
}
}
loadUser();
In web development you will most often combine these tools with the Fetch API:
JavaScript
async function getPosts() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch failed:", error);
}
}
Always handle errors with try/catch when using async/await, and prefer this style over long chains of .then() for clarity.
You can learn what is a freeCodeCamp Responsive Web Design Certification using beginner’s guide.
How to Work with Closures in Everyday Code?
A closure is created when an inner function remembers and can access variables from its outer function even after the outer function has finished running. Closures are fundamental to modern JavaScript patterns such as data privacy, event handlers, and function factories.
JavaScript
function createCounter() {
let count = 0; // private variable
return {
increment() {
count += 1;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
The inner methods continue to access count long after createCounter has returned. This technique is useful for encapsulating state without exposing it globally.
Closures also appear naturally in event listeners and timers:
JavaScript
function setupButton(id) {
let clicks = 0;
const button = document.getElementById(id);
button.addEventListener("click", () => {
clicks += 1;
console.log(`Button clicked ${clicks} times`);
});
}
Each call to setupButton creates its own private clicks variable that the event handler closes over.
How to Perform DOM Manipulation with Modern JavaScript?
The Document Object Model (DOM) is the browser’s representation of the page. Modern JavaScript provides clean, efficient ways to select elements, update content, and respond to user actions.
Select elements with querySelector and querySelectorAll:
JavaScript
const title = document.querySelector("h1");
const items = document.querySelectorAll(".list-item");
Update content and attributes:
JavaScript
title.textContent = "Updated Heading";
title.classList.add("highlight");
const link = document.querySelector("a");
link.setAttribute("href", "https://example.com");
Create and insert new elements:
JavaScript
const list = document.querySelector("ul");
const newItem = document.createElement("li");
newItem.textContent = "New item";
list.appendChild(newItem);
Attach event listeners using arrow functions for concise handlers:
JavaScript
const button = document.querySelector("#submit");
button.addEventListener("click", async (event) => {
event.preventDefault();
const data = await getPosts();
// update the page with the data
const container = document.querySelector("#results");
container.innerHTML = data
.slice(0, 5)
.map(post => `<p>${post.title}</p>`)
.join("");
});
Prefer textContent over innerHTML when inserting plain text to avoid security risks. Use classList methods (add, remove, toggle) instead of manually editing the className string.
Putting It All Together in a Small Web Project
Combine the techniques above to build a simple interactive feature. The following example fetches posts, stores a private counter with a closure, and updates the page:
JavaScript
function createPostLoader(containerId) {
let loadCount = 0;
const container = document.getElementById(containerId);
async function load() {
try {
loadCount += 1;
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await response.json();
container.innerHTML = `
<p>Loaded ${loadCount} time(s)</p>
${posts.slice(0, 3).map(p => `<article><h3>${p.title}</h3></article>`).join("")}
`;
} catch (error) {
container.textContent = "Failed to load posts.";
}
}
return { load };
}
const loader = createPostLoader("app");
document.querySelector("#refresh").addEventListener("click", () => loader.load());
This pattern demonstrates promises, async/await, closures, and DOM updates working together, exactly the skills needed for real web development.
Modern JavaScript rewards consistent practice. Begin with small scripts that use let/const and arrow functions, then add asynchronous data fetching, and finally incorporate closures and DOM updates. Over time these tools become second nature and enable you to build faster, cleaner, and more reliable web applications.
