Modern JavaScript (ES6+)
Meet Ananya. π©βπ»
She has just joined a small startup in Bengaluru that is building a food delivery app called "DesiEats" π. On her first day, her team lead Rahul opens an old file from 2012 and sighs:
var self = this;
var name = user && user.profile && user.profile.name ? user.profile.name : "Guest";
var items = order.items.concat([]);
function Restaurant(name) { this.name = name; }
Restaurant.prototype.open = function () { /* ... */ };
"This code works," says Rahul, "but nobody enjoys reading it. Let's rebuild DesiEats with modern JavaScript."
In this lesson, we'll follow Ananya as she rebuilds the app, step by step. Along the way, you'll learn the features that make JavaScript cleaner, safer, and more fun to write. π
What Does "Modern JavaScript" Mean?
JavaScript is updated every year. A group called TC39 decides which new features get added to the official rulebook, ECMAScript.
The biggest update came in 2015, called ES6 (or ES2015). Since then, a smaller update has arrived every year: ES2016, ES2017, and so on.
π Modern JavaScript (often called ES6+) means all the features added from 2015 onwards.
Here's our roadmap for today:
| # | Feature | What it helps with |
|---|---|---|
| 1 | let, const, arrow functions | Safer variables, shorter functions |
| 2 | ES modules | Splitting code into files |
| 3 | Classes and private fields | Blueprints for objects |
| 4 | Constructors and static methods | Setting up objects, class-level helpers |
| 5 | Inheritance and composition | Reusing code |
| 6 | Destructuring, spread, and rest | Unpacking and copying data |
| 7 | Optional chaining and nullish coalescing | Safe reading and smart defaults |
| 8 | Logical assignment operators | Shorter "set if needed" code |
| 9 | Iterables and iterators | How for...of really works |
| 10 | Generators | Functions that can pause |
| 11 | Map and Set | Better collections |
| 12 | Other handy features | async/await, at(), structuredClone, and more |
Grab your chai β, and let's begin!
Chapter 1: A Quick Warm-Up (let, const, and Arrow Functions)
Before rebuilding anything, Rahul gives Ananya two house rules.
Rule 1: Use const and let, never var
constβ the variable can't be reassignedletβ the variable can change- Both live only inside their block
{ }, which avoids strange bugs thatvarused to cause
Rule 2: Use arrow functions for short, simple functions
Arrow functions are shorter, and when the body is a single expression, you can skip return and the { }.
β οΈ Remember from the Objects lesson: arrow functions don't have their own
this, so don't use them as object methods.
"Great," says Rahul. "Now let's organise the project."
Chapter 2: ES Modules (Splitting Code into Files)
The old DesiEats code lived in one giant file with 5,000 lines. π± Finding anything was like searching for your keys in a messy room.
π Definition: An ES module is a JavaScript file that can share (export) its code and use (import) code from other files. Each module has its own private scope.
Think of modules like departments in a restaurant π½οΈ. The kitchen, the billing counter, and the delivery team each do their own job. They share only what's needed with each other.
π‘ Module examples need multiple files, so they are shown as normal code blocks, not in the playground.
Named exports: sharing many things
// π utils/price.js
export const GST_RATE = 0.05;
export function addGst(amount) {
return amount + amount * GST_RATE;
}
export function formatPrice(amount) {
return `βΉ${amount.toFixed(2)}`;
}
// π app.js
import { addGst, formatPrice } from "./utils/price.js";
console.log(formatPrice(addGst(200))); // βΉ210.00
Key points:
- The names inside
{ }must match the exported names. - You only import what you need.
Default export: sharing one main thing
A file can have one default export, its "main" item.
// π models/Restaurant.js
export default class Restaurant {
constructor(name) {
this.name = name;
}
}
// π app.js
import Restaurant from "./models/Restaurant.js"; // no { }, any name works
const r = new Restaurant("Sagar Ratna");
Renaming and importing everything
import { formatPrice as rupees } from "./utils/price.js"; // rename
import * as Price from "./utils/price.js"; // import everything
console.log(rupees(99)); // βΉ99.00
console.log(Price.GST_RATE); // 0.05
Named vs default: quick comparison
| Point | Named export | Default export |
|---|---|---|
| How many per file | Many | Only one |
| Export syntax | export const x = ... | export default ... |
| Import syntax | import { x } from "..." | import x from "..." |
| Name when importing | Must match (or use as) | Any name you like |
Using modules in the browser
<script type="module" src="app.js"></script>
The type="module" part is required.
Why modules are great
- π Organised: each file has one clear job
- π Private by default: anything you don't
exportstays hidden - β»οΈ Reusable: import the same helper anywhere
- π― Clear dependencies: you can see exactly what each file uses
π‘ Fun fact: Modules automatically run in strict mode, which catches common mistakes early.
"Now each part of DesiEats has its own home," smiles Ananya. "Next: the restaurants themselves!"
Chapter 3: Classes and Private Fields
DesiEats has hundreds of restaurants. Each one has a name, a cuisine, a rating, and a menu. Creating each object by hand would be exhausting. π©
π Definition: A class is a blueprint for creating objects. Every object made from a class has the same structure, but its own data.
Think of a class like a cookie cutter πͺ. The cutter is the class. Each cookie you cut is an object (also called an instance). Same shape, different flavours!
Your first class
Key points:
- The class name uses PascalCase:
Restaurant, notrestaurant. new Restaurant(...)creates a new object from the blueprint.- Methods like
describe()are shared by every restaurant.
The problem: anyone can change anything π¬
The solution: private fields with #
π Definition: A private field starts with
#. It can only be used inside the class. Code outside the class cannot read or change it.
Think of it like a bank locker π. Only the bank's own staff (the class methods) can open it.
Trying to read wallet.#balance from outside the class is a SyntaxError. JavaScript won't even run the code.
Private methods
Methods can be private too:
π‘ Before
#existed, developers used an underscore, like_balance, to mean "please don't touch." But that was only a polite request.#is a real lock.
Public class fields
You can also declare normal (public) fields at the top of a class, without the constructor:
Chapter 4: Constructors and Static Methods
The constructor: setting up a new object
π Definition: The constructor is a special method that runs automatically when you create an object with
new. It sets up the object's starting values.
Think of it like the welcome desk ποΈ at a hotel. Every new guest passes through it once, gets a room number, and receives a key.
Key points:
- A class can have only one
constructor. - It can have default values, like
city = "Bengaluru". - It can validate input:
Static methods: belonging to the class itself
Some functions are about the class as a whole, not about one specific object.
π Definition: A static method or property belongs to the class itself, not to the objects made from it. You call it on the class name:
ClassName.method().
Think of a school π«. Each student (object) has their own name and marks. But "total number of students" belongs to the school, not to any one student.
Static factory methods
A popular use of static is a factory: a method that creates objects in a special way.
π‘ You already use static methods every day:
Math.max(),Array.isArray(),Object.keys(), andNumber.parseInt()are all static!
| Instance method | Static method | |
|---|---|---|
| Called on | An object: r1.describe() | The class: Restaurant.compare() |
Uses this for | That one object | The class itself |
| Example | wallet.pay(100) | Math.max(1, 2) |
Chapter 5: Inheritance and Composition
DesiEats grows. Now there are different kinds of restaurants: cloud kitchens, cafΓ©s, and sweet shops. They share a lot, but each has something special. How can Ananya reuse code without copying it?
There are two main ways: inheritance and composition.
Inheritance: "is a" π¨βπ©βπ§
π Definition: Inheritance lets one class (the child) get all the properties and methods of another class (the parent) using the
extendskeyword.
Think of how children inherit things from their parents, like their surname or eye colour, and still have their own unique qualities.
Key points:
extendsβ "this class is a kind of that class"super(...)β calls the parent's constructor. It must come before usingthis.super.method()β calls the parent's version of a method- Writing a method with the same name in the child is called overriding
β οΈ The problem with too much inheritance
Ananya gets excited and builds a family tree:
Restaurant
βββ DeliveryRestaurant
βββ VegDeliveryRestaurant
βββ VegDeliveryRestaurantWithDineIn
βββ ... π΅
Now a new cafΓ© wants dine-in and delivery, but it's not veg-only. Where does it fit? Nowhere! The family tree becomes rigid and confusing. This is often called the "gorilla-banana problem": you wanted a banana, but you got the gorilla holding it, and the whole jungle too. π¦π
Composition: "has a" π§©
π Definition: Composition means building an object by combining small, separate abilities, instead of inheriting from a long family tree.
Think of LEGO blocks π§±. Instead of one fixed toy, you snap together the pieces you need.
Each restaurant picks exactly the abilities it needs. No confusing family tree!
Composition with classes
You can also compose using classes, by giving an object other objects as helpers:
Inheritance vs composition
| Point | Inheritance | Composition |
|---|---|---|
| Relationship | "is a" (a CloudKitchen is a Restaurant) | "has a" (a Checkout has a PaymentService) |
| Keyword | extends | Combining objects |
| Flexibility | Rigid family tree | Mix and match π§© |
| Best for | Clear, simple, shallow hierarchies | Sharing many small abilities |
π Popular advice: "Prefer composition over inheritance." Use inheritance for simple, clear "is a" relationships, and composition when you need flexibility.
Chapter 6: Destructuring, Spread, and Rest
Every order that arrives from the DesiEats server is a big object. Ananya is tired of writing order.customer.name, order.customer.phone, order.total... again and again. π«
Object destructuring
π Definition: Destructuring lets you unpack values from objects or arrays into separate variables, in one line.
Think of opening a tiffin box π± and putting each item on its own plate.
Array destructuring
Destructuring in function parameters β
This is how most modern code is written:
Spread ...: spreading things out
Spread expands an array or object into its individual pieces.
Rest ...: collecting things together
Rest looks the same (...), but it collects many values into one.
π‘ Easy trick: On the right side of
=or in a function call,...spreads things out. On the left side of=or in function parameters,...collects them.
Chapter 7: Optional Chaining and Nullish Coalescing
One night, the DesiEats app crashes. π₯ Ananya checks the error:
TypeError: Cannot read properties of undefined (reading 'city')
A new customer hadn't added an address yet, and the code tried to read user.address.city.
Optional chaining ?.
π Definition: Optional chaining
?.lets you safely read a value deep inside an object. If any part along the way isnullorundefined, it stops and returnsundefinedinstead of crashing.
Think of a security guard π at each gate. If a gate is closed, you calmly turn back instead of crashing into it.
| Syntax | Use for |
|---|---|
obj?.prop | A property |
obj?.[key] | Bracket access or an array item |
obj.method?.() | A method that might not exist |
Nullish coalescing ??
Now Ananya wants to show "City not added" when there's no city. She first tries ||:
The problem: || treats every falsy value (0, "", false) as "missing."
π Definition: Nullish coalescing
??gives a default value only when the left side isnullorundefined. Real values like0,"", andfalseare kept.
|| vs ??
| Value on the left | value || "default" | value ?? "default" |
|---|---|---|
null | "default" | "default" |
undefined | "default" | "default" |
0 | "default" β οΈ | 0 β
|
"" | "default" β οΈ | "" β
|
false | "default" β οΈ | false β
|
π Power combo:
?.and??work beautifully together:user.address?.city ?? "Not added".
Chapter 8: Logical Assignment Operators
Ananya notices she keeps writing lines like these:
if (!settings.theme) settings.theme = "light";
if (cart.items === null || cart.items === undefined) cart.items = [];
if (user.isPremium) user.discount = 20;
Modern JavaScript has shortcuts for exactly this, called logical assignment operators.
π Definition: Logical assignment operators (
||=,&&=,??=) combine a logical check and an assignment in one step. They only assign when the condition is met.
| Operator | Meaning | Same as |
|---|---|---|
a ||= b | Assign if a is falsy | a || (a = b) |
a &&= b | Assign if a is truthy | a && (a = b) |
a ??= b | Assign if a is null or undefined | a ?? (a = b) |
??=: fill in only what's missing
||=: replace empty or falsy values
&&=: update only if it already exists
Real example: counting orders per restaurant
π‘ Which one should I use? When in doubt, pick
??=. It's the safest, because it never overwrites real values like0orfalse.
Chapter 9: Iterables and Iterators
Ananya loves for...of. It works on arrays, strings, Maps, and Sets. But one day she tries it on her own Menu object:
"Why does for...of work on arrays, but not on my object?" she wonders. Let's find out! π΅οΈββοΈ
Two important words
π Iterable: An object that can be looped over with
for...of. It has a special method called[Symbol.iterator].π Iterator: An object that hands out values one at a time, through a
next()method.
Think of a token machine ποΈ at a bank:
- The bank is the iterable. You can ask it for a token machine.
- The token machine is the iterator. Every time you press the button (
next()), it gives you the next token. - When the tokens run out, it says "done."
Seeing an iterator in action
Every array secretly has a [Symbol.iterator] method. Let's call it by hand:
Each call to next() returns an object with two properties:
valueβ the current itemdoneβtruewhen there's nothing left
This is exactly what for...of does behind the scenes! It keeps calling next() until done is true.
Making your own iterable
To make Menu work with for...of, give it a [Symbol.iterator] method that returns an iterator:
Now for...of, spread ..., and array destructuring all work with menu. π
What uses iterables?
| Feature | Example |
|---|---|
for...of | for (const x of iterable) |
| Spread | [...iterable] |
| Array destructuring | const [a, b] = iterable |
Array.from() | Array.from(iterable) |
new Set() / new Map() | new Set(iterable) |
Built-in iterables: arrays, strings, Maps, Sets, arguments, and many browser lists. Plain objects are not iterable.
π "Writing
next()by hand is a lot of work," Ananya admits. Rahul smiles: "That's why we have generators."
Chapter 10: Generators
π Definition: A generator is a special function that can pause and resume. It is written with
function*, and it usesyieldto give out values one at a time.
Think of a TV series πΊ on a streaming app. You watch one episode, then pause. When you come back, it continues from where you stopped, not from the beginning. A generator works the same way.
Your first generator
How it works:
- Calling
orderStatus()doesn't run the code. It returns a generator object (which is an iterator). - Each
next()runs the code until the nextyield, then pauses. - When there are no more
yields,donebecomestrue.
Generators work with for...of
Generators are iterable, so you can loop over them directly:
The easy way to make an iterable β¨
Remember the long Menu iterator from Chapter 9? Here's the same thing with a generator:
Just a few lines instead of a whole next() method. π
Generators with classes
Infinite generators
Because a generator pauses, it can produce values forever without freezing your computer. It only makes the next value when you ask for it.
β οΈ Never use
for...ofor spread...on an infinite generator. They would try to read forever! Usenext()or add abreak.
Taking only what you need
Sending values into a generator
next(value) can also send a value back into the generator. The value becomes the result of the paused yield.
When are generators useful?
- π Making objects iterable easily
- βΎοΈ Creating endless sequences, like IDs or pages of results
- π’ Lazy work: calculating values only when they are needed
- π§© Step-by-step processes, like order tracking or form wizards
Chapter 11: Map and Set (Better Collections)
As DesiEats grows, Ananya needs better tools to store data.
Set: a collection of unique values
π Definition: A Set stores unique values. Duplicates are ignored automatically.
Map: key-value pairs with superpowers
π Definition: A Map stores key-value pairs like an object, but any value (even an object) can be a key, it remembers the insertion order, and it has a handy
.size.
| Need | Use |
|---|---|
| A list of unique values | Set |
| Key-value pairs with any type of key, or frequent adding/removing | Map |
| Simple, fixed data like a user profile | A normal object { } |
Chapter 12: Other Handy Modern Features
Here's a quick tour of more modern features that Ananya uses every day. π
async / await: waiting without freezing
Fetching data from a server takes time. async/await lets you write "waiting" code that reads top to bottom, like normal code.
You'll learn Promises and async/await in detail in the Async JavaScript lesson.
Array.prototype.at(): easy negative indexes
structuredClone(): true deep copy
Object.hasOwn(): safe key checking
Non-mutating array methods
Numeric separators
Object.groupBy(): grouping made easy
β οΈ Some very new features, like
Object.groupBy()andtoSorted(), need an up-to-date browser.
The Happy Ending π
Three months later, Rahul opens the new DesiEats code. Instead of the messy old file, he sees this:
Classes, private fields, static members, destructuring, default values, ??=, ?., ??, and generators, all working together in a few clean lines. π
"This," says Rahul, "is modern JavaScript."
Ananya smiles and orders a celebratory masala dosa. On DesiEats, of course. π
Common Mistakes to Avoid
| Mistake | Problem | Fix |
|---|---|---|
Forgetting type="module" in HTML | import/export don't work | <script type="module"> |
Using this before super() in a child class | β ReferenceError | Call super(...) first |
Reading #private from outside the class | β SyntaxError | Add a public method or getter |
| Calling a static method on an object | undefined / TypeError | Call it on the class: ClassName.method() |
Using || for defaults when 0 or "" are valid | Real values get replaced | Use ?? |
Using for...of on a plain object | β TypeError: not iterable | Add [Symbol.iterator] or use Object.entries() |
| Spreading an infinite generator | Runs forever | Use next() or break |
| Deep inheritance trees | Rigid, confusing code | Prefer composition |
Quick Summary Table
| Feature | Example |
|---|---|
| Named export / import | export const x = 1; β import { x } from "./a.js"; |
| Default export / import | export default App; β import App from "./App.js"; |
| Class | class Cart { constructor() { } } |
| Private field | #total = 0; |
| Static method | static create() { } β Cart.create() |
| Inheritance | class Cafe extends Restaurant { } |
| Composition | { ...canDeliver(state), ...canCook(state) } |
| Destructuring | const { name, city = "Pune" } = user; |
| Spread / rest | [...a, ...b] / function f(...args) { } |
| Optional chaining | user.address?.city |
| Nullish coalescing | value ?? "default" |
| Logical assignment | a ??= b, a ||= b, a &&= b |
| Iterable | [Symbol.iterator]() { return { next() { } }; } |
| Generator | function* gen() { yield 1; } |
Key Takeaways
- Modern JavaScript (ES6+) adds features from 2015 onwards that make code cleaner, safer, and easier to read.
- ES modules split code into files with
exportandimport. Classes are blueprints for objects, and#privatefields keep data truly safe. - Constructors set up new objects, and static members belong to the class itself. Use inheritance for simple "is a" relationships, and prefer composition for flexible "has a" designs.
- Destructuring, spread/rest,
?.,??, and??=/||=/&&=make working with data short and safe. - Iterables power
for...of, and generators (function*+yield) are the easiest way to create them, including endless, lazy sequences.