Basics of ES6: A Brief Introduction

If you’re not using modern JavaScript yet, you should be. It’s cleaner, more productive, and more fun than writing traditional, old-school JavaScript.
Some ES6+ code won’t run on all browsers natively, but tools like Babel and Webpack can help us transpile our code into browser-compliant code. Luckily for Node.js developers, ES6 is available without the need for transpiling since it now supports most of the features explored below. For ES7+ (which this article covers), you’ll still have to use a tool like Babel.
The Basics
Variable Declarations
let
vs. const
vs. var
Three ways to declare variables is a little excessive, but really we are only dealing with two types now: const
and let
.
-
As a rule of thumb, try to always use
const
, unless you plan on changing the value of the variable. Usingconst
by default encourages immutability (sort of) and minimizes accidental variable reassignments and other potential scoping issues. -
Use
let
only when the value of the variable needs to change. -
⚠️ Avoid using
var
at all costs. There’s just no need forvar
now thatlet
andconst
are available to us.var
declarations are hoisted, which can lead to unexpected naming collisions and hard-to-spot bugs.
Unlike var
, let
and const
variables are block-scoped. This means that if you declare these inside a for
loop, for example, they can only be accessed within the enclosing for
block and not the outer function block. The same rule applies to all other block types (e.g. if
, switch
, function
, etc.).
Object Destructuring
Accessing deep properties inside an object is a common task, yet there’s never been a clean way of expressing it until now. Object destructuring allows us to pick properties out of a deep object structure with ease:
// Traditional JavaScript var name = this.state.form.user.name var bio = get(this, "state.form.user.gender") // with lodash // ES6+ let { name, bio } = this.state.form.user
Destructuring unpacks properties from objects (or values from arrays) into distinct variables. This means that we make copies of each extracted property instead of referencing the original object’s property, so we won’t alter the original source data if we change name
or bio
.
Enhanced Object Literals
This is best explained with a code example, but it boils down to this:
• Property values derived from a named variable will inherit its name as a key without the need to specify the value
• Computed property names can be declared inside an object definition by using the array operators ([]
)
This is what that actually looks like:
// Traditional JavaScript var book = { uid: uid, name: name, getPage: getPage } book[category + 'Pages'] = pages // ES6+ const book = { // instead of `uid: uid`, since they share the same name uid, name, getPage, // dynamic property keys can be defined from within the object definition [`${category}Pages`]: pages }
Spread Operator
The spread operator is an incredibly useful tool. It helps us copy objects (shallow copies, anyway) and arrays without altering the original data, and more importantly, without needing to use push
, slice
, concat
, or Object.create
to do it. These are a few of the different ways you can use it with Arrays and Objects:
/** * Arrays [] */ const fruit = ['apples', 'kiwi', 'bananas'] // A left join // ['apples', 'kiwi', 'bananas', 'pineapple', 'watermelon'] const copyLeft = [...fruit, 'pineapple', 'watermelon'] // A right join // ['watermelon', 'pineapple', 'apples', 'kiwi', 'bananas'] const copyRight = ['watermelon', 'pineapple', ...fruit] /** * Objects {} */ const sample = { id: 1, name: 'Item 1', category: 'Media', children: { /* ... */ } } // Quick and easy way to copy an object (or array) const shallowCopy = { ...sample } // Make a copy of `item` with updated `id` and `name` values const copyUpdate = { ...sample, id: 2, name: `Copy of ${item.name}` }
That's great for a bunch of different use cases, but what about when you actually need to make a deep clone of an object?
Structured Clones
To make a deep clone of an object or array, I highly recommend the new structured clone native API method.
const deepObject = Object.freeze({ a: { b: { c: 12, } }, circularReference: deepObject, }); const clone = structuredClone(deepObject);
Notice that using structuredClone
allows us to make a deep clone of an object even if it includes a circular reference to itself.
Template Literals
Strings composed using the +
symbol can be difficult to read. The new template literal syntax allows us to interpolate variables within a string declaration nicely. It’s also a much better way to write multi-line strings.
Template strings use `` quotes instead of ""
or ''
, and variables can be interpolated inside them using the ${variable}
syntax. For example:
// ES5 var foo = 'Hello, ' + user.name + '. You are ' + user.age + ' years old. Please, have a fruit 🥝' // ES6+ const bar = `Hello, ${user.name}. You are ${user.age} years old. Please, have a fruit 🍏` // Multi-line const baz = ` Hello ${user.name}, This is a multiline string. You are ${user.age} years old. Please, have a fruit 🍏 `
Arrow Functions
Remember callback hell? For those of you who haven’t had the pleasure, it’s a term used to describe very deep callback function chains that sometimes use variables like self
or that
to access different scopes (a.k.a this
) throughout the chain.
Overly-nested callback functions are difficult to read which makes it hard to navigate where we may be in the chain.
Arrow functions provide a way for us to declare a function without creating a new scope or closure. In other words, the value of this
won’t change as you define a function within another function block, i.e. a callback.
const Movie = function (id) { this.id = id this.title = '' this.description = '' // Notice the => function declaration syntax const update = (response) => { // `this` here refers to the scope of the outer Page block instead of this function this.id = uuidv4() this.title = response.data.title this.description = response.data.description } // Only 1 argument, so parentheses aren't necessary // You can choose to omit `{}` brackets, which will implicitly return the value of the last expression! const onError = error => console.log(error) http.get(`/pages/${this.id}`) .then(update) .catch(onError) }
What's Next?
This article barely scratches the surface as to what's new in ES6, but my hope is it's a solid introduction. There's too much to cover in one article, so I think that's where I'll leave it for today.
Next, I think we can take a closer look at other important features like modules, async/await, optional chaining, and nullish coalescing.