11 JavaScript ES6 Features Every Developer Needs To Know
JavaScript runs the world.
It loaded the article you're currently reading, it's allowing you to scroll with your finger (or mouse) to see more content, and is also probably doing a million other things that we take for granted on a daily basis.
Developers who've mastered the inner workings of JavaScript will remind you that it wasn't always this way, though.
When JavaScript began, not only was it less powerful than it is now, but it was also less usable.
As programmers, we know that every technology we use should be powerful but what about user-friendly? If that technology makes creating programs difficult or unwieldy, it's probably time to switch tech stacks.
Luckily, JavaScript has gone through several iterations and upgrades, the most recent being ES6. With that update came a lot of much-needed features that make JavaScript (by far) the most functional tool in a web developer's arsenal.
1. Template Literals
Template literals are a convenient way of inserting variables within strings.
Here's the old way:
//The old way
let person = { firstName: "Ben", occupation: "Developer"};
let oldString = "Nice to meet you, " + person.firstName + "!";
Dealing with spacing and punctuation is a nightmare with this method, especially for long strings with lots of variables.
In ES6, we can do a bit better.
Use backticks to denote the start of a template string, and insert any variables you want using the ${ variableHere }
syntax, like so:
//The new way
let person = { firstName: "Ben", occupation: "Developer"};
let templateString = `Nice to meet you, ${person.firstName}!`;
This is a really convenient tool that I use all the time when I'm constructing Strings, and should be a part of every JavaScript developer's toolkit.
2. Default Function Parameters
In ES6, we finally have the option to insert default function parameters.
In earlier versions of JavaScript, we had to use logic operators to check our inputs:
//The old way
let sum = function(a, b){
a = a || 10;
b = b || 10;
// function logic goes here...
}
Now, instead of having to check each parameter, we can specify its value in the function definition:
//The new way
let sum = function(a = 10, b = 10){
// function logic goes here...
}
Not only is this less error-prone but it's also more readable.
Your colleagues and future-you will thank you for using default parameters, especially when code review rolls around.
3. The Spread Operator
This is a great little feature, and one of my favorite updates from ES6.
The spread operator, denoted as ...
, allows us to extract the elements of an array and populate another array (or object).
Here are some examples of what you can do with the spread operator:
let arrayOne = [1, 2, 3];
let arrayTwo = [4, 5, 6];
//Make a shallow copy
let shallowCopy = [...arrayOne];
//Merge two arrays
let mergedArrays = [...arrayOne, ...arrayTwo];
While the spread operator is extremely versatile and a great tool to know, there's still a bit of lag in terms of browser support.
To recreate similar functionality while still maintaining backward compatibility, you can use Object.assign()
for most use cases.
4. Object Destructuring
Object destructuring allows devlopers to extract only the necessary components of object or arrays to increase code performance and readability.
Let's look at a few examples:
//Object destructuring here:
function printCatInfo({name, hobby}) {
console.log(`${name} likes to ${hobby} all day.
`);
}
let kitty = {
name: "Sir Scratchewan",
age: 20,
breed: "Domestic Long-hair",
hobby: "play with yarn"
}
printCatInfo(kitty);
//prints: "Sir Scratchewan likes to play with yarn all day."
In the function declaration, I wrapped the parameters in braces, specifying which properties we need in the function, i.e. {name, hobby}
– the inputs need to match up perfectly for the function to correctly identify which properties it needs from the object.
Destructuring also helps you (and other code reviewers) understand functions more easily by hinting at which object properties are actually used.
5. Muli-Line Strings
Strings are cumbersome to work with, there's no denying that.
But, multi-line strings were especially bad before ES6.
Here's what they looked like:
//The old way
let multiline = 'multi-line string multi-line string,\n\t'
+ 'another line here\n\t'
+ 'another line here\n\t'
+ 'another line here\n\t'
;
In modern JS, we don't have to deal with all the new-line escaping because ES6 supports multi-line strings.
Just wrap your multi-line string with backticks, like so:
//The new way
let multiline = `this is a multi-line string!
Very easy to handle in ES6.
Even with ${variable.name}!
Much pleased, very wow.
`
Since we're already using backticks, you can also insert template literals, as we see in the third line of the multi-line string.
In general, I typically default to backtick-delimited strings for the flexibility (a string might become multi-line later in development, who knows).
6. Arrow Functions
Arrow functions are a complete game-changer in JavaScript, and something most frontend/full-stack developers use every day.
Let's look at code to understand them a bit more:
//The old way
function (param){
return param + 5;
}
//The new way
(param) => {
param + 5;
}
//Even simpler:
param => param + 5; // this is a completely valid ES6 function
Arrow functions are syntactic sugar for declaring and using functions, and allow us to condense functions into much cleaner code.
The =>
symbol is what separates our parameters from the return statement, and gives us the flexibility to omit function
in our declarations.
7. Class Syntax
Classes have a been a bit controversial in JavaSCript, but anyone coming from Java or any other OOP-oriented language will welcome them with open arms.
In ES6, creating classes is really just syntactic sugar for prototypal inheritance, which the mechanism JavaScript uses to manage object relationships.
Here's how classes look in ES6:
class Cat(){
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
toString() {
return `${name} is a ${breed} type of cat
.`;
}
}
class FluffyCat extends Cat(){
static default() {
return new FluffyCat('FlufferNutter', 'Ragdoll', 10);
}
constructor(name, breed, fluffiness) {
super(name, breed);
this.fluffiness = fluffiness;
}
}
For Java enthusiasts, this syntax will look and feel familiar.
In practice, though, a lot of the features for classes in ES6 are overshadowed by functional programming.
8. Let & Const
Before ES6, the default variable declaration keyword was var
and had a number of problems.
Most notably, variables declared with var
had global scope which led to a lot of general frustration in the developer community.
ES6's introduced let
and const
as a response to this, and it's really cleared up variable declaration best practices.
Here's how they work:
let greeting = "hi!"
let counter = 5;
if(counter > 3){
let secondGreeting = "hey there!"
}
console.log(secondGreeting);
// this will cause an error because let is block-scoped
When working with const
, as the name implies, we're not able to change the value of the variable once it's set:
const num = 5;
num = 7; //not allowed
//Const and arrays
const array = [1, 2, 3, 4];
array.pop(); // not allowed, it directly mutates the array
let newArray = [1, 2, 3];
array = newArray; // this is allowed, array now points to a new reference
The nuances of let
and const
are a bit tricky, but they offer a big improvement over var
and will help you ensure your code is bug-free and much more consistent.
9. Modules
JavaScript was originally used to create small programs or scripts that would run in the browser and handle a few key tasks (rendering resources, animations, and so on).
As web development became more reliant on JavaScript and its related technologies, programs quickly grew in size and complexity. It became apparent pretty fast that there needed to be a mechanism to split those large programs into smaller chunks, and import only the necessary parts when needed.
This is the basic context behind modules, a features in ES6 that allows programmers to label a section of code (a module) and inject that functionality into other programs.
The basic functions of modules can be acheived with two keywords – import
and export
– let's start with export
.
You can export pretty much anything in JavaScript, including the following:
- Functions
- Objects
- Variables
- Classes
Here's how it looks like in code:
/* 📁 File: example.js */
//Export array
export let days = ["Monday", "Tuesday", "Wednesday", Thursday", "Friday"];
//Export object
export let person = {name: "Ben", occupation: "Blogger"};
//Export class
export class Blogger() {
constructor(name){
this.name = name;
}
}
We declared all of the above exports in example.js
, so to use them in another file, we'd need to import each item:
/* 📁 File: main.js */
import {days, person, Blogger} from "./example.js";
let newBlogger = new Blogger("Rebecca"); // using imported object
In the above code, we must declare the proper syntax of the modules from example.js
or else they won't be imported properly. We can, however, choose to rename the variables or functions we're importing with the as
keyword:
/* 📁 File: main.js */
import {Blogger as newClassLabel} from "./example.js";
let newBlogger = new newClassLabel("Rebecca"); // using imported object
The as
keyword makes it very easy to keep your import statements clean and concise, especially if the modules you're importing have confusing variable or function names.
10. Promises
Promises represent the outcome of an event that occurs asynchronously.
In ES6, the interface we can use, as developers, to handle asynchronous data through promises is cleaner and more streamlined.
Let's look at an example.
Say we're building a little social media website and we want users to upload profile pictures. Once they upload a picture, we save the image to our database, but only if it's below 5 MB.
Seems reasonable enough – but how do promises fit in?
Well, we would ideally display a message to the user after they upload a picture – either “Your image was successfully uploaded” or “This image was too large”. The key is that we need to wait for our database to finishing checking the image before we can can display any message.
This example underlines a key principle with Promises – they can either be successful or unsuccessful, and it's our job as the developer to handle both cases.
Here's what the first bit of code looks like:
// Set up Promise
let myPromise = new Promise((resolve, reject) => {
//specify when promise should resolve
if (uploadImageToDatabase(myImageObject)) {
resolve("Successfully uploaded");
}
else {
reject("Unsuccessful");
}
}));
This is the basic set up for a Promise. It takes two arguments, the resolve
and reject
, which you can use to specify what happens when the Promises is successful or when it fails.
In the code snippet above, I invoke the method uploadImageToDatabase
, which is the asynchronous method we're interested in. When it returns a result, if it's successful (a.k.a returns true) the promise will resolve, or else it will fail.
Now that the Promise is set up, we can decide what to do next when we find out the result – we can accomplish this with the then
keyword:
//Handle Promise
myPromise.then((resolvedValue) => {
//invoke method to display the correct message
displayMessageToUser(resolvedValue); // "Successfully Uploaded"
}, (error) => {
displayMessageToUser(error); //"Unsuccessful"
});
Remember, in our Promise declaration above we set the resolve
function to return “Image Successfully Uploaded” . When we use then
later on, we can access that value and then display it to the user as a message.
11. Array Helper Functions
Array helper functions are functions bundled into ES6 that make it easier to manipulate or access data in arrays.
There are plenty of different array helper functions out there, each with a specific use case, but for now we'll look at two examples: forEach()
and map()
.
Here's how forEach()
works:
let arrayOfFruits = ["Apples", "Oranges", "Bananas"];
arrayOfFruits.forEach( fruitItem => console.log("I like " + fruitItem);
//Will print out:
//I like Apples
//I like Oranges
//I like Bananas
This is nifty for when you need to iterate through all the items in an array and perform some kind of functionality to each.
The next example that developers often use is map()
which allows you to take each item in an array and pass it through a function – map()
also returns the new array (where each of the items has been mutated by the function you define).
Here's how it works:
//Base array (before map)
let people = [ {name: "Ben", occupation: "Developer"}, {name: "Sarah", occupation: "Manager"}];
//mapped array
let occupations = people.map( person => person.occupation );
console.log(occupations);
// prints out: ["Developer", "Manager"];
The mapping function, person => person.occupation
, says that for each person object in the array, return the String that corresponds to that person's occupation and put it our new array.
The map()
function (and its siblings filter()
and reduce()
) are incredibly useful for manipulating object-based arrays, like you see in the example above. They offer a concise, readable syntax that's preferable over the equivalent code that uses for or while loops.
The Bottom Line
JavaScript is the core of our online world.
It runs our browsers, it runs any services or tools we use on the daily, and it's even rendering this article right now for you to read.
As web developers, a major tenet of our work is using JavaScript and all of its many features as best we can.
Luckily, the most recent update to JavaScript (ES6) made coding with JavaScript way easier – it introduced a ton of new features and syntax that allow web developers to program more efficiently.
If you're interested in web development as a career, it's a great idea to learn what ES6 can do – it will help you write cleaner, simpler, but more powerful code.
Did I miss any key features from ES6 that you like to use? Let me know in the comments below!