Recent JavaScript Features Worth Knowing
The language has not stopped evolving. Each yearly ECMAScript edition adds small, focused features that quietly remove old workarounds. Below is a tour of recent additions that you will reach for almost weekly once you know they exist.
Array.prototype.at
Indexing from the end of an array used to mean arr[arr.length - 1]. The at method takes a negative index directly — and works on strings and typed arrays too.
const items = ["a", "b", "c", "d"]; items.at(0); // "a" items.at(-1); // "d" items.at(-2); // "c" "hello".at(-1); // "o"
findLast and findLastIndex
Search an array from the end. Previously you reversed a copy or wrote a manual loop.
const nums = [3, 8, 2, 9, 4, 6]; nums.find(n => n > 5); // 8 (first from the start) nums.findLast(n => n > 5); // 6 (first from the end) nums.findLastIndex(n => n > 5); // 5
Object.hasOwn
The safe replacement for obj.hasOwnProperty(key). It works even when the object has no prototype (created with Object.create(null)) or has overridden hasOwnProperty.
const o = Object.create(null);
o.foo = 1;
// Old way — TypeError, because o has no `hasOwnProperty`.
// o.hasOwnProperty("foo");
Object.hasOwn(o, "foo"); // true
Object.hasOwn(o, "bar"); // falsetoSorted, toReversed, toSpliced
Mutation-free versions of the classic array methods. They return a new array and leave the original untouched — perfect for React state and any reducer-style code.
const xs = [3, 1, 4, 1, 5]; const sorted = xs.toSorted(); // [1, 1, 3, 4, 5] const reversed = xs.toReversed(); // [5, 1, 4, 1, 3] const without0 = xs.toSpliced(0, 1); // [1, 4, 1, 5] const replaced = xs.with(1, 99); // [3, 99, 4, 1, 5] console.log(xs); // still [3, 1, 4, 1, 5]
structuredClone
A built-in deep clone that handles Date, Map, Set, ArrayBuffer, typed arrays and cyclic references. No more JSON.parse(JSON.stringify(x)) and its many failure modes.
const original = {
when: new Date(),
tags: new Set(["js", "ts"]),
by: { name: "Ada" },
};
original.self = original; // a cycle
const copy = structuredClone(original);
copy.by.name = "Lin";
console.log(original.by.name); // "Ada" — untouched
console.log(copy.tags instanceof Set); // true
console.log(copy.self === copy); // true — cycle preservedFunctions, DOM nodes and class instances with private fields are not cloneable — they throw a
DataCloneError.Available in modern browsers and Node 17+.
Error cause
When you catch one error and re-throw a more meaningful one, you can keep the original as the cause. Stack traces in modern runtimes display the chain.
async function loadUser(id) {
try {
return await fetch("/users/" + id).then(r => r.json());
} catch (err) {
throw new Error("loadUser(" + id + ") failed", { cause: err });
}
}
try {
await loadUser(42);
} catch (e) {
console.log(e.message); // loadUser(42) failed
console.log(e.cause); // the original network/JSON error
}Promise.withResolvers
The classic deferred pattern in one call. You get a promise and its resolve/reject functions out together.
Before
function deferred() {
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
return { promise, resolve, reject };
}After
const { promise, resolve, reject } = Promise.withResolvers();
// Pass `resolve` to anything that needs to settle the promise later.
button.addEventListener("click", () => resolve("clicked"), { once: true });
const value = await promise;Array grouping
Object.groupBy and Map.groupBy turn a list into buckets in one line.
const people = [
{ name: "Ada", role: "engineer" },
{ name: "Lin", role: "designer" },
{ name: "Sam", role: "engineer" },
];
const byRole = Object.groupBy(people, p => p.role);
// {
// engineer: [{ name: "Ada", ... }, { name: "Sam", ... }],
// designer: [{ name: "Lin", ... }],
// }Numeric separators
Underscores inside number literals make long constants readable. They are purely visual — the value is unchanged.
const MAX_INT = 9_007_199_254_740_991; const FEE = 0.000_001; const HEX = 0xFF_EC_DE_5E;
The big picture
Read-only array methods (
toSorted,toReversed,toSpliced,with) prevent silent mutation bugs.structuredCloneandPromise.withResolversretire two long-standing manual workarounds.Object.hasOwnand Errorcauseare tiny upgrades that make defensive code less fragile.Most of these need only a modern engine — no Babel plugin, no polyfill — but check
caniuseif you ship to older browsers.