index
This appendix is a non-exhaustive list of new syntactic features and methods that were added to JavaScript in ES6. These features are the most commonly used and most helpful.
While this appendix doesn't cover ES6 classes, we go over the basics while learning about components in the book. In addition, this appendix doesn't include descriptions of some larger new features like promises and generators. If you'd like more info on those or on any topic below, we encourage you to reference the Mozilla Developer Network's website (MDN).
Prefer const
and let
over var
const
and let
over var
If you've worked with ES5 JavaScript before, you're likely used to seeing variables declared with var
:
Both the const
and let
statements also declare variables. They were introduced in ES6.
Use const
in cases where a variable is never re-assigned. Using const
makes this clear to whoever is reading your code. It refers to the "constant" state of the variable in the context it is defined within.
If the variable will be re-assigned, use let
.
We encourage the use of const
and let
instead of var
. In addition to the restriction introduced by const
, both const
and let
are block scoped as opposed to function scoped. This scoping can help avoid unexpected bugs.
Arrow functions
There are three ways to write arrow function bodies. For the examples below, let's say we have an array of city objects:
If we write an arrow function that spans multiple lines, we must use braces to delimit the function body like this:
Note that we must also explicitly specify a return
for the function.
However, if we write a function body that is only a single line (or single expression) we can use parentheses to delimit it:
Notably, we don't use return
as it's implied.
Furthermore, if your function body is terse you can write it like so:
The terseness of arrow functions is one of two reasons that we use them. Compare the one-liner above to this:
Of greater benefit, though, is how arrow functions bind the this
object.
The traditional JavaScript function declaration syntax (function () {}
) will bind this
in anonymous functions to the global object. To illustrate the confusion this causes, consider the following example:
The method printSongs()
iterates over this.songs
with forEach()
. In this context, this
is bound to the object (jukebox
) as expected. However, the anonymous function passed to forEach()
binds its internal this
to the global object. As such, this.printSong(song)
calls the function declared at the top of the example, not the method on jukebox
.
JavaScript developers have traditionally used workarounds for this behavior, but arrow functions solve the problem by capturing the this
value of the enclosing context. Using an arrow function for printSongs()
has the expected result:
For this reason, throughout the book we use arrow functions for all anonymous functions.
Modules
ES6 formally supports modules using the import
/export
syntax.
Named exports
Inside any file, you can use export
to specify a variable the module should expose. Here's an example of a file that exports two functions:
Now, anywhere we wanted to use these functions we could use import
. We need to specify which functions we want to import. A common way of doing this is using ES6's destructuring assignment syntax to list them out like this:
Importantly, the function that was not exported (saySomething
) is unavailable outside of the module.
Also note that we supply a relative path to from
, indicating that the ES6 module is a local file as opposed to an npm package.
Instead of inserting an export
before each variable you'd like to export, you can use this syntax to list off all the exposed variables in one area:
We can also specify that we'd like to import all of a module's functionality underneath a given namespace with the import * as <Namespace>
syntax:
Default export
The other type of export is a default export. A module can only contain one default export:
This is a common pattern for libraries. It means you can easily import the library wholesale without specifying what individual functions you want:
It's not uncommon for a module to use a mix of both named exports and default exports. For instance, with react-dom
, you can import ReactDOM
(a default export) like this:
Or, if you're only going to use the render()
function, you can import the named render()
function like this:
To achieve this flexibility, the export implementation for react-dom
looks something like this:
If you want to play around with the module syntax, check out the folder code/webpack/es6-modules
.
For more reading on ES6 modules, see this article from Mozilla: "ES6 in Depth: Modules".
Object.assign()
Object.assign()
We use Object.assign()
often throughout the book. We use it in areas where we want to create a modified version of an existing object.
Object.assign()
accepts any number of objects as arguments. When the function receives two arguments, it copies the properties of the second object onto the first, like so:
It is idiomatic to pass in three arguments to Object.assign()
. The first argument is a new JavaScript object, the one that Object.assign()
will ultimately return. The second is the object whose properties we'd like to build off of. The last is the changes we'd like to apply:
Object.assign()
is a handy method for working with "immutable" JavaScript objects.
Template literals
In ES5 JavaScript, you'd interpolate variables into strings like this:
With ES6 template literals, we can create the same string like this:
The spread operator (...
)
...
)In arrays, the ellipsis ...
operator will expand the array that follows into the parent array. The spread operator enables us to succinctly construct new arrays as a composite of existing arrays.
Here is an example:
Notice how this is different than if we wrote:
Enhanced object literals
In ES5, all objects were required to have explicit key and value declarations:
In ES6, you can use this terser syntax whenever the property name and variable name are the same:
Lots of open source libraries use this syntax, so it's good to be familiar with it. But whether you use it in your own code is a matter of stylistic preference.
Default arguments
With ES6, you can specify a default value for an argument in the case that it is undefined
when the function is called.
This:
Can be written as this:
In both cases, using the function looks like this:
Whenever the argument b
in the example above is undefined
, the default argument is used. Note that null
will not use the default argument:
Destructuring assignments
For arrays
In ES5, extracting and assigning multiple elements from an array looked like this:
In ES6, we can use the destructuring syntax to accomplish the same task like this:
The variables in the array on the left are "matched" and assigned to the corresponding elements in the array on the right. Note that 'onion'
is ignored and has no variable bound to it.
For objects
We can do something similar for extracting object properties into variables:
Parameter context matching
We can use these same principles to bind arguments inside a function to properties of an object supplied as an argument:
We do this often with functional React components:
Here, we use destructuring to extract the props into variables (ingredients
and onClick
) that we then use inside the component's function body.
Last updated