ECMAScript 2015, also known as ES6, introduced a lot of new features to JavaScript. Since then, new features have been released incrementally each year. Features from ES6 and beyond are commonly referred to as Modern JavaScript because of these significant changes. This blog explores a few commonly used ES6, ES7, ES8, ES9, and ES10 features.
ECMAScript is a scripting language specification. JavaScript, a programming language, is an implementation of ECMAScript specifications. ECMA International is an association that develops the ES specifications. TC39, a committee within ECMA International, determines how JavaScript works and what new features it should have. In 2015, there was a huge shift because ES6 was introduced, meaning a lot of new features were released. (The previous release to ES6 was in 2011, ES5.1). And since 2015, TC39 has released new features incrementally each year. As a result, there aren't huge feature releases and huge gaps in time between releases.
In plain English, ES is short for ECMAScript. ES is a standard, kinda like a book of rules. Scripting languages follow ES standards. And JavaScript is a scripting language that follows those ES standards.
The var
keyword defines a variable globally, or locally to an entire function, regardless of block scope. Variables declared with var
can be hoisted, reassigned, and redeclared.
The let
keyword allows for block-scoped variables. Variables declared with let
cannot be hoisted or redeclared. But can be reassigned.
var name = 'Lisa'
let name = 'Lisa'
name = 'Bart' // 'Bart'
let name = 'Maggie' // 'Maggie'
The const
keyword allows for block-scoped variables. Variables declared with const
cannot be hoisted, redeclared, nor reassigned.
const name = 'Lisa'
name = 'Bart' // TypeError, cannot reassign constant variable
const name = 'Maggie' // SyntaxError, cannot redeclare constant variable
Variables declared with const
are mutable.
const simpson = { name: 'Lisa' }
simpson.name = 'Bart' // mutate name
simpson.address = '742 Evergreen Terrace' // add address
console.log(simpson) // { name: 'Bart', address: '742 Evergreen Terrace' }
simpson = { name: 'Bart' } // TypeError, cannot reassign constant variable
```
- [MDN reference: const](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const)
### Arrow functions
Arrow function expressions are a syntactically shorter way of writing [regular function expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function). Arrow functions do not have bindings to `this`, shouldn't be used as methods, and cannot be used as constructors.
```js:title=ES5
function sum(a, b) {
console.log(a + b)
}
const sum = function(a, b) {
console.log(a + b)
}
const sum = (a, b) => {
console.log(a + b)
}
Template literals allow us to embed expressions in strings with cleaner syntax. Template literals are enclosed by backticks. They can have placeholders indicated with brackets and a dollar sign, like ${variableName}
.
let firstName = 'Lisa'
let lastName = 'Simpson'
'Hello, ' + firstName + ' ' + lastName + '.'
// 'Hello, Lisa Simpson.'
;`Hello, ${firstName} ${lastName}.`
// 'Hello, Lisa Simpson.'
Destructuring allows us to unpack values from arrays, or properties from objects, into distinct variables. Destructuring is heavily used in React.
Array destructuring:
const numbers = [1, 2, 3, 4, 5, 6, 7]
const one = numbers[0]
const two = numbers[1]
console.log(one) // 1
console.log(two) // 2
const [one, two, three, ...rest] = numbers
console.log(one) // 1
console.log(two) // 2
console.log(rest) // [3, 4, 5, 6, 7]
Object destructuring:
const lisa = {
age: 8,
school: 'Springfield Elementary',
}
const age = lisa.age
const school = lisa.school
console.log(age) // 8
console.log(school) // 'Springfield Elementary'
const { age, school } = lisa
console.log(age) // 8
console.log(school) // 'Springfield Elementary'
Spread syntax allows iterables like an array expressions or strings to be expanded.
Spread syntax can be used to expand an array.
const family = ['Lisa', 'Bart', 'Maggie', 'Marge', 'Homer']
const pets = ["Santa's Little Helper", 'Snowball']
const theSimpsons = [...family, ...pets]
console.log(theSimpsons)
// ['Lisa', 'Bart', 'Maggie', 'Marge', 'Homer', "Santa's Little Helper", 'Snowball']
Spread syntax can be used for function arguments.
const array = [1, 2, 3]
const sumNumbers = (a, b, c) => a + b + c
console.log(sumNumbers(...array)) // 6
The for...of
statement creates a loop iterating over iterable objects like arrays, maps, and sets.
const array = ['Lisa', 'Marge', 'Maggie']
for (let i = 0; i < array.length; i++) {
console.log(array[i]) // 'Lisa', 'Marge', 'Maggie'
}
for (i of array) {
console.log(i) // 'Lisa', 'Marge', 'Maggie'
}
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
function firstHello() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('First hello!')
resolve()
}, 5000)
})
}
function secondHello() {
console.log('Second hello!!')
}
console.log(firstHello().then(secondHello))
// Promise pending <-- promise pends for 5 seconds
// First hello!
// Second hello!!
The includes()
method returns true
if an array includes a certain specified value, and returns false
if it doesn't.
const simpsons = ['Lisa', 'Bart', 'Maggie', 'Marge', 'Homer']
simpsons.includes('Lisa') // returns true
simpsons.includes('Ned') // returns false
The exponentiation operator (**) returns the result of the first operand raised to the power of the second operand.
The exponentiation operator is right-associative. For example, 2 ** 3 ** 3
is equal to 2 ** (3 ** 3)
Math.pow(2, 3) // 8
2 ** 3 // 8
2 ** (3 ** 3) // 134217728
The Object.values()
method takes in an object as an argument and returns an array of the object's values. The values are returned in the same order as if invoked by a for...in
loop.
const character = {
name: 'Lisa',
age: 8,
school: 'Springfield Elementary',
}
console.log(Object.values(character))
// ['Lisa', 8, 'Springfield Elementary']
The Object.entries()
method takes in an object as an argument and returns an array with arrays of key-value pairs. The values are returned in the same order as if invoked by a for...in
loop.
const character = {
name: 'Lisa',
age: 8,
school: 'Springfield Elementary',
}
console.log(Object.entries(character))
// [[name: 'Lisa'], [age: 8], [school: 'Springfield Elementary']]
The padEnd()
method pads the end of a given string with another string until it reaches the specified length. The other string may repeat multiple times if needed.
'The'.padEnd(10) // 'The '
'The'.padEnd(10, ' Simpsons') // 'The Simpso'
'The'.padEnd(12, ' Simpsons') // 'The Simpsons'
'The'.padEnd(15, ' Simpsons') // 'The Simpsons Si'
The padStart()
method pads the start of a given string with another string until it reaches the specified length. The other string may repeat multiple times if needed.
'Simpsons'.padStart(10) // ' Simpsons'
'Simpsons'.padStart(10, 'The ') // 'ThSimpsons'
'Simpsons'.padStart(12, 'The ') // 'The Simpsons'
'Simpsons'.padStart(15, 'The ') // 'The TheSimpsons'
A trailing comma is the last comma at the end of the last element. It comes in handy if you need to add a new line without modifying the previous last line since it already has a comma. The trailing comma also makes version-control diffs cleaner.
const character = {
name: 'Lisa',
age: 8,
school: 'Springfield Elementary',
}
Async/await is syntactic sugar on top of promises. This makes asynchronous code easier to read and write.
An async function knows it might expect an await
keyword to invoke asynchronous code. An async function also returns a promise rather than directly returning a value. The async
keyword in front of a function turns the function into an async function.
The await
keyword only works inside async functions. The await
keyword is used to pause code on a line with a promise until the promise is fufilled. Meanwhile, the rest of the code continues to execute.
Here's what the syntax looks like:
async function hello() {
return (greeting = await Promise.resolve('Hello'))
}
Here's an example that illustrates the power of async/await:
function delayedHello() {
return new Promise(res => {
setTimeout(() => res(console.log('A delayed hello!')), 5000)
})
}
async function logHello() {
await delayedHello()
}
console.log('Before logHello')
logHello()
console.log('After logHello')
// Before logHello
// After logHello
// A delayed hello! <-- logs after 5 seconds has passed
The finally()
method always executes no matter what, whether the Promise is resolved or rejected.
const hello = () => {
console.log('hello')
}
try {
hello()
} catch (error) {
console.log(error)
} finally {
console.log('all done!')
}
// hello
// all done! <-- logs when promise is resolved
try {
functionDoesNotExist()
} catch (error) {
console.log(error)
} finally {
console.log('all done!')
}
// ReferenceError: functionDoesNotExist is not defined
// all done <-- logs when promise is rejected
The flat()
method creates a new array with all sub-arrays elements concatenated into it. It has an optional argument, depth, with default 1. Depth specifies how deep the nested array should be flattened.
const array = [1, 2, 3, [4, 5]]
console.log(array.flat())
// [1, 2, 3, 4, 5]
const array2 = [1, 2, [3, [4, 5]]]
console.log(array2.flat())
// [1, 2, 3, [4, 5]]
console.log(array2.flat(2))
// [1, 2, 3, 4, 5]
The flat.map()
method maps each element using a mapping function, then flattens the result into a new array. It's the same as using map()
, then flat()
with a depth of 1.
const array = [1, 2, 3, 4, 5]
console.log(array.map(x => [x * 2]))
// [[2], [4], [6], [8], [10]]
console.log(array.map(x => [x * 2]).flat())
// [2, 4, 6, 8, 10]
const array = [1, 2, 3, 4, 5]
console.log(array.flatMap(x => [x * 2]))
// [2, 4, 6, 8, 10]
The Object.fromEntries()
method transforms a list of key-value pairs into an object.
const lisaArray = [
['grade', 2],
['school', 'Springfield Elementary'],
]
console.log(Object.fromEntries(lisaArray))
// {grade: 2, school: "Springfield Elementary"}
const lisaMap = new Map([
['grade', 2],
['school', 'Springfield Elementary'],
])
console.log(Object.fromEntries(lisaMap))
// {grade: 2, school: "Springfield Elementary"}
The trimStart()
method removes whitespace from the beginning of a string.
const simpsons = ' The Simpsons '
console.log(simpsons.trimStart())
// 'The Simpsons '
The trimEnd()
method removes whitespace from the end of a string.
const simpsons = ' The Simpsons '
console.log(simpsons.trimEnd())
// ' The Simpsons'
In try... catch
, the error parameter is now optional.
try {
// do something
} catch (err) {
// handle error
}
try {
// do something
} catch {
// handle error
}
And there we have it! You now know how to use a lot of commonly used ES6+ JavaScript features. Test your JavaScript knowledge with some JavaScript questions by Lydia Hallie.