jonathanjuliani
EcmaScript (Javascript and NodeJS) - What's New from ES7 to ES10
JavaScript ES7–ES10 highlights: includes, object spread, flat, fromEntries, and real-world gotchas. Practical guide with code.
ES7 to ES10: the features you use every day without knowing where they came from
If you use JS day to day, you've almost certainly used includes(), object spread, and flat()—but do you know which ES version each came from? Probably not. Most developers don't.
ES7 through ES10 landed some of the most practical additions to the language, and knowing where they came from helps you understand their limitations.

This isn't an exhaustive list of every spec change. The goal is the features you'll actually hit in day-to-day work—with the edge cases the docs tend to skip over.
What you'll learn:
Array.includes()andString.includes()— and the gotcha with objects that trips everyone upObject.values()andObject.entries()— and their ES10 sibling that reverses them- Object spread operator — why it replaced
Object.assignfor good Promise.finally()andfor await...of— where each one fitsflat(),flatMap(),Object.fromEntries(),trimStart/End, and optional catch binding- Why stable
sort()in ES10 matters more than you'd expect
Prerequisites: comfortable with JavaScript basics — functions, arrays, objects, and a rough idea of what a Promise is.
ES7: includes, exponentiation operator
The new features in ES7 are:
includesmethod on Arraysincludesmethod on Strings- Exponentiation operator
**
Wait, you've been using includes all this time and didn't realise it was "that recent"? Yeah, same. Let's code it up—it's simple:
includes() returns true or false depending on whether the string contains the character, word, or phrase you pass in. Case-sensitive.
First example with strings:
// testing includes() with strings
const phrase = 'Hello world'
let result = phrase.includes('w') // returns true
console.log(result)
// it's case sensitive
result = phrase.includes('L') // returns false
console.log(result)
result = phrase.includes('x') // returns false
console.log(result)Now with arrays:
const animals = ['dog', 'cat', 'bear']
result = animals.includes('dog')
console.log(result) // returns true
result = animals.includes('me')
console.log(result) // returns false — yep, I'm not an animal :D
const numbers = [1, 2, 3, 4, 5]
result = numbers.includes(3)
console.log(result) // returns true
result = numbers.includes(0)
console.log(result) // returns false
// let's make it more interesting:
const arrayOfObjects = [
{ id: 1, desc: 'Obj 1' },
{ id: 2, desc: 'Obj 2' },
]
result = arrayOfObjects.includes(4) // ?
console.log(result) // false — expected
result = arrayOfObjects.includes({}) // ?
console.log(result) // also false
result = arrayOfObjects.includes({ id: 1, desc: 'Obj 1' })
console.log(result) // false — wait, WHAT?!includes works great until we start dealing with objects inside arrays.
But it makes sense if you think about it: includes does a simple equality check, and comparing objects requires deeper logic (checking each key and value). We'd need a different approach for that—topic for another post.
Now the exponentiation operator, also very simple:
// exponentiation
const squareOf = (num) => num ** 2
console.log(squareOf(5)) // returns 25
const cubeOf = (num) => num ** 3
console.log(cubeOf(5)) // returns 125To raise a number to a power, use ** followed by the exponent. Clean and readable.
ES8: padStart, padEnd, Object.values, Object.entries
Here are the main features from ES8 that you might have already used without realising they came from this release:
- String padding with
padStartandpadEnd Object.valuesObject.entries
padStart and padEnd
padStart and padEnd add padding to the beginning or end of a string:
const name = 'Jonathan'
console.log(name.padStart(10))
// Jonathan // <= 10-character padding at the start
console.log(name.padEnd(10))
// Jonathan // <= 10-character padding at the endObject.values
Object.values returns all the values of an object's keys as an array:
const obj = {
id: 1,
name: 'test',
desc: 'test description',
}
const values = Object.values(obj)
console.log(values)
// [ 1, 'test', 'test description' ]Super simple, right? So now when you have an object and just need its values, you know what to reach for.
Object.entries
And its bigger sibling Object.entries returns all key/value pairs as an array of [key, value] tuples:
const obj = {
id: 1,
name: 'test',
desc: 'test description',
}
const entries = Object.entries(obj)
console.log(entries)
// [
// [ 'id', 1 ],
// [ 'name', 'test' ],
// [ 'desc', 'test description' ]
// ]With both of these methods, you can easily convert an object into something you can iterate over—then throw a for, map, reduce, or whatever you need at it.
Top-tier utilities. If you have a cool real-world example using these, drop it in the comments.
ES9: spread in objects, finally, for await
A few highlights from ES9:
- Spread operator on objects
finallyfor await
Spread in objects: replacing Object.assign
Spread already worked well with arrays, but with objects it wasn't great until ES9. Now we can destructure an object—pull out what we want and throw the rest into a ...rest variable:
// object spread operator
const obj = {
id: 1,
name: 'Jonathan',
bio: 'Fullstack developer — lorem ipsum I got tired of writing',
}
const { id, ...rest } = obj
console.log(id)
// 1
console.log(rest)
// {
// name: 'Jonathan',
// bio: 'Fullstack developer — lorem ipsum I got tired of writing'
// }We can also create a shallow copy of an object using spread, without needing Object.assign:
const obj2 = { ...obj }
console.log(obj2)
// { id: 1,
// name: 'Jonathan',
// bio: 'Fullstack developer — lorem ipsum I got tired of writing' }And the position of the key doesn't matter—you can extract any key from anywhere in the object, and the rest lands in ...rest:
const { name, ...restOfObj } = obj
console.log(name)
// Jonathan
console.log(restOfObj)
// { id: 1,
// bio: 'Fullstack developer — lorem ipsum I got tired of writing' }finally: the block that always runs
If you've worked in other languages, you've probably seen finally. It took a while, but it's here in JavaScript now. You can use it in try/catch blocks and promise chains.
In the example below, I fetch from a basic API and use finally to log a string—notice that finally executes even before the fetch completes:
const url = 'https://jsonplaceholder.typicode.com/users'
const fetchUsers = async () => {
const data = await fetch(url)
.then((data) => console.log(data))
.catch((err) => console.log('Oops, something went wrong...'))
.finally(console.log('No matter what happens, I run.'))
}
fetchUsers()for await...of: iterating promises in sequence
This one is a bit trickier to get at first.
The idea: you need to await multiple async operations in a loop. You could do Promise.all(fetch(url1), fetch(url2), ...), or you could write out 10 await statements one by one (please don't), or you use for await:
// 3 URLs:
const urls = [
'https://jsonplaceholder.typicode.com/users',
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/albums',
]
// fetching all 3 using for await
const fetchData = async (urls) => {
const fetchArray = urls.map((url) => fetch(url))
for await (const result of fetchArray) {
const data = await result.json()
console.log(data)
}
}
fetchData(urls)ES10: flat, fromEntries, trimStart/End, stable sort
ES2019 additions:
flatandflatMapObject.fromEntriestrimStartandtrimEndcatch{}without a parametersort()is always stable now
flat: flattening nested arrays
Quick note: some features like flat() don't work in older online repls, so test this one in your browser console (as long as it's not from 1997).
flat() is a new method on the Array prototype. It takes nested arrays (arrays inside arrays) and "flattens" them into the parent array. Simple example:
const array = [1, 2, [10, 20]]
console.log(array.flat())
// [1, 2, 10, 20]By default flat() only goes one level deep. But you can pass it a depth, or Infinity to flatten everything no matter how deep:
const array = [
1,
2,
[10, 20, [100, 200, [1000, 2000, [1, 4, 5, 6], [134, 1234, 5666, 77, [12, 15, 6]]]]],
[50, 38, 80],
]
console.log(array.flat(Infinity))
// [1, 2, 10, 20, 100, 200, 1000, 2000, 1, 4, 5, 6, 134, 1234, 5666, 77, 12, 15, 6, 50, 38, 80]flatMap
flatMap is like doing a map() followed by flat(1). You iterate each item, do something with it, and the result is automatically flattened one level:
const array = [1, 2, 3, 4]
console.log(array.flatMap((value) => [value, value * 2]))
// [1, 2, 2, 4, 3, 6, 4, 8]In the example above: for each item we return [item, item * 2]. Normally this would give us an array of arrays—but flatMap flattens that one level, giving us a single flat array. I'll leave you to play around with this one.
Object.fromEntries: the entries() companion
Remember Object.entries() which gives you all key/value pairs as an array? Object.fromEntries() does the exact opposite—it takes an array of [key, value] pairs and turns them back into an object:

Nice, right? This is super useful when a backend or database returns data as arrays of pairs and you want to convert them into a proper object to work with.
trimStart and trimEnd
According to the ECMAScript spec, trimStart and trimEnd are better-named alternatives to the existing trimLeft and trimRight. Same function—strip whitespace from the start or end of a string:

Note: I used JSON.stringify() in the console.log to make the spaces visible at the start and end of the string. You don't need any JSON utilities in your actual code.
catch without a parameter
Admit it: you've written console.log(err) inside a catch just to stop the linter from complaining about an unused variable.
We've all been there. But now you can skip the parameter entirely:
try {
// something risky
} catch {
// handle the error without declaring `err`
}Clean.
sort() is always stable now: why it matters
I won't go deep into sorting algorithm theory here, but a quick concept you need:
A stable sort preserves the relative order of elements that are considered equal. An unstable sort may swap equal elements around.

In the image: in an unstable sort, A and B (equal in value) might swap positions—same with D and C. In a stable sort, they stay in the original order.
From ES10 onward, Array.sort() is required to be stable by the spec. If you had bugs related to unstable sort behaviour before—no more worrying.
Want to know more about stable vs unstable sorts? Drop it in the comments.
TL;DR — ES7 to ES10 quick reference
| Feature | Version | What it does | Gotcha |
|---|---|---|---|
Array.includes() | ES7 | Check if value exists — returns boolean | Reference comparison: doesn't work with objects |
String.includes() | ES7 | Check if substring exists | Case-sensitive |
** (exponentiation) | ES7 | Raises to a power | — |
padStart / padEnd | ES8 | Pads a string to a target length | — |
Object.values() | ES8 | Array of the object's values | — |
Object.entries() | ES8 | Array of [key, value] pairs | — |
Object spread ... | ES9 | Copies / destructures objects | Shallow copy — nested objects still share references |
Promise.finally() | ES9 | Runs always, with or without error | — |
for await...of | ES9 | Iterates promises in sequence | Sequential, not parallel like Promise.all |
flat() | ES10 | Flattens nested arrays | Only 1 level by default — use flat(Infinity) |
flatMap() | ES10 | map() + flat(1) combined | Only flattens 1 level |
Object.fromEntries() | ES10 | Array of pairs → object | Reverse of Object.entries() |
trimStart / trimEnd | ES10 | Removes whitespace from start / end | Aliases for trimLeft / trimRight |
catch {} no param | ES10 | Omits the err param when unused | — |
sort() stable | ES10 | Preserves relative order of equal elements | Behaviour was inconsistent across browsers before |
Next steps
Want to go deeper on some of these features?
requirevsimport/export: Now that you know what ES9 and ES10 added, see how CommonJS modules compare to ES Modules: Understandingrequire,exports, andmodule.exports- Data Structures with Node.js: See modern JavaScript features in action across a full implementation series: Data Structures: a summary of the most used ones
- Official TC39 history of every proposal: tc39.es/ecma262
Before you go
If you spotted a mistake or want to add something, let me know.
Let's share knowledge and code—best way to learn and actually remember things.
Subscribe to the Engineering Ledger
Get architecture and performance notes in your inbox. Same list as the timed prompt—subscribe here anytime.
Related articles
Promises in JavaScript: from zero to async/await
JavaScript Promises and async/await from the ground up: states, chaining, error handling, and the gotchas official docs tend to skip.
Read storySpread and Rest in JavaScript: arrays, objects, and the shallow copy gotchas
Spread operator and rest in JavaScript: how they work in arrays, objects, and functions, and the shallow copy gotchas that catch every developer.
Read story