17 New(ish) Vanilla JavaScript Features You Might Have Missed

Feature image: 17 New(ish) Vanilla JavaScript Features You Might Have Missed

JavaScript: the language many love to hate. Yeah, we've all seen the memes about its quirks and limitations. Maybe that's why we slap a whole typing system on top of it. And add one, two, or... ten thousand npm packages into our apps.

But today, we're doing something different. We're exploring some exciting new features that have landed in the language over the past years. Because, sometimes, vanilla is exactly what you need.

If you're looking to cut down on heavy dependencies or just get up to date, this article is for you. And, if you feel like it, count how many new tricks you learn and let us know!

Alright, let's jump in.

Intl

1. Intl.ListFormat: Turn lists into locale-appropriate concatenated strings

You might have used Intl to format dates, but did you know you can use Intl.ListFormat to format lists?

This function turns arrays into readable strings that follow the rules of your locale. It automatically adds in the correct punctuation, conjunctions, and grammar, so you don't have to worry about providing templates for each language yourself.

Before

const cities = ['London', 'Tokyo', 'Paris'];
 
const english = cities.join(', ').replace(/, ([^,]*)$/, ', and $1');
// "London, Tokyo, and Paris"
 
const french = cities.join(', ').replace(/, ([^,]*)$/, ' et $1');
// "London, Tokyo et Paris"

After

const cities = ['London', 'Tokyo', 'Paris'];
 
const english = new Intl.ListFormat('en').format(cities);
// "London, Tokyo, and Paris"
 
const french = new Intl.ListFormat('fr').format(cities);
// "London, Tokyo et Paris"

Available across browsers since April 2021 · MDN · Can I Use

2. Intl.RelativeTimeFormat: Display relative time

This method shows the correct times in each locale without the hassle of date math, plural rules, or big libraries.

Before

const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000;
 
function timeAgo(date) {
const diff = Math.round((Date.now() - date) / 1000 / 60 / 60 / 24);
return diff === 1 ? '1 day ago' : `${diff} days ago`;
}
 
const ago = timeAgo(twoDaysAgo); // "2 days ago"

After

const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000;
const twoDaysFromNow = Date.now() + 2 * 24 * 60 * 60 * 1000;
 
function timeAgo(date) {
const diff = Math.round((date - Date.now()) / 1000 / 60 / 60 / 24);
return formatter.format(diff, 'day');
}
 
timeAgo(twoDaysAgo); // "2 days ago"
timeAgo(twoDaysFromNow); // "in 2 days"

Available across browsers since September 2020 · MDN · Can I Use

3. Intl.DurationFormat: Build locale-appropriate duration descriptions

You used to need a library like date-fns or custom logic units and pluralizations to format durations. Now, Intl.DurationFormat does all that for you, no extra dependencies required.

Before

import { formatDuration } from 'date-fns';
import { fr, pt } from 'date-fns/locale';
 
const duration = {
hours: 1,
minutes: 46,
seconds: 40
};
 
formatDuration(duration, { format: ['hours', 'minutes', 'seconds'], locale: fr });
// '1 heure 46 minutes 40 secondes'
 
formatDuration(duration, { format: ['hours', 'minutes', 'seconds'], locale: pt });
// '1 hora 46 minutos 40 segundos'

After

const duration = {
hours: 1,
minutes: 46,
seconds: 40,
};
 
new Intl.DurationFormat("fr-FR", { style: "long" }).format(duration);
// '1 heure 46 minutes 40 secondes'
 
new Intl.DurationFormat("pt", { style: "long" }).format(duration);
// '1 hora 46 minutos 40 segundos'

Works across the latest devices and browser versions since March 2025 · MDN · Can I Use

Objects

4. Object.groupBy(): Group related data by key

Grouping is a pretty common operation, but until recently, JavaScript didn't offer a built-in way to group elements in an array. You had to rely on Lodash's groupBy, or write your own function.

That changes with Object.groupBy(): it groups elements of an iterable based on the string value returned from a callback function. The result is an object where each property represents a group that containing an array of the grouped elements.

Before

import groupBy from 'lodash/groupBy';
 
const people = [
{ age: 20, name: 'Alice' },
{ age: 30, name: 'Bob' },
{ age: 20, name: 'Carol' }
];
 
const grouped = groupBy(people, p => p.age);
/*
{
20: [{ age: 20, name: 'Alice' }, { age: 20, name: 'Carol' }],
30: [{ age: 30, name: 'Bob' }]
}
*/

After

const people = [
{ age: 20, name: 'Alice' },
{ age: 30, name: 'Bob' },
{ age: 20, name: 'Carol' }
];
 
const grouped = Object.groupBy(people, p => p.age);
/*
{
20: [{ age: 20, name: 'Alice' }, { age: 20, name: 'Carol' }],
30: [{ age: 30, name: 'Bob' }]
}
*/

Works across the latest devices and browser versions since March 2024 · MDN · Can I Use

5. Object.entries(): Turn a JavaScript object into a key/value list

This method converts an object into an array of [key, value] pairs. This can be helpful when you want to loop through both keys and values, or transform them into a different format. Although it is far from the newest method on the list, it is still worth mentioning, especially to connect it with the next one: fromEntries.

Before

const team = { Tom: 18, Bob: 23 };
 
const result = [];
for (const key in team) {
if (team.hasOwnProperty(key)) {
result.push([key, team[key]]);
}
}
// [["Tom", 18], ["Bob", 23]]

After

const team = { Tom: 18, Bob: 23 };
 
const result = Object.entries(team);
// [["Tom", 18], ["Bob", 23]]

Available across browsers since March 2017 · MDN · Can I Use

6. Object.fromEntries(): Turn a key/value list into an object

This method is the opposite of entries(). In the past, one might've used reduce() or Lodash's fromPairs(), but now we can use a native function to convert an array of key/value pairs to an object:

Before

const team = [['Tom', 18], ['Bob', 23]];
 
const result = team.reduce((accumulator, [key, value]) => {
accumulator[key] = value;
return accumulator;
}, {});
// { Tom: 18, Bob: 23 }

After

const team = [['Tom', 18], ['Bob', 23]];
 
const result = Object.fromEntries(team);
// { Tom: 18, Bob: 23 }

Available across browsers since January 2020 · MDN · Can I Use

7. Object.hasOwn(): Check whether an object contains a property

If you're an OG JavaScript developer, you've probably used hasOwnProperty to check whether an object directly contains a property, rather than a property inherited from its prototype. It usually worked fine, but could cause problems with objects created using Object.create(null) or with objects from external sources that, for some reason, override hasOwnProperty.

Now, there's Object.hasOwn(): a simpler and safer way to check if a property exists directly on an object.

Before

const user = { name: 'Alice' };
 
const hasName = Object.prototype.hasOwnProperty.call(user, 'name');
// true

After

const user = { name: 'Alice' };
 
const hasName = Object.hasOwn(user, 'name');
// true

Available across browsers since March 2022 · MDN · Can I Use

Arrays

8. at(): Grab items from an array using a method instead of braces

The at() method is a new, convenient way to access elements in an array by index. It returns the element at the specified position, or undefined if the index is out of range. And if you use a negative index, it counts backward from the end of the array.

Before

const arr = [10, 20, 30, 40];
 
const last = arr[arr.length - 1];
// 40
 
const secondToLast = arr[arr.length - 2];
// 30

After

const arr = [10, 20, 30, 40];
 
const last = arr.at(-1);
// 40
 
const secondToLast = arr.at(-2);
// 30

Available across browsers since March 2022 · MDN · Can I Use

9. findLast() and findLastIndex(): Search arrays starting from the end

You're probably familiar with find and findIndex, but what if you need to search from the end of an array? That's where findLast and findLastIndex come in. No more reversing arrays or writing awkward loops.

Before

const arr = [1, 2, 3, 4, 5, 6, 7];
 
const lastEven = arr.reverse().find(x => x % 2 === 0);
// 6

After

const arr = [1, 2, 3, 4, 5, 6, 7];
 
const lastEven = arr.findLast(x => x % 2 === 0);
// 6

Available across browsers since August 2022 · MDN · Can I Use

10. flat(): Flatten nested arrays into a list

The flat() method creates a new array by recursively merging nested sub-arrays up to the depth you specify. It's a handy way to flatten complex, multi-level arrays into a simpler, one-dimensional structure (or as flat as you need it to be).

For example, if a comment section contains nested comments, and then, some of those replies have further nested responses, flat() would merge all of the comments into a single array.

Before

import flattenDeep from 'lodash/flattenDeep';
 
const arr = [1, [2, [3, 4]], 5];
 
const flat = flattenDeep(arr);
// [1, 2, 3, 4, 5]

After

const arr = [1, [2, [3, 4]], 5];
 
const flat = arr.flat(2);
// [1, 2, 3, 4, 5]

Available across browsers since January 2020 · MDN · Can I Use

11. toSorted(), toReversed(), and toSpliced(): Return new arrays without changing the original

The classic sort, reverse, and splice methods mutate the original array. So, if you wanted to sort an array without changing the original, you had to create a copy and sort it.

The new methods—toSorted(), toReversed(), and toSpliced()—work like their classic counterparts but return new arrays instead, leaving the original untouched. That's a big win for functional programming and avoiding side effects.

Before

const arr = [3, 1, 2];
 
const sorted = [...arr].sort((a, b) => a - b);
// [1, 2, 3]

After

const arr = [3, 1, 2];
 
const sorted = arr.toSorted((a, b) => a - b);
// [1, 2, 3]

Works across the latest devices and browser versions since July 2023 · MDN · Can I Use

Strings

12. replaceAll(): Swap out every matching word in one go

Have you ever used replace() and wondered why it only replaces the first match? Yeah, my younger self did too. I got used to using a regex, but now there's a new method: replaceAll. And yes, it does exactly what you'd expect.

Before

const str = 'home sweet home';
 
const result = str.replace(/home/g, 'revenge');
// "revenge sweet revenge"

After

const str = 'home sweet home';
 
const result = str.replaceAll('home', 'revenge');
// "revenge sweet revenge"

Available across browsers since August 2020 · MDN · Can I Use

13. startsWith(), endsWith(), and includes(): Spot what's in your strings in a jiffy

These methods have been around for a decade, but I still see codebases using indexOf and lastIndexOf to check for substrings. That's like using the Hubble Deep Space telescope when all you need is a magnifying glass.

Before

const str = 'hello world';
 
const starts = str.indexOf('hello') === 0;
// true
 
const ends = str.lastIndexOf('world') === str.length - 'world'.length;
// true
 
const has = str.indexOf('lo wo') !== -1;
// true

After

const str = 'hello world';
 
const starts = str.startsWith('hello');
// true
 
const ends = str.endsWith('world');
// true
 
const has = str.includes('lo wo');
// true

Available across browsers since September 2015

14. padStart() and padEnd(): Add spacing or characters to consistently format strings

Ever needed to pad a string with extra characters to reach a certain length? Format numbers with leading zeros? Align text in columns? Add trailing dots for loading effects?

padStart() and padEnd() let us pad strings with extra characters.

Before

const num = '5';
const padded = ('00' + num).slice(-3);
// '005'
 
const word = 'hi';
const paddedWord = word + '...'.slice(word.length);
// 'hi..'

After

const num = '5'.padStart(3, '0');
// '005'
 
const word = 'hi'.padEnd(4, '.');
// 'hi..'

Available across browsers since April 2017

15. trimStart() and trimEnd(): Snip whitespace from just one side of your string

Finally! No more slicing or regexes just to remove whitespace from one side of a string. This is great for processing data with inconsistent formatting, fixing whitespace from copy-pasted content, and cleaning up user inputs without losing intentional spacing.

Before

const messy = ' hello ';
 
const left = messy.replace(/^\s+/, '');
// 'hello '
 
const right = messy.replace(/\s+$/, '');
// ' hello'

After

const messy = ' hello ';
 
const left = messy.trimStart();
// 'hello '
 
const right = messy.trimEnd();
// ' hello'

Available across browsers since January 2020

... and more!

16. Numeric Separators: Make big numbers easier on the eyes

This one's all about readability. When you're dealing with big numbers, a misplaced zero can cost you hours of debugging. Numeric separators use underscores to make actual values clear at a glance. And hey, you can do the same in PHP!

Before

const budget = 1000000; // Is this 1 million or 10 million?

After

const budget = 1_000_000; // Clearly 1 million!

Available across browsers since mid 2019 · MDN · Can I Use

17. structuredClone: Make true clones of objects

Deep cloning objects in JavaScript used to be quite a headache.

The old JSON.parse(JSON.stringify(obj)) trick only worked for simple data: no Dates, Maps, Sets, or circular references. Lodash's cloneDeep helped, but often felt like overkill.

Now, there's a built-in, reliable way to clone almost anything: structuredClone().

Before

const original = { a: 1, b: { c: 2 }, timestamp: new Date() };
 
const copy = JSON.parse(JSON.stringify(original));
 
const type = typeof copy.timestamp
// "string"
 
const isDate = copy.timestamp instanceof Date
// false

After

const original = { a: 1, b: { c: 2 }, timestamp: new Date() };
 
const copy = structuredClone(original);
 
const type = typeof copy.timestamp
// "object"
 
const isDate = copy.timestamp instanceof Date
// true

Available across browsers since March 2022 · MDN · Can I Use

In Closing

If you've made it this far, you're probably as excited as I am about where JavaScript is headed. Every year, the TC39 committee that shapes the ECMAScript standards adds excellent new tools to the language. And browsers are shipping them faster than ever!

So here's a challenge for you: next time you reach for Lodash, date-fns, or any other dependency, pause and see if vanilla JS can handle it. You might be surprised how much the language can do on its own. Plus, your final bundle will be lighter, which means faster sites and apps.

How many of these methods did you already know? And how many projects are you itching to refactor now? Let us know! 😉

Until next time!

Get our latest insights in your inbox:

By submitting this form, you acknowledge our Privacy Notice.

Hey, let’s talk.

By submitting this form, you acknowledge our Privacy Notice.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Thank you!

We appreciate your interest. We will get right back to you.