Article

ECMAScript 6 Tutorial 2025

Learn everything about ECMAScript and its evolution, especially the important ES6 JavaScript features like let, const, arrow functions, promises, and modules.

ECMAScript is a standard that defines scripting languages like JavaScript . Managed by ECMA International, it serves as the blueprint for various scripting languages such as JScript and ActionScript , but JavaScript is the most widely adopted implementation of ECMAScript.

ECMAScript releases periodic updates to make scripting languages more efficient and user-friendly. Let’s dive into JavaScript ES6 (also known as ECMAScript 2015), one of the most important versions, and explore the key features it introduced.

JavaScript ES6 , or ECMAScript 2015, was a major update that revolutionized JavaScript by improving readability and efficiency. Before ES6, JavaScript was less effective in handling large-scale applications. ES6 introduced new syntax and features that made JavaScript cleaner and more powerful.

In ES6, let and const replace var for variable declarations. Let provides block scoping , which means variables declared with let are only accessible within the code block they are defined. Var , on the other hand, is function-scoped and can cause issues with accidental variable reuse.

Below, let ’s block scope allows us to declare the variable codeSmith which will only be accessible within the code block between lines 22-25 and is not tied to the codeSmith variable declared on line 20. If we were to execute the code above using var instead of let , our code would reassign the value of codeSmith on line 23.

Similar to var , variables declared using let are mutable . This means that if you initially assign a value to a let variable, you can reassign (change) that value later on with no issue.

Let’s say you have a function that keeps track of log in attempts. You initialize a variable let loginAttempts = 0. Each time a user tries to log in unsuccessfully, you want to increment loginAttempts so that, once it hits 3, the user needs to wait one hour before attempting to log in again.

Often, we want our variables’ values to be immutable, meaning that they cannot be reassigned. This is where the const keyword comes in.

Const ensures a variable cannot be reassigned after initialization, although objects and arrays can still be mutated. This is particularly useful for ensuring immutability within your code.

Just like let , the const keyword is block scoped. Below, a variable is declared using const on line 20. Within the code block between lines 23 and 26, a variable with the same label of codeSmith is declared but only accessible within that code block.

It’s important to note that, while variables declared with const cannot be reassigned , their values can still be mutated if they reference an object (including arrays). For example, suppose Jane owns a house (represented by an object). She can repaint the walls or renovate rooms (mutating the object’s properties), but she cannot sell her current house and buy a completely new one (reassigning the variable to a different object).

Below, the value of the first element “array” can be reassigned as arrays are mutable. However, trying to reassign “ array” to a completely new array (or any other value) will throw an error.

Arrow functions simplify the syntax for writing function expressions. They are especially useful for small functions and improve code readability. The syntax is shorter compared to traditional function expressions:

A function declaration is when a function is defined without being assigned to a variable. A function expression is when a function is assigned to a variable. In JavaScript ES6, arrow functions allow for shorter, and oftentimes, more readable syntax for writing function expressions .

A function expression is initialized using var.

Using ES6, that expression can (and should) be initialized using const . This ensures that secretNumber cannot accidentally be reassigned to anything else. Using arrow function syntax, secretNumber is defined using shorter syntax The return keyword can be omitted if you are writing a function as a single line statement as seen on line 48.

Arrow functions are good to use when you have to write a simple function like the one in the example above and do not necessarily need a function body. They are also useful within higher order functions (map, filter, reduce, etc.).

Arrow functions should not be used when you need your function to be bound to its own this object or when defining constructor functions. Check out this article to learn more about JavaScript Arrow functions . Template literals make string interpolation and multi-line strings much easier than traditional string concatenation.

Template literals are an incredibly useful feature introduced by JavaScript ES6 and you’re going to find yourself using them quite a lot throughout your career. Before their introduction in 2015, strings had to be concatenated in the following way:

The example above works fine, but if you’re concatenating long strings, this can take quite a bit of time and become pretty tedious. This is remedied using a template literal . In the example below: Backticks must be used instead of single or double quotes. The parameter to be added into the string must be placed within a dollar sign and curly brackets ( ${name} ).

Template literals can also be used for expression embedding as seen below.

It’s important to also note that template literals preserve line breaks allowing you to create strings that span multiple lines without having to use ‘/n’.

Destructuring allows for easy extraction of values from objects or arrays and assigning them to variables in a cleaner, more readable way.

If you’re new to ES6, destructuring may feel a bit difficult to wrap your head around because the syntax is different from traditional variable assignments but, with a little practice, it will start to feel intuitive. Destructuring allows you to access properties within an object and assign them to a variable at the same time.

Multiple properties can be accessed within the object at the same time. Note that the order of the keys does not matter, but the variable being initialized must have the same name as the corresponding property key in the object ( exObject ). For example, below,

const { doggies } = exObj would log ‘undefined’ .

Destructuring can be used for arrays as well. In the example below, the variable dog will have the value of the element with the matching index in exArray (0 in this case). Similar to object destructuring , multiple elements can be accessed at the same time. You can also skip elements in the array by leaving gaps.

For a more detailed breakdown of destructuring in JavaScript, check out this article . The spread operator is used to take the elements of an array or object and spread them out into individual items.

Below, we see an example of the spread operator being used to spread the properties in obj to be combined with newObj resulting in an object with four properties.

This JavaScript ES6 feature is also an excellent way to concatenate two arrays. Below, the spread operator spreads the elements within array out into individual elements within newArray resulting in one large array with all five elements.

In the example above, the spread operator creates a shallow copy of exArray and pastes the elements into newArray . Below, we see what would happen if we did not use the spread operator. exArray is defined and assigned to an array containing strings ‘hello’, ‘hi’, and ‘bye’.

newArray1 creates a new array containing a reference to exArray. newArray2 creates a new array containing a shallow copy of the elements within exArray . Compare the console.log’s of newArray1 and newArray2 on lines 78 and 79 On line 81, the first element in exArray is reassigned to ‘hotdog’

Because newArray1 contains a reference to exArray this change is reflected on line 82

This change is not reflected on line 83 because the spread operator created a shallow copy of exArray when newArray2 was defined on line 77.

This JavaScript ES6 feature is particularly useful when defining a function that may take an unknown number of arguments. The rest parameter creates an iterable array containing all of the remaining arguments after the named arguments have been assigned. Below is a simple example of how an unknown number of inputs can be operated on within a function using the rest parameter to transform those inputs into an array.

For more information on the spread operator and rest parameters , be sure to check out Codesmith’s blog on the subjects.

Another major feature added to JavaScript by ES6 is classes . Before ES6, inheritance was handled by manually manipulating objects’ prototypal chains to share methods and properties. For example:

Above, each new instance of the Animal constructor function will have its own unique species property (seen on line 124). Because the speak method has been placed in the Animal prototype, each new instance of Animal (dog and monkey) will have access to it as we see on lines 126 and 127.

This syntax can take a while to understand and is considered to be a bit antiquated today. Below we see an example of what is often referred to as “syntactic sugar”. Everything is still happening the same under the hood, but ES6 classes provide a more concise syntax for implementing inheritance.

A class of Animal initialized on line 113. This class has a constructor function that takes in a type . Each new instance of Animal will have its own unique species property.

On line 120, we see the ES6 syntax for adding a method ( speak) to a class’s prototype as opposed to manually adding it (ie: Animal.prototype.speak = function(){...}).

With ES6 classes, unique data (like this.species ) goes directly on the constructor while the functionality that we want each instance to have access to (like the speak method) belongs on the prototype.

Prototypal inheritance and ES6 classes are incredibly powerful. It can take a little bit of time to get the hang of it but, luckily, Codesmith offers some fantastic resources on the subjects. Be sure to take a look at their “ Hard Parts ” lecture and CSX unit on “Prototypal Inheritance and ES6 Classes”.

JavaScript’s ES6 Promises were introduced to provide a simple, uniformed way of handling asynchronous code. Before ES6, asynchronous operations were primarily handled using callbacks and third party libraries.

With the Promise Object , engineers have complete agency over the asynchronous code they want to write in a standardized, straight forward syntax. This has improved readability, allowed for the chaining of asynchronous functions, and made error handling a lot easier.

A Promise is an object that represents a value that may not be available right away but will be resolved eventually. The Promise Object : Has one of three states, Pending, Fulfilled, or Rejected. Is created using the Promise Constructor with the new keyword (sound familiar?).

The Promise Constructor will take in a function (known as the executor ). The executor takes two parameters, resolve and reject . promiseEx is will either resolve or reject after 1000 milliseconds (1 second) if promiseEx resolves we use .then() to handle whatever we pass into resolve() (‘truthy answer!’ in this case)

otherwise, we use .catch() to handle anything passed into reject() (‘falsy answer!’).

Promises are fundamental to modern JavaScript asynchronous programming but they can take time to get comfortable with so don’t stress if this feels a bit confusing. With some practice, you’ll be using Promises in no time, we promise! Check out this article for a deeper dive on JavaScript Promises .

Default parameters are a small, yet incredibly useful change in ES6 that simplify your code and help with error prevention. Instead of having to write extra code to set default values when an argument is missing, like this: ES6 lets us define default values directly in the function declaration! Like this:

In the example above, the default value of ‘Friend’ is used when the function is called with a missing argument. When called again with ‘Sam’ passed in, the default of ‘Friend’ is overwritten by ‘Sam’. Default parameters even allow us to use expressions, and reference other parameters, like this:

‘width’ defaults to 60 inches (standard queen size). The length parameter is calculated using an expression that references the width parameter multiplied by 1.33

If there are no arguments provided, the default parameters would evaluate to 60 and 80 so our function would return 4797 square inches.

On line 11, we pass in 76 (standard king width). The default width is overwritten by 76, so our length parameter evaluates to an argument of 101.08, and our function returns 8092.8 square inches. That might be a silly example (Sam may or may not have been shopping for a mattress this week), but it perfectly demonstrates the flexibility and simplification that default parameters provide!

By now, you’ve probably used a general for loop to iterate over a string or array and access each character or element by its index . A for…of loop iterates over a collection of data and directly accesses its values instead of its keys or indices . Below, a for…of loop is used to access the values of strings and arrays.

Below, see how a for…in loop is used to iterate over an array and access each index (instead of the value of each index).

Note: It is advised to use for…in loops for objects but not for arrays. This is because a for…in loop will iterate through custom properties on an array. To avoid side effects like the unexpected behavior seen in the example above, only use for…in loops for objects.

Set Objects are data structures released by ES6 that are very similar to arrays . Much like arrays , Set Objects : Can be expanded using the spread operator . Contain iterator methods (though many function differently than array iterator methods). Can use the forEach method to iterate through and perform functions on their values.

Can use for…of loops to iterate through their values. However, there are several key differences between Sets and arrays : Unlike arrays, Set Objects do not allow for duplicate values. Sets do not support index-based searches (ie: example[0]).

Sets do not support array methods such as reduce , map , filter , find , and more. However, they can be converted into arrays and then perform these methods. This can be done by simply using the spread operator .

Below, we see an example of a new Set object being initialized with the label setEx . Notice how only one string of ‘hi’ is found in setEx and, on line 12, we can convert it into an array and assign it to the variable setAsArray .

Another key advantage of using Sets over Arrays is that Sets are more efficient when it comes to looking up their values. Lookup operations in Sets are much faster (usually O(1) - constant). Below, invoking setEx.has() is faster than invoking arrayEx.includes() because the Array.includes() must iterate through the entire array whereas Set.has() does not.

Be sure to check out other useful Set methods like delete(), clear(), size(), forEach(), values(), and more and you’ll quickly find how powerful these data structures can be!

Map objects are another useful data structure in ES6 JavaScript that share some similarities with regular objects, but also offer some improvements in functionality for dealing with key-value pairs. Much like objects, Maps : Provide the ability to add and update keys and values at runtime.

Contain iterator methods like .keys() , .values() , and .entries() , which are similar to Object.keys(obj) , Object.entries(obj) , etc., but they don’t return arrays and work a bit differently under the hood. Allow for the deletion of keys with their corresponding values.

The main differences and improvements that Maps bring to the table, include:

Non-string keys: Where normal objects always convert keys into strings, Maps allow for any data type including numbers, strings, objects, and even functions to be used as keys.

Insertion order maintenance: Where normal objects will not always guarantee the insertion order of entries to be maintained for iteration, Map objects can always be iterated over in the exact order that the entries were inserted.

Built-in Methods: Normal objects require manual syntax with assignment operators and utility functions to add, update, search, and delete; while Maps have methods like .set() , .get() , .has() , and .delete() , which use a hash table under the hood for constant O(1) lookup time.

Creates an object using object literal syntax . Manually adds entries to the object on lines 4 and 5. Accesses elements of the object using dot notation on line 9. Modifies the object's properties using the assignment operator on line 11. This example would work well for basic cases, but here’s an example of how Map objects open up the realm of possibility:

On line 3, we create a new instance of Map by calling its global constructor, and assign it the label map.

On lines 6-8, the built-in .set() method adds key value pairs to our new Map instance. Notice how each of the keys we’re adding as the first argument to .set() are different data types. On lines 11-12 we use the .get() method to access our values directly if we know that a key exists within the Map object.

If we are unsure that a key exists within a Map object, we check for it by using the .has() method, as seen on line 15. On line 18, we use Map’s built-in .delete() method passing in a key to remove a key-value from our Map object.

Maps are a major efficiency upgrade in working with key-value pairs. Be sure to check out other useful Map methods, like .clear() , .size() , and .forEach() .

ECMAScript 6 introduced many useful global methods - methods (functions) that can be used anywhere in your code without calling a new instance of an object or class. Some of the most popular global methods in ES6 JavaScript include:

Array.from() - converts data types with length properties into true arrays. You may pass a second argument to it, which can be a map function to transform elements as you convert them into an array. Array.find() - finds the first instance of an element in an array that satisfies a provided function.

Array.findIndex() - very similar to Array.find() but returns the index of the first element in an array that satisfies a provided function. String.includes() - returns whether or not a string includes a specified substring. String.startsWith() - checks if a string starts with the specified substring.