JavaScriptLatest Language Features

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.

JS
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.

JS
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.

JS
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");   // false
toSorted, 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.

JS
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]
Why it matters
Reducers in Redux and components in React rely on detecting reference changes. Mutating the previous state breaks both. These methods make the right thing easy.
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.

JS
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 preserved
  • Functions, 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.

JS
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

JS
function deferred() {
  let resolve, reject;
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
  return { promise, resolve, reject };
}

After

JS
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.

JS
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.

JS
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.

  • structuredClone and Promise.withResolvers retire two long-standing manual workarounds.

  • Object.hasOwn and Error cause are tiny upgrades that make defensive code less fragile.

  • Most of these need only a modern engine — no Babel plugin, no polyfill — but check caniuse if you ship to older browsers.

Stay current
Each ECMAScript edition adds a small set of features. Skim the yearly release notes — the additions are short, real, and almost always useful in code you already write.