JavaScript Tutorial menu

JavaScript Modern Features (ES2020+)

JavaScript gets new features every year. This lesson covers the newer ones (from 2020 onwards) that you will see in modern code and job interviews.

Optional Chaining in Depth

The ES6 lesson showed ?. for properties. It also works for array items and method calls:

CodeMeans
obj?.propRead prop only if obj is not null or undefined
obj?.[key]Same, with a key in brackets or an array index
obj.method?.()Call the method only if it exists

Example

javascript
const order = {
  id: 501,
  items: [{ name: "Shalwar Kameez", price: 3500 }],
  customer: null
};

console.log(order.items?.[0]?.name);        // Shalwar Kameez
console.log(order.items?.[5]?.name);        // undefined
console.log(order.customer?.phone);         // undefined, no error
console.log(order.sendReceipt?.());         // undefined: no such method

The whole chain stops at the first null or undefined and gives undefined, instead of crashing.

?? vs ||

Both give a fallback value, but they treat "empty" values differently:

Example

javascript
const settings = { volume: 0, nickname: "", theme: null };

console.log(settings.volume || 50);     // 50  (0 is falsy, so it is replaced)
console.log(settings.volume ?? 50);     // 0   (0 is a real value, kept)
console.log(settings.nickname ?? "Guest");   // ""  (empty string kept)
console.log(settings.theme ?? "light");      // light

Use ?? when 0, "" or false are valid values. || replaces every falsy value, which can hide real data like a price of 0.

Logical Assignment

Three short operators combine a check with an assignment:

  • a ??= b gives a the value b only if a is null or undefined
  • a ||= b gives a the value b only if a is falsy
  • a &&= b gives a the value b only if a is truthy

Example

javascript
const profile = { name: "Zara" };
profile.city ??= "Karachi";      // added, because it was missing
profile.name ??= "Unknown";      // unchanged, it already exists
console.log(profile);
Output
{ name: 'Zara', city: 'Karachi' }

Numeric Separators

Underscores make long numbers easy to read. JavaScript ignores them:

Example

javascript
const budget = 1_500_000;   // fifteen lakh
console.log(budget + 1);    // 1500001

Array.prototype.at()

at(index) reads an item, and negative numbers count from the end. No more arr[arr.length - 1].

Example

javascript
const queue = ["Ali", "Sana", "Hamza", "Zainab"];
console.log(queue.at(0));    // Ali
console.log(queue.at(-1));   // Zainab (last)
console.log(queue.at(-2));   // Hamza

Strings have at() too: "Karachi".at(-1) is "i".

Copying Array Methods

sort(), reverse() and splice() change the original array. Their newer partners return a new array and leave the original alone:

Changes originalReturns a copy
sort()toSorted()
reverse()toReversed()
splice()toSpliced()
arr[i] = xwith(i, x)

Example

html
<!DOCTYPE html>
<html>
<body>

<p id="demo"></p>

<script>
const prices = [850, 120, 2400, 499];

const cheapFirst = prices.toSorted((a, b) => a - b);
const newestFirst = prices.toReversed();
const fixed = prices.with(1, 150);

document.getElementById("demo").innerHTML =
  "Original: " + prices + "<br>" +
  "toSorted: " + cheapFirst + "<br>" +
  "toReversed: " + newestFirst + "<br>" +
  "with(1, 150): " + fixed + "<br>" +
  "Original is still: " + prices;
</script>

</body>
</html>
Try it Yourself »
Output
Original: 850,120,2400,499
toSorted: 120,499,850,2400
toReversed: 499,2400,120,850
with(1, 150): 850,150,2400,499
Original is still: 850,120,2400,499

These are very useful in React and other frameworks, where you should not change data in place.

findLast and findLastIndex

Like find() and findIndex(), but they search from the end:

Example

javascript
const payments = [500, 1200, 300, 2000, 700];
console.log(payments.findLast(p => p > 1000));       // 2000
console.log(payments.findLastIndex(p => p > 1000));  // 3

structuredClone: Real Deep Copies

The spread operator { ...obj } makes a shallow copy: objects inside are still shared. structuredClone() copies everything, at every level. It also handles Date, Map and Set.

Example

javascript
const original = {
  name: "Ayesha",
  marks: { maths: 80 },
  joined: new Date(2026, 0, 10)
};

const shallow = { ...original };
const deep = structuredClone(original);

shallow.marks.maths = 95;   // also changes original!
console.log(original.marks.maths);   // 95

deep.marks.maths = 50;      // original is safe
console.log(original.marks.maths);   // still 95
console.log(deep.joined instanceof Date);   // true

structuredClone cannot copy functions or DOM elements.

Object.groupBy

Object.groupBy(array, fn) sorts items into groups using the key your function returns:

Example

html
<!DOCTYPE html>
<html>
<body>

<p id="demo"></p>

<script>
const students = [
  { name: "Ali", course: "Python" },
  { name: "Sana", course: "Web" },
  { name: "Hamza", course: "Python" },
  { name: "Zara", course: "Web" },
  { name: "Imran", course: "AI" }
];

const byCourse = Object.groupBy(students, s => s.course);

let html = "";
for (const [course, list] of Object.entries(byCourse)) {
  html += course + ": " + list.map(s => s.name).join(", ") + "<br>";
}
document.getElementById("demo").innerHTML = html;
</script>

</body>
</html>
Try it Yourself »
Output
Python: Ali, Hamza
Web: Sana, Zara
AI: Imran

Map.groupBy() does the same but returns a Map.

More Handy Additions

  • str.replaceAll("a", "b") replaces every match, without a regex
  • Object.hasOwn(obj, "key") checks if an object has its own property
  • Promise.withResolvers() creates a promise together with its resolve and reject
  • arr.flat() and arr.flatMap() flatten nested arrays

Exercise

Exercise

What does [10, 20, 30, 40].at(-1) return?

Learn JavaScript with a teacher

Join the Web Development course at Workownix, online or in Orangi Town, Karachi, and get a certificate.

See the course Ask on WhatsApp