Modern ES6+ Features and Syntax
1. Destructuring Assignment (array, object, nested)
| Type | Syntax | Description | Example |
|---|---|---|---|
| Array Destructuring | [a, b] = array |
Extract array elements into variables | [x, y] = [1, 2] |
| Object Destructuring | {a, b} = obj |
Extract object properties into variables | {name, age} = user |
| Skip Elements | [a, , c] = array |
Skip unwanted array elements | [x, , z] = [1, 2, 3] |
| Rest Elements | [a, ...rest] = array |
Collect remaining elements | [first, ...others] = [1,2,3] |
| Default Values | [a = 0] = array |
Fallback if undefined | {name = 'Guest'} = obj |
| Rename Properties | {a: newName} = obj |
Assign to different variable name | {name: userName} = obj |
| Nested Destructuring | {a: {b}} = obj |
Extract from nested structures | {user: {name}} = data |
| Mixed Destructuring | {a, b: [c, d]} = obj |
Combine object and array destructuring | {items: [first]} = obj |
Example: Destructuring patterns
// Array destructuring
const arr = [1, 2, 3, 4, 5];
const [first, second] = arr;
console.log(first, second); // 1, 2
const [a, b, c] = arr;
console.log(a, b, c); // 1, 2, 3
// Skip elements
const [x, , z] = arr;
console.log(x, z); // 1, 3
// Rest elements (...) must be last
const [head, ...tail] = arr;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// Default values
const [p = 10, q = 20] = [5];
console.log(p, q); // 5, 20
// Swap variables
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2, 1
// Object destructuring
const user = {name: 'Alice', age: 30, city: 'NYC'};
const {name, age} = user;
console.log(name, age); // 'Alice', 30
// Rename variables
const {name: userName, age: userAge} = user;
console.log(userName, userAge); // 'Alice', 30
// Default values
const {name, country = 'USA'} = user;
console.log(country); // 'USA' (not in user object)
// Rest properties
const {name, ...rest} = user;
console.log(rest); // {age: 30, city: 'NYC'}
// Nested destructuring
const data = {
user: {
name: 'Bob',
address: {
city: 'LA',
zip: '90001'
}
},
items: [1, 2, 3]
};
const {user: {name, address: {city}}} = data;
console.log(name, city); // 'Bob', 'LA'
// Array within object
const {items: [firstItem, secondItem]} = data;
console.log(firstItem, secondItem); // 1, 2
// Object within array
const users = [
{name: 'Alice', age: 30},
{name: 'Bob', age: 25}
];
const [{name: firstName}, {age: secondAge}] = users;
console.log(firstName, secondAge); // 'Alice', 25
// Function parameters
function greet({name, age = 18}) {
console.log(`${name} is ${age}`);
}
greet({name: 'Alice', age: 30}); // Alice is 30
greet({name: 'Bob'}); // Bob is 18
// Array parameters
function sum([a, b, c = 0]) {
return a + b + c;
}
sum([1, 2, 3]); // 6
sum([1, 2]); // 3 (c defaults to 0)
// Computed property names
const key = 'name';
const {[key]: value} = {name: 'Alice'};
console.log(value); // 'Alice'
// Destructuring in loops
const points = [
{x: 10, y: 20},
{x: 30, y: 40}
];
for (const {x, y} of points) {
console.log(x, y);
}
// Destructuring returned arrays
function getCoords() {
return [10, 20];
}
const [xCoord, yCoord] = getCoords();
// Destructuring returned objects
function getUser() {
return {name: 'Alice', age: 30};
}
const {name: n, age: a} = getUser();
// Mixed destructuring with defaults
const config = {
host: 'localhost',
port: 8080,
options: {
timeout: 3000,
retries: 3
}
};
const {
host = '0.0.0.0',
port = 80,
options: {
timeout = 5000,
retries = 5,
cache = true
} = {}
} = config;
// Destructuring with rest and rename
const person = {
firstName: 'John',
lastName: 'Doe',
age: 30,
city: 'NYC'
};
const {
firstName: fName,
lastName: lName,
...details
} = person;
console.log(fName, lName); // 'John', 'Doe'
console.log(details); // {age: 30, city: 'NYC'}
// Ignoring values
const [, , third] = [1, 2, 3, 4];
console.log(third); // 3
// Destructuring with existing variables
let existing1, existing2;
[existing1, existing2] = [10, 20];
// Must use parentheses for objects
let e1, e2;
({e1, e2} = {e1: 10, e2: 20}); // Parens required!
// Failed matches = undefined
const {nonExistent} = {};
console.log(nonExistent); // undefined
const [missing] = [];
console.log(missing); // undefined
// Practical: extracting data from API response
const response = {
data: {
user: {
id: 123,
profile: {
name: 'Alice',
email: 'alice@example.com'
}
}
},
meta: {
timestamp: Date.now()
}
};
const {
data: {
user: {
id: userId,
profile: {name: displayName, email}
}
}
} = response;
console.log(userId, displayName, email);
Note: Destructuring works with any iterable (arrays, strings, Maps, Sets). Use
...
for rest (must be last). Defaults only apply for undefined (not null). Object
destructuring needs parentheses for existing variables.
2. Spread and Rest Syntax Applications
| Context | Operator | Purpose | Example |
|---|---|---|---|
| Spread (Arrays) | [...arr] |
Expand array elements | [...arr1, ...arr2] |
| Spread (Objects) | {...obj} |
Expand object properties | {...obj1, ...obj2} |
| Spread (Function Calls) | fn(...args) |
Pass array as individual arguments | Math.max(...numbers) |
| Rest (Parameters) | (...args) |
Collect remaining arguments into array | function f(...args) |
| Rest (Destructuring) | [a, ...rest] |
Collect remaining elements/properties | {x, ...rest} = obj |
Example: Spread and rest usage
// Spread in array literals
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
const withMiddle = [...arr1, 'middle', ...arr2];
console.log(withMiddle); // [1, 2, 3, 'middle', 4, 5, 6]
// Array cloning (shallow)
const original = [1, 2, 3];
const copy = [...original];
copy.push(4); // Doesn't affect original
// Spread strings
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// Spread with Sets (removes duplicates)
const arr = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4]
// Spread in function calls
const numbers = [5, 1, 9, 3, 7];
Math.max(...numbers); // 9 (instead of Math.max(5, 1, 9, 3, 7))
console.log(...numbers); // 5 1 9 3 7
// Spread objects (shallow merge)
const obj1 = {a: 1, b: 2};
const obj2 = {b: 3, c: 4};
const merged = {...obj1, ...obj2};
console.log(merged); // {a: 1, b: 3, c: 4} (obj2.b overwrites obj1.b)
// Adding/overriding properties
const user = {name: 'Alice', age: 30};
const updated = {...user, age: 31, city: 'NYC'};
console.log(updated); // {name: 'Alice', age: 31, city: 'NYC'}
// Object cloning (shallow)
const originalObj = {a: 1, b: {c: 2}};
const clonedObj = {...originalObj};
clonedObj.b.c = 3; // Affects original! (nested objects not cloned)
// Rest parameters (collect into array)
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
// Rest must be last parameter
function logAll(first, second, ...rest) {
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
}
logAll(1, 2, 3, 4, 5);
// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
const {x, y, ...rest} = {x: 1, y: 2, z: 3, w: 4};
console.log(x, y); // 1, 2
console.log(rest); // {z: 3, w: 4}
// Combining spread and rest
function multiply(multiplier, ...numbers) {
return numbers.map(n => n * multiplier);
}
const nums = [1, 2, 3];
multiply(2, ...nums); // [2, 4, 6]
// Converting arguments to array (old way vs new)
function oldWay() {
const args = Array.prototype.slice.call(arguments);
return args;
}
function newWay(...args) {
return args;
}
// Spread with iterables
const map = new Map([['a', 1], ['b', 2]]);
const mapArr = [...map];
console.log(mapArr); // [['a', 1], ['b', 2]]
const set = new Set([1, 2, 3]);
const setArr = [...set];
console.log(setArr); // [1, 2, 3]
// Practical: remove item from array
const items = [1, 2, 3, 4, 5];
const index = 2;
const removed = [...items.slice(0, index), ...items.slice(index + 1)];
console.log(removed); // [1, 2, 4, 5]
// Practical: insert item
const insert = [...items.slice(0, index), 99, ...items.slice(index)];
console.log(insert); // [1, 2, 99, 3, 4, 5]
// Practical: conditional spread
const baseConfig = {host: 'localhost', port: 8080};
const isDev = true;
const config = {
...baseConfig,
...(isDev && {debug: true, verbose: true})
};
// If isDev: {host: 'localhost', port: 8080, debug: true, verbose: true}
// If !isDev: {host: 'localhost', port: 8080}
// Practical: merge with priority
function mergeOptions(defaults, user) {
return {...defaults, ...user};
}
const options = mergeOptions(
{timeout: 5000, retries: 3},
{retries: 5} // Overrides defaults.retries
);
// Practical: flatten array one level
const nested = [[1, 2], [3, 4], [5]];
const flat = [].concat(...nested);
console.log(flat); // [1, 2, 3, 4, 5]
// Better: use flat()
const flatBetter = nested.flat();
// Spread with null/undefined (error)
// const spread = {...null}; // TypeError
// const spread = {...undefined}; // TypeError
// But arrays are OK
const arr = [...(someVar || [])]; // Safe fallback
// Order matters in objects
const a = {x: 1, y: 2};
const b = {y: 3, z: 4};
{...a, ...b}; // {x: 1, y: 3, z: 4} (b.y wins)
{...b, ...a}; // {x: 1, y: 2, z: 4} (a.y wins)
// Spread doesn't deep clone
const obj = {
a: 1,
nested: {b: 2}
};
const clone = {...obj};
clone.nested.b = 3; // Affects original!
console.log(obj.nested.b); // 3
// Deep clone requires recursion or library
const deepClone = JSON.parse(JSON.stringify(obj)); // Quick but limited
// Rest with default parameters
function greet(greeting = 'Hello', ...names) {
return `${greeting} ${names.join(' and ')}`;
}
greet('Hi', 'Alice', 'Bob'); // "Hi Alice and Bob"
greet(undefined, 'Alice'); // "Hello Alice"
Warning: Spread/rest creates shallow copies (nested
objects/arrays still referenced). Rest must be last parameter/element. Spread objects only copies enumerable own
properties. Can't spread null/undefined.
3. Default Parameters and Parameter Destructuring
| Feature | Syntax | Description | Example |
|---|---|---|---|
| Default Value | function f(a = default) |
Fallback if undefined (not null) | f(x = 0) |
| Expression Default | function f(a = expr()) |
Default can be expression; evaluated when needed | f(x = Date.now()) |
| Reference Previous | function f(a, b = a) |
Later params can reference earlier ones | f(x, y = x * 2) |
| Destructured Params | function f({x, y}) |
Destructure object/array parameter | f({x: 1, y: 2}) |
| Destructure + Default | function f({x = 0}) |
Defaults within destructuring | f({x: 1} = {}) |
Example: Default parameters
// Basic default parameters
function greet(name = 'Guest') {
return `Hello, ${name}!`;
}
greet('Alice'); // "Hello, Alice!"
greet(); // "Hello, Guest!"
greet(undefined); // "Hello, Guest!"
greet(null); // "Hello, null!" (null is NOT undefined!)
// Multiple defaults
function createUser(name = 'Anonymous', age = 18, active = true) {
return {name, age, active};
}
createUser('Alice', 30); // {name: 'Alice', age: 30, active: true}
createUser('Bob'); // {name: 'Bob', age: 18, active: true}
// Expression as default (evaluated each call)
function log(message, timestamp = Date.now()) {
console.log(`[${timestamp}] ${message}`);
}
log('First'); // [1702812345123] First
log('Second'); // [1702812345456] Second (different timestamp)
// Function call as default
function getDefault() {
console.log('Getting default');
return 42;
}
function withDefault(x = getDefault()) {
return x;
}
withDefault(10); // 10 (getDefault not called)
withDefault(); // 42 (getDefault called, logs "Getting default")
// Reference previous parameters
function createRange(start = 0, end = start + 10) {
return {start, end};
}
createRange(5); // {start: 5, end: 15}
createRange(); // {start: 0, end: 10}
// Parameters evaluated left-to-right
function multiply(a, b = a, c = a * b) {
return c;
}
multiply(2); // 4 (b=2, c=2*2)
multiply(2, 3); // 6 (c=2*3)
multiply(2, 3, 4); // 4 (c explicitly set)
// Can't reference later parameters
function invalid(a = b, b = 1) { // ReferenceError!
return a;
}
// Object destructuring parameters
function drawCircle({x, y, radius, color = 'black'}) {
console.log(`Circle at (${x}, ${y}), r=${radius}, color=${color}`);
}
drawCircle({x: 10, y: 20, radius: 5});
// "Circle at (10, 20), r=5, color=black"
drawCircle({x: 0, y: 0, radius: 10, color: 'red'});
// "Circle at (0, 0), r=10, color=red"
// Default for entire parameter object
function config({host = 'localhost', port = 8080} = {}) {
return {host, port};
}
config({host: '0.0.0.0'}); // {host: '0.0.0.0', port: 8080}
config(); // {host: 'localhost', port: 8080}
// Array destructuring parameters
function sum([a = 0, b = 0, c = 0]) {
return a + b + c;
}
sum([1, 2, 3]); // 6
sum([1, 2]); // 3 (c defaults to 0)
sum([1]); // 1 (b and c default to 0)
// Nested destructuring with defaults
function createUser({
name = 'Anonymous',
profile: {
age = 18,
email = 'no-email'
} = {}
} = {}) {
return {name, age, email};
}
createUser({name: 'Alice', profile: {age: 30}});
// {name: 'Alice', age: 30, email: 'no-email'}
createUser({name: 'Bob'});
// {name: 'Bob', age: 18, email: 'no-email'}
createUser();
// {name: 'Anonymous', age: 18, email: 'no-email'}
// Rest with defaults
function logAll(first = 'default', ...rest) {
console.log('First:', first);
console.log('Rest:', rest);
}
logAll(); // First: default, Rest: []
logAll('a', 'b', 'c'); // First: a, Rest: ['b', 'c']
// Complex example: options object
function ajax(url, {
method = 'GET',
headers = {},
body = null,
timeout = 5000,
retries = 3
} = {}) {
console.log({url, method, headers, body, timeout, retries});
}
ajax('/api/data'); // All defaults
ajax('/api/data', {method: 'POST', body: '{}'});
// Default in arrow functions
const greet = (name = 'Guest') => `Hello, ${name}!`;
// Destructuring in arrow functions
const getArea = ({width = 0, height = 0}) => width * height;
getArea({width: 10, height: 20}); // 200
getArea({width: 10}); // 0 (height defaults to 0)
// Optional destructuring
function processData(data = {}) {
const {name = 'Unknown', value = 0} = data;
return {name, value};
}
processData(); // {name: 'Unknown', value: 0}
processData({name: 'Test'}); // {name: 'Test', value: 0}
// Default with computed properties
const key = 'port';
function server({[key]: p = 8080} = {}) {
return p;
}
server({port: 3000}); // 3000
server(); // 8080
// Avoiding undefined vs null confusion
function safe(value = 'default') {
return value ?? 'fallback'; // Use nullish coalescing
}
safe(); // 'default' (undefined)
safe(undefined); // 'default'
safe(null); // 'fallback' (null not undefined)
safe(0); // 0
safe(''); // ''
// Practical: API request options
function fetchData(url, {
method = 'GET',
cache = true,
retries = 3,
onSuccess = () => {},
onError = console.error
} = {}) {
// Implementation
}
Note: Defaults only apply when parameter is
undefined (not null,
0, '', etc). Expressions evaluated on each call. Can reference previous params. Use
= {} for destructured param defaults.
4. Enhanced Object Literals and Method Shorthand
| Feature | ES6 Syntax | ES5 Equivalent | Description |
|---|---|---|---|
| Property Shorthand | {name, age} |
{name: name, age: age} |
Variable name as property name |
| Method Shorthand | {method() {}} |
{method: function() {}} |
Concise method syntax |
| Computed Properties | {[expr]: value} |
obj[expr] = value (after creation) | Dynamic property names |
| Computed Methods | {[expr]() {}} |
obj[expr] = function() {} | Dynamic method names |
| Getter/Setter | {get prop() {}, set prop(v) {}} |
Object.defineProperty | Accessor properties |
Example: Enhanced object literals
// Property shorthand
const name = 'Alice';
const age = 30;
// ES6
const user = {name, age};
// ES5 equivalent
const userOld = {name: name, age: age};
console.log(user); // {name: 'Alice', age: 30}
// Method shorthand
const calculator = {
// ES6
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
// ES5 equivalent
multiply: function(a, b) {
return a * b;
}
};
calculator.add(5, 3); // 8
// Computed property names
const prop = 'dynamicKey';
const value = 42;
const obj = {
[prop]: value,
['key' + 2]: 'value2',
[`computed_${Date.now()}`]: 'timestamp'
};
console.log(obj.dynamicKey); // 42
console.log(obj.key2); // 'value2'
// Computed method names
const methodName = 'sayHello';
const greeter = {
[methodName]() {
return 'Hello!';
},
[`${methodName}Loud`]() {
return 'HELLO!';
}
};
greeter.sayHello(); // 'Hello!'
greeter.sayHelloLoud(); // 'HELLO!'
// Combining features
const id = 1;
const name = 'Product';
const price = 99.99;
const product = {
id,
name,
price,
// Method shorthand
getInfo() {
return `${this.name}: ${this.price}`;
},
// Computed property
[`item_${id}`]: true,
// Getter
get displayPrice() {
return `${this.price.toFixed(2)}`;
},
// Setter
set discount(percent) {
this.price = this.price * (1 - percent / 100);
}
};
product.getInfo(); // "Product: $99.99"
product.displayPrice; // "$99.99"
product.discount = 10; // Sets price to 89.99
// Getters and setters
const person = {
firstName: 'John',
lastName: 'Doe',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
};
person.fullName; // "John Doe"
person.fullName = 'Jane Smith';
console.log(person.firstName); // "Jane"
// Async methods
const api = {
async fetchData() {
const response = await fetch('/api/data');
return response.json();
},
async* generateData() {
for (let i = 0; i < 5; i++) {
yield await fetchItem(i);
}
}
};
// Generator methods
const iterator = {
*range(start, end) {
for (let i = start; i < end; i++) {
yield i;
}
}
};
for (const num of iterator.range(1, 5)) {
console.log(num); // 1, 2, 3, 4
}
// Computed properties with symbols
const MY_KEY = Symbol('myKey');
const obj = {
[MY_KEY]: 'hidden value',
public: 'visible'
};
obj[MY_KEY]; // 'hidden value'
// Dynamic object building
function createUser(name, age, ...permissions) {
return {
name,
age,
permissions,
hasPermission(perm) {
return this.permissions.includes(perm);
},
[`is${name}`]: true
};
}
const user = createUser('Admin', 30, 'read', 'write');
user.isAdmin; // true
user.hasPermission('read'); // true
// Mixing with spread
const defaults = {
timeout: 5000,
retries: 3
};
const config = {
...defaults,
host: 'localhost',
port: 8080,
getUrl() {
return `http://${this.host}:${this.port}`;
}
};
// Nested enhanced literals
const data = {
user: {
name,
age,
getInfo() {
return `${this.name}, ${this.age}`;
}
},
metadata: {
created: Date.now(),
format() {
return new Date(this.created).toISOString();
}
}
};
// __proto__ property (not recommended)
const parent = {
greet() {
return 'Hello from parent';
}
};
const child = {
__proto__: parent,
greet() {
return super.greet() + ' and child';
}
};
child.greet(); // "Hello from parent and child"
// Practical: factory function
function createPoint(x, y) {
return {
x,
y,
distanceFrom(other) {
const dx = this.x - other.x;
const dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
},
toString() {
return `(${this.x}, ${this.y})`;
}
};
}
const p1 = createPoint(0, 0);
const p2 = createPoint(3, 4);
p1.distanceFrom(p2); // 5
// Practical: configuration object
const env = 'production';
const appConfig = {
env,
get isProduction() {
return this.env === 'production';
},
get isDevelopment() {
return this.env === 'development';
},
[`${env}_url`]: 'https://api.example.com',
log(message) {
if (this.isDevelopment) {
console.log(message);
}
}
};
Note: Property shorthand uses variable name as key. Method shorthand omits
: function. Computed properties use [expression]. Getters/setters accessed like
properties. Methods can be async, generators, or both.
5. Template Literals and Tagged Templates
| Feature | Syntax | Description | Example |
|---|---|---|---|
| Basic Template | `text` |
Backtick-delimited string | `Hello` |
| Interpolation | `text ${expr}` |
Embed expression result | `x = ${x}` |
| Multi-line | `line1<br>line2` |
Preserve newlines | `First<br>Second` |
| Tagged Template | tag`text ${expr}` |
Function processes template | html`<div>${content}</div>` |
| Raw Strings | String.raw`text` |
Get raw string (no escape processing) | String.raw`C:\temp` |
Example: Template literals
// Basic template literals
const name = 'Alice';
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"
// Expression interpolation
const a = 10;
const b = 20;
console.log(`${a} + ${b} = ${a + b}`); // "10 + 20 = 30"
// Any expression
const price = 99.99;
console.log(`Price: ${price.toFixed(2)}`); // "Price: $99.99"
// Function calls
function upper(str) {
return str.toUpperCase();
}
console.log(`Name: ${upper(name)}`); // "Name: ALICE"
// Multi-line strings
const html = `
<div>
<h1>Title</h1>
<p>Content</p>
</div>
`;
// Preserves indentation and newlines
// Old way (ES5)
const oldHtml =
'<div>\n' +
' <h1>Title</h1>\n' +
' <p>Content</p>\n' +
'</div>';
// Nested templates
const user = {name: 'Bob', age: 30};
const info = `User: ${user.name} (${user.age > 18 ? 'Adult' : 'Minor'})`;
// Escaping backticks
const text = `This is a backtick: \``;
// Tagged templates (custom processing)
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
return result + str + `<mark>${value}</mark>`;
}, '');
}
const score = 95;
const result = highlight`Your score is ${score}%`;
// "Your score is <mark>95</mark>%"
// Tagged template: SQL escaping (example)
function sql(strings, ...values) {
return strings.reduce((query, str, i) => {
const value = values[i];
const escaped = typeof value === 'string'
? value.replace(/'/g, "''")
: value;
return query + str + (escaped !== undefined ? escaped : '');
}, '');
}
const userId = 123;
const userName = "O'Brien";
const query = sql`SELECT * FROM users WHERE id = ${userId} AND name = '${userName}'`;
// "SELECT * FROM users WHERE id = 123 AND name = 'O''Brien'"
// Tagged template: HTML escaping
function html(strings, ...values) {
const escape = (str) => String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
return strings.reduce((result, str, i) => {
const value = values[i] !== undefined ? escape(values[i]) : '';
return result + str + value;
}, '');
}
const userInput = '<script>alert("XSS")</script>';
const safe = html`<div>${userInput}</div>`;
// "<div><script>alert("XSS")</script></div>"
// String.raw (no escape processing)
const path = String.raw`C:\Users\Documents\file.txt`;
console.log(path); // "C:\Users\Documents\file.txt" (backslashes preserved)
// Normal template would process escapes
const normalPath = `C:\Users\Documents\file.txt`;
// Escape sequences like \U, \D, \f would be interpreted
// Raw string with interpolation
const dir = 'Documents';
const rawPath = String.raw`C:\${dir}\file.txt`;
console.log(rawPath); // "C:\Documents\file.txt"
// Access raw strings in tagged templates
function showRaw(strings, ...values) {
console.log('Cooked:', strings);
console.log('Raw:', strings.raw);
console.log('Values:', values);
}
showRaw`Line 1\nLine 2 ${42}`;
// Cooked: ['Line 1
// Line 2 ', '']
// Raw: ['Line 1\\nLine 2 ', '']
// Values: [42]
// Practical: styled components (React)
function css(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] || '');
}, '');
}
const Button = css`
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
padding: 10px 20px;
`;
// Practical: i18n (internationalization)
function i18n(strings, ...values) {
const key = strings.join('{}');
// Look up translation for key
return translate(key, values);
}
const count = 5;
const message = i18n`You have ${count} new messages`;
// Practical: GraphQL queries
const gql = (strings, ...values) => strings.join('');
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
`;
// Practical: conditional parts
const showDetails = true;
const card = `
<div class="card">
<h3>${name}</h3>
${showDetails ? `<p>Age: ${age}</p>` : ''}
</div>
`;
// Practical: array to list
const items = ['Apple', 'Banana', 'Cherry'];
const list = `
<ul>
${items.map(item => `<li>${item}</li>`).join('\n ')}
</ul>
`;
// Expression can be any valid JavaScript
console.log(`Random: ${Math.random()}`);
console.log(`Array length: ${[1, 2, 3].length}`);
console.log(`Object: ${JSON.stringify({a: 1})}`);
// Template in template
const inner = `inner text`;
const outer = `outer ${`nested ${inner}`} text`;
// Empty interpolation
const empty = `Value: ${''}`;
console.log(empty); // "Value: "
// Undefined/null
console.log(`Value: ${undefined}`); // "Value: undefined"
console.log(`Value: ${null}`); // "Value: null"
Note: Template literals use backticks (`). Interpolate with
${expression}.
Preserve newlines/indentation. Tagged templates: function(strings, ...values) where
strings.raw has unprocessed escapes. Expressions converted to strings.
6. Symbol Type and Symbol Registry
| Method/Property | Syntax | Description | Returns |
|---|---|---|---|
| Symbol() | Symbol([description]) |
Create unique symbol; optional description | Unique symbol value |
| Symbol.for() | Symbol.for(key) |
Get/create global symbol by key | Symbol (reuses if exists) |
| Symbol.keyFor() | Symbol.keyFor(sym) |
Get key for global symbol | String key or undefined |
| description | symbol.description |
Get symbol description | String or undefined |
| typeof | typeof sym |
Type check | 'symbol' |
Well-Known Symbols
| Symbol | Purpose | Used By |
|---|---|---|
| Symbol.iterator | Define default iterator | for...of, spread, destructuring |
| Symbol.asyncIterator | Define async iterator | for await...of |
| Symbol.toStringTag | Customize Object.prototype.toString | Object.prototype.toString.call() |
| Symbol.toPrimitive | Convert object to primitive | Type coercion |
| Symbol.hasInstance | Customize instanceof behavior | instanceof operator |
Example: Symbols
// Create unique symbols
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false (always unique)
// Symbols with descriptions
const id = Symbol('id');
const userId = Symbol('id');
console.log(id === userId); // false (still unique despite same description)
// Description property
console.log(id.description); // "id"
// typeof
console.log(typeof id); // "symbol"
// Symbols as object keys (hidden from normal iteration)
const obj = {
name: 'Alice',
[id]: 123
};
console.log(obj[id]); // 123
console.log(obj.name); // 'Alice'
// Not enumerable in for...in
for (let key in obj) {
console.log(key); // Only logs "name" (not id)
}
// Not in Object.keys()
console.log(Object.keys(obj)); // ['name']
// Symbols visible with Object.getOwnPropertySymbols()
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]
// Or Reflect.ownKeys() (all keys)
console.log(Reflect.ownKeys(obj)); // ['name', Symbol(id)]
// Global symbol registry (shared across realms)
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2); // true (same key = same symbol)
// Get key for global symbol
console.log(Symbol.keyFor(globalSym1)); // "app.id"
// Local symbols have no key
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined
// Well-known symbols: Symbol.iterator
const iterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return {value: data[index++], done: false};
}
return {done: true};
}
};
}
};
for (const value of iterable) {
console.log(value); // 1, 2, 3
}
// Symbol.toStringTag (customize toString)
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // "[object MyClass]"
// Symbol.toPrimitive (customize type conversion)
const obj2 = {
value: 100,
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return this.value;
case 'string':
return `${this.value}`;
case 'default':
return this.value;
default:
throw new Error('Invalid hint');
}
}
};
console.log(+obj2); // 100 (number hint)
console.log(`${obj2}`); // "$100" (string hint)
console.log(obj2 + 50); // 150 (default hint)
// Symbol.hasInstance (customize instanceof)
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([1, 2, 3] instanceof MyArray); // true
// Private object properties (convention)
const _internal = Symbol('internal');
class Counter {
constructor() {
this[_internal] = 0;
}
increment() {
this[_internal]++;
}
getValue() {
return this[_internal];
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter[_internal]); // undefined (no access to local symbol)
// But symbols are not truly private
const symbols = Object.getOwnPropertySymbols(counter);
console.log(counter[symbols[0]]); // 1 (can still access)
// Symbols in JSON (ignored)
const data = {
name: 'Alice',
[Symbol('secret')]: 'hidden'
};
console.log(JSON.stringify(data)); // {"name":"Alice"}
// Cannot convert symbol to string directly
const sym = Symbol('test');
// String(sym); // TypeError
// "" + sym; // TypeError
sym.toString(); // "Symbol(test)" (explicit toString OK)
// Symbols in Maps/Sets
const map = new Map();
const key = Symbol('key');
map.set(key, 'value');
console.log(map.get(key)); // "value"
// Practical: unique constants
const STATUS = {
PENDING: Symbol('pending'),
APPROVED: Symbol('approved'),
REJECTED: Symbol('rejected')
};
let orderStatus = STATUS.PENDING;
if (orderStatus === STATUS.PENDING) {
console.log('Order is pending');
}
// Practical: metadata keys (avoid collisions)
const METADATA_KEY = Symbol.for('app.metadata');
function addMetadata(obj, data) {
obj[METADATA_KEY] = data;
}
function getMetadata(obj) {
return obj[METADATA_KEY];
}
const user = {name: 'Alice'};
addMetadata(user, {created: Date.now()});
// Practical: protocol/interface check
const SERIALIZABLE = Symbol.for('serializable');
class User {
[SERIALIZABLE]() {
return JSON.stringify(this);
}
}
function serialize(obj) {
if (SERIALIZABLE in obj) {
return obj[SERIALIZABLE]();
}
throw new Error('Not serializable');
}
// Symbol.match, Symbol.replace, etc. (regex methods)
class MyMatcher {
[Symbol.match](str) {
return str.includes('test') ? ['test'] : null;
}
}
const matcher = new MyMatcher();
console.log('testing'.match(matcher)); // ['test']
Warning: Symbols are unique (except global registry via
Symbol.for()). Not
enumerable in for...in or Object.keys(), but visible with
Object.getOwnPropertySymbols(). Not truly private. Can't convert to string with + or
''.
7. WeakMap and WeakSet Collections
| Collection | Key Type | Values | GC Behavior |
|---|---|---|---|
| WeakMap | Objects only | Any type | Keys weakly held; GC if no other references |
| WeakSet | Objects only | Objects (values are keys) | Values weakly held; GC if no other references |
WeakMap Methods
| Method | Syntax | Description | Returns |
|---|---|---|---|
| set() | weakMap.set(key, value) |
Add/update key-value pair | WeakMap instance |
| get() | weakMap.get(key) |
Get value for key | Value or undefined |
| has() | weakMap.has(key) |
Check if key exists | Boolean |
| delete() | weakMap.delete(key) |
Remove key-value pair | Boolean (true if deleted) |
WeakSet Methods
| Method | Syntax | Description | Returns |
|---|---|---|---|
| add() | weakSet.add(value) |
Add object to set | WeakSet instance |
| has() | weakSet.has(value) |
Check if value exists | Boolean |
| delete() | weakSet.delete(value) |
Remove value from set | Boolean (true if deleted) |
Example: WeakMap and WeakSet
// WeakMap basics
const weakMap = new WeakMap();
const obj1 = {name: 'Alice'};
const obj2 = {name: 'Bob'};
// Set values (keys must be objects)
weakMap.set(obj1, 'data for obj1');
weakMap.set(obj2, 'data for obj2');
// Get values
console.log(weakMap.get(obj1)); // "data for obj1"
// Check existence
console.log(weakMap.has(obj2)); // true
// Delete
weakMap.delete(obj1);
console.log(weakMap.has(obj1)); // false
// Primitives not allowed as keys
// weakMap.set('string', 'value'); // TypeError
// weakMap.set(123, 'value'); // TypeError
// weakMap.set(Symbol(), 'value'); // TypeError
// Garbage collection example
let user = {name: 'Alice'};
weakMap.set(user, {loginCount: 5});
user = null; // No more references to object
// Object and its WeakMap entry can be garbage collected
// No size property
console.log(weakMap.size); // undefined
// Not iterable
// for (let entry of weakMap) {} // TypeError
// weakMap.forEach() // undefined (no method)
// WeakSet basics
const weakSet = new WeakSet();
const item1 = {id: 1};
const item2 = {id: 2};
// Add objects
weakSet.add(item1);
weakSet.add(item2);
// Check membership
console.log(weakSet.has(item1)); // true
// Delete
weakSet.delete(item1);
console.log(weakSet.has(item1)); // false
// Primitives not allowed
// weakSet.add('string'); // TypeError
// weakSet.add(123); // TypeError
// Practical: Private data for objects
const privateData = new WeakMap();
class Person {
constructor(name, ssn) {
this.name = name; // Public
privateData.set(this, {ssn}); // Private
}
getSSN() {
return privateData.get(this).ssn;
}
}
const person = new Person('Alice', '123-45-6789');
console.log(person.name); // "Alice"
console.log(person.getSSN()); // "123-45-6789"
console.log(person.ssn); // undefined (not accessible)
// When person is garbage collected, privateData entry is too
// Practical: DOM node metadata
const nodeMetadata = new WeakMap();
function trackNode(node, metadata) {
nodeMetadata.set(node, metadata);
}
function getNodeData(node) {
return nodeMetadata.get(node);
}
const div = document.createElement('div');
trackNode(div, {clicks: 0, created: Date.now()});
// When div is removed from DOM and no references exist, GC cleans up
// Practical: Cache/memoization
const cache = new WeakMap();
function expensiveOperation(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
// Expensive computation
const result = obj.value * 2;
cache.set(obj, result);
return result;
}
const data = {value: 10};
expensiveOperation(data); // Computes and caches
expensiveOperation(data); // Returns cached result
// When data is no longer referenced, cache entry is GC'd
// Practical: Object flagging with WeakSet
const processedObjects = new WeakSet();
function processObject(obj) {
if (processedObjects.has(obj)) {
console.log('Already processed');
return;
}
// Process object
console.log('Processing', obj);
processedObjects.add(obj);
}
const obj = {id: 1};
processObject(obj); // "Processing {id: 1}"
processObject(obj); // "Already processed"
// When obj is GC'd, WeakSet entry is too
// Practical: Preventing circular reference leaks
class Node {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(node) {
this.children.push(node);
}
}
// Track parent-child relationships without preventing GC
const parentMap = new WeakMap();
function setParent(child, parent) {
parentMap.set(child, parent);
}
function getParent(child) {
return parentMap.get(child);
}
const parent = new Node('parent');
const child = new Node('child');
parent.addChild(child);
setParent(child, parent);
// WeakMap vs Map comparison
const regularMap = new Map();
const weakMap2 = new WeakMap();
let key = {id: 1};
regularMap.set(key, 'value');
weakMap2.set(key, 'value');
key = null;
// regularMap still holds reference to object (prevents GC)
// weakMap2 does not hold reference (allows GC)
// Use cases for WeakMap:
// 1. Private instance data
// 2. Metadata for objects
// 3. Caching results for objects
// 4. DOM node annotations
// 5. Prevent memory leaks with object associations
// Use cases for WeakSet:
// 1. Track processed objects
// 2. Mark objects without modifying them
// 3. Prevent duplicate processing
// 4. Object flags/tags
// Limitations:
// - Keys/values must be objects (no primitives)
// - Not enumerable (can't iterate)
// - No size property
// - No clear() method
// - Can't get all keys/values
// Why WeakMap instead of Map with cleanup?
// - Automatic garbage collection
// - No manual memory management needed
// - Prevents memory leaks
// - Cleaner code (no cleanup logic)
// When NOT to use WeakMap/WeakSet:
// - Need to iterate over entries
// - Need to know collection size
// - Keys/values are primitives
// - Need to serialize/deserialize
// - Need persistent storage
// WeakMap with object literals (careful!)
weakMap.set({}, 'value'); // No reference = immediately GC'd!
console.log(weakMap.get({})); // undefined (different object)
// Must keep reference
const key2 = {};
weakMap.set(key2, 'value');
console.log(weakMap.get(key2)); // "value"
Note: WeakMap/WeakSet keys/values must be objects (no
primitives). Entries automatically garbage collected when no other references exist. Not enumerable (no size,
forEach, keys, values, entries). Use for private data, metadata, caching without memory leaks.
Section 15 Summary:
- Destructuring: Extract array/object values with
[a, b] = arror{x, y} = obj; supports defaults, rest, skip, rename, nesting; works in parameters - Spread: Expand arrays/objects with
...in literals/calls ([...arr],{...obj}); shallow copy; merge collections; spread strings/iterables - Rest: Collect remaining items with
...rest(must be last); gather function arguments into array; collect remaining properties in destructuring - Defaults: Parameter fallbacks with
= value; only forundefined(notnull); expressions evaluated each call; can reference previous parameters - Object Literals: Property shorthand
{x, y}; method shorthandmethod() {}; computed keys[expr]; getters/setters; can be async/generator - Templates: Backtick strings with interpolation
`text ${expr}`; multi-line; tagged templatestag`...`for custom processing;String.rawfor raw strings - Symbols: Unique immutable values via
Symbol(); global registrySymbol.for(key); hidden from normal enumeration; well-known symbols customize behavior (iterator, toStringTag, toPrimitive) - WeakMap/WeakSet: Collections with object-only keys/values; weak references (allow GC); no size/iteration; use for private data, metadata, caching without memory leaks