Some of the features introduced in ECMAScript 6 (ES6) and later versions of JavaScript are not supported by all (old) browsers. However, it’s safe to use them in Node.js and Express.js applications, as long as you are using a recent version of Node.js.
Here is introduced some of the features we will be using in this course.
Also, you might need to recap the first year JavaScript course. Especially the Arrays, objects and functions section.
Use const for variables that are not reassigned, and let for variables that are reassigned. Never use var.
Rule of thumb: use const by default, and only use let if you know that the variable will be reassigned (const won’t work). Never use var.
{ and }) you have a code block)let and const bindings have a local scope. var behaves in different manner. Just don’t use it.const a = 6;
let b = 3;
{
const a = 9;
const c = 4;
{
let c = 1;
console.log(a + b + c); // --> 13
b = 2;
}
console.log(a + b); // --> 11
}
console.log(a + b); // --> 8
${}) for inserting variables or expressions.Example:
const name = 'Alice';
const greeting = `Hello ${name}!`;
console.log(greeting); // 'Hello Alice!'
Arrow functions were introduced in ECMAScript 6 (ES6) and provide a more concise syntax for creating functions. They offer a cleaner and often more readable way to write functions, especially for short, one-line operations. Here’s what you need to know about arrow functions:
() => {} syntax.=>, and then the function body (enclosed in curly braces).this value from the surrounding code.new keyword.this context in arrow functions can be advantageous. They use lexical scoping, meaning they inherit the this value from the enclosing function or context.array.map(), array.filter(), and other cases where a concise function is required.Examples:
// Regular function
function multiply(a, b) {
return a * b;
}
// Equivalent arrow function
const multiply = (a, b) => a * b;
// Using arrow function with array.map()
const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map((num) => num ** 2);
this context or more than one statement.In arrow functions, parentheses play a crucial role in defining parameters and in certain cases, the function body. Here are the key rules to keep in mind:
(x) => x * 2 can be simplified to x => x * 2.(x, y) => x + y.{} around the function body. The return value of the expression will be automatically returned.return statements, you must enclose the function body in curly braces {}.Single parameter, single expression:
const double = (x) => x * 2;
Multiple parameters, single expression:
const sum = (x, y) => x + y;
Multiple parameters, multiple statements:
const multiplyAndLog = (x, y) => {
const result = x * y;
console.log(result);
return result;
};
When you want to return an object literal directly from an arrow function, you need to enclose the object in parentheses. This is to avoid confusion with the function body being mistaken for a block.
const createPerson = (name, age) => ({name, age});
As mentioned earlier, arrow functions with a single expression have an implicit return. This means that the result of the expression is automatically returned without needing the return keyword.
const double = (x) => x * 2; // Implicit return of x * 2
Remember that the choice of parentheses depends on the context and the structure of your arrow function. Following these rules will help you create arrow functions that are both concise and clear in their intent.
Modules are a way to organize, structure, and encapsulate code in JavaScript applications. They allow you to break down your code into smaller, manageable pieces, making it easier to maintain, collaborate, and reuse. ES6 introduced a standardized module system that provides a clean and efficient way to work with modules.
Exporting Modules: You can use the export keyword to export functions, variables, classes, or objects from a module.
// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
Importing Modules: The import statement is used to import functionalities from other modules.
// index.js
import {add, subtract} from './math.mjs';
console.log(add(5, 3)); // 8
Default Exports: A module can have a default export, which is the main exported value from the module.
// utility.mjs
export default function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// index.js
import capitalize from './utility.mjs';
Namespace Imports: You can import all exported values from a module into a single object using the * as syntax.
// library.mjs
export const func1 = () => {};
export const func2 = () => {};
// index.js
import * as library from './library.mjs';
library.func1(); // Calling Function 1
library.func2();
Modules provide a clean and organized way to structure your codebase, making it easier to manage larger projects and promote code reusability. As you become more comfortable with modules, you’ll find them invaluable for maintaining a well-organized and maintainable codebase.
The export default statement allows you to export a single value as the default export from a module. This value is typically the main or most important functionality that you want to share from the module.
// utility.mjs
const greet = (name) => `Hello, ${name}!`;
export default greet;
// index.js
import greet from './utility.mjs';
console.log(greet('Alice')); // 'Hello, Alice!'
In this example, the greet function is exported as the default export from the utility.mjs module, and it’s imported and used in the index.js file.
export { ... }Named exports allow you to export multiple values from a module, each with its own name. This is useful when you want to export multiple functions, variables, or classes from the same module.
// math.mjs
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
export {add, subtract}
// index.js
import {add, subtract} from './math.mjs';
console.log(add(5, 3)); // 8
In this example, both the add and subtract functions are exported as named exports from the math.mjs module and imported into the index.js file.
You can combine export default and named exports in the same module to create a flexible and comprehensive module API.
// utilities.mjs
export default function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const greet = (name) => `Hello, ${name}!`;
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
export {greet, capitalize}
// index.js
import getRandomNumber, {greet, capitalize} from './utilities.mjs';
console.log(getRandomNumber(1, 100));
console.log(greet('Alice'));
console.log(capitalize('hello'));
In this example, the getRandomNumber function is exported as the default export, while the greet and capitalize functions are exported as named exports from the utilities.mjs module.
The ES6 module syntax (import/export) is natively supported by modern web browsers. However, there are some considerations to keep in mind:
When using ES6 modules in a browser environment, you need to specify the type="module" attribute in the script
tag.
<script type="module" src="app.js"></script>
In this case the defer attribute is not required, since the processing of the script contents is deferred.
Node.js introduced support for ES6 modules starting from version 12. Before that, Node.js only supported the CommonJS module syntax (require/module.exports). Here are some important considerations when using ES6 modules in Node.js:
.mjs extension for module files, or enable ESM in .js files by adding "type": "module" to your package.json. This is the preferred way in this course.require and module.exports syntax in Node.js. However, mixing ESM and CommonJS
syntax in the same file can lead to confusion.Error handling is an important aspect of writing robust and reliable code. JavaScript provides a built-in Error class that can be used to create and throw errors. Errors are handled using the try...catch statement, which allows you to catch errors and handle them gracefully.
try {...} block: statements to be executed.catch {...} block: statements to be executed if an exception is thrown in the try-block.finally {...} block: statements to be executed after the try-block and catch-block execute. The statements in the finally-block execute even if no catch-block handles the exception.throw can be used to generate user-defined exceptionsconst divideNumbers = (dividend, divisor) => {
if (divisor === 0) {
throw new Error("Cannot divide by zero");
}
return dividend / divisor;
};
try {
let result = divideNumbers(10, 0);
console.log("The result is " + result);
} catch (error) {
console.error("Caught an error: " + error.message);
} finally {
console.log("Execution of divideNumbers is complete.");
}
divideNumbers function checks if the divisor is zero. If it is, it throws an error.try block attempts to execute the divideNumbers function.finally block is executed after the try and catch blocks, regardless of whether an exception was caught or not. It’s often used for cleanup or other necessary final steps.Destructuring is a feature in JavaScript that allows you to extract values from objects and arrays, and assign them to variables using a concise syntax. It simplifies code by providing a more elegant way to work with structured data.
Array Destructuring: Destructuring arrays allows you to extract values based on their positions.
const [first, second, third] = [1, 2, 3];
console.log(first); // 1
Object Destructuring: Destructuring objects enables you to extract values based on their property names.
const person = { name: 'Alice', age: 30 };
const { name, age } = person;
console.log(name); // 'Alice'
Default Values: You can provide default values during destructuring in case the property is undefined or missing.
const { role = 'Guest' } = user;
console.log(role); // 'Guest'
Renaming Variables: Destructuring also allows you to rename variables during extraction.
const { name: fullName } = person;
console.log(fullName); // 'Alice'
Nested Destructuring: Destructuring can be used to extract values from nested objects and arrays.
const company = { name: 'TechCorp', address: { city: 'Silicon Valley' } };
const { address: { city } } = company;
console.log(city); // 'Silicon Valley'
Function Parameters: Destructuring is useful for extracting function parameters from objects.
function displayPerson({ name, age }) {
console.log(`Name: ${name}, Age: ${age}`);
}
Swapping Variables: Destructuring provides an elegant way to swap variable values without using a temporary variable.
let a = 5, b = 10;
[a, b] = [b, a];
console.log(a, b); // 10, 5
Rest Element: Destructuring can use the rest element (...) to capture remaining elements.
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first, rest); // 1, [2, 3, 4, 5]
Destructuring is a versatile feature that significantly enhances code readability and makes working with complex data structures more intuitive.
Special operators in JavaScript provide functionality for handling specific operations, making your code more concise and efficient. In this section, we’ll explore some of the important special operators and their applications:
? :)condition ? expressionIfTrue : expressionIfFalseconst age = 20;
const isAdult = age >= 18 ? 'Adult' : 'Minor';
console.log(isAdult); // 'Adult'
const age = 20;
let isAdult;
if (age >= 18) {
isAdult = 'Adult';
} else {
isAdult = 'Minor';
}
console.log(isAdult); // 'Adult'
In both cases, the code checks if the age is greater than or equal to 18. If the condition is true, the person is considered an adult; otherwise, they are considered a minor. The ternary operator offers a more compact way to achieve the same result as the if-else statement.
...)const originalArray = [1, 2, 3];
const newArray = [...originalArray, 4, 5];
console.log(newArray); // [1, 2, 3, 4, 5]
...)const calculateSum = (...numbers) => {
console.log('input values: ', numbers); //-> input values: [1, 2, 3, 4, 5] (array)
return numbers.reduce((acc, number) => acc + number, 0);
}
// input parameters is list of numbers, not an array
calculateSum(1, 2, 3, 4, 5); // 15
||)||) is used to provide a fallback value when the first operand is falsy (evaluates to false). It’s commonly employed to handle default values or to conditionally assign values.const defaultValue = null;
const value = defaultValue || 'Fallback Value';
In the example above, if defaultValue is falsy (null, undefined, 0, empty string, etc.), the logical OR operator will return 'Fallback Value'.
?.)The optional chaining operator provides a concise way to access nested properties of an object without causing an error if any of the intermediate properties are undefined or null. It short-circuits the evaluation and returns undefined if any part of the chain is not present.
Example:
const person = {
name: 'Alice',
address: {
city: 'Wonderland',
},
};
const cityName = person.address?.city; // 'Wonderland'
const missingCity = person.address?.country?.city; // undefined
In this example, if address or country were not defined, the optional chaining operator ensures that no error
occurs, and the result is undefined.
The ?. operator can be used in various contexts, including:
The optional chaining operator is particularly useful when dealing with data that may not always be complete or consistent, helping you avoid runtime errors and providing a safer way to access properties and navigate complex object structures.