4.9: ES6+ Modern Features
Learn modern JavaScript best practices including ES6+ features, code organization, and debugging techniques. Discover destructuring, template literals, modules, and how to write clean, maintainable JavaScript code.
1. Destructuring (Recap & Advanced)
Object Destructuring
Basic:
const user = { name: "Alice", age: 30, city: "NYC" };
// Old way
const name = user.name;
const age = user.age;
// Destructuring
const { name, age } = user;
console.log(name, age); // Alice 30
Renaming:
const { name: userName, age: userAge } = user;
console.log(userName, userAge); // Alice 30
Default values:
const { name, country = "USA" } = user;
console.log(country); // USA (default, not in object)
Nested destructuring:
const user = {
name: "Alice",
address: {
city: "NYC",
zip: "10001"
}
};
const { address: { city, zip } } = user;
console.log(city, zip); // NYC 10001
Rest in objects:
const { name, ...rest } = user;
console.log(name); // Alice
console.log(rest); // { age: 30, city: "NYC" }
Array Destructuring
Basic:
const colors = ["red", "green", "blue"];
const [first, second, third] = colors;
console.log(first); // red
Skipping elements:
const [, , third] = colors;
console.log(third); // blue
Rest in arrays:
const [first, ...rest] = colors;
console.log(first); // red
console.log(rest); // ["green", "blue"]
Swapping variables:
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
Python comparison:
colors = ["red", "green", "blue"]
first, second, third = colors
first, *rest = colors # Python unpacking
a, b = 1, 2
a, b = b, a # Swap
2. Template Literals (Advanced)
Tagged Templates
Functions that process template literals:
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<mark>${values[i]}</mark>` : "");
}, "");
}
const name = "Alice";
const age = 30;
const result = highlight`Hello ${name}, you are ${age} years old`;
console.log(result);
// Hello <mark>Alice</mark>, you are <mark>30</mark> years old
Use cases:
- SQL query builders
- HTML sanitization
- i18n (internationalization)
- Styling (styled-components)
Multiline Strings
const message = `
Hello!
This is a multi-line string.
Line breaks are preserved.
`;
// Compare to old way
const oldWay = "Hello!\n" +
"This is a multi-line string.\n" +
"Line breaks are preserved.";
3. Optional Chaining (?.)
Safely access nested properties without errors:
Problem:
const user = {
name: "Alice",
address: {
city: "NYC"
}
};
// This throws error if user.profile doesn't exist
// console.log(user.profile.email); // Error!
Old solution:
const email = user && user.profile && user.profile.email;
Optional chaining:
const email = user?.profile?.email;
console.log(email); // undefined (no error!)
// With arrays
const firstPost = user?.posts?.[0];
// With functions
const result = user?.getProfile?.();
Python comparison: Python doesn't have this (would need try/except or multiple checks):
# Python
email = None
if user and hasattr(user, 'profile') and hasattr(user.profile, 'email'):
email = user.profile.email
4. Nullish Coalescing (??)
Returns right side only if left is null or undefined:
// Old way (||)
const port = config.port || 3000;
// Problem: if port is 0, uses 3000 (0 is falsy)
// Nullish coalescing (??)
const port = config.port ?? 3000;
// Only uses 3000 if port is null/undefined
// Examples
0 ?? 10; // 0 (0 is not null/undefined)
"" ?? "default"; // "" (empty string is not null/undefined)
false ?? true; // false
null ?? 10; // 10
undefined ?? 10; // 10
Combined with optional chaining:
const username = user?.profile?.name ?? "Guest";
5. ES Modules
Export
Named exports:
// utils.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
export class User {
constructor(name) {
this.name = name;
}
}
// Or export at bottom
function subtract(a, b) {
return a - b;
}
export { subtract };
Default export:
// calculator.js
export default class Calculator {
add(a, b) {
return a + b;
}
}
// Or
class Calculator { /* ... */ }
export default Calculator;
// Or inline
export default function calculate() { /* ... */ }
Import
Named imports:
import { add, PI } from "./utils.js";
console.log(add(5, 3)); // 8
console.log(PI); // 3.14159
// Rename
import { add as sum } from "./utils.js";
// Import all
import * as utils from "./utils.js";
console.log(utils.add(5, 3));
console.log(utils.PI);
Default import:
import Calculator from "./calculator.js";
const calc = new Calculator();
Mixed:
import Calculator, { add, PI } from "./utils.js";
Python comparison:
# Python
from utils import add, PI
from utils import add as sum_func
import utils
from calculator import Calculator
Note: ES modules work differently in browser vs Node.js. In browser, need <script type="module">.
6. Modern Array Methods
Array.prototype.at()
Access array elements (supports negative indices):
const arr = [1, 2, 3, 4, 5];
arr.at(0); // 1
arr.at(-1); // 5 (last element)
arr.at(-2); // 4
// Old way for last element
arr[arr.length - 1]; // 5
Python comparison:
arr = [1, 2, 3, 4, 5]
arr[0] # 1
arr[-1] # 5 (Python has this built-in!)
Array.prototype.flat()
Flatten nested arrays:
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]] (depth 1)
nested.flat(2); // [1, 2, 3, 4, 5, 6] (depth 2)
nested.flat(Infinity); // Fully flatten
Array.prototype.flatMap()
Map then flatten (one level):
const arr = [1, 2, 3];
// Old way
arr.map(x => [x, x * 2]).flat(); // [1, 2, 2, 4, 3, 6]
// flatMap
arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
// Practical example
const sentences = ["Hello world", "How are you"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["Hello", "world", "How", "are", "you"]
7. Sets
Set = Collection of unique values.
const set = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(set); // Set { 1, 2, 3, 4, 5 }
// Methods
set.add(6);
set.has(3); // true
set.delete(3);
set.size; // 5
set.clear(); // Remove all
// Iteration
for (const value of set) {
console.log(value);
}
// Convert to array
const arr = [...set];
const arr2 = Array.from(set);
Use cases:
- Remove duplicates
- Check membership (faster than array.includes)
- Mathematical sets (union, intersection)
Remove duplicates:
const arr = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4]
Python comparison:
# Python set
s = {1, 2, 3, 3, 4} # {1, 2, 3, 4}
s.add(5)
3 in s # True
s.remove(3)
len(s)
# Remove duplicates
arr = [1, 2, 2, 3, 3, 4]
unique = list(set(arr))
8. Maps
Map = Key-value pairs where keys can be any type (not just strings).
const map = new Map();
// Set values
map.set("name", "Alice");
map.set(1, "one");
map.set(true, "boolean key");
// Get values
map.get("name"); // "Alice"
map.get(1); // "one"
// Check existence
map.has("name"); // true
// Delete
map.delete("name");
// Size
map.size; // 2
// Iteration
for (const [key, value] of map) {
console.log(key, value);
}
// Convert to array
const entries = [...map]; // [["name", "Alice"], [1, "one"], ...]
Map vs Object:
| Feature | Map | Object |
|---|---|---|
| Key types | Any | String/Symbol only |
| Key order | Insertion order | Not guaranteed* |
| Size | map.size | Object.keys(obj).length |
| Iteration | for...of | for...in (needs checks) |
| Performance | Better for frequent add/remove | Better for static data |
*Modern JS guarantees order for objects, but Map is explicit
Python comparison:
# Python dict (similar to object)
d = {"name": "Alice", 1: "one"}
d["name"] # "Alice"
"name" in d # True
del d["name"]
len(d)
for key, value in d.items():
print(key, value)
9. Object Enhancements
Shorthand Properties
const name = "Alice";
const age = 30;
// Old
const user = { name: name, age: age };
// Shorthand
const user = { name, age };
Shorthand Methods
// Old
const obj = {
greet: function() {
return "Hello";
}
};
// Shorthand
const obj = {
greet() {
return "Hello";
}
};
Computed Property Names
const key = "dynamicKey";
const obj = {
[key]: "value",
[`${key}2`]: "value2",
[1 + 2]: "three"
};
console.log(obj);
// { dynamicKey: "value", dynamicKey2: "value2", 3: "three" }
Object.entries() & Object.fromEntries()
const obj = { name: "Alice", age: 30 };
// To entries
const entries = Object.entries(obj);
console.log(entries); // [["name", "Alice"], ["age", 30]]
// From entries
const newObj = Object.fromEntries(entries);
console.log(newObj); // { name: "Alice", age: 30 }
// Use case: filter object properties
const filtered = Object.fromEntries(
Object.entries(obj).filter(([key, value]) => value > 25)
);
console.log(filtered); // { age: 30 }
10. Symbols
Symbol = Unique, immutable identifier.
const sym1 = Symbol("description");
const sym2 = Symbol("description");
console.log(sym1 === sym2); // false (always unique)
// Use as object keys
const user = {
name: "Alice",
[Symbol("id")]: 123
};
// Symbol properties are hidden from Object.keys()
console.log(Object.keys(user)); // ["name"]
// But visible with Object.getOwnPropertySymbols()
console.log(Object.getOwnPropertySymbols(user));
Use cases:
- Truly private properties (hard to access)
- Avoid naming conflicts
- Built-in symbols (Symbol.iterator, etc.)
11. Practical Examples
Example 1: API Response Handler
async function fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
const data = await response.json();
// Destructuring with defaults
const {
name = "Unknown",
email,
profile: { bio = "No bio" } = {}
} = data;
return { name, email, bio };
}
Example 2: Data Transformation
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Charlie", active: true }
];
// Get active user names
const activeNames = users
.filter(u => u.active)
.map(({ name }) => name); // Destructuring in map
console.log(activeNames); // ["Alice", "Charlie"]
// Convert to Map (id → user)
const userMap = new Map(
users.map(u => [u.id, u])
);
console.log(userMap.get(1)); // { id: 1, name: "Alice", ... }
Example 3: Configuration with Defaults
function createServer(config = {}) {
const {
port = 3000,
host = "localhost",
ssl = false,
...otherOptions
} = config;
console.log(`Server: ${ssl ? "https" : "http"}://${host}:${port}`);
console.log("Other options:", otherOptions);
}
createServer({ port: 8080, debug: true });
// Server: http://localhost:8080
// Other options: { debug: true }
12. Practical Exercises
Exercise 4.9.1: Destructuring Practice
Rewrite using destructuring:
function displayUser(user) {
const name = user.name;
const age = user.age;
const city = user.address.city;
console.log(name, age, city);
}
Exercise 4.9.2: Safe Property Access
Rewrite using optional chaining and nullish coalescing:
function getEmail(user) {
if (user && user.contact && user.contact.email) {
return user.contact.email;
}
return "no-email@example.com";
}
Exercise 4.9.3: Unique Values
Remove duplicates and count occurrences:
const items = ["apple", "banana", "apple", "orange", "banana", "apple"];
// Output: { apple: 3, banana: 2, orange: 1 }
Exercise 4.9.4: Module System
Create a math utilities module with:
- Named exports for functions (add, subtract, multiply, divide)
- Default export for Calculator class
- Import and use in another file
13. Knowledge Check
Question 1: What's the difference between optional chaining (?.) and nullish coalescing (??)?
Show answer
Optional chaining (?.) safely accesses nested properties. Nullish coalescing (??) provides default value only for null/undefined (not falsy values like 0 or "").Question 2: When would you use a Map instead of an Object?
Show answer
Use Map when: keys aren't strings, need guaranteed order, frequent add/remove operations, need size property, or iterating key-value pairs.Question 3: What does Array.flat() do?
Show answer
Flattens nested arrays by specified depth. flat(1) removes one level of nesting, flat(Infinity) fully flattens.Question 4: How do Sets help remove duplicates?
Show answer
Sets only store unique values. Create Set from array, then convert back: [...new Set(array)].Question 5: What's the difference between named and default exports?
Show answer
Named exports use exact name in imports. Default export can be imported with any name. Module can have multiple named exports but only one default.14. Key Takeaways
- Destructuring extracts values from arrays/objects concisely
- Template literals support multiline strings and expressions
- Optional chaining (?.) safely accesses nested properties
- Nullish coalescing (??) provides defaults for null/undefined only
- ES modules use import/export for code organization
- Modern array methods: at() (negative indices), flat() (flatten), flatMap()
- Sets store unique values, useful for removing duplicates
- Maps allow any type as keys, better for dynamic key-value data
- Object shorthand: { name } instead of { name: name }
- Symbols create unique identifiers
15. Further Resources
Documentation:
Next Steps
Amazing! You now know modern JavaScript features used in production code.
In Lesson 4.10: JavaScript Best Practices & Debugging, you'll learn professional coding practices, debugging techniques, and performance optimization.
Next: Lesson 4.10 - JavaScript Best Practices & Debugging →