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, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    
    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] = arr or {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 for undefined (not null); expressions evaluated each call; can reference previous parameters
  • Object Literals: Property shorthand {x, y}; method shorthand method() {}; computed keys [expr]; getters/setters; can be async/generator
  • Templates: Backtick strings with interpolation `text ${expr}`; multi-line; tagged templates tag`...` for custom processing; String.raw for raw strings
  • Symbols: Unique immutable values via Symbol(); global registry Symbol.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