-
ECMAScript is a standardized version of JavaScript with the goal of unifying the language's specifications and features. As all major browsers and JavaScript-runtimes follow this specification, the term ECMAScript is interchangeable with the term JavaScript.
-
Most of the challenges on freeCodeCamp use the ECMAScript 5 (ES5) specification of the language, finalized in 2009.
-
The most recent standardized version is called ECMAScript 6 (ES6), released in 2015.This new version of the language adds some powerful features that will be covered in this section of challenges, including:
- Arrow functions
- Classes
- Modules
- Promises
- Generators
let
andconst
-
Note: Not all browsers support ES6 features. If you use ES6 in your own projects, you may need to use a program (transpiler) to convert your ES6 code into ES5 until browsers support ES6.
- One of the biggest problems with declaring variables with the
var
keyword is that you can overwrite variable declarations without an error.
var camper = 'James';
var camper = 'David';
console.log(camper);
// logs 'David
- In a small application, you might not run into this type of problem, but when your code becomes larger, you might accidentally overwrite a variable that you did not intend to overwrite.
- Because this behavior does not throw an error, searching and fixing bugs becomes more difficult.
- A new keyword called
let
was introduced in ES6 to solve this potential issue with thevar
keyword. - - If you were to replacevar
withlet
in the variable declarations of the code above, the result would be an error.
let camper = 'James';
let camper = 'David'; // throws an error
- When using
let
, a variable with the same name can only be declared once. - Note the
"use strict"
. This enables Strict Mode, which catches common coding mistakes and "unsafe" actions. For instance:
"use strict";
x = 3.14; // throws an error because x is not declared
-
When you declare a variable with the
var
keyword, it is declared globally, or locally if declared inside a function. -
When you declare a variable with the
let
keyword inside a block, statement, or expression, its scope is limited to that block, statement, or expression. -
Example:
var numArray = [];
for (var i = 0; i < 3; i++) {
numArray.push(i);
}
console.log(numArray);
// returns [0, 1, 2]
console.log(i);
// returns 3
- With the
var
keyword,i
is declared globally. So wheni++
is executed, it updates the global variable. This code is similar to the following:
var numArray = [];
var i;
for (i = 0; i < 3; i++) {
numArray.push(i);
}
console.log(numArray);
// returns [0, 1, 2]
console.log(i);
// returns 3
- This behavior will cause problems if you were to create a function and store it for later use inside a for loop that uses the
i
variable. - This is because the stored function will always refer to the value of the updated global
i
variable.
var printNumTwo;
for (var i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
// returns 3
- The
let
keyword does not follow this behavior:
'use strict';
let printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
// returns 2
console.log(i);
// returns "i is not defined"
const
has all the awesome features thatlet
has, with the added bonus that variables declared usingconst
are read-only. They are a constant value, which means that once a variable is assigned with const, it cannot be reassigned.- You should always name variables you don't want to reassign using the
const
keyword. This helps when you accidentally attempt to reassign a variable that is meant to stay constant. - A common practice when naming constants is to use all uppercase letters, with words separated by an underscore.
- Note: It is common for developers to use uppercase variable identifiers for immutable values and lowercase or camelCase for mutable values (objects and arrays).
"use strict";
const FAV_PET = "Cats";
FAV_PET = "Dogs"; // returns error
-
Some developers prefer to assign all their variables using
const
by default, unless they know they will need to reassign the value. Only in that case, they uselet
. -
However, it is important to understand that objects (including arrays and functions) assigned to a variable using
const
are still mutable. Using theconst
declaration only prevents reassignment of the variable identifier.
"use strict";
const s = [5, 6, 7];
s = [1, 2, 3]; // throws error, trying to assign a const
s[2] = 45; // works just as it would with an array declared with var or let
console.log(s); // returns [5, 6, 45]
const
declaration alone doesn't really protect your data from mutation. To ensure your data doesn't change, JavaScript provides a functionObject.freeze
to prevent data mutation.- Once the object is frozen, you can no longer add, update, or delete properties from it. Any attempt at changing the object will be rejected without an error.
let obj = {
name:"FreeCodeCamp",
review:"Awesome"
};
Object.freeze(obj);
obj.review = "bad"; // will be ignored. Mutation not allowed
obj.newProp = "Test"; // will be ignored. Mutation not allowed
console.log(obj);
// { name: "FreeCodeCamp", review:"Awesome"}
- Example: Use
Object.freeze()
to prevent mathematical constants from changing.
function freezeObj() {
"use strict";
const MATH_CONSTANTS = {
PI: 3.14
};
Object.freeze(MATH_CONSTANTS);
try {
MATH_CONSTANTS.PI = 99;
} catch (ex) {
console.log(ex);
}
return MATH_CONSTANTS.PI;
}
const PI = freezeObj();
- In JavaScript, we often don't need to name our functions, especially when passing a function as an argument to another function. Instead, we create inline functions. We don't need to name these functions because we do not reuse them anywhere else.
- To achieve this, we often use the following syntax:
const myFunc = function() {
const myVar = "value";
return myVar;
}
- ES6 provides us with the syntactic sugar to not have to write anonymous functions this way. Instead, you can use arrow function syntax:
const myFunc = () => {
const myVar = "value";
return myVar;
}
- When there is no function body, and only a return value, arrow function syntax allows you to omit the keyword return as well as the brackets surrounding the code. This helps simplify smaller functions into one-line statements:
- This code will still return value by default.
const myFunc = () => "value";
// doubles input value and returns it
const doubler = (item) => item * 2;
- If an arrow function has a single argument, the parentheses enclosing the argument may be omitted.
// the same function, without the argument parentheses
const doubler = item => item * 2;
- It is possible to pass more than one argument into an arrow function.
// multiplies the first input value by the second and returns it
const multiplier = (item, multi) => item * multi;
- Example:
const myConcat = (arr1, arr2) => {
"use strict";
return arr1.concat(arr2);
};
// test your code
console.log(myConcat([1, 2], [3, 4, 5]));
- In order to help us create more flexible functions, ES6 introduces default parameters for functions.
const greeting = (name = "Anonymous") => "Hello " + name;
console.log(greeting("John")); // Hello John
console.log(greeting()); // Hello Anonymous
- In order to help us create more flexible functions, ES6 introduces the rest parameter for function parameters.
- With the rest parameter, you can create functions that take a variable number of arguments.
- These arguments are stored in an array that can be accessed later from inside the function.
- The rest parameter eliminates the need to check the args array and allows us to apply
map()
,filter()
andreduce()
on the parameters array. - Relevant LInks
function howMany(...args) {
return "You have passed " + args.length + " arguments.";
}
console.log(howMany(0, 1, 2)); // You have passed 3 arguments.
console.log(howMany("string", null, [1, 2, 3], { })); // You have passed 4 arguments.
- Example: Modify the function
sum
using the rest parameter in such a way that the functionsum
is able to take any number of arguments and return their sum.
const sum = (...args) => {
return args.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
-
ES6 introduces the spread operator, which allows us to expand arrays and other expressions in places where multiple parameters or elements are expected.
-
The ES5 code below uses
apply()
to compute the maximum value in an array:
var arr = [6, 89, 3, 45];
var maximus = Math.max.apply(null, arr); // returns 89
- We had to use
Math.max.apply(null, arr)
becauseMath.max(arr)
returnsNaN
.Math.max()
expects comma-separated arguments, but not an array. The spread operator makes this syntax much better to read and maintain. ...arr
returns an unpacked array. In other words, it spreads the array.
const arr = [6, 89, 3, 45];
const maximus = Math.max(...arr); // returns 89
- However, the spread operator only works in-place, like in an argument to a function or in an array literal. The following code will not work:
const spreaded = ...arr; // will throw a syntax error
- Example: Copy all contents of
arr1
into another arrayarr2
using the spread operator.
const arr1 = ["JAN", "FEB", "MAR", "APR", "MAY"];
let arr2;
(function() {
"use strict";
arr2 = [...arr1];
})();
console.log(arr2);
-
Destructuring assignment is special syntax introduced in ES6, for neatly assigning values taken directly from an object.
-
Consider the following ES5 code:
const user = { name: 'John Doe', age: 34 };
const name = user.name; // name = 'John Doe'
const age = user.age; // age = 34
- Here's an equivalent assignment statement using the ES6 destructuring syntax:
- You can extract as many or few values from the object as you want.
const { name, age } = user;
// name = 'John Doe', age = 34
- Example: Replace the two assignments with an equivalent destructuring assignment. It should still assign the variables
today
andtomorrow
the values of today and tomorrow from theHIGH_TEMPERATURES
object.
const HIGH_TEMPERATURES = {
yesterday: 75,
today: 77,
tomorrow: 80
};
// change code below this line
const { today, tomorrow } = HIGH_TEMPERATURES;
// change code above this line
console.log(yesterday) // should be not defined
console.log(today); // should be 77
console.log(tomorrow); // should be 80
-
Destructuring allows you to assign a new variable name when extracting values. You can do this by putting the new name after a colon when assigning the value.
-
Using the same object from the last example:
const user = { name: 'John Doe', age: 34 };
- Here's how you can give new variable names in the assignment:
- You may read it as "get the value of
user.name
and assign it to a new variable nameduserName
" and so on.
const { name: userName, age: userAge } = user;
// userName = 'John Doe', userAge = 34
- Using an object similar to previous examples:
const user = {
johnDoe: {
age: 34,
email: 'johnDoe@freeCodeCamp.com'
}
};
- Here's how to extract the values of object properties and assign them to variables with the same name:
const { johnDoe: { age, email }} = user;
- And here's how you can assign an object properties' values to variables with different names:
const { johnDoe: { age: userAge, email: userEmail }} = user;
- Destructuring an array lets us do exactly that:
const [a, b] = [1, 2, 3, 4, 5, 6];
console.log(a, b); // 1, 2
- We can also access the value at any index in an array with destructuring by using commas to reach the desired index:
const [a, b,,, c] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c); // 1, 2, 5
- Example: Use destructuring assignment to swap the values of
a
andb
so that a receives the value stored inb
, andb
receives the value stored ina
.
let a = 8, b = 6;
[a, b] = [b, a];
- The result is similar to Array.prototype.slice(), as shown below:
- As in, you cannot use the rest parameter to catch a subarray that leaves out the last element of the original array.
const [a, b, ...arr] = [1, 2, 3, 4, 5, 7];
console.log(a, b); // 1, 2
console.log(arr); // [3, 4, 5, 7]
- Consider the code below:
const profileUpdate = (profileData) => {
const { name, age, nationality, location } = profileData;
// do something with these variables
}
- This effectively destructures the object sent into the function. This can also be done in-place:
const profileUpdate = ({ name, age, nationality, location }) => {
/* do something with these fields */
}
-
This removes some extra lines and makes our code look neat.
-
This has the added benefit of not having to manipulate an entire object in a function — only the fields that are needed are copied inside the function.
-
Example: Use destructuring assignment within the argument to the function half to send only max and min inside the function.
const stats = {
max: 56.78,
standard_deviation: 4.34,
median: 34.54,
mode: 23.87,
min: -0.75,
average: 35.85
};
const half = ({ max, min }) => (max + min) / 2.0;
- Template literals allow you to create multi-line strings and to use string interpolation features to create strings.
- Consider the code below:
const person = {
name: "Zodiac Hasbro",
age: 56
};
// Template literal with multi-line and string interpolation
const greeting = `Hello, my name is ${person.name}!
I am ${person.age} years old.`;
console.log(greeting); // prints
// Hello, my name is Zodiac Hasbro!
// I am 56 years old.
-
Firstly, the example uses backticks (`), not quotes (
'
or"
), to wrap the string. -
Secondly, notice that the string is multi-line, both in the code and the output. This saves inserting
\n
within strings. -
The
${variable}
syntax used above is a placeholder. -
Similarly, you can include other expressions in your string literal, for example
${a + b}
. -
Example:
-
Use template literal syntax with backticks to display each entry of the result object's failure array. Each entry should be wrapped inside an li element with the class attribute text-warning, and listed within the resultDisplayArray.
-
Use an iterator method (any kind of loop) to get the desired output (shown below).
[ '<li class="text-warning">no-var</li>', '<li class="text-warning">var-on-top</li>', '<li class="text-warning">linebreak</li>' ]
-
const result = {
success: ["max-length", "no-amd", "prefer-arrow-functions"],
failure: ["no-var", "var-on-top", "linebreak"],
skipped: ["id-blacklist", "no-dup-keys"]
};
function makeList(arr) {
"use strict";
const resultDisplayArray = [];
for (let i = 0; i < arr.length; i++) {
resultDisplayArray.push(`<li class="text-warning">${arr[i]}</li>`);
}
return resultDisplayArray;
}
/**
* makeList(result.failure) should return:
* [ `<li class="text-warning">no-var</li>`,
* `<li class="text-warning">var-on-top</li>`,
* `<li class="text-warning">linebreak</li>` ]
**/
const resultDisplayArray = makeList(result.failure);
const result = {
success: ["max-length", "no-amd", "prefer-arrow-functions"],
failure: ["no-var", "var-on-top", "linebreak"],
skipped: ["id-blacklist", "no-dup-keys"]
};
function makeList(arr) {
"use strict";
// change code below this line
const resultDisplayArray = arr.map(item => `<li class="text-warning">${item}</li>`);
// change code above this line
return resultDisplayArray;
}
/**
* makeList(result.failure) should return:
* [ `<li class="text-warning">no-var</li>`,
* `<li class="text-warning">var-on-top</li>`,
* `<li class="text-warning">linebreak</li>` ]
**/
const resultDisplayArray = makeList(result.failure);
- Consider the following code:
const getMousePosition = (x, y) => ({
x: x,
y: y
});
- You can simply write x once, and it will be converted tox: x (or something equivalent) under the hood.
- Here is the same function from above rewritten to use this new syntax:
const getMousePosition = (x, y) => ({ x, y });
- Example: Use object property shorthand with object literals to create and return an object with name, age and gender properties.
const createPerson = (name, age, gender) => {
"use strict";
// change code below this line
return {
name,
age,
gender
};
// change code above this line
};
- When defining functions within objects in ES5, we have to use the keyword
function
as follows:
const person = {
name: "Taylor",
sayHello: function() {
return `Hello! My name is ${this.name}.`;
}
};
- With ES6, You can remove the
function
keyword and colon altogether when defining functions in objects. Here's an example of this syntax:
const person = {
name: "Taylor",
sayHello() {
return `Hello! My name is ${this.name}.`;
}
};
-
It should be noted that the
class
syntax is just syntax, and not a full-fledged class-based implementation of an object-oriented paradigm, unlike in languages such as Java, js, Ruby, etc. -
In ES5, we usually define a constructor function and use the
new
keyword to instantiate an object.
var SpaceShuttle = function(targetPlanet){
this.targetPlanet = targetPlanet;
}
var zeus = new SpaceShuttle('Jupiter');
- The
class
syntax simply replaces the constructor function creation:
class SpaceShuttle {
constructor(targetPlanet) {
this.targetPlanet = targetPlanet;
}
}
const zeus = new SpaceShuttle('Jupiter');
- It should be noted that the
class
keyword declares a new function, to which a constructor is added. This constructor is invoked whennew
is called to create a new object. - Notes:
- UpperCamelCase should be used by convention for ES6 class names, as in
SpaceShuttle
used above. - The constructor method is a special method for creating and initializing an object created with a class. You will learn more about it in the Object Oriented Programming section of the JavaScript Algorithms And Data Structures Certification.
- UpperCamelCase should be used by convention for ES6 class names, as in
-
You can obtain values from an object and set the value of a property within an object.
-
These are classically called getters and setters.
-
Getter functions are meant to simply return (get) the value of an object's private variable to the user without the user directly accessing the private variable.
-
Setter functions are meant to modify (set) the value of an object's private variable based on the value passed into the setter function. This change could involve calculations, or even overwriting the previous value completely.
class Book {
constructor(author) {
this._author = author;
}
// getter
get writer() {
return this._author;
}
// setter
set writer(updatedAuthor) {
this._author = updatedAuthor;
}
}
const lol = new Book('anonymous');
console.log(lol.writer); // anonymous
lol.writer = 'wut';
console.log(lol.writer); // wut
-
Notice the syntax used to invoke the getter and setter. They do not even look like functions. Getters and setters are important because they hide internal implementation details.
-
Note: It is convention to precede the name of a private variable with an underscore (
_
). However, the practice itself does not make a variable private. -
Example:
class Thermostat {
constructor(fahrenheit) {
this.fahrenheit = fahrenheit;
}
get temperature() {
return (5 / 9) * (this.fahrenheit - 32);
}
set temperature(celsius) {
this.fahrenheit = (celsius * 9.0) / 5 + 32;
}
}
-
ES6 introduced a way to easily share code among JavaScript files. This involves exporting parts of a file for use in one or more other files, and importing the parts you need, where you need them. In order to take advantage of this functionality, you need to create a script in your HTML document with a type of
module
. Here’s an example:<script type="module" src="filename.js"></script>
-
Example:
<html>
<body>
<!-- add your code below -->
<script type="module" src="index.js"></script>
<!-- add your code above -->
</body>
</html>
- Imagine a file called
math_functions.js
that contains several functions related to mathematical operations. One of them is stored in a variable,add
, that takes in two numbers and returns their sum. You want to use this function in several different JavaScript files. In order to share it with these other files, you first need toexport
it.
export const add = (x, y) => {
return x + y;
}
- The above is a common way to export a single function, but you can achieve the same thing like this:
const add = (x, y) => {
return x + y;
}
export { add };
- When you export a variable or function, you can import it in another file and use it without having to rewrite the code.
- You can export multiple things by repeating the first example for each thing you want to export, or by placing them all in the export statement of the second example, like this:
export { add, subtract };
import
allows you to choose which parts of a file or module to load. In the previous lesson, the examples exportedadd
from themath_functions.js
file. Here's how you can import it to use in another file:
import { add } from './math_functions.js';
- You can import more than one item from the file by adding them in the
import
statement like this:
import { add, subtract } from './math_functions.js';
- This can be done with the
import * as
syntax. Here's an example where the contents of a file namedmath_functions.js
are imported into a file in the same directory:
import * as myMathModule from "./math_functions.js";
- The above
import
statement will create an object calledmyMathModule
. This is just a variable name, you can name it anything. - The object will contain all of the exports from
math_functions.js
in it, so you can access the functions like you would any other object property. - Here's how you can use the
add
andsubtract
functions that were imported:
myMathModule.add(2,3);
myMathModule.subtract(5,3);
-
In the
export
lesson, you learned about the syntax referred to as a named export. This allowed you to make multiple functions and variables available for use in other files. -
There is another
export
syntax you need to know, known as export default. Usually you will use this syntax if only one value is being exported from a file. It is also used to create a fallback value for a file or module. -
Below are examples using
export default
:
// named function
export default function add(x, y) {
return x + y;
}
// anonymous function
export default function(x, y) {
return x + y;
}
- Since
export default
is used to declare a fallback value for a module or file, you can only have one value be a default export in each module or file. - Additionally, you cannot use
export default
withvar
,let
, orconst
.
- To import a default export, you need to use a different
import
syntax. In the following example,add
is the default export of themath_functions.js
file. Here is how to import it:
import add from "./math_functions.js";
- The syntax differs in one key place. The imported value,
add
, is not surrounded by curly braces ({}
).add
here is simply a variable name for whatever the default export of themath_functions.js
file is. You can use any name here when importing a default.
- A promise in JavaScript is exactly what it sounds like - you use it to make a promise to do something, usually asynchronously.
- When the task completes, you either fulfill your promise or fail to do so.
Promise
is a constructor function, so you need to use thenew
keyword to create one.- It takes a function, as its argument, with two parameters -
resolve
andreject
. These are methods used to determine the outcome of the promise. The syntax looks like this:
const myPromise = new Promise((resolve, reject) => {
});
- A promise has three states:
pending
,fulfilled
, andrejected
. - The promise you created in the last challenge is forever stuck in the
pending
state because you did not add a way to complete the promise. - The
resolve
andreject
parameters given to the promise argument are used to do this. resolve
is used when you want your promise to succeed, andreject
is used when you want it to fail.- These are methods that take an argument, as seen below.
const myPromise = new Promise((resolve, reject) => {
if(condition here) {
resolve("Promise was fulfilled");
} else {
reject("Promise was rejected");
}
});
-
The example above uses strings for the argument of these functions, but it can really be anything.
-
Often, it might be an object, that you would use data from, to put on your website or elsewhere.
-
Example:
const makeServerRequest = new Promise((resolve, reject) => {
// responseFromServer represents a response from a server
let responseFromServer;
if(responseFromServer) {
resolve("We got the data");
} else {
reject("Data not received");
}
});
- Promises are most useful when you have a process that takes an unknown amount of time in your code (i.e. something asynchronous), often a server request.
- When you make a server request it takes some amount of time, and after it completes you usually want to do something with the response from the server.
- This can be achieved by using the
then
method. Thethen
method is executed immediately after your promise is fulfilled withresolve
. Here’s an example:
myPromise.then(result => {
// do something with the result.
});
- Example:
const makeServerRequest = new Promise((resolve, reject) => {
// responseFromServer is set to true to represent a successful response from a server
let responseFromServer = true;
if(responseFromServer) {
resolve("We got the data");
} else {
reject("Data not received");
}
});
makeServerRequest.then(result => {
console.log(result);
});
catch
is the method used when your promise has been rejected. It is executed immediately after a promise'sreject
method is called. Here’s the syntax:error
is the argument passed in to thereject
method.- Note: the
then
andcatch
methods can be chained to the promise declaration if you choose.
myPromise.catch(error => {
// do something with the error.
});
- Example:
const makeServerRequest = new Promise((resolve, reject) => {
// responseFromServer is set to false to represent an unsuccessful response from a server
let responseFromServer = false;
if(responseFromServer) {
resolve("We got the data");
} else {
reject("Data not received");
}
});
makeServerRequest.then(result => {
console.log(result);
});
makeServerRequest.catch(error => {
console.log(error);
});