0% found this document useful (0 votes)
3 views10 pages

Advanced JS Part4 Advanced Language Features

The document is a practical guide on advanced JavaScript features, covering topics such as symbols, iterators, generators, strict mode, and error handling. Each chapter provides detailed explanations, examples, and best practices for using these features effectively. The guide emphasizes the importance of robust error handling and modern JavaScript practices for cleaner, more maintainable code.

Uploaded by

rajjasra1970
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views10 pages

Advanced JS Part4 Advanced Language Features

The document is a practical guide on advanced JavaScript features, covering topics such as symbols, iterators, generators, strict mode, and error handling. Each chapter provides detailed explanations, examples, and best practices for using these features effectively. The guide emphasizes the importance of robust error handling and modern JavaScript practices for cleaner, more maintainable code.

Uploaded by

rajjasra1970
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Advanced JavaScript

Advanced Language Features


A Practical Guide

2026

Chapter 17: Symbols


Creating and using symbols
Why use symbols?
Well-known symbols
Global symbol registry
Best practices
Chapter 18: Iterators
The iterator protocol, manually
Making a custom object iterable
Built-in iterables
Best practices
Chapter 19: Generators
Basic generator syntax
yield pauses execution
Passing values into a generator
Delegating with yield*
Async generators
Best practices
Chapter 20: Strict Mode
Enabling strict mode
What strict mode changes
Best practices
Chapter 21: Error Handling and try/catch
Basic try/catch/finally
Catching specific error types
Custom error classes
Async error handling
Optional catch binding and error causes
Best practices
Part 4 Summary
Chapter 17: Symbols
Symbol is a primitive type introduced in ES6 that creates guaranteed-unique values, typically used as object property keys
that won’t collide with other keys — even ones with the same description.

Creating and using symbols

const id1 = Symbol("id");


const id2 = Symbol("id");
[Link](id1 === id2); // false - every symbol is unique, even with the same description

const user = {
name: "Alice",
[id1]: 12345
};
[Link](user[id1]); // 12345
[Link]([Link](user)); // ["name"] - symbol keys are hidden from normal enumeration

Why use symbols?

They let you attach “hidden” metadata to objects without risking collisions with existing or future string keys — useful in
libraries that shouldn’t clash with user-defined properties.

const LIBRARY_META = Symbol("libraryMeta");

function tag(obj, meta) {


obj[LIBRARY_META] = meta;
return obj;
}

const config = { debug: true };


tag(config, { version: "1.0" });
[Link](config); // { debug: true } - the symbol property doesn't show up in normal logs/loops

Well-known symbols

JavaScript itself uses symbols to let you customize built-in behavior. The most common is [Link] , covered in Chapter
18.

class Range {
constructor(start, end) {
[Link] = start;
[Link] = end;
}
[[Link]]() {
let current = [Link];
const end = [Link];
return {
next() {
return current <= end
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
}

[Link]([...new Range(1, 5)]); // [1, 2, 3, 4, 5]

Global symbol registry

[Link](key) creates (or reuses) a symbol shared across your entire application via a global registry — unlike Symbol() ,
which always creates a new unique value.
const a = [Link]("[Link]");
const b = [Link]("[Link]");
[Link](a === b); // true - same key returns the same symbol

Best practices

Use symbols for library-internal metadata or “protocol” properties (like custom iterators), not as a general privacy
mechanism — private class fields ( #field ) are better for true privacy.
Use [Link]() only when you deliberately need the same symbol across different files/modules.
Chapter 18: Iterators
An iterator is any object implementing the iterator protocol: a next() method that returns { value, done } . Iterables are
objects implementing [Link] , which makes them work with for...of , spread, and destructuring.

The iterator protocol, manually

function makeCounter(limit) {
let count = 0;
return {
next() {
if (count < limit) {
return { value: count++, done: false };
}
return { value: undefined, done: true };
}
};
}

const counter = makeCounter(3);


[Link]([Link]()); // { value: 0, done: false }
[Link]([Link]()); // { value: 1, done: false }
[Link]([Link]()); // { value: 2, done: false }
[Link]([Link]()); // { value: undefined, done: true }

Making a custom object iterable

Any object becomes iterable — and therefore usable in for...of , spread [...obj] , and destructuring — by implementing
[Link] .

class LinkedList {
constructor() {
[Link] = [];
}
add(item) {
[Link](item);
return this;
}
[[Link]]() {
let index = 0;
const items = [Link];
return {
next: () => index < [Link]
? { value: items[index++], done: false }
: { value: undefined, done: true }
};
}
}

const list = new LinkedList().add("a").add("b").add("c");


for (const item of list) {
[Link](item); // a, b, c
}
[Link]([...list]); // ['a', 'b', 'c']

Built-in iterables

Arrays, strings, Map , Set , and NodeList are all iterable by default — this is precisely why for...of works on all of them.

for (const char of "hi") [Link](char); // h, i


for (const [key, value] of new Map([["a", 1], ["b", 2]])) [Link](key, value);

Best practices

Implement [Link] on custom data structures (queues, trees, linked lists) so they integrate naturally with
for...of and spread.
Prefer for...of over manually calling .next() unless you need fine-grained control over iteration.
Chapter 19: Generators
A generator is a special function ( function* ) that can pause and resume execution, automatically implementing the iterator
protocol for you.

Basic generator syntax

function* countUpTo(max) {
for (let i = 1; i <= max; i++) {
yield i;
}
}

const gen = countUpTo(3);


[Link]([Link]()); // { value: 1, done: false }
[Link]([Link]()); // { value: 2, done: false }
[Link]([Link]()); // { value: 3, done: false }
[Link]([Link]()); // { value: undefined, done: true }

// Generators are iterable, so they work with for...of directly


for (const n of countUpTo(5)) [Link](n); // 1 2 3 4 5

yield pauses execution

Each call to .next() resumes the function until the next yield , making generators ideal for lazy sequences — including infinite
ones.

function* infiniteIds() {
let id = 1;
while (true) {
yield id++;
}
}

const ids = infiniteIds();


[Link]([Link]().value); // 1
[Link]([Link]().value); // 2
[Link]([Link]().value); // 3
// This never runs out of memory because values are produced on demand

Passing values into a generator

function* chatBot() {
const name = yield "What's your name?";
const mood = yield `Hi ${name}, how are you?`;
return `Nice talking to you, ${name} (${mood})`;
}

const bot = chatBot();


[Link]([Link]().value); // "What's your name?"
[Link]([Link]("Alice").value); // "Hi Alice, how are you?"
[Link]([Link]("great").value); // "Nice talking to you, Alice (great)"

Delegating with yield*

function* inner() {
yield 1;
yield 2;
}
function* outer() {
yield 0;
yield* inner();
yield 3;
}
[Link]([...outer()]); // [0, 1, 2, 3]
Async generators

Generators combine with async / await to lazily produce asynchronous values — useful for paginated API results.

async function* fetchPages(url) {


let nextUrl = url;
while (nextUrl) {
const res = await fetch(nextUrl);
const page = await [Link]();
yield [Link];
nextUrl = [Link];
}
}

for await (const items of fetchPages("/api/items")) {


[Link](items);
}

Best practices

Use generators for lazy evaluation: infinite sequences, large datasets processed in chunks, or custom iteration logic.
Use async generators for streaming/paginated data instead of loading everything into memory at once.
Chapter 20: Strict Mode
"use strict" opts your code into a restricted variant of JavaScript that catches common mistakes and disables some confusing
legacy behavior.

Enabling strict mode

"use strict"; // at the top of a file - applies to the whole file

function example() {
"use strict"; // or at the top of a single function
}

Note: ES6 modules and classes are always in strict mode automatically — you don’t need the directive in modern module-
based code.

What strict mode changes

Assigning to undeclared variables throws instead of creating a global:

"use strict";
x = 10; // ReferenceError: x is not defined

Silent failures become real errors:

"use strict";
const obj = [Link]({ name: "Alice" });
[Link] = "Bob"; // TypeError (non-strict mode fails silently instead)

Duplicate parameter names are disallowed:

"use strict";
function broken(a, a) {} // SyntaxError: Duplicate parameter name not allowed in this context

this is undefined in plain function calls, instead of the global object:

"use strict";
function whoAmI() {
[Link](this);
}
whoAmI(); // undefined (non-strict mode: the global/window object)

Octal literals and with statements are disallowed entirely — both are legacy sources of bugs.

Best practices

In modern code, you get strict mode for free via ES modules and classes — no need to add the directive manually in most
projects.
If writing plain <script> (non-module) code, add "use strict" at the top of every file to catch mistakes early.
Chapter 21: Error Handling and try/catch
Robust error handling is what separates prototype code from production code.

Basic try/catch/finally

try {
const data = [Link]("{ invalid json");
} catch (error) {
[Link]("Failed to parse:", [Link]);
} finally {
[Link]("This always runs, error or not");
}

Catching specific error types

try {
riskyOperation();
} catch (error) {
if (error instanceof TypeError) {
[Link]("A type error occurred:", [Link]);
} else if (error instanceof RangeError) {
[Link]("A range error occurred:", [Link]);
} else {
throw error; // re-throw anything we don't know how to handle
}
}

Custom error classes

class ValidationError extends Error {


constructor(message, field) {
super(message);
[Link] = "ValidationError";
[Link] = field;
}
}

function validateAge(age) {
if (age < 0) {
throw new ValidationError("Age cannot be negative", "age");
}
return age;
}

try {
validateAge(-5);
} catch (error) {
if (error instanceof ValidationError) {
[Link](`Validation failed on field "${[Link]}": ${[Link]}`);
}
}

Async error handling

async function loadData() {


try {
const response = await fetch("/api/data");
if (![Link]) throw new Error(`HTTP ${[Link]}`);
return await [Link]();
} catch (error) {
[Link]("Load failed:", [Link]);
return null; // graceful fallback
}
}

Optional catch binding and error causes


try {
doSomething();
} catch {
// ES2019: you don't have to name the error if you don't need it
[Link]("Something went wrong");
}

// ES2022: chain the underlying cause of an error


try {
parseConfig();
} catch (err) {
throw new Error("Failed to start application", { cause: err });
}

Best practices

Never use empty catch blocks that swallow errors silently — at minimum, log them.
Create custom error classes for domain-specific failures ( ValidationError , NotFoundError ) to make handling logic in catch
blocks precise.
Use { cause } to preserve the original error when wrapping/rethrowing, so debugging doesn’t lose context.
Part 4 Summary

Feature Purpose

Symbol Unique, collision-free property keys

Iterator protocol Makes custom objects work with for...of /spread

Generators ( function* ) Pausable functions, lazy sequences

Strict mode Catches silent errors and legacy footguns

try/catch/finally Structured error handling

Next: Part 5 — Browser Storage, covering localStorage and sessionStorage .

You might also like