ES6+ Features
Modernize your JavaScript skills. Learn destructuring, template literals, let/const, template tags, spread/rest, and modules.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is destructuring in JavaScript and how does it work for arrays and objects?
Destructuring is a concise syntax for extracting values from arrays or properties from objects into individual variables.
// Array destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first, second, third); // red green blue
// Skipping elements
const [primary, , tertiary] = colors;
console.log(primary, tertiary); // red blue
// Default values
const [a = 'default1', b = 'default2'] = ['custom'];
console.log(a, b); // custom default2
// Swapping variables without a temp variable
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
// Object destructuring
const user = { name: 'John', age: 30, city: 'NYC' };
const { name, age } = user;
console.log(name, age); // John 30
// Renaming while destructuring
const { name: userName, age: userAge } = user;
console.log(userName, userAge); // John 30
// Default values for objects
const { country = 'USA' } = user;
console.log(country); // USA - property doesn't exist, uses default
// Nested destructuring
const company = { name: 'Tech Corp', address: { city: 'SF', zip: '94000' } };
const { address: { city, zip } } = company;
console.log(city, zip); // SF 94000
// Destructuring in function parameters
function greet({ name, age }) {
console.log(`${name} is ${age} years old`);
}
greet(user); // John is 30 years old
Q2. How do the spread and rest operators work, and how are they different?
Both use the same ... syntax, but spread expands an iterable into individual elements, while rest collects multiple elements into a single array or object.
| Operator | Purpose | Context |
|---|---|---|
| Spread | Expands array/object into individual items | Function calls, array/object literals |
| Rest | Collects remaining items into an array/object | Function parameters, destructuring |
// SPREAD - arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
const copy = [...arr1]; // shallow copy
// SPREAD - objects
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3 }
// SPREAD in function calls
function sum(a, b, c) { return a + b + c; }
const nums = [1, 2, 3];
console.log(sum(...nums)); // 6 - expands array into arguments
// SPREAD to copy with overrides (immutable updates)
const user = { name: 'John', age: 30 };
const updatedUser = { ...user, age: 31 }; // creates new object, doesn't mutate original
console.log(updatedUser); // { name: 'John', age: 31 }
// REST - function parameters, collects remaining args
function multiply(multiplier, ...numbers) {
return numbers.map(n => n * multiplier);
}
console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]
// REST in destructuring - collects remaining elements
const [firstItem, ...restItems] = [1, 2, 3, 4];
console.log(firstItem, restItems); // 1 [2, 3, 4]
// REST in object destructuring
const { id, ...otherProps } = { id: 1, name: 'John', age: 30 };
console.log(id, otherProps); // 1 { name: 'John', age: 30 }
Q3. How do template literals improve string handling in JavaScript?
Template literals use backticks instead of quotes, enabling embedded expressions, multi-line strings, and tagged templates without messy concatenation.
// Old way - string concatenation
const name = 'John';
const age = 30;
const oldWay = 'My name is ' + name + ' and I am ' + age + ' years old.';
// Template literals - interpolation
const newWay = `My name is ${name} and I am ${age} years old.`;
console.log(newWay);
// Expressions inside ${}
const a = 5, b = 10;
console.log(`Sum is ${a + b}`); // Sum is 15
console.log(`${a > b ? 'a is bigger' : 'b is bigger'}`); // b is bigger
// Multi-line strings without \n concatenation
const multiLine = `Line 1
Line 2
Line 3`;
console.log(multiLine);
// Function calls inside template literals
function formatCurrency(amount) {
return `$${amount.toFixed(2)}`;
}
const price = 19.5;
console.log(`Total: ${formatCurrency(price)}`); // Total: $19.50
// Nested template literals
const isVIP = true;
const message = `Welcome ${isVIP ? `VIP ${name}` : name}!`;
// Tagged templates - custom processing of template strings
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return `${result}${str}${values[i] ? `<mark>${values[i]}</mark>` : ''}`;
}, '');
}
const product = 'Laptop', discount = '20%';
console.log(highlight`Buy ${product} and get ${discount} off!`);
// Buy <mark>Laptop</mark> and get <mark>20%</mark> off!
Q4. What are optional chaining (?.) and nullish coalescing (??) operators?
Both operators were introduced to safely handle null/undefined values without verbose manual checks.
| Operator | Purpose |
|---|---|
| ?. (optional chaining) | Safely accesses nested properties, returns undefined instead of throwing if a link is null/undefined |
| ?? (nullish coalescing) | Returns the right-hand value only if the left is null or undefined (not other falsy values) |
// Without optional chaining - verbose and error-prone
const user = { address: { city: 'NYC' } };
const city1 = user && user.address && user.address.city;
// With optional chaining
const city2 = user?.address?.city;
console.log(city2); // NYC
// Safe when a property doesn't exist
const zip = user?.address?.zip?.code;
console.log(zip); // undefined - no error thrown
// Optional chaining with function calls
const obj = { greet: () => 'Hello' };
console.log(obj.greet?.()); // Hello
console.log(obj.sayBye?.()); // undefined - method doesn't exist, no error
// Optional chaining with array access
const arr = { items: [1, 2, 3] };
console.log(arr.items?.[0]); // 1
console.log(arr.missing?.[0]); // undefined
// NULLISH COALESCING - only null/undefined trigger the fallback
const count = 0;
console.log(count || 10); // 10 - WRONG, 0 is falsy so || triggers fallback
console.log(count ?? 10); // 0 - CORRECT, 0 is not null/undefined
const emptyString = '';
console.log(emptyString || 'default'); // 'default' - falsy triggers ||
console.log(emptyString ?? 'default'); // '' - '' is not nullish
// Combining both operators
const settings = { theme: null };
const theme = settings?.theme ?? 'light'; // light, because theme is explicitly null
console.log(theme);
Q5. How do ES6 classes work and how do they compare to prototype-based inheritance?
ES6 classes are syntactic sugar over JavaScript's existing prototype-based inheritance model - they don't introduce a new inheritance mechanism, just a cleaner syntax for it.
// ES6 class syntax
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
static create(name) {
return new Animal(name); // static method - called on the class, not an instance
}
}
const dog = new Animal('Dog');
console.log(dog.speak()); // Dog makes a sound
// Class inheritance with extends and super
class Dog extends Animal {
constructor(name, breed) {
super(name); // calls Animal's constructor
this.breed = breed;
}
speak() {
return `${super.speak()}, specifically a bark`; // calls parent method too
}
}
const myDog = new Dog('Rex', 'Labrador');
console.log(myDog.speak()); // Rex makes a sound, specifically a bark
console.log(myDog instanceof Animal); // true
// Equivalent using prototype-based syntax (pre-ES6)
function AnimalOld(name) {
this.name = name;
}
AnimalOld.prototype.speak = function() {
return `${this.name} makes a sound`;
};
function DogOld(name, breed) {
AnimalOld.call(this, name); // manual constructor chaining
this.breed = breed;
}
DogOld.prototype = Object.create(AnimalOld.prototype); // manual prototype chain setup
DogOld.prototype.constructor = DogOld;
// Under the hood, classes still use prototypes
console.log(typeof Animal); // 'function' - classes ARE functions
console.log(dog.__proto__ === Animal.prototype); // true
Q6. What are ES6 modules and how do import/export work?
ES6 modules provide a native, standardized way to split code into reusable files, each with its own scope, replacing older patterns like CommonJS or IIFE-based modules.
// math.js - named exports
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
// Alternative: export all at once at the bottom
function multiply(a, b) { return a * b; }
export { multiply };
// main.js - importing named exports
import { add, subtract, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
// Renaming imports
import { add as sum } from './math.js';
console.log(sum(2, 3)); // 5
// Importing everything as a namespace object
import * as MathUtils from './math.js';
console.log(MathUtils.add(2, 3)); // 5
// user.js - default export (one per file)
export default class User {
constructor(name) {
this.name = name;
}
}
// Importing a default export - name can be anything
import User from './user.js';
const u = new User('John');
// Combining default and named exports
export default function App() {}
export const VERSION = '1.0.0';
import App, { VERSION } from './app.js';
// Dynamic imports - loaded on demand, returns a Promise
async function loadModule() {
const module = await import('./math.js');
console.log(module.add(1, 2)); // 3
}
// Modules are singletons - imported only once, state is shared across all importers
// and modules run in strict mode automatically, with their own top-level scope
Q7. What are Map and Set in JavaScript, and how do they differ from plain objects and arrays?
| Structure | Key type | Order preserved? | Size property |
|---|---|---|---|
| Object | String/Symbol only | Mostly, with edge cases | No direct property, need Object.keys().length |
| Map | Any type (objects, functions, etc.) | Yes, insertion order | .size |
| Array | Numeric index only | Yes | .length |
| Set | N/A - stores unique values | Yes, insertion order | .size |
// MAP - key-value pairs with ANY key type
const userRoles = new Map();
const userObj = { id: 1 };
userRoles.set('john', 'admin');
userRoles.set(userObj, 'editor'); // object as a key - impossible with plain objects
console.log(userRoles.get('john')); // admin
console.log(userRoles.get(userObj)); // editor
console.log(userRoles.size); // 2
// Iterating a Map
for (const [key, value] of userRoles) {
console.log(key, value);
}
userRoles.has('john'); // true
userRoles.delete('john');
// Converting Map to array
const entries = [...userRoles]; // [[key, value], ...]
// SET - collection of unique values
const uniqueNumbers = new Set([1, 2, 2, 3, 3, 3]);
console.log(uniqueNumbers); // Set { 1, 2, 3 } - duplicates removed
console.log(uniqueNumbers.size); // 3
uniqueNumbers.add(4);
uniqueNumbers.has(2); // true
uniqueNumbers.delete(1);
// Practical use: removing duplicates from an array
const arr = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4]
// Iterating a Set
for (const value of uniqueNumbers) {
console.log(value);
}
Q8. How do default parameters work in ES6 functions?
Default parameters let you specify fallback values for function arguments when they're not passed or passed as undefined, eliminating manual checks that were common before ES6.
// Old way - manual default value checks
function greetOld(name) {
name = name || 'Guest'; // works, but has issues with falsy values like ''
return `Hello, ${name}`;
}
// ES6 default parameters
function greet(name = 'Guest') {
return `Hello, ${name}`;
}
console.log(greet()); // Hello, Guest
console.log(greet('John')); // Hello, John
console.log(greet(undefined)); // Hello, Guest - undefined triggers default
console.log(greet('')); // Hello, - empty string is a valid value, NOT replaced
console.log(greet(null)); // Hello, null - null is also NOT replaced (unlike ||)
// Defaults can reference earlier parameters
function createUser(name, greeting = `Hello, ${name}`) {
return greeting;
}
console.log(createUser('John')); // Hello, John
// Defaults can be function calls, evaluated each time the default is needed
function getDefaultRole() {
return 'guest';
}
function addUser(name, role = getDefaultRole()) {
console.log(`${name}: ${role}`);
}
addUser('John'); // John: guest
// Combining with destructuring
function createConfig({ theme = 'light', fontSize = 14 } = {}) {
return { theme, fontSize };
}
console.log(createConfig()); // { theme: 'light', fontSize: 14 }
console.log(createConfig({ theme: 'dark' })); // { theme: 'dark', fontSize: 14 }
// Parameters before a default one must still be provided or explicitly undefined
function example(a, b = 10, c) {
console.log(a, b, c);
}
example(1, undefined, 3); // 1 10 3 - must pass undefined to skip 'b'
Q9. What are computed property names and shorthand property syntax in ES6 objects?
ES6 added shortcuts for writing object literals more concisely, especially useful when variable names match property names or property names need to be dynamic.
// SHORTHAND PROPERTIES - when variable name matches key
const name = 'John';
const age = 30;
// Old way
const userOld = { name: name, age: age };
// ES6 shorthand
const user = { name, age };
console.log(user); // { name: 'John', age: 30 }
// SHORTHAND METHODS
const calculatorOld = {
add: function(a, b) { return a + b; }
};
const calculator = {
add(a, b) { return a + b; } // shorthand method syntax
};
console.log(calculator.add(2, 3)); // 5
// COMPUTED PROPERTY NAMES - dynamic keys using expressions
const propName = 'email';
const dynamicObj = {
[propName]: 'john@example.com', // key computed from variable
[`${propName}Verified`]: true // key computed from expression
};
console.log(dynamicObj); // { email: 'john@example.com', emailVerified: true }
// Practical use case - building objects from dynamic keys
function createFieldObject(fieldName, value) {
return { [fieldName]: value };
}
console.log(createFieldObject('username', 'john_doe'));
// { username: 'john_doe' }
// Combining shorthand and computed properties
function createUser(name, age) {
const idKey = 'userId';
return {
name, // shorthand
age, // shorthand
[idKey]: Math.random().toString(36).slice(2) // computed
};
}
console.log(createUser('John', 30));
Q10. What is array/object destructuring combined with the spread operator used for in immutable state updates?
Combining destructuring and spread is the standard pattern for updating state without mutating the original data - essential in frameworks like React and Redux which rely on immutability for change detection.
// Updating an object immutably
const state = { user: 'John', theme: 'light', notifications: true };
// WRONG - mutates the original object directly
function updateThemeWrong(state, newTheme) {
state.theme = newTheme; // mutation - bad for React/Redux
return state;
}
// CORRECT - spread creates a new object
function updateTheme(state, newTheme) {
return { ...state, theme: newTheme }; // new object, original untouched
}
const newState = updateTheme(state, 'dark');
console.log(state.theme); // light - original unchanged
console.log(newState.theme); // dark
// Updating nested state immutably
const appState = {
user: { name: 'John', settings: { theme: 'light' } }
};
const updatedAppState = {
...appState,
user: {
...appState.user,
settings: {
...appState.user.settings,
theme: 'dark'
}
}
};
// Removing a property immutably using destructuring + rest
const user = { id: 1, name: 'John', password: 'secret123' };
const { password, ...safeUser } = user; // extract password, keep the rest
console.log(safeUser); // { id: 1, name: 'John' } - password removed
// Updating an array immutably
const todos = [{ id: 1, done: false }, { id: 2, done: false }];
const updatedTodos = todos.map(todo =>
todo.id === 1 ? { ...todo, done: true } : todo
);
console.log(updatedTodos); // id:1 is updated, id:2 unchanged, original array untouched
// Adding an item immutably
const withNewTodo = [...todos, { id: 3, done: false }];
// Removing an item immutably
const withoutFirst = todos.filter(todo => todo.id !== 1);
ES6+ Features
Modernize your JavaScript skills. Learn destructuring, template literals, let/const, template tags, spread/rest, and modules.
What is destructuring in JavaScript and how does it work for arrays and objects?
Destructuring is a concise syntax for extracting values from arrays or properties from objects into individual...
How do the spread and rest operators work, and how are they different?
Both use the same ... syntax, but spread expands an iterable into individual elements, while rest collects mul...
How do template literals improve string handling in JavaScript?
Template literals use backticks instead of quotes, enabling embedded expressions, multi-line strings, and tagg...
What are optional chaining (?.) and nullish coalescing (??) operators?
Both operators were introduced to safely handle null/undefined values without verbose manual checks.OperatorPu...
How do ES6 classes work and how do they compare to prototype-based inheritance?
ES6 classes are syntactic sugar over JavaScript's existing prototype-based inheritance model - they don't intr...
What are ES6 modules and how do import/export work?
ES6 modules provide a native, standardized way to split code into reusable files, each with its own scope, rep...
What are Map and Set in JavaScript, and how do they differ from plain objects and arrays?
StructureKey typeOrder preserved?Size propertyObjectString/Symbol onlyMostly, with edge casesNo direct propert...
How do default parameters work in ES6 functions?
Default parameters let you specify fallback values for function arguments when they're not passed or passed as...
What are computed property names and shorthand property syntax in ES6 objects?
ES6 added shortcuts for writing object literals more concisely, especially useful when variable names match pr...
What is array/object destructuring combined with the spread operator used for in immutable state updates?
Combining destructuring and spread is the standard pattern for updating state without mutating the original da...