Flash cards
Review the key moves
What is the main idea behind Node.js ES6+ Features?
Lesson checks
Practice each idea before moving on
Short Mimo-style checks built from this lesson's code, terms, and sequence.
Which statement best captures the main point of this lesson?
Complete the missing token from the example code.
// Using ___ (can be changed)Put the learning moves in the order that makes the concept easiest to apply.
What is ES6+?
ES6 (ECMAScript 2015) and later versions add powerful new features to JavaScript that make your code more expressive, concise, and safer.
Node.js has excellent support for modern JavaScript features.
Node.js Compatibility: All modern versions of Node.js (10+) have excellent support for ES6+ features.
Newer versions support even more recent JavaScript additions from ES2020 and beyond.
These modern JavaScript features help you
- Write cleaner, more readable code
- Avoid common JavaScript pitfalls
- Create more maintainable applications
- Reduce the need for external libraries
let and const
The let and const keywords replaced var as the preferred way to declare variables:
- let allows you to declare variables that can be reassigned
- const declares variables that cannot be reassigned (but object properties can still be modified)
- Both are block-scoped, unlike var which is function-scoped
Example: let and const
// Using let (can be changed)
let score = 10;
score = 20;
// Using const (cannot be reassigned)
const MAX_USERS = 100;
// Block scope with let
if (true) {
let message = 'Hello';
console.log(message); // Works here
}Arrow Functions
Arrow functions provide a concise syntax for writing functions and automatically bind this to the surrounding context.
Key benefits of arrow functions
- Shorter syntax for simple functions
- Implicit return for one-line expressions
- Lexical this binding (arrow functions don't create their own this context)
Example: Arrow Functions
// Traditional function function add(a, b) {
return a + b;
}
// Arrow function (same as above)
const addArrow = (a, b) => a + b;
// Single parameter (no parentheses needed)
const double = num => num * 2;
// No parameters (parentheses needed)
const sayHello = () => 'Hello!';
// Using with array methods
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled);When NOT to use arrow functions
- Object methods (where you need
thisto reference the object) - Constructor functions (arrow functions can't be used with
new) - Event handlers where
thisshould refer to the element
Template Literals
Template literals (template strings) provide an elegant way to create strings with embedded expressions using backticks ( ` ).
Key features of template literals
- String interpolation with ${expression} syntax
- Multi-line strings without escape characters
- Tagged templates for advanced string processing
Example: Template Literals
// Basic string interpolation
const name = 'Alice';
console.log(`Hello, ${name}!`);
// Multi-line string
const message = `
This is a multi-line
string in JavaScript.
`;
console.log(message);
// Simple expression
const price = 10;
const tax = 0.2;
console.log(`Total: $${price * (1 + tax)}`);Destructuring
Destructuring allows you to extract values from arrays or properties from objects into distinct variables with a concise syntax.
Key features of destructuring
- Extract multiple values in a single statement
- Assign default values to extracted properties
- Rename properties during extraction
- Skip elements in arrays
- Extract deeply nested properties
Example: Object Destructuring
// Basic object destructuring
const user = { name: 'Alice', age: 30, location: 'New York' };
const { name, age } = user;
console.log(name, age);Example: Array Destructuring
// Basic array destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first, second, third);
// Skipping elements
const [primary, , tertiary] = colors;
console.log(primary, tertiary);Spread and Rest Operators
The spread and rest operators (both written as ... ) allow you to work with multiple elements more efficiently.
- Spread operator : Expands iterables (arrays, objects, strings) into individual elements
- Rest operator : Collects multiple elements into a single array or object
Example: Spread Operator
// Array spread - combining arrays
const numbers = [1, 2, 3];
const moreNumbers = [4, 5, 6];
const combined = [...numbers, ...moreNumbers];
console.log(combined);
// Array spread - converting string to array of characters
const chars = [...'hello'];
console.log(chars);Example: Rest Operator
// Rest parameter in functions function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5));Default Parameters
ES6+ allows you to specify default values for function parameters, eliminating the need for manual parameter checking in many cases.
Key benefits of default parameters
- Cleaner function definitions without manual checking
- More explicit function signatures
- Default values are only used when parameters are undefined or not provided
- Default values can be expressions, not just simple values
Example: Default Parameters
// Basic default parameter function greet(name = 'Guest') {
return `Hello, ${name}!`;
}
console.log(greet());
console.log(greet('Kai'));Classes
ES6 introduced class syntax to JavaScript, providing a clearer and more concise way to create objects and implement inheritance.
Under the hood, JavaScript classes are still based on prototypes.
Key features of JavaScript classes
- Cleaner syntax for creating constructor functions and methods
- Built-in support for inheritance using extends
- Static methods attached to the class, not instances
- Getter and setter methods for more controlled property access
- Private fields for better encapsulation (ES2022+)
Example: Class Basics
// Simple class with constructor and method class Person { constructor(name, age) { this.name = name; this.age = age;
}
greet() {
return `Hello, I'm ${this.name}!`; } } // Create an instance const person = new Person('Alice', 25); console.log(person.greet()); // Hello, I'm Alice!Example: Class Inheritance
// Parent class class Animal { constructor(name) { this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
}
// Child class class Dog extends Animal { speak() {
return `${this.name} barks!`;
}
}
const dog = new Dog('Rex');
console.log(dog.speak());Example: Private Class Fields (ES2022+)
// Class with private field (# prefix)
class Counter {
#count = 0; // Private field
increment() {
this.#count++;
}
getCount() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getCount());
// console.log(counter.#count); // Error: Private fieldPromises and Async/Await
Modern JavaScript provides powerful tools for handling asynchronous operations, making it much easier to work with code that involves delays, API calls, or I/O operations.
Promises
Promises represent values that may not be available yet.
They provide a more elegant way to handle asynchronous operations compared to callbacks.
A Promise is in one of these states:
- Pending : Initial state, neither fulfilled nor rejected
- Fulfilled : Operation completed successfully
- Rejected : Operation failed
Example: Basic Promises
// Creating a promise
const fetchData = () => {
return new Promise((resolve, reject) => {
// Simulating an API call
setTimeout(() => {
const data = { id: 1, name: 'Product' };
const success = true;
if (success) {
resolve(data); // Fulfilled with data
} else {
reject(new Error('Failed to fetch data')); // Rejected with error
}
}, 1000);
});
};
// Using a promise
console.log('Fetching data...');
fetchData()
.then(data => {
console.log('Data received:', data);
return data.id; // Return value is passed to the next .then()
})
.then(id => {
console.log('Processing ID:', id);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Operation completed (success or failure)');
});
console.log('Continuing execution while fetch happens in background');Async/Await
Async/await (introduced in ES2017) provides a cleaner syntax for working with promises, making asynchronous code look and behave more like synchronous code.
Example: Async/Await
// Function that returns a promise
const fetchUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: `User ${id}` });
} else {
reject(new Error('Invalid user ID'));
}
}, 1000);
});
};
// Using async/await
async function getUserData(id) {
try {
console.log('Fetching user...');
const user = await fetchUser(id); // Waits for the promise to resolve
console.log('User data:', user);
// You can use the result directly
return `${user.name}'s profile`;
} catch (error) {
// Handle errors with try/catch
console.error('Error fetching user:', error.message);
return 'Guest profile';
}
}
// Async functions always return promises
console.log('Starting...');
getUserData(1)
.then(result => console.log('Result:', result))
.catch(error => console.error('Unexpected error:', error));
console.log('This runs before getUserData completes');Common Async/Await Mistakes
- Forgetting that async functions always return promises
- Not handling errors with try/catch
- Running operations sequentially when they could run in parallel
- Using await outside of an async function
- Awaiting non-promise values (unnecessary but harmless)
ES Modules
ES Modules (ESM) provide a standardized way to organize and share code. They were introduced in ES2015 and are now supported natively in Node.js.
Key benefits of ES Modules
- Static module structure (imports are analyzed at compile time)
- Default and named exports/imports
- Better dependency management
- Tree-shaking (eliminating unused code)
Example: ES Modules
File: math.js
// Named exports
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Default export
export default class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}File: app.js
// Import default export
import Calculator from './math.js';
// Import named exports
import { PI, add, multiply } from './math.js';
// Import with alias
import { add as mathAdd } from './math.js';
// Import all exports as a namespace
import * as MathUtils from './math.js';
const calc = new Calculator();
console.log(calc.subtract(10, 5)); // 5
console.log(add(2, 3)); // 5
console.log(mathAdd(4, 5)); // 9
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.multiply(2, 3)); // 6To use ES Modules in Node.js, you can either:
- Use the .mjs extension for module files
- Add "type": "module" to your package.json
- Use the --experimental-modules flag (older Node.js versions)
The CommonJS module system ( require() and module.exports ) is still widely used in Node.js. ES Modules and CommonJS can coexist in the same project, but they have different semantics.
Enhanced Object Literals
ES6+ introduced several improvements to object literals that make object creation more concise and expressive.
Example: Enhanced Object Literals
// Property shorthand
const name = 'Alice';
const age = 30;
// Instead of {name: name, age: age}
const person = { name, age };
console.log(person);
// Method shorthand
const calculator = {
// Instead of add: function(a, b) { ... } add(a, b) {
return a + b;
}, subtract(a, b) {
return a - b;
}
};
console.log(calculator.add(5, 3));Optional Chaining and Nullish Coalescing
Modern JavaScript introduces syntax to safely access nested properties and provide fallback values.
Optional Chaining (?.)
Optional chaining lets you access deeply nested object properties without worrying about null or undefined values in the chain.
Example: Optional Chaining
function getUserCity(user) {
return user?.address?.city;
}
const user1 = {
name: 'Alice',
address: { city: 'New York', country: 'USA' }
};
const user2 = {
name: 'Bob'
};
const user3 = null;
console.log(getUserCity(user1)); // 'New York'
console.log(getUserCity(user2)); // undefined
console.log(getUserCity(user3)); // undefinedNullish Coalescing (??)
The nullish coalescing operator (??) provides a default value when a value is null or undefined (but not for other falsy values like 0 or "").
Example: Nullish Coalescing
function calculatePrice(price, tax) {
// Only uses default if tax is null or undefined
return price + (tax ?? 0.1) * price;
}
console.log(calculatePrice(100, 0)); // 100 (correct! tax of 0 was used)
console.log(calculatePrice(100, null)); // 110 (using default)Modern Asynchronous Patterns
Modern JavaScript provides powerful patterns for handling asynchronous operations. Understanding when to use sequential vs parallel execution can significantly improve your application's performance.
Sequential vs Parallel Execution
- Sequential: Operations run one after another, each waiting for the previous to complete
- Parallel: Operations run simultaneously, which is more efficient when operations are independent
Example: Sequential Execution
// Helper function to simulate an API call
function fetchData(id) {
return new Promise(resolve => {
setTimeout(() => resolve(`Data for ID ${id}`), 1000);
});
}
// Sequential execution (~3 seconds total)
async function fetchSequential() {
console.time('sequential');
const data1 = await fetchData(1);
const data2 = await fetchData(2);
const data3 = await fetchData(3);
console.timeEnd('sequential');
return [data1, data2, data3];
}
// Run the sequential example fetchSequential().then(results => { console.log('Sequential results:', results);
});