From c542f121bd73432ea860f6b132dcb77e790d51af Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 16:28:09 -0300 Subject: [PATCH 001/128] chore: add OpenCode configuration with Context7 and GitHub MCP servers --- opencode.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 opencode.json diff --git a/opencode.json b/opencode.json new file mode 100644 index 00000000..9222cb5f --- /dev/null +++ b/opencode.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "context7": { + "type": "remote", + "url": "https://mcp.context7.com/mcp" + }, + "github": { + "type": "local", + "command": ["bunx", "@modelcontextprotocol/server-github"], + "environment": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PERSONAL_ACCESS_TOKEN}", + "GITHUB_TOOLSETS": "repos,issues,pull_requests,actions,code_security" + } + } + } +} From 888cd5bc1fb3332a35ff07e78ca1213a313832ab Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 16:35:05 -0300 Subject: [PATCH 002/128] docs: add CLAUDE.md with project context and conventional commits guidelines --- CLAUDE.md | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..d5516267 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,181 @@ +# 33 JavaScript Concepts - Project Context + +## Overview + +This repository is a curated collection of **33 essential JavaScript concepts** that every JavaScript developer should know. It serves as a comprehensive learning resource and study guide for developers at all levels, from beginners to advanced practitioners. + +The project was recognized by GitHub as one of the **top open source projects of 2018** and has been translated into 40+ languages by the community. + +## Project Purpose + +- Help developers master fundamental and advanced JavaScript concepts +- Provide curated resources (articles, videos, books) for each concept +- Serve as a reference guide for interview preparation +- Foster community contributions through translations and resource additions + +## Repository Structure + +``` +33-js-concepts/ +├── README.md # Main content with all 33 concepts and resources +├── CONTRIBUTING.md # Guidelines for contributors +├── CODE_OF_CONDUCT.md # Community standards +├── LICENSE # MIT License +├── package.json # Project metadata +├── opencode.json # OpenCode AI assistant configuration +└── github-image.png # Project banner image +``` + +## The 33 Concepts + +1. Call Stack +2. Primitive Types +3. Value Types and Reference Types +4. Implicit, Explicit, Nominal, Structuring and Duck Typing +5. == vs === vs typeof +6. Function Scope, Block Scope and Lexical Scope +7. Expression vs Statement +8. IIFE, Modules and Namespaces +9. Message Queue and Event Loop +10. setTimeout, setInterval and requestAnimationFrame +11. JavaScript Engines +12. Bitwise Operators, Type Arrays and Array Buffers +13. DOM and Layout Trees +14. Factories and Classes +15. this, call, apply and bind +16. new, Constructor, instanceof and Instances +17. Prototype Inheritance and Prototype Chain +18. Object.create and Object.assign +19. map, reduce, filter +20. Pure Functions, Side Effects, State Mutation and Event Propagation +21. Closures +22. High Order Functions +23. Recursion +24. Collections and Generators +25. Promises +26. async/await +27. Data Structures +28. Expensive Operation and Big O Notation +29. Algorithms +30. Inheritance, Polymorphism and Code Reuse +31. Design Patterns +32. Partial Applications, Currying, Compose and Pipe +33. Clean Code + +## Content Format + +Each concept in the README follows this structure: + +1. **Concept Title** - Numbered heading +2. **Description** - Brief ECMAScript-based explanation +3. **Reference** - MDN or official documentation links +4. **Articles** - Curated blog posts and tutorials +5. **Videos** - YouTube tutorials and conference talks +6. **Books** - Recommended reading (when applicable) + +## Contributing Guidelines + +### Adding Resources +- Resources should be high-quality and educational +- Include author name in the link text +- Follow the existing format for consistency + +### Creating Translations +1. Fork the repository +2. Translate content in your fork +3. Add translation link to the Community section in README +4. Submit a PR with title "Add [language] translation" + +### Resource Format +```markdown +- [Article/Video Title — Author Name](URL) +``` + +## Git Commit Conventions + +This project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification. All commits must adhere to this format for consistency and automated changelog generation. + +### Commit Message Format + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +### Commit Types + +| Type | Description | +|------|-------------| +| `feat` | New features or content additions (e.g., new resources, new concepts) | +| `fix` | Bug fixes, broken link corrections, typo fixes | +| `docs` | Documentation changes (README updates, CONTRIBUTING updates) | +| `style` | Formatting changes (markdown formatting, whitespace) | +| `refactor` | Content restructuring without adding new resources | +| `chore` | Maintenance tasks (config updates, dependency updates) | +| `ci` | CI/CD configuration changes | +| `perf` | Performance improvements | +| `test` | Adding or updating tests | +| `build` | Build system or external dependency changes | +| `revert` | Reverting a previous commit | + +### Examples + +```bash +# Adding a new resource +feat: add article about closures by John Doe + +# Fixing a broken link +fix: update broken MDN link in Promises section + +# Documentation update +docs: update contributing guidelines for translations + +# Maintenance task +chore: update opencode.json configuration + +# Adding content to existing concept +feat(closures): add video tutorial by Fun Fun Function + +# Multiple changes in body +feat: add new resources for async/await + +- Add article by JavaScript Teacher +- Add video tutorial by Traversy Media +- Update reference links +``` + +### Rules + +1. **Use lowercase** for the type and description +2. **No period** at the end of the description +3. **Use imperative mood** ("add" not "added", "fix" not "fixed") +4. **Keep the first line under 72 characters** +5. **Reference issues** in the footer when applicable (e.g., `Closes #123`) + +## MCP Servers Available + +This project has OpenCode configured with: + +1. **Context7** - Documentation search (`use context7` in prompts) +2. **GitHub** - Repository management (`use github` in prompts) + +## Important Notes + +- This is primarily a documentation/resource repository, not a code library +- The main content lives in `README.md` +- Translations are maintained in separate forked repositories +- Community contributions are welcome and encouraged +- MIT Licensed + +## Maintainer + +**Leonardo Maldonado** - [@leonardomso](https://github.com/leonardomso) + +## Links + +- Repository: https://github.com/leonardomso/33-js-concepts +- Issues: https://github.com/leonardomso/33-js-concepts/issues +- Original Article: [33 Fundamentals Every JavaScript Developer Should Know](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1) by Stephen Curtis From 34d9c27c37b347bc3e2c300cb013398a5e003637 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 17:40:09 -0300 Subject: [PATCH 003/128] feat: add Mintlify documentation site with all 33 JavaScript concepts --- CLAUDE.md | 77 +++++++++-- docs/concepts/algorithms.mdx | 43 ++++++ docs/concepts/async-await.mdx | 77 +++++++++++ docs/concepts/big-o-notation.mdx | 45 ++++++ docs/concepts/bitwise-operators.mdx | 38 +++++ docs/concepts/call-stack.mdx | 56 ++++++++ docs/concepts/clean-code.mdx | 56 ++++++++ docs/concepts/closures.mdx | 69 +++++++++ docs/concepts/collections-generators.mdx | 52 +++++++ docs/concepts/currying-composition.mdx | 58 ++++++++ docs/concepts/data-structures.mdx | 46 ++++++ docs/concepts/design-patterns.mdx | 56 ++++++++ docs/concepts/dom.mdx | 55 ++++++++ docs/concepts/equality-operators.mdx | 56 ++++++++ docs/concepts/event-loop.mdx | 46 ++++++ docs/concepts/expression-statement.mdx | 43 ++++++ docs/concepts/factories-classes.mdx | 41 ++++++ docs/concepts/higher-order-functions.mdx | 53 +++++++ docs/concepts/iife-modules.mdx | 61 ++++++++ docs/concepts/inheritance-polymorphism.mdx | 49 +++++++ docs/concepts/javascript-engines.mdx | 42 ++++++ docs/concepts/map-reduce-filter.mdx | 66 +++++++++ docs/concepts/new-constructor.mdx | 30 ++++ docs/concepts/object-create-assign.mdx | 51 +++++++ docs/concepts/primitive-types.mdx | 56 ++++++++ docs/concepts/promises.mdx | 65 +++++++++ docs/concepts/prototype.mdx | 70 ++++++++++ docs/concepts/pure-functions.mdx | 53 +++++++ docs/concepts/recursion.mdx | 53 +++++++ docs/concepts/scope.mdx | 67 +++++++++ docs/concepts/this-call-apply-bind.mdx | 64 +++++++++ docs/concepts/timers.mdx | 40 ++++++ docs/concepts/type-coercion.mdx | 63 +++++++++ docs/concepts/value-reference-types.mdx | 47 +++++++ docs/contributing.mdx | 91 ++++++++++++ docs/docs.json | 131 ++++++++++++++++++ docs/index.mdx | 95 +++++++++++++ docs/introduction.mdx | 154 +++++++++++++++++++++ docs/translations.mdx | 94 +++++++++++++ 39 files changed, 2401 insertions(+), 8 deletions(-) create mode 100644 docs/concepts/algorithms.mdx create mode 100644 docs/concepts/async-await.mdx create mode 100644 docs/concepts/big-o-notation.mdx create mode 100644 docs/concepts/bitwise-operators.mdx create mode 100644 docs/concepts/call-stack.mdx create mode 100644 docs/concepts/clean-code.mdx create mode 100644 docs/concepts/closures.mdx create mode 100644 docs/concepts/collections-generators.mdx create mode 100644 docs/concepts/currying-composition.mdx create mode 100644 docs/concepts/data-structures.mdx create mode 100644 docs/concepts/design-patterns.mdx create mode 100644 docs/concepts/dom.mdx create mode 100644 docs/concepts/equality-operators.mdx create mode 100644 docs/concepts/event-loop.mdx create mode 100644 docs/concepts/expression-statement.mdx create mode 100644 docs/concepts/factories-classes.mdx create mode 100644 docs/concepts/higher-order-functions.mdx create mode 100644 docs/concepts/iife-modules.mdx create mode 100644 docs/concepts/inheritance-polymorphism.mdx create mode 100644 docs/concepts/javascript-engines.mdx create mode 100644 docs/concepts/map-reduce-filter.mdx create mode 100644 docs/concepts/new-constructor.mdx create mode 100644 docs/concepts/object-create-assign.mdx create mode 100644 docs/concepts/primitive-types.mdx create mode 100644 docs/concepts/promises.mdx create mode 100644 docs/concepts/prototype.mdx create mode 100644 docs/concepts/pure-functions.mdx create mode 100644 docs/concepts/recursion.mdx create mode 100644 docs/concepts/scope.mdx create mode 100644 docs/concepts/this-call-apply-bind.mdx create mode 100644 docs/concepts/timers.mdx create mode 100644 docs/concepts/type-coercion.mdx create mode 100644 docs/concepts/value-reference-types.mdx create mode 100644 docs/contributing.mdx create mode 100644 docs/docs.json create mode 100644 docs/index.mdx create mode 100644 docs/introduction.mdx create mode 100644 docs/translations.mdx diff --git a/CLAUDE.md b/CLAUDE.md index d5516267..4a9c8ee8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,13 +17,23 @@ The project was recognized by GitHub as one of the **top open source projects of ``` 33-js-concepts/ -├── README.md # Main content with all 33 concepts and resources -├── CONTRIBUTING.md # Guidelines for contributors -├── CODE_OF_CONDUCT.md # Community standards -├── LICENSE # MIT License -├── package.json # Project metadata -├── opencode.json # OpenCode AI assistant configuration -└── github-image.png # Project banner image +├── docs/ # Mintlify documentation site +│ ├── docs.json # Mintlify configuration +│ ├── index.mdx # Homepage +│ ├── introduction.mdx # Getting started guide +│ ├── contributing.mdx # Contribution guidelines +│ ├── translations.mdx # Community translations +│ └── concepts/ # 33 concept pages +│ ├── call-stack.mdx +│ ├── primitive-types.mdx +│ └── ... (all 33 concepts) +├── README.md # Main GitHub README +├── CONTRIBUTING.md # Guidelines for contributors +├── CODE_OF_CONDUCT.md # Community standards +├── LICENSE # MIT License +├── package.json # Project metadata +├── opencode.json # OpenCode AI assistant configuration +└── github-image.png # Project banner image ``` ## The 33 Concepts @@ -162,10 +172,61 @@ This project has OpenCode configured with: 1. **Context7** - Documentation search (`use context7` in prompts) 2. **GitHub** - Repository management (`use github` in prompts) +## Documentation Site (Mintlify) + +The project includes a Mintlify documentation site in the `/docs` directory. + +### Local Development + +```bash +# Install Mintlify CLI +npm i -g mint + +# Start dev server +cd docs +mint dev +``` + +The site will be available at `http://localhost:3000`. + +### Documentation Structure + +- **Getting Started**: Homepage and introduction +- **Fundamentals**: Concepts 1-6 (Call Stack through Scope) +- **Functions & Execution**: Concepts 7-10 +- **Under the Hood**: Concepts 11-13 +- **Object-Oriented JS**: Concepts 14-18 +- **Functional Programming**: Concepts 19-23 +- **Async JavaScript**: Concepts 24-26 +- **Advanced Topics**: Concepts 27-33 + +### Adding/Editing Concept Pages + +Each concept page is in `docs/concepts/` and follows this template: + +```mdx +--- +title: "Concept Name" +description: "Brief description" +--- + +## Overview +[Explanation of the concept] + +## Reference +[MDN or official docs links] + +## Articles +[Curated articles with CardGroup components] + +## Videos +[Curated videos with CardGroup components] +``` + ## Important Notes - This is primarily a documentation/resource repository, not a code library -- The main content lives in `README.md` +- The main content lives in `README.md` and `/docs` (Mintlify site) - Translations are maintained in separate forked repositories - Community contributions are welcome and encouraged - MIT Licensed diff --git a/docs/concepts/algorithms.mdx b/docs/concepts/algorithms.mdx new file mode 100644 index 00000000..156c9129 --- /dev/null +++ b/docs/concepts/algorithms.mdx @@ -0,0 +1,43 @@ +--- +title: "Algorithms" +description: "Common algorithms implemented in JavaScript" +--- + +## Overview + +An **algorithm** is a step-by-step procedure for solving a problem or accomplishing a task. Understanding common algorithms and their implementations in JavaScript is essential for technical interviews and writing efficient code. + +## Articles + + + + GitHub Repository + + + By Oleksii Trekhleb + + + +- [JS: Interview Algorithm](http://www.thatjsdude.com/interview/js1.html) +- [Algorithms in JavaScript — Thon Ly](https://medium.com/siliconwat/algorithms-in-javascript-b0bed68f4038) +- [JavaScript Objects, Square Brackets and Algorithms — Dmitri Grabov](https://medium.freecodecamp.org/javascript-objects-square-brackets-and-algorithms-e9a2916dc158) +- [Atwood's Law applied to CS101 - Classic algorithms and data structures implemented in JavaScript](https://github.com/felipernb/algorithms.js) +- [Data Structures and Algorithms library in JavaScript](https://github.com/yangshun/lago) +- [Collection of computer science algorithms and data structures written in JavaScript](https://github.com/idosela/algorithms-in-javascript) +- [Algorithms and Data Structures in JavaScript — Oleksii Trekhleb](https://dev.to/trekhleb/algorithms-and-data-structures-in-javascript-49i3) + +## Videos + + + + By Codevolution + + + By FreeCodeCamp + + + +- [Data Structures and Algorithms in Javascript | DSA with JS - RoadsideCoder](https://www.youtube.com/playlist?list=PLKhlp2qtUcSZtJefDThsXcsAbRBCSTgW4) +- [Javascript Algorithms + Data Structures - KodingKevin](https://www.youtube.com/playlist?list=PLn2ipk-jqgZiAHiA70hOxAj8RMUeqYNK3) +- [JavaScript Data Structures: Getting Started - Academind](https://www.youtube.com/watch?v=41GSinwoMYA) +- [Algorithms and Data Structures - The Coding Train (Daniel Shiffman)](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH) diff --git a/docs/concepts/async-await.mdx b/docs/concepts/async-await.mdx new file mode 100644 index 00000000..a895f754 --- /dev/null +++ b/docs/concepts/async-await.mdx @@ -0,0 +1,77 @@ +--- +title: "async/await" +description: "Modern asynchronous JavaScript syntax" +--- + +## Overview + +**async/await** is syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous code. An `async` function always returns a Promise, and `await` pauses the execution until the Promise resolves. + + +`async/await` was introduced in ES2017 (ES8) and has become the preferred way to handle asynchronous operations due to its readability and cleaner error handling with try/catch blocks. + + +## Reference + + + JavaScript.Info documentation + + +## Books + + + + By Marijn Haverbeke + + + By Dr. Axel Rauschmayer + + + +## Articles + + + + By Gokul N K + + + By Flavio Copes + + + +- [Asynchronous Javascript using async/await — Joy Warugu](https://scotch.io/tutorials/asynchronous-javascript-using-async-await) +- [Javascript — ES8 Introducing async/await Functions — Ben Garrison](https://medium.com/@_bengarrison/javascript-es8-introducing-async-await-functions-7a471ec7de8a) +- [How to escape async/await hell — Aditya Agarwal](https://medium.freecodecamp.org/avoiding-the-async-await-hell-c77a0fb71c4c) +- [Understanding JavaScript's async await — Nicolás Bevacqua](https://ponyfoo.com/articles/understanding-javascript-async-await) +- [JavaScript Async/Await: Serial, Parallel and Complex Flow — TechBrij](https://techbrij.com/javascript-async-await-parallel-sequence) +- [From JavaScript Promises to Async/Await: why bother? — Chris Nwamba](https://blog.pusher.com/promises-async-await/) +- [Flow Control in Modern JS: Callbacks to Promises to Async/Await — Craig Buckler](https://www.sitepoint.com/flow-control-callbacks-promises-async-await/) +- [How to improve your asynchronous Javascript code with async and await — Indrek Lasn](https://medium.freecodecamp.org/improve-your-asynchronous-javascript-code-with-async-and-await-c02fc3813eda) +- [Making Fetches Easy With Async Await — Mickey Sheridan](https://medium.com/@micksheridan.24/making-fetches-easy-with-async-await-8a1246efa1f6) +- [7 Reasons Why JavaScript Async/Await Is Better Than Plain Promises — Mostafa Gaafar](https://dev.to/gafi/7-reasons-to-always-use-async-await-over-plain-promises-tutorial-4ej9) +- [JavaScript: Promises or async-await — Gokul N K](https://medium.com/better-programming/should-i-use-promises-or-async-await-126ab5c98789) +- [Async / Await: From Zero to Hero — Zhi Yuan](https://dev.to/zhiyuanamos/async-await-from-zero-to-hero-a22) +- [JavaScript Visualized: Promises & Async/Await — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke) +- [Making asynchronous programming easier with async and await — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) +- [JavaScript Async/Await Tutorial – Learn Callbacks, Promises, and Async/Await by Making Ice Cream](https://www.freecodecamp.org/news/javascript-async-await-tutorial-learn-callbacks-promises-async-await-by-making-icecream/) +- [Better Than Promises - JavaScript Async/Await](https://blog.webdevsimplified.com/2021-11/async-await/) + +## Videos + + + + By Traversy Media + + + By Wes Bos + + + +- [Asynchrony: Under the Hood — Shelley Vohr](https://www.youtube.com/watch?v=SrNQS8J67zc) +- [async/await in JavaScript - What, Why and How — Fun Fun Function](https://www.youtube.com/watch?v=568g8hxJJp4&index=3&list=PL0zVEGEvSaeHJppaRLrqjeTPnCH6) +- [async/await Part 1 - Topics of JavaScript/ES8 — The Coding Train](https://www.youtube.com/watch?v=XO77Fib9tSI&index=3&list=PLRqwX-V7Uu6bKLPQvPRNNE65kBL62mVfx) +- [async/await Part 2 - Topics of JavaScript/ES8 — The Coding Train](https://www.youtube.com/watch?v=chavThlNz3s&index=4&list=PLRqwX-V7Uu6bKLPQvPRNNE65kBL62mVfx) +- [Complete Guide to JS Async & Await ES2017/ES8 — Colt Steele](https://www.youtube.com/watch?v=krAYA4rvbdA) +- [Tips for using async/await in JavaScript — James Q Quick](https://www.youtube.com/watch?v=_9vgd9XKlDQ) +- [JavaScript Async Await — Web Dev Simplified](https://www.youtube.com/watch?v=V_Kr9OSfDeU) +- [Promise async and await in javascript — Hitesh Choudhary](https://youtu.be/Gjbr21JLfgg?si=SDCVKr9ONw2GsNdT) diff --git a/docs/concepts/big-o-notation.mdx b/docs/concepts/big-o-notation.mdx new file mode 100644 index 00000000..471bb1a0 --- /dev/null +++ b/docs/concepts/big-o-notation.mdx @@ -0,0 +1,45 @@ +--- +title: "Expensive Operation and Big O Notation" +description: "Understanding algorithm complexity in JavaScript" +--- + +## Overview + +**Big O notation** is a mathematical notation used to describe the upper bound of an algorithm's time or space complexity. It helps developers understand how an algorithm's performance scales as the input size grows, which is crucial for writing efficient code. + + +Common Big O complexities from fastest to slowest: O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n²) quadratic, O(2ⁿ) exponential. + + +## Articles + + + + By César Antón Dorantes + + + By Tim Roberts + + + +- [Big O in JavaScript — Gabriela Medina](https://medium.com/@gmedina229/big-o-in-javascript-36ff67766051) +- [Big O Search Algorithms in JavaScript — Bradley Braithwaite](https://www.bradoncode.com/blog/2012/04/big-o-algorithm-examples-in-javascript.html) +- [Algorithms in plain English: time complexity and Big-O Notation — Michael Olorunnisola](https://medium.freecodecamp.org/time-is-complex-but-priceless-f0abd015063c) +- [An Introduction to Big O Notation — Joseph Trettevik](https://dev.to/lofiandcode/an-introduction-to-big-o-notation-210o) + +## Videos + + + + By Eric Traub + + + By Web Dev Simplified + + + +- [Essential Big O for JavaScript Developers — Dave Smith](https://www.youtube.com/watch?v=KatlvCFHPRo) +- [Big O Notation - Time Complexity Analysis — WebTunings](https://www.youtube.com/watch?v=ALl86xJiTD8) +- [JavaScript Algorithms: Big-O Notation - Codevolution](https://www.youtube.com/watch?v=3yUuo7TqMW8) +- [JavaScript Algorithms Crash Course: Learn Algorithms & "Big O" from the Ground Up! - Academind](https://www.youtube.com/watch?v=JgWm6sQwS_I) +- [Big O Notation - Data Structures and Algorithms in Javascript - RoadSideCoder](https://www.youtube.com/watch?v=LaexPVi1VRE) diff --git a/docs/concepts/bitwise-operators.mdx b/docs/concepts/bitwise-operators.mdx new file mode 100644 index 00000000..50632a02 --- /dev/null +++ b/docs/concepts/bitwise-operators.mdx @@ -0,0 +1,38 @@ +--- +title: "Bitwise Operators, Type Arrays and Array Buffers" +description: "Low-level operations in JavaScript" +--- + +## Overview + +Bitwise operators treat their operands as a sequence of 32 bits (zeros and ones), rather than as decimal, hexadecimal, or octal numbers. While not commonly used in everyday JavaScript programming, they are essential for certain low-level operations, performance optimizations, and working with binary data through TypedArrays and ArrayBuffers. + +## Articles + + + + By Alexander Kondov + + + By ian m + + + +- [JavaScript Bitwise Operators — w3resource](https://www.w3resource.com/javascript/operators/bitwise-operator.php) +- [Bitwise Operators in Javascript — Joe Cha](https://medium.com/bother7-blog/bitwise-operators-in-javascript-65c4c69be0d3) +- [A Comprehensive Primer on Binary Computation and Bitwise Operators in javascript — Paul Brown](https://medium.com/techtrument/a-comprehensive-primer-on-binary-computation-and-bitwise-operators-in-javascript-81acf8341f04) +- [How can I understand Bitwise operation in JavaScript?](https://www.quora.com/How-can-I-understand-Bitwise-operation-in-JavaScript) + +## Videos + + + + By Programming with Mosh + + + By Alex Hyett + + + +- [JS Bitwise Operators and Binary Numbers — Steve Griffith](https://www.youtube.com/watch?v=RRyxCmLX_ag) +- [Deep Dive into Blobs, Files, and ArrayBuffers — Steve Griffith](https://www.youtube.com/watch?v=ScZZoHj7mqY) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx new file mode 100644 index 00000000..77ee577a --- /dev/null +++ b/docs/concepts/call-stack.mdx @@ -0,0 +1,56 @@ +--- +title: "Call Stack" +description: "Understanding how JavaScript tracks function execution" +--- + +## Overview + +The call stack is a mechanism that the JavaScript interpreter uses to keep track of function execution within a program. In JavaScript, functions are executed in the order they are called. The call stack follows the **Last In, First Out (LIFO)** principle, meaning that the last function pushed onto the stack is the first one to be executed. + + +According to the ECMAScript specification, the call stack is defined as part of the execution context. Whenever a function is called, a new execution context is created and placed at the top of the stack. Once the function completes, its execution context is removed from the stack, and control returns to the previous context. + + +This helps manage synchronous code execution, as each function call must complete before the next one can begin. + +## Reference + + + Official MDN documentation on the Call Stack + + +## Articles + + + + By Gaurav Pandvia + + + By Charles Freeborn + + + +- [Javascript: What Is The Execution Context? What Is The Call Stack? — Valentino Gagliardi](https://medium.com/@valentinog/javascript-what-is-the-execution-context-what-is-the-call-stack-bd23c78f10d1) +- [What is the JS Event Loop and Call Stack? — Jess Telford](https://gist.github.com/jesstelford/9a35d20a2aa044df8bf241e00d7bc2d0) +- [Understanding Execution Context and Execution Stack in Javascript — Sukhjinder Arora](https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0) +- [How JavaScript Works Under The Hood: An Overview of JavaScript Engine, Heap and, Call Stack — Bipin Rajbhar](https://dev.to/bipinrajbhar/how-javascript-works-under-the-hood-an-overview-of-javascript-engine-heap-and-call-stack-1j5o) + +## Videos + + + + By Philip Roberts — JSConf EU + + + By Colt Steele + + + +- [Javascript: the Call Stack explained — Coding Blocks India](https://www.youtube.com/watch?v=w6QGEiQceOM) +- [What is the Call Stack? — Eric Traub](https://www.youtube.com/watch?v=w7QWQlkLY_s) +- [The Call Stack — Kevin Drumm](https://www.youtube.com/watch?v=Q2sFmqvpBe0) +- [Understanding JavaScript Execution — Codesmith](https://www.youtube.com/watch?v=Z6a1cLyq7Ac&list=PLWrQZnG8l0E4kd1T_nyuVoxQUaYEWFgcD) +- [How JavaScript Code is executed? & Call Stack — Akshay Saini](https://www.youtube.com/watch?v=iLWTnMzWtj4&list=PLlasXeu85E9cQ32gLCvAvr9vNaUccPVNP) +- [Call Stacks - CS50](https://www.youtube.com/watch?v=aCPkszeKRa4) +- [Learn the JavaScript Call Stack - codecupdev](https://www.youtube.com/watch?v=HXqXPGS96rw) +- [JavaScript Functions and the Call Stack - Chidre'sTechTutorials](https://www.youtube.com/watch?v=P6H-T4cUDR4) diff --git a/docs/concepts/clean-code.mdx b/docs/concepts/clean-code.mdx new file mode 100644 index 00000000..4ed834dc --- /dev/null +++ b/docs/concepts/clean-code.mdx @@ -0,0 +1,56 @@ +--- +title: "Clean Code" +description: "Writing maintainable and readable JavaScript" +--- + +## Overview + +**Clean code** is code that is easy to understand, easy to change, and easy to maintain. It follows consistent conventions, uses meaningful names, keeps functions small and focused, and avoids unnecessary complexity. Writing clean code is a skill that improves with practice and attention to detail. + + +Clean code is not just about making code work—it's about making code that other developers (including your future self) can easily read, understand, and modify. + + +## Articles + + + + By freeCodeCamp + + + By Ryan McDermott + + + +- [Function parameters in JavaScript Clean Code — Kevin Peters](https://medium.com/@kevin_peters/function-parameters-in-javascript-clean-code-4caac109159b) +- [Keeping your code clean — Samuel James](https://codeburst.io/keeping-your-code-clean-d30bcffd1a10) +- [Best Practices for Using Modern JavaScript Syntax — M. David Green](https://www.sitepoint.com/modern-javascript-best-practices/) +- [best practices for cross node/web development - Jimmy Wärting](https://github.com/aspect-build/aspect-cli/issues/305) +- [Writing Clean Code - Dylan Paulus](https://dev.to/ganderzz/on-writing-clean-code-57cm) +- [Writing Clean Code and The Practice of Programming - Nityesh Agarwal](https://dev.to/nityeshaga/writing-clean-code-and-the-practice-of-programming-actionable-advice-for-beginners-5f0k) +- [Clean code, dirty code, human code - Daniel Irvine](https://dev.to/d_ir/clean-code-dirty-code-human-code-6nm) +- [Practical Ways to Write Better JavaScript - Ryland G](https://dev.to/taillogs/practical-ways-to-write-better-javascript-26d4) +- [The Must-Know Clean Code Principles - Kesk on Medium](https://medium.com/swlh/the-must-know-clean-code-principles-1371a14a2e75) +- [How to use destructuring in JavaScript to write cleaner, more powerful code - freecodecamp](https://www.freecodecamp.org/news/how-to-use-destructuring-in-javascript-to-write-cleaner-more-powerful-code-9d1b38794050/) +- [Write Clean Code Using JavaScript Object Destructuring - Asel Siriwardena](https://betterprogramming.pub/write-clean-code-using-javascript-object-destructuring-3551302130e7) + +## Books + + + By Robert C. Martin + + +## Videos + + + + By Fireship + + + By freeCodeCamp + + + +- [JavaScript Best Practices and Coding Conventions - Write Clean Code](https://youtu.be/RMN_bkZ1KM0?si=Ssg3cNZ_DB7CIwKQ) +- [JavaScript Clean Code](https://youtu.be/vPXzVNmCPg4?si=QR1k4E6Zx5H4mfcs) +- [Tips On Learning How To Code](https://www.youtube.com/watch?v=0wHyoBPc6zs) diff --git a/docs/concepts/closures.mdx b/docs/concepts/closures.mdx new file mode 100644 index 00000000..5078f79a --- /dev/null +++ b/docs/concepts/closures.mdx @@ -0,0 +1,69 @@ +--- +title: "Closures" +description: "Understanding how functions remember their scope" +--- + +## Overview + +A **closure** is the combination of a function bundled together with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created. + + +Closures are one of the most powerful features in JavaScript. They enable data privacy, function factories, and are fundamental to many JavaScript patterns. + + +## Reference + + + + MDN documentation + + + JavaScript.Info + + + +## Articles + + + + By Olivier De Meulder + + + By Richard Bovell + + + +- [Understanding JavaScript Closures — Codesmith](https://codeburst.io/understanding-javascript-closures-da6aab330302) +- [Understand Closures in JavaScript — Brandon Morelli](https://codeburst.io/understand-closures-in-javascript-d07852fa51e7) +- [A simple guide to help you understand closures in JavaScript — Prashant Ram](https://medium.freecodecamp.org/javascript-closures-simplified-d0d23fa06ba4) +- [Understanding JavaScript Closures: A Practical Approach — Paul Upendo](https://scotch.io/tutorials/understanding-javascript-closures-a-practical-approach) +- [Understanding JavaScript: Closures — Alexander Kondov](https://hackernoon.com/understanding-javascript-closures-4188edf5ea1b) +- [How to use JavaScript closures with confidence — Léna Faure](https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b) +- [JavaScript closures by example — tyler](https://howchoo.com/g/mge2mji2mtq/javascript-closures-by-example) +- [JavaScript — Closures and Scope — Alex Aitken](https://codeburst.io/javascript-closures-and-scope-3784c75b9290) +- [Discover the power of closures in JavaScript — Cristi Salcescu](https://medium.freecodecamp.org/discover-the-power-of-closures-in-javascript-5c472a7765d7) +- [Closure, Currying and IIFE in JavaScript — Ritik](https://dev.to/ritik_dev_js/what-the-hack-is-closure-currying-and-iife-in-javascript-32m9) +- [Understanding Closures in JavaScript — Sukhjinder Arora](https://blog.bitsrc.io/a-beginners-guide-to-closures-in-javascript-97d372284dda) +- [Closures: Using Memoization — Brian Barbour](https://dev.to/steelvoltage/closures-using-memoization-3597) +- [whatthefuck.is · A Closure - Dan Abramov](https://whatthefuck.is/closure) +- [Do you know Closures - Mohamed Khaled](https://dev.to/this_mkhy/do-you-know-es6-part-3-advanced-3fcl#Closures-2) + +## Videos + + + + By Codesmith + + + By Akshay Saini — Namaste Javascript + + + +- [Javascript Closure — techsith](https://www.youtube.com/watch?v=71AtaJpJHw0) +- [Closures — Fun Fun Function](https://www.youtube.com/watch?v=CQqwU2Ixu-U) +- [Closures in JavaScript — techsith](https://www.youtube.com/watch?v=-xqJo5VRP4A) +- [JavaScript Closures 101: What is a closure? — JavaScript Tutorials](https://www.youtube.com/watch?v=yiEeiMN2Khs) +- [Closures — freeCodeCamp](https://www.youtube.com/watch?v=1JsJx1x35c0) +- [JavaScript Closures — CodeWorkr](https://www.youtube.com/watch?v=-rLrGAXK8WE) +- [CLOSURES en JavaScript: Qué son y cómo funcionan - Carlos Azaustre](https://youtu.be/xa8lhVwQBw4) +- [Learn Closures In 7 Minutes - Web Dev Simplified](https://www.youtube.com/watch?v=3a0I8ICR1Vg) diff --git a/docs/concepts/collections-generators.mdx b/docs/concepts/collections-generators.mdx new file mode 100644 index 00000000..0b6ffb05 --- /dev/null +++ b/docs/concepts/collections-generators.mdx @@ -0,0 +1,52 @@ +--- +title: "Collections and Generators" +description: "Understanding iterables and lazy evaluation in JavaScript" +--- + +## Overview + +ES6 introduced new collection types like **Map**, **Set**, **WeakMap**, and **WeakSet**, as well as **generators** - functions that can be paused and resumed. Generators provide an elegant way to work with iterables and implement lazy evaluation patterns. + +## Reference + + + MDN documentation + + +## Articles + + + + By Kyle Pennell + + + By Vladislav Stepanov + + + +- [ES6 WeakMaps, Sets, and WeakSets in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-weakmaps-sets-and-weaksets-in-depth) +- [Map, Set, WeakMap and WeakSet — JavaScript.Info](https://javascript.info/map-set-weakmap-weakset) +- [Maps in ES6 - A Quick Guide — Ben Mildren](https://dev.to/mildrenben/maps-in-es6---a-quick-guide-35pk) +- [ES6 — Set vs Array — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-set-vs-array-what-and-when-efc055655e1a) +- [ES6 — Map vs Object — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-map-vs-object-what-and-when-b80621932373) +- [Array vs Set vs Map vs Object — Real-time use cases in Javascript (ES6/ES7) — Rajesh Babu](https://codeburst.io/array-vs-set-vs-map-vs-object-real-time-use-cases-in-javascript-es6-47ee3295329b) +- [How to create an array of unique values in JavaScript using Sets — Claire Parker-Jones](https://dev.to/claireparker/how-to-create-an-array-of-unique-values-in-javascript-using-sets-5dg6) +- [What You Should Know About ES6 Maps — Just Chris](https://hackernoon.com/what-you-should-know-about-es6-maps-dc66af6b9a1e) +- [ES6 Maps in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-maps-in-depth) +- [Understanding Generators in ES6 JavaScript with Examples — Arfat Salman](https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5) +- [The Basics of ES6 Generators — Kyle Simpson](https://davidwalsh.name/es6-generators) +- [An Introduction to JavaScript Generators — Alice Kallaugher](https://dev.to/kallaugher/an-introduction-to-javascript-generators-1224) + +## Videos + + + + By Traversy Media + + + By Traversy Media + + + +- [The Differences between ES6 Maps and Sets — Steve Griffith](https://www.youtube.com/watch?v=m4abICrldQI) +- [Javascript Generators - THEY CHANGE EVERYTHING - ES6 Generators Harmony Generators — LearnCode.academy](https://www.youtube.com/watch?v=QO07THdLWQo) diff --git a/docs/concepts/currying-composition.mdx b/docs/concepts/currying-composition.mdx new file mode 100644 index 00000000..b6439edb --- /dev/null +++ b/docs/concepts/currying-composition.mdx @@ -0,0 +1,58 @@ +--- +title: "Partial Applications, Currying, Compose and Pipe" +description: "Advanced functional programming techniques in JavaScript" +--- + +## Overview + +**Currying** transforms a function with multiple arguments into a sequence of functions each taking a single argument. **Partial application** fixes a number of arguments to a function, producing another function of smaller arity. **Compose** and **pipe** are techniques for combining simple functions to build more complex ones. + +## Books + + + By Kyle Simpson + + +## Articles + + + + By Pragyan Das + + + By Joel Thoms + + + +- [Functional Composition: compose() and pipe() — Anton Paras](https://medium.com/@acparas/what-i-learned-today-july-2-2017-ab9a46dbf85f) +- [Why The Hipsters Compose Everything: Functional Composing In JavaScript — A. Sharif](http://busypeoples.github.io/post/functional-composing-javascript/) +- [A Gentle Introduction to Functional JavaScript pt III: Functions for making functions — James Sinclair](https://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-functions/) +- [Curry And Compose (why you should be using something like ramda in your code) — jsanchesleao](https://jsleao.wordpress.com/2015/02/22/curry-and-compose-why-you-should-be-using-something-like-ramda-in-your-code/) +- [Function Composition in JavaScript with Pipe — Andy Van Slaars](https://vanslaars.io/post/create-pipe-function/) +- [Practical Functional JavaScript with Ramda — Andrew D'Amelio, Yuri Takhteyev](https://developer.telerik.com/featured/practical-functional-javascript-ramda/) +- [The beauty in Partial Application, Currying, and Function Composition — Joel Thoms](https://hackernoon.com/the-beauty-in-partial-application-currying-and-function-composition-d885bdf0d574) +- [Curry or Partial Application? — Eric Elliott](https://medium.com/javascript-scene/curry-or-partial-application-8150044c78b8) +- [Partial Application in JavaScript — Ben Alman](http://benalman.com/news/2012/09/partial-application-in-javascript/) +- [Partial Application of Functions — Functional Reactive Ninja](https://hackernoon.com/partial-application-of-functions-dbe7d9b80760) +- [So You Want to be a Functional Programmer pt. I — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-1-1f15e387e536) +- [An introduction to the basic principles of Functional Programming — TK](https://medium.freecodecamp.org/an-introduction-to-the-basic-principles-of-functional-programming-a2c2a15c84) +- [Concepts of Functional Programming in javascript — TK](https://medium.com/the-renaissance-developer/concepts-of-functional-programming-in-javascript-6bc84220d2aa) +- [A practical guide to writing more functional JavaScript — Nadeesha Cabral](https://medium.freecodecamp.org/a-practical-guide-to-writing-more-functional-javascript-db49409f71) +- [A simple explanation of functional pipe in JavaScript — Ben Lesh](https://dev.to/benlesh/a-simple-explanation-of-functional-pipe-in-javascript-2hbj) + +## Videos + + + + By Chyld Studios + + + By Theodore Anderson + + + +- [Function Composition - Functional JavaScript — NWCalvank](https://www.youtube.com/watch?v=mth5WpEc4Qs) +- [JavaScript Function Composition Explained — Theodore Anderson](https://www.youtube.com/watch?v=Uam37AlzPYw) +- [Let's code with function composition — Fun Fun Function](https://www.youtube.com/watch?v=VGB9HbL1GHk) +- [Partial Application vs. Currying — NWCalvank](https://www.youtube.com/watch?v=DzLkRsUN2vE) +- [JavaScript Partial Application — Theodore Anderson](https://www.youtube.com/watch?v=jkebgHEcvac) diff --git a/docs/concepts/data-structures.mdx b/docs/concepts/data-structures.mdx new file mode 100644 index 00000000..5d58ec34 --- /dev/null +++ b/docs/concepts/data-structures.mdx @@ -0,0 +1,46 @@ +--- +title: "Data Structures" +description: "Understanding data structures in JavaScript" +--- + +## Overview + +Data structures are ways of organizing and storing data so that it can be accessed and modified efficiently. JavaScript provides built-in data structures like Arrays and Objects, as well as ES6 additions like Map and Set. Understanding data structures is crucial for writing efficient algorithms. + +## Articles + + + + By Thon Ly + + + By Oleksii Trekhleb + + + +- [Data Structures: Objects and Arrays ― Chris Nwamba](https://scotch.io/courses/10-need-to-know-javascript-concepts/data-structures-objects-and-arrays) +- [Data structures in JavaScript — Benoit Vallon](http://blog.benoitvallon.com/data-structures-in-javascript/data-structures-in-javascript/) +- [Playing with Data Structures in Javascript — Anish K.](https://blog.cloudboost.io/playing-with-data-structures-in-javascript-stack-a55ebe50f29d) +- [The Little Guide of Queue in JavaScript — Germán Cutraro](https://hackernoon.com/the-little-guide-of-queue-in-javascript-4f67e79260d9) +- [All algorithms writing with JavaScript in the book 'Algorithms Fourth Edition'](https://github.com/barretlee/algorithms) +- [Collection of classic computer science paradigms in JavaScript](https://github.com/nzakas/computer-science-in-javascript) +- [All the things you didn't know you wanted to know about data structures](https://github.com/jamiebuilds/itsy-bitsy-data-structures) +- [JavaScript Data Structures: 40 Part Series — miku86](https://dev.to/miku86/series/3259) +- [Data Structures: Understanding Graphs — Rachel Hawa](https://medium.com/javascript-in-plain-english/data-structures-understanding-graphs-82509d35e6b5) +- [Data Structures Two Ways: Linked List (Pt 1) — Freddie Duffield](https://dev.to/freddieduffield/data-structures-two-ways-linked-list-2n61) +- [Data Structures Two Ways: Linked List (Pt 2) — Freddie Duffield](https://dev.to/freddieduffield/data-structures-two-ways-linked-list-pt2-2i60) +- [Graph Data Structures Explained in JavaScript — Adrian Mejia](https://dev.to/amejiarosario/graph-data-structures-for-beginners-5edn) + +## Videos + + + + By Eduonix Learning Solutions + + + By freeCodeCamp + + + +- [Learning JavaScript Data Structures and Algorithms: Sorting — Packt Video](https://www.youtube.com/watch?v=Ymh_AurrMbA) +- [JavaScript Data Structures: Getting Started — Academind](https://www.youtube.com/watch?v=41GSinwoMYA&ab_channel=Academind) diff --git a/docs/concepts/design-patterns.mdx b/docs/concepts/design-patterns.mdx new file mode 100644 index 00000000..4868d604 --- /dev/null +++ b/docs/concepts/design-patterns.mdx @@ -0,0 +1,56 @@ +--- +title: "Design Patterns" +description: "Proven solutions to common programming problems" +--- + +## Overview + +**Design patterns** are reusable solutions to commonly occurring problems in software design. They represent best practices evolved over time by experienced developers. Understanding design patterns helps you write more maintainable, scalable, and efficient code. + +## Books + + + + By Addy Osmani + + + By Ross Harmes and Dustin Diaz + + + +## Articles + + + + By Germán Cocca + + + By Devan Patel + + + +- [JavaScript Design Patterns – Beginner's Guide to Mobile Web Development — Soumyajit Pathak](https://medium.com/beginners-guide-to-mobile-web-development/javascript-design-patterns-25f0faaaa15) +- [JavaScript Design Patterns — Akash Pal](https://medium.com/front-end-hacking/javascript-design-patterns-ed9d4c144c81) +- [JavaScript Design Patterns: Understanding Design Patterns in JavaScript - Sukhjinder Arora](https://blog.bitsrc.io/understanding-design-patterns-in-javascript-13345223f2dd) +- [All the 23 (GoF) design patterns implemented in Javascript — Felipe Beline](https://github.com/fbeline/Design-Patterns-JS) +- [The Power of the Module Pattern in JavaScript — jsmanifest](https://medium.com/better-programming/the-power-of-the-module-pattern-in-javascript-3c73f7cd10e8) +- [Design Patterns for Developers using JavaScript pt. I — Oliver Mensah](https://dev.to/omensah/design-patterns-for-developers-using-javascript----part-one--b3e) +- [Design Patterns for Developers using JavaScript pt. II — Oliver Mensah](https://dev.to/omensah/design-patterns-for-developers-using-javascript---part-two--3p39) +- [Design patterns in modern JavaScript development](https://levelup.gitconnected.com/design-patterns-in-modern-javascript-development-ec84d8be06ca) +- [JavaScript Design Pattern — Module Pattern - Factory Pattern — Moon](https://medium.com/javascript-in-plain-english/javascript-design-pattern-module-pattern-555737eccecd) +- [Design Patterns: Null Object - Carlos Caballero](https://medium.com/better-programming/design-patterns-null-object-5ee839e37892) +- [Strategy Pattern - Francesco Ciulla](https://dev.to/francescoxx/strategy-pattern-5oh) +- [Adapter Pattern - Francesco Ciulla](https://dev.to/francescoxx/adapter-pattern-5bjk) +- [The Power of Composite Pattern in JavaScript - jsmanifest](https://dev.to/jsmanifest/the-power-of-composite-pattern-in-javascript-2732) +- [JavaScript Patterns Workshop — Lydia Hallie](https://javascriptpatterns.vercel.app/patterns) + +## Videos + + + + By Udacity + + + By Scott Allen + + diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx new file mode 100644 index 00000000..9d548e64 --- /dev/null +++ b/docs/concepts/dom.mdx @@ -0,0 +1,55 @@ +--- +title: "DOM and Layout Trees" +description: "Understanding the Document Object Model and how browsers render pages" +--- + +## Overview + +The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as a tree of nodes and objects, allowing programming languages to interact with the page. + +## Reference + + + Official MDN documentation + + +## Books + + + By Marijn Haverbeke + + +## Articles + + + + By Tania Rascia + + + By Leonardo Maldonado + + + +- [JavaScript DOM Tutorial with Example — Guru99](https://www.guru99.com/how-to-use-dom-and-events-in-javascript.html) +- [What is the DOM? — Chris Coyier](https://css-tricks.com/dom/) +- [Traversing the DOM with JavaScript — Zell Liew](https://zellwk.com/blog/dom-traversals/) +- [DOM Tree](https://javascript.info/dom-nodes) +- [How to traverse the DOM in Javascript — Vojislav Grujić](https://medium.com/javascript-in-plain-english/how-to-traverse-the-dom-in-javascript-d6555c335b4e) +- [Render Tree Construction — Ilya Grigorik](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction) +- [What exactly is the DOM?](https://bitsofco.de/what-exactly-is-the-dom/) +- [JavaScript DOM](https://www.javascripttutorial.net/javascript-dom/) + +## Videos + + + + By The Net Ninja + + + By Traversy Media + + + +- [JavaScript DOM Manipulation Methods — Web Dev Simplified](https://www.youtube.com/watch?v=y17RuWkWdn8) +- [JavaScript DOM Traversal Methods — Web Dev Simplified](https://www.youtube.com/watch?v=v7rSSy8CaYE) +- [Traversing the Dom with Javascript — Steve Griffith](https://www.youtube.com/watch?v=Pr4LLrmDLLo) diff --git a/docs/concepts/equality-operators.mdx b/docs/concepts/equality-operators.mdx new file mode 100644 index 00000000..db345897 --- /dev/null +++ b/docs/concepts/equality-operators.mdx @@ -0,0 +1,56 @@ +--- +title: "== vs === vs typeof" +description: "Understanding equality operators and type checking in JavaScript" +--- + +## Overview + +According to the ECMAScript specification, JavaScript includes both strict (`===`) and loose (`==`) equality operators, which behave differently when comparing values. + + + + This operator performs type coercion before comparing two values. If the values are of different types, JavaScript will attempt to convert one or both values to a common type before comparison, which can lead to unexpected results. + + + This operator compares both the value and the type without any type coercion. If the two values are not of the same type, the comparison will return false. + + + The `typeof` operator is used to check the data type of a variable. While it's generally reliable, there are certain quirks, like how `typeof null` returns "object" instead of "null", due to a long-standing behavior in JavaScript's implementation. + + + +## Articles + + + + By Brandon Morelli + + + By Panu Pitkamaki + + + +- [Why Use the Triple-Equals Operator in JavaScript? — Louis Lazaris](https://www.impressivewebs.com/why-use-triple-equals-javascipt/) +- [What is the difference between == and === in JavaScript? — Craig Buckler](https://www.oreilly.com/learning/what-is-the-difference-between-and-in-javascript) +- [Why javascript's typeof always return "object"? — Stack Overflow](https://stackoverflow.com/questions/3787901/why-javascripts-typeof-always-return-object) +- [Checking Types in Javascript — Toby Ho](http://tobyho.com/2011/01/28/checking-types-in-javascript/) +- [How to better check data types in JavaScript — Webbjocke](https://webbjocke.com/javascript-check-data-types/) +- [Checking for the Absence of a Value in JavaScript — Tomer Aberbach](https://tomeraberba.ch/html/post/checking-for-the-absence-of-a-value-in-javascript.html) +- [Difference Between == and === in Javascript — Scaler](https://www.scaler.com/topics/javascript/difference-between-double-equals-and-triple-equals-in-javascript/) +- [Difference between == and === in JavaScript — GeeksforGeeks](https://www.geeksforgeeks.org/difference-between-double-equal-vs-triple-equal-javascript/) +- [=== vs == Comparision in JavaScript — FreeCodeCamp](https://www.freecodecamp.org/news/javascript-triple-equals-sign-vs-double-equals-sign-comparison-operators-explained-with-examples/) + +## Videos + + + + By Java Brains + + + By Web Dev Simplified + + + +- [Javascript typeof operator — DevDelight](https://www.youtube.com/watch?v=qPYhTPt_SbQ) +- [=== vs == in javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=a0S1iG3TgP0) +- [The typeof operator in JS - CodeVault](https://www.youtube.com/watch?v=NSS5WRcv7yM) diff --git a/docs/concepts/event-loop.mdx b/docs/concepts/event-loop.mdx new file mode 100644 index 00000000..22ad09f5 --- /dev/null +++ b/docs/concepts/event-loop.mdx @@ -0,0 +1,46 @@ +--- +title: "Message Queue and Event Loop" +description: "Understanding JavaScript's concurrency model" +--- + +## Overview + +The Event Loop is a critical part of JavaScript's concurrency model, ensuring non-blocking behavior by processing tasks in an asynchronous manner. Understanding how it interacts with the Message Queue and Microtasks is key to mastering JavaScript behavior. + + +JavaScript is single-threaded, but the Event Loop allows it to perform non-blocking operations by offloading operations to the browser's Web APIs, then queuing callbacks to be executed when the call stack is empty. + + +## Articles + + + + By Anoop Raveendran + + + By Lydia Hallie + + + +- [Understanding JS: The Event Loop — Alexander Kondov](https://hackernoon.com/understanding-js-the-event-loop-959beae3ac40) +- [The JavaScript Event Loop — Flavio Copes](https://flaviocopes.com/javascript-event-loop/) +- [Tasks, microtasks, queues and schedules — Jake Archibald](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) +- [Visualising the JavaScript Event Loop with a Pizza Restaurant analogy — Priyansh Jain](https://dev.to/presto412/visualising-the-javascript-event-loop-with-a-pizza-restaurant-analogy-47a8) +- [Understanding and Optimizing JavaScript's Event Loop — Xiuer Old](https://medium.com/javascript-zone/understanding-and-optimizing-javascripts-event-loop-717ae0095038) + +## Videos + + + + By Philip Roberts — JSConf EU + + + By Jake Archibald — JSConf.Asia 2018 + + + +- [JavaScript Event Loop — ComScience Simplified](https://www.youtube.com/watch?v=XzXIMZMN9k4) +- [I'm stuck in an Event Loop — Philip Roberts](https://www.youtube.com/watch?v=6MXRNXXgP_0) +- [Desmitificando el Event Loop (Spanish)](https://www.youtube.com/watch?v=Eqq2Rb7LzYE) +- [Callbacks, Sincrono, Assíncrono e Event Loop (PT-BR)](https://www.youtube.com/watch?v=6lbBaM18X3g) +- [JavaScript Event Loop: How it Works and Why it Matters in 5 Minutes - James Q Quick](https://www.youtube.com/watch?v=6lbBaM18X3g) diff --git a/docs/concepts/expression-statement.mdx b/docs/concepts/expression-statement.mdx new file mode 100644 index 00000000..8ce940ba --- /dev/null +++ b/docs/concepts/expression-statement.mdx @@ -0,0 +1,43 @@ +--- +title: "Expression vs Statement" +description: "Understanding the difference between expressions and statements in JavaScript" +--- + +## Overview + +According to the ECMAScript specification, **expressions** produce a value, and **statements** are instructions to perform an action, such as variable assignment or control flow. + + +Function declarations are hoisted and can be called before they are defined in the code, while function expressions are not hoisted and must be defined before being invoked. + + +## Articles + + + + By Promise Tochi + + + By Paul Wilkins + + + +- [JavaScript Function — Declaration vs Expression — Ravi Roshan](https://medium.com/@raviroshan.talk/javascript-function-declaration-vs-expression-f5873b8c7b38) +- [Function Declarations vs. Function Expressions — Mandeep Singh](https://medium.com/@mandeep1012/function-declarations-vs-function-expressions-b43646042052) +- [Function Declarations vs. Function Expressions — Anguls Croll](https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/) +- [Expression statement — MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Expression_statement) + +## Videos + + + + By Hexlet + + + By WebTunings + + + +- [Javascript Function Expression Vs Declaration For Beginners — Dev Material](https://www.youtube.com/watch?v=qz7Nq1tV7Io) +- [The difference between an expression and a statement in JavaScript](https://youtu.be/eWTuFoBYiwg) +- [Expression in javascript | Statement in javascript - Sathelli Srikanth](https://www.youtube.com/watch?v=cVDs3TZ-kXs) diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx new file mode 100644 index 00000000..5dc5d76f --- /dev/null +++ b/docs/concepts/factories-classes.mdx @@ -0,0 +1,41 @@ +--- +title: "Factories and Classes" +description: "Object creation patterns in JavaScript" +--- + +## Overview + +JavaScript provides multiple ways to create objects. ES6 introduced the `class` syntax, which provides a cleaner way to create objects and implement inheritance. Factory functions offer an alternative approach that can be more flexible in certain scenarios. + +## Articles + + + + By Tania Rascia + + + By Majid + + + +- [Better JavaScript with ES6, Pt. II: A Deep Dive into Classes ― Peleke Sengstacke](https://scotch.io/tutorials/better-javascript-with-es6-pt-ii-a-deep-dive-into-classes) +- [Understand the Factory Design Pattern in Plain JavaScript — Aditya Agarwal](https://medium.com/front-end-hacking/understand-the-factory-design-pattern-in-plain-javascript-20b348c832bd) +- [Factory Functions in JavaScript — Josh Miller](https://atendesigngroup.com/blog/factory-functions-javascript) +- [The Factory Pattern in JS ES6 — SnstsDev](https://medium.com/@SntsDev/the-factory-pattern-in-js-es6-78f0afad17e9) +- [Class vs Factory function: exploring the way forward — Cristi Salcescu](https://medium.freecodecamp.org/class-vs-factory-function-exploring-the-way-forward-73258b6a8d15) +- [How ES6 classes really work and how to build your own — Robert Grosse](https://medium.com/@robertgrosse/how-es6-classes-really-work-and-how-to-build-your-own-fd6085eb326a) +- [Understanding `super` in JavaScript](https://jordankasper.com/understanding-super-in-javascript) +- [An Easy Guide To Understanding Classes In JavaScript](https://dev.to/lawrence_eagles/an-easy-guide-to-understanding-classes-in-javascript-3bcm) + +## Videos + + + + By Programming with Mosh + + + By Fun Fun Function + + + +- [Javascript Tutorial Function Factories — Crypto Chan](https://www.youtube.com/watch?v=R7-IwpH80UE) diff --git a/docs/concepts/higher-order-functions.mdx b/docs/concepts/higher-order-functions.mdx new file mode 100644 index 00000000..acbcd7a6 --- /dev/null +++ b/docs/concepts/higher-order-functions.mdx @@ -0,0 +1,53 @@ +--- +title: "High Order Functions" +description: "Functions that operate on other functions" +--- + +## Overview + +A **higher-order function** is a function that either takes one or more functions as arguments, returns a function as its result, or both. Higher-order functions are a key concept in functional programming and enable powerful patterns like callbacks, currying, and function composition. + + +Common higher-order functions in JavaScript include `map`, `filter`, `reduce`, `forEach`, `sort`, and `find`. Understanding these functions is essential for writing clean, functional JavaScript code. + + +## Books + + + By Marijn Haverbeke + + +## Articles + + + + By M. David Green + + + By Guido Schmitz + + + +- [First-class and Higher Order Functions: Effective Functional JavaScript — Hugo Di Francesco](https://hackernoon.com/effective-functional-javascript-first-class-and-higher-order-functions-713fde8df50a) +- [Higher Order Functions in JavaScript — John Hannah](https://www.lullabot.com/articles/higher-order-functions-in-javascript) +- [Just a reminder on how to use high order functions — Pedro Filho](https://github.com/pedroapfilho/high-order-functions) +- [Understanding Higher-Order Functions in JavaScript — Sukhjinder Arora](https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad) +- [Higher Order Functions - A pragmatic approach — emmanuel ikwuoma](https://dev.to/nuel_ikwuoma/higher-order-functions-a-pragmatic-approach-51fb) + +## Videos + + + + By Traversy Media + + + By Fun Fun Function + + + +- [Higher Order Functions in Javascript — Raja Yogan](https://www.youtube.com/watch?v=dTlpYnmBW9I) +- [Higher Order Iterators in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=GYRMNp1SKXA) +- [Higher Order Functions in JavaScript — The Coding Train](https://www.youtube.com/watch?v=H4awPsyugS0) +- [Part 1: An Introduction to Callbacks and Higher Order Functions - Codesmith](https://www.youtube.com/watch?v=7E8ctomPQJw) +- [Part 2: Understanding Why We Need Higher Order Functions - Codesmith](https://www.youtube.com/watch?v=28MXziDZkE4) +- [Higher-Order Functions ft. Functional Programming - Akshay Saini](https://www.youtube.com/watch?v=HkWxvB1RJq0) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx new file mode 100644 index 00000000..997a923b --- /dev/null +++ b/docs/concepts/iife-modules.mdx @@ -0,0 +1,61 @@ +--- +title: "IIFE, Modules and Namespaces" +description: "Code organization patterns in JavaScript" +--- + +## Overview + +With the introduction of ES6 modules, the role of IIFEs (Immediately Invoked Function Expressions) in scope isolation has diminished but they still remain relevant for certain use cases. + +## Reference + + + + MDN documentation + + + MDN documentation + + + MDN documentation + + + +## Articles + + + + By Chandra Gundamaraju + + + By Jurgen Van de Moere + + + +- [JavaScript Immediately Invoked Function Expression — javascripttutorial.net](https://www.javascripttutorial.net/javascript-immediately-invoked-function-expression-iife/) +- [Modules ― Exploring JS](http://exploringjs.com/es6/ch_modules.html) +- [Understanding ES6 Modules — Craig Buckler](https://www.sitepoint.com/understanding-es6-modules/) +- [An overview of ES6 Modules in JavaScript — Brent Graham](https://blog.cloud66.com/an-overview-of-es6-modules-in-javascript/) +- [ES6 Modules in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-modules-in-depth) +- [ES6 modules, Node.js and the Michael Jackson Solution — Alberto Gimeno](https://medium.com/dailyjs/es6-modules-node-js-and-the-michael-jackson-solution-828dc244b8b) +- [JavaScript Modules: A Beginner's Guide — Preethi Kasireddy](https://medium.freecodecamp.org/javascript-modules-a-beginner-s-guide-783f7d7a5fcc) +- [Using JavaScript modules on the web — Addy Osmani & Mathias Bynens](https://developers.google.com/web/fundamentals/primers/modules) +- [IIFE: Immediately Invoked Function Expressions — Parwinder](https://dev.to/bhagatparwinder/iife-immediately-invoked-function-expressions-49c5) +- [Javascript Module Bundlers — Vanshu Hassija](https://sassy-butter-197.notion.site/Javascript-bundlers-016932b17b0744e983c2cc0db31e6f02) + +## Videos + + + + By freeCodeCamp + + + By Kyle Robinson + + + +- [Understanding JavaScript IIFE — Sheo Narayan](https://www.youtube.com/watch?v=I5EntfMeIIQ) +- [ES6 - Modules — Ryan Christiani](https://www.youtube.com/watch?v=aQr2bV1BPyE) +- [ES6 Modules in the Real World — Sam Thorogood](https://www.youtube.com/watch?v=fIP4pjAqCtQ) +- [ES6 Modules — TempleCoding](https://www.youtube.com/watch?v=5P04OK6KlXA) +- [JavaScript IIFE (Immediately Invoked Function Expressions) — Steve Griffith](https://www.youtube.com/watch?v=Xd7zgPFwVX8&) diff --git a/docs/concepts/inheritance-polymorphism.mdx b/docs/concepts/inheritance-polymorphism.mdx new file mode 100644 index 00000000..904501d5 --- /dev/null +++ b/docs/concepts/inheritance-polymorphism.mdx @@ -0,0 +1,49 @@ +--- +title: "Inheritance, Polymorphism and Code Reuse" +description: "Object-oriented programming principles in JavaScript" +--- + +## Overview + +**Inheritance** allows objects to inherit properties and methods from other objects. **Polymorphism** allows objects of different types to be treated as objects of a common type. These OOP principles, combined with effective code reuse strategies, help create maintainable and scalable JavaScript applications. + +## Reference + + + + MDN documentation + + + JavaScript.Info + + + +## Articles + + + + By Rupesh Mishra + + + By David Catuhe + + + +- [JavaScript — Inheritance, delegation patterns and Object linking — NC Patro](https://codeburst.io/javascript-inheritance-25fe61ab9f85) +- [Object Oriented JavaScript: Polymorphism with examples — Knoldus Blogs](https://blog.knoldus.com/object-oriented-javascript-polymorphism-with-examples/) +- [Program Like Proteus — A beginner's guide to polymorphism in Javascript — Sam Galson](https://medium.com/yld-blog/program-like-proteus-a-beginners-guide-to-polymorphism-in-javascript-867bea7c8be2) +- [Object-oriented JavaScript: A Deep Dive into ES6 Classes — Jeff Mott](https://www.sitepoint.com/object-oriented-javascript-deep-dive-es6-classes/) +- [Unlocking the Power of Polymorphism in JavaScript: A Deep Dive](https://prototypr.io/post/unlocking-the-power-of-polymorphism-in-javascript-a-deep-dive) + +## Videos + + + + By kudvenkat + + + By Traversy Media + + + +- [Polymorphism in JavaScript — kudvenkat](https://www.youtube.com/watch?v=zdovG9cuEBA) diff --git a/docs/concepts/javascript-engines.mdx b/docs/concepts/javascript-engines.mdx new file mode 100644 index 00000000..cd241667 --- /dev/null +++ b/docs/concepts/javascript-engines.mdx @@ -0,0 +1,42 @@ +--- +title: "JavaScript Engines" +description: "Understanding how JavaScript code is executed" +--- + +## Overview + +JavaScript engines are programs that execute JavaScript code. The most well-known engines include **V8** (Chrome, Node.js), **SpiderMonkey** (Firefox), and **JavaScriptCore** (Safari). These engines parse, compile, and execute JavaScript code, often using Just-In-Time (JIT) compilation for performance optimization. + +## Articles + + + + By Jen Looper + + + By DroidHead + + + +- [Understanding V8's Bytecode — Franziska Hinkelmann](https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775) +- [JavaScript essentials: why you should know how the engine works - Rainer Hahnekamp](https://www.freecodecamp.org/news/javascript-essentials-why-you-should-know-how-the-engine-works-c2cc0d321553) +- [JavaScript engine fundamentals: Shapes and Inline Caches](https://mathiasbynens.be/notes/shapes-ics) +- [JavaScript engine fundamentals: optimizing prototypes](https://mathiasbynens.be/notes/prototypes) +- [How V8 optimizes array operations](https://v8.dev/blog/elements-kinds) +- [JavaScript Internals: JavaScript engine, Run-time environment & setTimeout Web API — Rupesh Mishra](https://blog.bitsrc.io/javascript-internals-javascript-engine-run-time-environment-settimeout-web-api-eeed263b1617) + +## Videos + + + + By Mathias Bynens & Benedikt Meurer + + + By Akshay Saini — Namaste JavaScript + + + +- [How JavaScript Code is executed? How Javascript works behind the scenes](https://youtu.be/iLWTnMzWtj4) +- [Understanding the V8 JavaScript Engine - freeCodeCamp Talks](https://www.youtube.com/watch?v=xckH5s3UuX4) +- [JavaScript Under The Hood - JavaScript Engine Overview - Traversy Media](https://www.youtube.com/watch?v=oc6faXVc54E) +- [Arindam Paul - JavaScript VM internals, EventLoop, Async and ScopeChains](https://www.youtube.com/watch?v=QyUFheng6J0) diff --git a/docs/concepts/map-reduce-filter.mdx b/docs/concepts/map-reduce-filter.mdx new file mode 100644 index 00000000..279eb24d --- /dev/null +++ b/docs/concepts/map-reduce-filter.mdx @@ -0,0 +1,66 @@ +--- +title: "map, reduce, filter" +description: "Array transformation methods in JavaScript" +--- + +## Overview + +`map`, `reduce`, and `filter` are powerful array methods in JavaScript that enable functional programming patterns. They allow you to transform, aggregate, and filter data in arrays without mutating the original array. + + + + Creates a new array by calling a function on every element of the original array and storing the results. + + + Creates a new array with all elements that pass a test implemented by the provided function. + + + Executes a reducer function on each element of the array, resulting in a single output value. + + + +## Articles + + + + By Bojan Gvozderac + + + By João Miguel Cunha + + + +- [JavaScript's Map, Reduce, and Filter — Dan Martensen](https://danmartensen.svbtle.com/javascripts-map-reduce-and-filter) +- [How to Use Map, Filter, & Reduce in JavaScript — Peleke Sengstacke](https://code.tutsplus.com/tutorials/how-to-use-map-filter-reduce-in-javascript--cms-26209) +- [JavaScript — Learn to Chain Map, Filter, and Reduce — Brandon Morelli](https://codeburst.io/javascript-learn-to-chain-map-filter-and-reduce-acd2d0562cd4) +- [Javascript data structure with map, reduce, filter and ES6 — Deepak Gupta](https://codeburst.io/write-beautiful-javascript-with-%CE%BB-fp-es6-350cd64ab5bf) +- [Understanding map, filter and reduce in Javascript — Luuk Gruijs](https://hackernoon.com/understanding-map-filter-and-reduce-in-javascript-5df1c7eee464) +- [Functional Programming in JS: map, filter, reduce (Pt. 5) — Omer Goldberg](https://hackernoon.com/functional-programming-in-js-map-filter-reduce-pt-5-308a205fdd5f) +- [Arrow Functions: Fat and Concise Syntax in JavaScript — Kyle Pennell](https://www.sitepoint.com/es6-arrow-functions-new-fat-concise-syntax-javascript/) +- [JavaScript: Arrow Functions for Beginners — Brandon Morelli](https://codeburst.io/javascript-arrow-functions-for-beginners-926947fc0cdc) +- [When (and why) you should use ES6 arrow functions — and when you shouldn't — Cynthia Lee](https://medium.freecodecamp.org/when-and-why-you-should-use-es6-arrow-functions-and-when-you-shouldnt-3d851d7f0b26) +- [Simplify your JavaScript – Use .map(), .reduce(), and .filter() — Etienne Talbot](https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d) +- [JavaScript's Reduce Method Explained By Going On a Diet — Kevin Kononenko](https://blog.codeanalogies.com/2018/07/24/javascripts-reduce-method-explained-by-going-on-a-diet/) +- [How to write your own map, filter and reduce functions in JavaScript — Hemand Nair](https://medium.freecodecamp.org/how-to-write-your-own-map-filter-and-reduce-functions-in-javascript-ab1e35679d26) +- [JavaScript Map – How to Use the JS .map() Function — FreeCodeCamp](https://www.freecodecamp.org/news/javascript-map-how-to-use-the-js-map-function-array-method/) + +## Videos + + + + By Lydia Hallie + + + By Akshaay Saini + + + +- [Functional JavaScript: Map, forEach, Reduce, Filter — Theodore Anderson](https://www.youtube.com/watch?v=vytzLlY_wmU) +- [JavaScript Array superpowers: Map, Filter, Reduce (part I) — Michael Rosata](https://www.youtube.com/watch?v=qTeeVd8hOFY) +- [JavaScript Array superpowers: Map, Filter, Reduce (part 2) — Michael Rosata](https://www.youtube.com/watch?v=gIm9xLYudL0) +- [JavaScript Higher Order Functions - Filter, Map, Sort & Reduce — Epicop](https://www.youtube.com/watch?v=zYBeEPxNSbw) +- [Arrow functions in JavaScript - What, Why and How — Fun Fun Function](https://www.youtube.com/watch?v=6sQDTgOqh-I) +- [Learning Functional Programming with JavaScript — Anjana Vakil - JSUnconf](https://www.youtube.com/watch?v=e-5obm1G_FY&t=1521s) +- [Reduce basics - Part 3 of FP in JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=Wl98eZpkp-c) +- [Reduce Advanced - Part 4 of FP in JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=1DMolJ2FrNY&t=621s) +- [Different array methods in 1 minute | Midudev (Spanish)](https://youtu.be/Ah7-PPjQ5Ls) diff --git a/docs/concepts/new-constructor.mdx b/docs/concepts/new-constructor.mdx new file mode 100644 index 00000000..70b54d1e --- /dev/null +++ b/docs/concepts/new-constructor.mdx @@ -0,0 +1,30 @@ +--- +title: "new, Constructor, instanceof and Instances" +description: "Understanding object instantiation in JavaScript" +--- + +## Overview + +The `new` operator creates an instance of a user-defined object type or of one of the built-in object types. When you use `new`, JavaScript creates a new empty object, sets the prototype, binds `this` to the new object, and returns the object. + + +The `instanceof` operator tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object. This is useful for checking if an object was created by a particular constructor. + + +## Articles + + + + By Brandon Morelli + + + By Cynthia Lee + + + +- [Constructor, operator "new" — JavaScript.Info](https://javascript.info/constructor-new) +- [Understanding JavaScript Constructors — Faraz Kelhini](https://css-tricks.com/understanding-javascript-constructors/) +- [Use Constructor Functions — Openclassrooms](https://openclassrooms.com/en/courses/3523231-learn-to-code-with-javascript/4379006-use-constructor-functions) +- [Beyond `typeof` and `instanceof`: simplifying dynamic type checks — Dr. Axel Rauschmayer](http://2ality.com/2017/08/type-right.html) +- [Function and Object, instances of each other — Kiro Risk](https://javascriptrefined.io/function-and-object-instances-of-each-other-1e1095d5faac) +- [JavaScript instanceof operator](https://flexiple.com/javascript/instanceof-javascript) diff --git a/docs/concepts/object-create-assign.mdx b/docs/concepts/object-create-assign.mdx new file mode 100644 index 00000000..4e2dee49 --- /dev/null +++ b/docs/concepts/object-create-assign.mdx @@ -0,0 +1,51 @@ +--- +title: "Object.create and Object.assign" +description: "Object manipulation methods in JavaScript" +--- + +## Overview + +`Object.create()` creates a new object with the specified prototype object and properties. `Object.assign()` copies all enumerable own properties from one or more source objects to a target object and returns the modified target object. + +## Reference + + + + MDN documentation + + + MDN documentation + + + +## Articles + + + + By Rupesh Mishra + + + By Rob Gravelle + + + +- [Basic Inheritance with Object.create — Joshua Clanton](http://adripofjavascript.com/blog/drips/basic-inheritance-with-object-create.html) +- [Object.create() In JavaScript — GeeksforGeeks](https://www.geeksforgeeks.org/object-create-javascript/) +- [Understanding the difference between Object.create() and the new operator — Jonathan Voxland](https://medium.com/@jonathanvox01/understanding-the-difference-between-object-create-and-the-new-operator-b2a2f4749358) +- [JavaScript Object Creation: Patterns and Best Practices — Jeff Mott](https://www.sitepoint.com/javascript-object-creation-patterns-best-practises/) +- [Dealing With Objects in JavaScript With Object.assign, Object.keys and hasOwnProperty](https://www.digitalocean.com/community/tutorials/js-dealing-with-objects) +- [Copying Objects in JavaScript ― Orinami Olatunji](https://scotch.io/bar-talk/copying-objects-in-javascript) +- [JavaScript: Object.assign() — Thiago S. Adriano](https://codeburst.io/javascript-object-assign-bc9696dcbb6e) +- [How to deep clone a JavaScript Object — Flavio Copes](https://flaviocopes.com/how-to-clone-javascript-object/) +- [Object.create(): When and Why to Use — VZing](https://dev.to/vzing/object-create-when-and-why-to-use-20m9) + +## Videos + + + + By Aaron Writes Code + + + By techsith + + diff --git a/docs/concepts/primitive-types.mdx b/docs/concepts/primitive-types.mdx new file mode 100644 index 00000000..f023c57d --- /dev/null +++ b/docs/concepts/primitive-types.mdx @@ -0,0 +1,56 @@ +--- +title: "Primitive Types" +description: "Understanding JavaScript's fundamental data types" +--- + +## Overview + +According to the ECMAScript specification, JavaScript has six primitive data types: **string**, **number**, **bigint**, **boolean**, **undefined**, and **symbol**. These types are immutable, meaning their values cannot be altered. There is also a special primitive type called **null**, which represents the intentional absence of any object value. + + +Primitive values are directly assigned to a variable, and when you manipulate a primitive type, you're working directly on the value. Unlike objects, primitives do not have properties or methods, but JavaScript automatically wraps primitive values with object counterparts when necessary (e.g., when calling methods on strings). + + +## Reference + + + Official MDN documentation on primitive values + + +## Articles + + + + By GeeksforGeeks + + + By Dr. Axel Rauschmayer + + + +- [What Every JavaScript Developer Should Know About Floating Point Numbers — Chewxy](https://blog.chewxy.com/2014/02/24/what-every-javascript-developer-should-know-about-floating-point-numbers/) +- [The Secret Life of JavaScript Primitives — Angus Croll](https://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/) +- [Primitive Types — Flow](https://flow.org/en/docs/types/primitives/) +- [(Not) Everything in JavaScript is an Object — Daniel Li](https://dev.to/d4nyll/not-everything-in-javascript-is-an-object) +- [Diving Deeper in JavaScripts Objects — Arfat Salman](https://blog.bitsrc.io/diving-deeper-in-javascripts-objects-318b1e13dc12) +- [The differences between Object.freeze() vs Const in JavaScript — Bolaji Ayodeji](https://medium.com/@bolajiayodeji/the-differences-between-object-freeze-vs-const-in-javascript-4eacea534d7c) +- [Object to primitive conversion — JavaScript.Info](https://javascript.info/object-toprimitive) +- [Methods of primitives - Javascript.info](https://javascript.info/primitives-methods) + +## Videos + + + + By Academind + + + By Simon Sez IT + + + +- [Value Types and Reference Types in JavaScript — Programming with Mosh](https://www.youtube.com/watch?v=e-_mDyqm2oU) +- [JavaScript Primitive Data Types — Avelx](https://www.youtube.com/watch?v=qw3j0A3DIzQ) +- [Everything you never wanted to know about JavaScript numbers — Bartek Szopka](https://www.youtube.com/watch?v=MqHDDtVYJRI) +- [What are variables in Javascript? — JS For Everyone](https://www.youtube.com/watch?v=B4Bbmei_thw) +- [TIPOS DE DATOS PRIMITIVOS en JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=cC65D2q5f8I) +- [Data Type in JavaScript - ScholarHat](https://www.youtube.com/watch?v=aFDvBjVjCh8) diff --git a/docs/concepts/promises.mdx b/docs/concepts/promises.mdx new file mode 100644 index 00000000..ed17def0 --- /dev/null +++ b/docs/concepts/promises.mdx @@ -0,0 +1,65 @@ +--- +title: "Promises" +description: "Handling asynchronous operations in JavaScript" +--- + +## Overview + +A **Promise** is an object representing the eventual completion or failure of an asynchronous operation. It allows you to attach callbacks to handle the success or failure of the operation, rather than passing callbacks into a function. + + +Promises have three states: **pending** (initial state), **fulfilled** (operation completed successfully), and **rejected** (operation failed). + + +## Reference + + + Official MDN documentation + + +## Articles + + + + By Jecelyn Yeen + + + By Eric Elliott + + + +- [Understanding promises in JavaScript — Gokul N K](https://hackernoon.com/understanding-promises-in-javascript-13d99df067c1) +- [An Overview of JavaScript Promises — Sandeep Panda](https://www.sitepoint.com/overview-javascript-promises/) +- [How to use Promises in JavaScript — Prashant Ram](https://medium.freecodecamp.org/promises-in-javascript-explained-277b98850de) +- [Implementing Promises In JavaScript — Maciej Cieslar](https://medium.freecodecamp.org/how-to-implement-promises-in-javascript-1ce2680a7f51) +- [JavaScript: Promises explained with simple real life analogies — Shruti Kapoor](https://codeburst.io/javascript-promises-explained-with-simple-real-life-analogies-dd6908092138) +- [Promises for Asynchronous Programming — Exploring JS](http://exploringjs.com/es6/ch_promises.html) +- [JavaScript Promises Explained By Gambling At A Casino — Kevin Kononenko](https://blog.codeanalogies.com/2018/08/26/javascript-promises-explained-by-gambling-at-a-casino/) +- [ES6 Promises: Patterns and Anti-Patterns — Bobby Brennan](https://medium.com/datafire-io/es6-promises-patterns-and-anti-patterns-bbb21a5d0918) +- [A Simple Guide to ES6 Promises — Brandon Morelli](https://codeburst.io/a-simple-guide-to-es6-promises-d71bacd2e13a) +- [ES6 Promises in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-promises-in-depth) +- [How to Write a JavaScript Promise — Brandon Wozniewicz](https://medium.freecodecamp.org/how-to-write-a-javascript-promise-4ed8d44292b8) +- [JavaScript Visualized: Promises & Async/Await — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke) +- [Best Practices for ES6 Promises — Basti Ortiz](https://dev.to/somedood/best-practices-for-es6-promises-36da) +- [Promise Basics - javascript.info](https://javascript.info/promise-basics) +- [The Complete JavaScript Promise Guide](https://blog.webdevsimplified.com/2021-09/javascript-promises) +- [Promise Chaining - javascript.info](https://javascript.info/promise-chaining) + +## Videos + + + + By Ryan Christiani + + + By Traversy Media + + + +- [Promises — Fun Fun Function](https://www.youtube.com/watch?v=2d7s3spWAzo) +- [Error Handling Promises in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=f8IgdnYIwOU) +- [Promises Part 1 - Topics of JavaScript/ES6 — The Coding Train](https://www.youtube.com/watch?v=QO4NXhWo_NM) +- [JavaScript Promise in 100 Seconds](https://www.youtube.com/watch?v=RvYYCGs45L4) +- [JavaScript Promise in 9 Minutes](https://youtu.be/3NjdOtHpcBM) +- [JavaScript Promises In 10 Minutes — Web Dev Simplified](https://www.youtube.com/watch?v=DHvZLI7Db8E) +- [Promises | Ep 02 Season 02 - Namaste JavaScript - Akshay Saini](https://youtu.be/ap-6PPAuK1Y?si=Ri1fopXeYjlrHzpf) diff --git a/docs/concepts/prototype.mdx b/docs/concepts/prototype.mdx new file mode 100644 index 00000000..f2938bc9 --- /dev/null +++ b/docs/concepts/prototype.mdx @@ -0,0 +1,70 @@ +--- +title: "Prototype Inheritance and Prototype Chain" +description: "Understanding JavaScript's inheritance model" +--- + +## Overview + +JavaScript uses prototypal inheritance, where objects can inherit directly from other objects. Every object has an internal link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with `null` as its prototype. This chain of prototypes is called the **prototype chain**. + +## Reference + + + Official MDN documentation + + +## Articles + + + + By Valentin PARSY + + + By Mathias Bynens + + + +- [JavaScript Prototype — NC Patro](https://codeburst.io/javascript-prototype-cb29d82b8809) +- [Prototypes in JavaScript — Rupesh Mishra](https://hackernoon.com/prototypes-in-javascript-5bba2990e04b) +- [Prototype in JavaScript: it's quirky, but here's how it works — Pranav Jindal](https://medium.freecodecamp.org/prototype-in-js-busted-5547ec68872) +- [Understanding JavaScript: Prototype and Inheritance — Alexander Kondov](https://hackernoon.com/understanding-javascript-prototype-and-inheritance-d55a9a23bde2) +- [Understanding Classes (ES5) and Prototypal Inheritance in JavaScript — Hridayesh Sharma](https://dev.to/_hridaysharma/understanding-classes-es5-and-prototypal-inheritance-in-javascript-n8d) +- [prototype, **proto** and Prototypal inheritance in JavaScript — Varun Dey](https://dev.to/varundey/prototype-proto-and-prototypal-inheritance-in-javascript-2inl) +- [Prototypal Inheritance — JavaScript.Info](https://javascript.info/prototype-inheritance) +- [How To Work with Prototypes and Inheritance in JavaScript — Tania Rascia](https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript) +- [Master JavaScript Prototypes & Inheritance — Arnav Aggarwal](https://codeburst.io/master-javascript-prototypes-inheritance-d0a9a5a75c4e) +- [JavaScript's Prototypal Inheritance Explained Using CSS — Nash Vail](https://medium.freecodecamp.org/understanding-prototypal-inheritance-in-javascript-with-css-93b2fcda75e4) +- [Prototypal Inheritance in JavaScript — Jannis Redmann](https://gist.github.com/derhuerst/a585c4916b1c361cc6f0) +- [Demystifying ES6 Classes And Prototypal Inheritance ― Neo Ighodaro](https://scotch.io/tutorials/demystifying-es6-classes-and-prototypal-inheritance) + +## Videos + + + + By Avelx + + + By Tyler Mcginnis + + + +- [JavaScript Prototype Inheritance Explained pt. I — techsith](https://www.youtube.com/watch?v=7oNWNlMrkpc) +- [JavaScript Prototype Inheritance Explained pt. II — techsith](https://www.youtube.com/watch?v=uIlj6_z_wL8) +- [JavaScript Prototype Inheritance Explained — Kyle Robinson](https://www.youtube.com/watch?v=qMO-LTOrJaE) +- [Advanced Javascript - Prototypal Inheritance In 1 Minute](https://www.youtube.com/watch?v=G6l5CHl67HQ) +- [An Overview Of Classical Javascript Classes and Prototypal Inheritance — Pentacode](https://www.youtube.com/watch?v=phwzuiJJPpQ) +- [Object Oriented JavaScript - Prototype — The Net Ninja](https://www.youtube.com/watch?v=4jb4AYEyhRc) +- [Prototype in JavaScript — kudvenkat](https://www.youtube.com/watch?v=2rkEbcptR64) +- [JavaScript Using Prototypes — O'Reilly](https://www.youtube.com/watch?v=oCwCcNvaXAQ) +- [Prototypes in Javascript - p5.js Tutorial — The Coding Train](https://www.youtube.com/watch?v=hS_WqkyUah8) + +## Books + + + + By Kyle Simpson + + + By Nicholas C. Zakas + + diff --git a/docs/concepts/pure-functions.mdx b/docs/concepts/pure-functions.mdx new file mode 100644 index 00000000..e2dda5a4 --- /dev/null +++ b/docs/concepts/pure-functions.mdx @@ -0,0 +1,53 @@ +--- +title: "Pure Functions, Side Effects, State Mutation and Event Propagation" +description: "Understanding functional programming concepts in JavaScript" +--- + +## Overview + +**Pure functions** are functions that always return the same output for the same input and have no side effects. They don't modify external state or depend on external mutable state. Understanding pure functions, side effects, and state mutation is fundamental to writing predictable and testable JavaScript code. + + +**Event propagation** refers to the order in which events are handled in the DOM, including the capturing phase (from ancestor to target) and bubbling phase (from target to ancestor). + + +## Articles + + + + By Omer Goldberg + + + By Eric Elliott + + + +- [JavaScript: What Are Pure Functions And Why Use Them? — James Jeffery](https://medium.com/@jamesjefferyuk/javascript-what-are-pure-functions-4d4d5392d49c) +- [Pure functions in JavaScript — @nicoespeon](http://www.nicoespeon.com/en/2015/01/pure-functions-javascript/) +- [Functional Programming: Pure Functions — Arne Brasseur](https://www.sitepoint.com/functional-programming-pure-functions/) +- [Making your JavaScript Pure — Jack Franklin](https://alistapart.com/article/making-your-javascript-pure) +- [Arrays, Objects and Mutations — Federico Knüssel](https://medium.com/@fknussel/arrays-objects-and-mutations-6b23348b54aa) +- [The State of Immutability — Maciej Sikora](https://medium.com/dailyjs/the-state-of-immutability-169d2cd11310) +- [How to deal with dirty side effects in your pure functional JavaScript — James Sinclair](https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/) +- [Preventing Side Effects in JavaScript — David Walsh](https://davidwalsh.name/preventing-sideeffects-javascript) +- [JavaScript: Pure Functions — William S. Vincent](https://wsvincent.com/javascript-pure-functions/) +- [Functional programming paradigms in modern JavaScript: Pure functions — Alexander Kondov](https://hackernoon.com/functional-programming-paradigms-in-modern-javascript-pure-functions-797d9abbee1) +- [Understanding Javascript Mutation and Pure Functions — Chidume Nnamdi](https://blog.bitsrc.io/understanding-javascript-mutation-and-pure-functions-7231cc2180d3) +- [Event Propagation — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) +- [Event Propagation — Bubbling and capturing](https://javascript.info/bubbling-and-capturing) + +## Videos + + + + By Hexlet + + + By Paul McBride + + + +- [JavaScript Pure Functions — Seth Alexander](https://www.youtube.com/watch?v=frT3H-eBmPc) +- [JavaScript Pure vs Impure Functions Explained — Theodore Anderson](https://www.youtube.com/watch?v=AHbRVJzpB54) +- [Pure Functions - Programação Funcional: Parte 1 - Fun Fun Function](https://www.youtube.com/watch?v=BMUiFMZr7vk) +- [Event Propagation - JavaScript Event Bubbling and Propagation - Steve Griffith](https://www.youtube.com/watch?v=JYc7gr9Ehl0) diff --git a/docs/concepts/recursion.mdx b/docs/concepts/recursion.mdx new file mode 100644 index 00000000..4711fe7c --- /dev/null +++ b/docs/concepts/recursion.mdx @@ -0,0 +1,53 @@ +--- +title: "Recursion" +description: "Understanding recursive functions in JavaScript" +--- + +## Overview + +**Recursion** is a programming technique where a function calls itself to solve a problem. A recursive function typically has a base case (stopping condition) and a recursive case. Recursion is particularly useful for solving problems that can be broken down into smaller, similar subproblems. + + +Always ensure your recursive function has a proper base case to prevent infinite recursion and stack overflow errors. + + +## Articles + + + + By Kevin Ennis + + + By Zak Frisch + + + +- [Learn and Understand Recursion in JavaScript — Brandon Morelli](https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea) +- [Recursion in Functional JavaScript — M. David Green](https://www.sitepoint.com/recursion-functional-javascript/) +- [Programming with JS: Recursion — Alexander Kondov](https://hackernoon.com/programming-with-js-recursion-31371e2bf808) +- [Anonymous Recursion in JavaScript — simo](https://dev.to/simov/anonymous-recursion-in-javascript) +- [Recursion, iteration and tail calls in JS — loverajoel](http://www.jstips.co/en/javascript/recursion-iteration-and-tail-calls-in-js/) +- [What is Recursion? A Recursive Function Explained with JavaScript Code Examples — Nathan Sebhastian](https://www.freecodecamp.org/news/what-is-recursion-in-javascript/) +- [Intro to Recursion — Brad Newman](https://medium.com/@newmanbradm/intro-to-recursion-984a8bd50f4b) +- [Accio Recursion!: Your New Favorite JavaScript Spell — Leanne Cabey](https://medium.datadriveninvestor.com/accio-recursion-your-new-favorite-javascript-spell-7e10d3125fb3) +- [Recursion Explained (with Examples) — Christina](https://dev.to/christinamcmahon/recursion-explained-with-examples-4k1m) + +## Videos + + + + By techsith + + + By Fun Fun Function + + + +- [Recursion and Recursive Functions — Hexlet](https://www.youtube.com/watch?v=vLhHyGTkjCs) +- [Recursion: Recursion() — JS Monthly — Lucas da Costa](https://www.youtube.com/watch?v=kGXVsd8pBLw) +- [Recursive Function in JavaScript — kudvenkat](https://www.youtube.com/watch?v=uyjsR9eNTIw) +- [What on Earth is Recursion? — Computerphile](https://www.youtube.com/watch?v=Mv9NEXX1VHc) +- [Javascript Tutorial 34: Introduction To Recursion — codedamn](https://www.youtube.com/watch?v=9NO5dXSlbv8) +- [Recursion, Iteration, and JavaScript: A Love Story | JSHeroes 2018 — Anjana Vakil](https://www.youtube.com/watch?v=FmiQr4nfoPQ) +- [Recursion crash course - Colt Steele](https://www.youtube.com/watch?v=lMBVwYrmFZQ&ab_channel=ColtSteele) +- [What Is Recursion - In Depth - Web Dev Simplified](https://www.youtube.com/watch?v=6oDQaB2one8) diff --git a/docs/concepts/scope.mdx b/docs/concepts/scope.mdx new file mode 100644 index 00000000..aba7ef13 --- /dev/null +++ b/docs/concepts/scope.mdx @@ -0,0 +1,67 @@ +--- +title: "Function Scope, Block Scope and Lexical Scope" +description: "Understanding variable scope in JavaScript" +--- + +## Overview + +The ECMAScript specification outlines three key types of scope: + + + + Variables declared within a function using `var` are only accessible within that function. This scope isolates variables from being accessed outside of the function where they are declared. + + + Introduced with ES6, variables declared with `let` and `const` are block-scoped. This means they are only accessible within the specific block `{}` in which they are defined, such as inside loops or conditionals. + + + Refers to how variable access is determined based on the physical location of the variables in the code. Functions are lexically scoped, meaning that they can access variables from their parent scope. + + + +## Books + + + By Kyle Simpson + + +## Articles + + + + By Brandon Morelli + + + By FreeCodeCamp + + + +- [Functions in JavaScript - Deepa Pandey](https://www.scaler.com/topics/javascript/javascript-functions/) +- [Emulating Block Scope in JavaScript — Josh Clanton](http://adripofjavascript.com/blog/drips/emulating-block-scope-in-javascript.html) +- [The Difference Between Function and Block Scope in JavaScript — Joseph Cardillo](https://medium.com/@josephcardillo/the-difference-between-function-and-block-scope-in-javascript-4296b2322abe) +- [Understanding Scope and Context in JavaScript — Ryan Morr](http://ryanmorr.com/understanding-scope-and-context-in-javascript/) +- [JavaScript Scope and Closures — Zell Liew](https://css-tricks.com/javascript-scope-closures/) +- [Understanding Scope in JavaScript — Wissam Abirached](https://developer.telerik.com/topics/web-development/understanding-scope-in-javascript/) +- [Understanding Scope in JavaScript ― Hammad Ahmed](https://scotch.io/tutorials/understanding-scope-in-javascript) +- [When to use a function declaration vs. a function expression ― Amber Wilkie](https://medium.freecodecamp.org/when-to-use-a-function-declarations-vs-a-function-expression-70f15152a0a0) +- [A JavaScript Fundamentals Cheat Sheet: Scope, Context, and "this" ― Alexandra Fren](https://dev.to/alexandrafren/a-javascript-fundamentals-cheat-sheet-scope-context-and-this-28ai) +- [Functions / Function scope ― MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#function_scope) + +## Videos + + + + By LearnCode.academy + + + By Kirupa Chinnathambi + + + +- [JavaScript Block Scope and Function Scope — mmtuts](https://www.youtube.com/watch?v=aK_nuUAdr8E) +- [What the Heck is Lexical Scope? — NWCalvank](https://www.youtube.com/watch?v=GhNA0r10MmA) +- [Variable Scope — Steve Griffith](https://www.youtube.com/watch?v=FyWdrCZZavQ) +- [Javascript Tutorials for Beginners — Mosh Hemadani](https://www.youtube.com/watch?v=W6NZfCO5SIk) +- [JavaScript Block scope vs Function scope - nivek](https://www.youtube.com/watch?v=IaTztAtoNEY) +- [Lexical scoping in javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=qT5S7GgIioE) +- [Modern Scope Handling in JavaScript (ES6 and Beyond) - Prashant Dewangan](https://www.youtube.com/watch?v=zMseUdOR7z8) diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx new file mode 100644 index 00000000..d5f5a589 --- /dev/null +++ b/docs/concepts/this-call-apply-bind.mdx @@ -0,0 +1,64 @@ +--- +title: "this, call, apply and bind" +description: "Understanding context and function binding in JavaScript" +--- + +## Overview + +The `this` keyword in JavaScript refers to the object that is executing the current function. Its value depends on how the function is called. The `call`, `apply`, and `bind` methods allow you to explicitly set the value of `this`. + +## Reference + + + + MDN documentation + + + MDN documentation + + + MDN documentation + + + +## Articles + + + + By Aniket Kudale + + + By Omer Goldberg + + + +- [JavaScript's Apply, Call, and Bind Methods are Essential for JavaScript Professionals — Richard Bovell](http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/) +- [The difference between call / apply / bind — Ivan Sifrim](https://medium.com/@ivansifrim/the-differences-between-call-apply-bind-276724bb825b) +- [What the hack is call, apply, bind in JavaScript — Ritik](https://dev.to/ritik_dev_js/what-the-hack-is-call-apply-bind-in-javascript-11ce) +- [Mastering 'this' in JavaScript: Callbacks and bind(), apply(), call() — Michelle Gienow](https://thenewstack.io/mastering-javascript-callbacks-bind-apply-call/) +- [JavaScript's apply, call, and bind explained by hosting a cookout — Kevin Kononenko](https://dev.to/kbk0125/javascripts-apply-call-and-bind-explained-by-hosting-a-cookout-32jo) +- [How AND When to use bind, call, and apply in Javascript — Eigen X](https://www.eigenx.com/blog/https/mediumcom/eigen-x/how-and-when-to-use-bind-call-and-apply-in-javascript-77b6f42898fb) +- [Let me explain to you what is `this`. (Javascript) — Jason Yu](https://dev.to/ycmjason/let-me-explain-to-you-what-is-this-javascript-44ja) +- [Understanding the "this" Keyword in JavaScript — Pavan](https://medium.com/quick-code/understanding-the-this-keyword-in-javascript-cb76d4c7c5e8) +- [How to understand the keyword this and context in JavaScript — Lukas Gisder-Dubé](https://medium.freecodecamp.org/how-to-understand-the-keyword-this-and-context-in-javascript-cd624c6b74b8) +- [What are call(), apply() and bind() in JavaScript — Amitav Mishra](https://jscurious.com/what-are-call-apply-and-bind-in-javascript/) +- [Understanding 'this' binding in JavaScript — Yasemin Cidem](https://yasemincidem.medium.com/understanding-this-binding-in-javascript-86687397c76d) +- [Top 7 tricky questions of 'this' keyword](https://dmitripavlutin.com/javascript-this-interview-questions/) + +## Videos + + + + By techsith + + + By Steve Griffith + + + +- [JavaScript Practical Applications of Call, Apply and Bind functions— techsith](https://www.youtube.com/watch?v=AYVYxezrMWA) +- [JavaScript (call, bind, apply) — curious aatma](https://www.youtube.com/watch?v=Uy0NOXLBraE) +- [Understanding Functions and 'this' In The World of ES2017 — Bryan Hughes](https://www.youtube.com/watch?v=AOSYY1_np_4) +- [bind and this - Object Creation in JavaScript - FunFunFunction](https://www.youtube.com/watch?v=GhbhD1HR5vk) +- [call, apply and bind method in JavaScript](https://www.youtube.com/watch?v=75W8UPQ5l7k&t=261s) +- [Javascript Interview Questions (Call, Bind and Apply) - Roadside Coder](https://youtu.be/VkmUOktYDAU?si=SdvLZ8FBmephPxjS) diff --git a/docs/concepts/timers.mdx b/docs/concepts/timers.mdx new file mode 100644 index 00000000..1239b74c --- /dev/null +++ b/docs/concepts/timers.mdx @@ -0,0 +1,40 @@ +--- +title: "setTimeout, setInterval and requestAnimationFrame" +description: "Working with timers and animation frames in JavaScript" +--- + +## Overview + +JavaScript provides several ways to schedule code execution: `setTimeout` for one-time delayed execution, `setInterval` for repeated execution, and `requestAnimationFrame` for smooth animations synchronized with the browser's refresh rate. + +## Articles + + + + By JavaScript.Info + + + By Chris Coyier + + + +- [Why not to use setInterval — Akanksha Sharma](https://dev.to/akanksha_9560/why-not-to-use-setinterval--2na9) +- [setTimeout VS setInterval — Develoger](https://develoger.com/settimeout-vs-setinterval-cff85142555b) +- [Understanding JavaScript's requestAnimationFrame() — JavaScript Kit](http://www.javascriptkit.com/javatutors/requestanimationframe.shtml) +- [Handling time intervals in JavaScript - Amit Merchant](https://www.amitmerchant.com/Handling-Time-Intervals-In-Javascript/) +- [Debounce – How to Delay a Function in JavaScript - Ondrej Polesny](https://www.freecodecamp.org/news/javascript-debounce-example/) + +## Videos + + + + By Coding Blocks India + + + By Akshay Saini + + + +- [setTimeout and setInterval in JavaScript — techsith](https://www.youtube.com/watch?v=TbCgGWe8LN8) +- [JavaScript Timers — Steve Griffith](https://www.youtube.com/watch?v=0VVJSvlUgtg) +- [JavaScript setTimeOut and setInterval Explained — Theodore Anderson](https://www.youtube.com/watch?v=mVKfrWCOB60) diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx new file mode 100644 index 00000000..4ae3cbc2 --- /dev/null +++ b/docs/concepts/type-coercion.mdx @@ -0,0 +1,63 @@ +--- +title: "Type Coercion" +description: "Implicit, Explicit, Nominal, Structuring and Duck Typing in JavaScript" +--- + +## Overview + +The ECMAScript specification defines JavaScript as a dynamically typed language, meaning that types are associated with values rather than variables, and type checking occurs at runtime. There are various ways JavaScript manages types: + + + + This occurs when JavaScript automatically converts one data type to another when required. For instance, JavaScript might convert a string to a number during an arithmetic operation. While this can simplify some code, it can also lead to unexpected results if not handled carefully. + + + Unlike implicit typing, explicit typing involves manually converting a value from one type to another using functions like `Number()`, `String()`, or `Boolean()`. + + + JavaScript doesn't natively support nominal typing, where types are explicitly declared and checked. However, TypeScript, a superset of JavaScript, brings this feature to help catch type errors during development. + + + In this type system, types are based on the structure or properties of the data. JavaScript is a structurally typed language where objects are compatible if they share the same structure (i.e., the same set of properties and methods). + + + This is a concept where an object's suitability is determined by the presence of certain properties and methods, rather than by the actual type of the object. JavaScript relies heavily on duck typing, where behavior is inferred from an object's properties rather than its declared type. + + + +## Articles + + + + By Promise Tochi + + + By Alexey Samoshkin + + + +- [Javascript Coercion Explained — Ben Garrison](https://hackernoon.com/javascript-coercion-explained-545c895213d3) +- [What exactly is Type Coercion in Javascript? - Stack Overflow](https://stackoverflow.com/questions/19915688/what-exactly-is-type-coercion-in-javascript) + +## Videos + + + + By Shirmung Bielefeld + + + By Hitesh Choudhary + + + +- [JavaScript Questions: What is Coercion? - Steven Hancock](https://www.youtube.com/watch?v=z4-8wMSPJyI) +- [Typing: Static vs Dynamic, Weak vs. Strong - Codexpanse](https://www.youtube.com/watch?v=C5fr0LZLMAs) +- [EL SISTEMA de TIPOS DE JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=0ei4nb49GKo) +- [Duck Typing in Javascript - Techmaker Studio](https://www.youtube.com/watch?v=oEpgyoMEkrM) +- [Duck Typing in Javascript - Programming with Kartik](https://youtu.be/e4X1KAuk6Bs?si=krZKbsM2i3tmIl2G) + +## Books + + + By Kyle Simpson + diff --git a/docs/concepts/value-reference-types.mdx b/docs/concepts/value-reference-types.mdx new file mode 100644 index 00000000..0a94dc9e --- /dev/null +++ b/docs/concepts/value-reference-types.mdx @@ -0,0 +1,47 @@ +--- +title: "Value Types and Reference Types" +description: "Understanding how data is stored and passed in JavaScript" +--- + +## Overview + +According to the ECMAScript specification, **value types** are stored directly in the location that the variable accesses. These include types like number, string, boolean, undefined, bigint, symbol, and null. When you assign a value type to a variable, the value itself is stored. + + +**Reference types**, on the other hand, are objects stored in the heap. Variables assigned to reference types actually store references (pointers) to the objects, not the objects themselves. When you assign a reference type to another variable, both variables reference the same object in memory. + + +## Articles + + + + By Arnav Aggarwal + + + By Bran van der Meer + + + +- [Value Types, Reference Types and Scope in JavaScript — Ben Aston](https://medium.com/@benastontweet/lesson-1b-javascript-fundamentals-380f601ba851) +- [Back to roots: JavaScript Value vs Reference — Miro Koczka](https://medium.com/dailyjs/back-to-roots-javascript-value-vs-reference-8fb69d587a18) +- [Grasp "By Value" and "By Reference" in JavaScript — Léna Faure](https://hackernoon.com/grasp-by-value-and-by-reference-in-javascript-7ed75efa1293) +- [JavaScript Reference and Copy Variables — Vítor Capretz](https://hackernoon.com/javascript-reference-and-copy-variables-b0103074fdf0) +- [JavaScript Primitive vs Reference Values](http://www.javascripttutorial.net/javascript-primitive-vs-reference-values/) +- [JavaScript by Reference vs. by Value — nrabinowitz](https://stackoverflow.com/questions/6605640/javascript-by-reference-vs-by-value) +- [JavaScript Interview Prep: Primitive vs. Reference Types — Mike Cronin](https://dev.to/mostlyfocusedmike/javascript-interview-prep-primitive-vs-reference-types-3o4f) +- [JavaScript map vs. forEach: When to Use Each One - Sajal Soni](https://code.tutsplus.com/tutorials/javascript-map-vs-foreach-when-to-use-each-one--cms-38365) + +## Videos + + + + By techsith + + + By Programming with Mosh + + + +- [VALORES vs REFERENCIAS en JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=AvkyOrWkuQc) +- [JavaScript - Reference vs Primitive Values/ Types - Academind](https://www.youtube.com/watch?v=9ooYYRLdg_g) +- [Value Types and Reference Types in JavaScript - Programming with Mosh](https://www.youtube.com/watch?v=e-_mDyqm2oU) diff --git a/docs/contributing.mdx b/docs/contributing.mdx new file mode 100644 index 00000000..798fb77b --- /dev/null +++ b/docs/contributing.mdx @@ -0,0 +1,91 @@ +--- +title: "Contributing" +description: "How to contribute to 33 JavaScript Concepts" +--- + +## Welcome Contributors! + +This project would not be possible without your help and support, and we appreciate your willingness to contribute! + + +By contributing, you agree that your contributions will be licensed under the [MIT license](https://github.com/leonardomso/33-js-concepts/blob/master/LICENSE). + + +## How to Contribute + + + + Start by forking the [main repository](https://github.com/leonardomso/33-js-concepts) to your GitHub account. + + + Add new resources, fix broken links, or improve existing content. + + + Create a pull request with a clear description of your changes. + + + +## Adding New Resources + +When adding new resources, please follow these guidelines: + + + + - Resources should be high-quality and educational + - Content should be accurate and up-to-date + - Prefer resources from reputable sources + + + Include the author name in the link text: + ```markdown + - [Article Title — Author Name](URL) + ``` + + + Place resources in the appropriate category: + - **Reference**: Official documentation (MDN, ECMAScript spec) + - **Articles**: Blog posts and tutorials + - **Videos**: YouTube tutorials and conference talks + - **Books**: Published books and free online books + + + +## Creating Translations + +We welcome translations to make this resource accessible to developers worldwide! + + + + Fork the [main repository](https://github.com/leonardomso/33-js-concepts) to your account. + + + Stay updated with changes by watching the main repository. + + + Translate the content in your forked repository. + + + Add a link to your translation in the Community section: + ```markdown + - [Your language (English name)](link-to-your-repo) — Your Name + ``` + + + Create a Pull Request with the title "Add [language] translation" + + + +## Code of Conduct + +Please read our [Code of Conduct](https://github.com/leonardomso/33-js-concepts/blob/master/CODE_OF_CONDUCT.md) before contributing. We are committed to providing a welcoming and inclusive environment for all contributors. + +## Questions? + +If you have any questions, feel free to: + +- Open an issue on [GitHub](https://github.com/leonardomso/33-js-concepts/issues) +- Reach out to the maintainer [@leonardomso](https://github.com/leonardomso) + + + Check out the repository and start contributing today! + diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 00000000..96847e98 --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "maple", + "name": "33 JavaScript Concepts", + "description": "33 concepts every JavaScript developer should know", + "colors": { + "primary": "#F7DF1E", + "light": "#FAFAFA", + "dark": "#1A1A1A" + }, + "favicon": "/favicon.svg", + "navbar": { + "links": [ + { + "label": "GitHub", + "href": "https://github.com/leonardomso/33-js-concepts" + } + ] + }, + "navigation": { + "tabs": [ + { + "tab": "Learn", + "groups": [ + { + "group": "Getting Started", + "icon": "rocket", + "pages": [ + "index", + "introduction" + ] + }, + { + "group": "Fundamentals", + "icon": "cube", + "pages": [ + "concepts/call-stack", + "concepts/primitive-types", + "concepts/value-reference-types", + "concepts/type-coercion", + "concepts/equality-operators", + "concepts/scope" + ] + }, + { + "group": "Functions & Execution", + "icon": "code", + "pages": [ + "concepts/expression-statement", + "concepts/iife-modules", + "concepts/event-loop", + "concepts/timers" + ] + }, + { + "group": "Under the Hood", + "icon": "gear", + "pages": [ + "concepts/javascript-engines", + "concepts/bitwise-operators", + "concepts/dom" + ] + }, + { + "group": "Object-Oriented JS", + "icon": "sitemap", + "pages": [ + "concepts/factories-classes", + "concepts/this-call-apply-bind", + "concepts/new-constructor", + "concepts/prototype", + "concepts/object-create-assign" + ] + }, + { + "group": "Functional Programming", + "icon": "filter", + "pages": [ + "concepts/map-reduce-filter", + "concepts/pure-functions", + "concepts/closures", + "concepts/higher-order-functions", + "concepts/recursion" + ] + }, + { + "group": "Async JavaScript", + "icon": "clock", + "pages": [ + "concepts/collections-generators", + "concepts/promises", + "concepts/async-await" + ] + }, + { + "group": "Advanced Topics", + "icon": "graduation-cap", + "pages": [ + "concepts/data-structures", + "concepts/big-o-notation", + "concepts/algorithms", + "concepts/inheritance-polymorphism", + "concepts/design-patterns", + "concepts/currying-composition", + "concepts/clean-code" + ] + } + ] + }, + { + "tab": "Community", + "groups": [ + { + "group": "Get Involved", + "icon": "users", + "pages": [ + "contributing", + "translations" + ] + } + ] + } + ] + }, + "footer": { + "socials": { + "github": "https://github.com/leonardomso/33-js-concepts", + "x": "https://x.com/leonardomso" + } + } +} diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 00000000..1c742c95 --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,95 @@ +--- +title: "33 JavaScript Concepts" +description: "33 concepts every JavaScript developer should know" +--- + +
+
+

33 JavaScript Concepts

+

+ Every JavaScript developer should know these fundamental concepts +

+ +
+
+ +## About This Project + +This repository was created with the intention of helping developers master their concepts in JavaScript. It is not a requirement, but a guide for future studies. + + +**Recognized by GitHub** as one of the [top open source projects of 2018](https://blog.github.com/2018-12-13-new-open-source-projects/)! + + +Based on an article written by [Stephen Curtis](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1), this project provides curated resources including articles, videos, and books for each of the 33 essential JavaScript concepts. + +## Explore the Concepts + + + + Call Stack, Primitive Types, Value vs Reference, Type Coercion, Equality Operators, and Scope + + + Expressions vs Statements, IIFE & Modules, Event Loop, and Timers + + + JavaScript Engines, Bitwise Operators, and the DOM + + + Classes, this keyword, Prototypes, and Object methods + + + map/reduce/filter, Pure Functions, Closures, Higher-Order Functions, and Recursion + + + Generators, Promises, and async/await + + + Data Structures, Big O, Algorithms, Design Patterns, and Clean Code + + + Available in 40+ languages thanks to our amazing community + + + +## Why Learn These Concepts? + +Understanding these 33 concepts will help you: + +- **Write better code** - Understand how JavaScript works under the hood +- **Debug faster** - Know why your code behaves the way it does +- **Ace interviews** - These concepts are commonly asked in technical interviews +- **Build scalable apps** - Apply best practices and design patterns + +## Get Started + + + + Start with the [Introduction](/introduction) to understand how to use this guide effectively. + + + Start with the [Call Stack](/concepts/call-stack) and work your way through the fundamentals. + + + Read the articles, watch the videos, and practice implementing these concepts in your own projects. + + + +## Contributing + +This project thrives on community contributions. Feel free to: + +- Submit a PR adding new resources +- Create translations in your native language +- Report issues or suggest improvements + + + Learn how you can contribute to this project + diff --git a/docs/introduction.mdx b/docs/introduction.mdx new file mode 100644 index 00000000..88aa18be --- /dev/null +++ b/docs/introduction.mdx @@ -0,0 +1,154 @@ +--- +title: "Introduction" +description: "How to use this guide to master JavaScript concepts" +--- + +## Welcome + +Welcome to **33 JavaScript Concepts** - a comprehensive guide to mastering the fundamental and advanced concepts that every JavaScript developer should know. + +This project was created by [Leonardo Maldonado](https://github.com/leonardomso) and has been contributed to by hundreds of developers worldwide. It has been translated into over 40 languages and was recognized by GitHub as one of the top open source projects of 2018. + +## How to Use This Guide + + + + The concepts are ordered from fundamental to advanced. If you're new to JavaScript, start with the **Fundamentals** section and work your way through. + + + Each concept page starts with an overview explaining the core idea based on the ECMAScript specification. + + + Each concept includes curated articles, videos, and book recommendations. Choose the format that works best for your learning style. + + + Reading alone isn't enough. Practice implementing these concepts in your own projects or coding exercises. + + + +## The 33 Concepts + +Here's a complete overview of all the concepts covered in this guide: + + + + 1. **Call Stack** - How JavaScript tracks function execution + 2. **Primitive Types** - String, Number, Boolean, Null, Undefined, Symbol, BigInt + 3. **Value Types vs Reference Types** - How data is stored and passed + 4. **Type Coercion** - Implicit and explicit type conversion + 5. **Equality Operators** - == vs === vs typeof + 6. **Scope** - Function, Block, and Lexical scope + + + + 7. **Expression vs Statement** - Understanding the difference + 8. **IIFE, Modules, and Namespaces** - Code organization patterns + 9. **Message Queue and Event Loop** - JavaScript's concurrency model + 10. **Timers** - setTimeout, setInterval, and requestAnimationFrame + + + + 11. **JavaScript Engines** - V8, SpiderMonkey, and how JS runs + 12. **Bitwise Operators** - Low-level operations and typed arrays + 13. **DOM and Layout Trees** - How browsers render pages + + + + 14. **Factories and Classes** - Object creation patterns + 15. **this, call, apply, and bind** - Context and function binding + 16. **new, Constructor, instanceof** - Object instantiation + 17. **Prototype Inheritance** - JavaScript's inheritance model + 18. **Object.create and Object.assign** - Object manipulation methods + + + + 19. **map, reduce, filter** - Array transformation methods + 20. **Pure Functions and Side Effects** - Functional programming basics + 21. **Closures** - Functions that remember their scope + 22. **Higher-Order Functions** - Functions that operate on functions + 23. **Recursion** - Functions that call themselves + + + + 24. **Collections and Generators** - Iterables and lazy evaluation + 25. **Promises** - Handling asynchronous operations + 26. **async/await** - Modern async syntax + + + + 27. **Data Structures** - Arrays, Objects, Maps, Sets, and more + 28. **Big O Notation** - Algorithm complexity analysis + 29. **Algorithms** - Common algorithms in JavaScript + 30. **Inheritance and Polymorphism** - OOP principles + 31. **Design Patterns** - Proven solutions to common problems + 32. **Currying and Composition** - Advanced functional techniques + 33. **Clean Code** - Writing maintainable JavaScript + + + +## Learning Path Recommendations + + + + If you're new to JavaScript, focus on these concepts first: + + 1. Call Stack + 2. Primitive Types + 3. Value vs Reference Types + 4. Scope + 5. Closures + 6. Promises + 7. async/await + + Take your time with each concept before moving on. + + + + If you have some JavaScript experience, prioritize: + + 1. Event Loop + 2. this, call, apply, bind + 3. Prototype Inheritance + 4. Higher-Order Functions + 5. Design Patterns + 6. Big O Notation + + These concepts will deepen your understanding significantly. + + + + For technical interviews, make sure you understand: + + 1. Call Stack & Event Loop + 2. Closures + 3. Promises & async/await + 4. this keyword + 5. Prototype Chain + 6. Big O Notation + 7. Common Algorithms + + Be prepared to explain these concepts and write code examples. + + + +## Resource Types + +Each concept page includes multiple types of resources: + + + + In-depth written explanations and tutorials + + + Visual explanations and conference talks + + + Comprehensive coverage for deep learning + + + +## Ready to Start? + + + Start your journey with the first concept: understanding how JavaScript's call stack works. + diff --git a/docs/translations.mdx b/docs/translations.mdx new file mode 100644 index 00000000..752cb353 --- /dev/null +++ b/docs/translations.mdx @@ -0,0 +1,94 @@ +--- +title: "Translations" +description: "33 JavaScript Concepts in 40+ languages" +--- + +## Community Translations + +Thanks to our amazing community, 33 JavaScript Concepts has been translated into over 40 languages! Feel free to submit a PR to add your own translation. + + +Want to contribute a translation? Check out our [Contributing Guide](/contributing) for instructions on how to create and submit a translation. + + +## Available Languages + + + + By Amr Elsekilly + + + By thewebmasterp + + + By Re Tian + + + By Tiago Boeing + + + By Suin Lee + + + By Adonis Mendoza + + + By İlker Demir + + + By Mihail Gumennii + + + +## All Translations + +| Language | Translator | Repository | +|----------|------------|------------| +| Arabic (اَلْعَرَبِيَّةُ‎) | Amr Elsekilly | [Link](https://github.com/amrsekilly/33-js-concepts) | +| Bulgarian (Български) | thewebmasterp | [Link](https://github.com/thewebmasterp/33-js-concepts) | +| Chinese (汉语) | Re Tian | [Link](https://github.com/stephentian/33-js-concepts) | +| Brazilian Portuguese | Tiago Boeing | [Link](https://github.com/tiagoboeing/33-js-concepts) | +| Korean (한국어) | Suin Lee | [Link](https://github.com/yjs03057/33-js-concepts.git) | +| Spanish (Español) | Adonis Mendoza | [Link](https://github.com/adonismendozaperez/33-js-conceptos) | +| Turkish (Türkçe) | İlker Demir | [Link](https://github.com/ilker0/33-js-concepts) | +| Russian (русский язык) | Mihail Gumennii | [Link](https://github.com/gumennii/33-js-concepts) | +| Vietnamese (Tiếng Việt) | Nguyễn Trần Chung | [Link](https://github.com/nguyentranchung/33-js-concepts) | +| Polish (Polski) | Dawid Lipinski | [Link](https://github.com/lip3k/33-js-concepts) | +| Persian (فارسی) | Majid Alavizadeh | [Link](https://github.com/majidalavizadeh/33-js-concepts) | +| Indonesian (Bahasa Indonesia) | Rijdzuan Sampoerna | [Link](https://github.com/rijdz/33-js-concepts) | +| French (Français) | Robin Métral | [Link](https://github.com/robinmetral/33-concepts-js) | +| Hindi (हिन्दी) | Vikas Chauhan | [Link](https://github.com/vikaschauhan/33-js-concepts) | +| Greek (Ελληνικά) | Dimitris Zarachanis | [Link](https://github.com/DimitrisZx/33-js-concepts) | +| Japanese (日本語) | oimo23 | [Link](https://github.com/oimo23/33-js-concepts) | +| German (Deutsch) | burhannn | [Link](https://github.com/burhannn/33-js-concepts) | +| Ukrainian (украї́нська мо́ва) | Andrew Savetchuk | [Link](https://github.com/AndrewSavetchuk/33-js-concepts-ukrainian-translation) | +| Sinhala (සිංහල) | Udaya Shamendra | [Link](https://github.com/ududsha/33-js-concepts) | +| Italian (Italiano) | Gianluca Fiore | [Link](https://github.com/Donearm/33-js-concepts) | +| Latvian (Latviešu) | Jānis Īvāns | [Link](https://github.com/ANormalStick/33-js-concepts) | +| Oromo (Afaan Oromoo) | Amanuel Dagnachew | [Link](https://github.com/Amandagne/33-js-concepts) | +| Thai (ภาษาไทย) | Arif Waram | [Link](https://github.com/ninearif/33-js-concepts) | +| Catalan (Català) | Mario Estrada | [Link](https://github.com/marioestradaf/33-js-concepts) | +| Swedish (Svenska) | Fenix Hongell | [Link](https://github.com/FenixHongell/33-js-concepts/) | +| Khmer (ខ្មែរ) | Chrea Chanchhunneng | [Link](https://github.com/Chhunneng/33-js-concepts) | +| Ethiopian (አማርኛ) | Miniyahil Kebede | [Link](https://github.com/hmhard/33-js-concepts) | +| Belarussian (Беларуская мова) | Dzianis Yafimau | [Link](https://github.com/Yafimau/33-js-concepts) | +| Uzbek (O'zbekcha) | Shokhrukh Usmonov | [Link](https://github.com/smnv-shokh/33-js-concepts) | +| Urdu (اردو) | Yasir Nawaz | [Link](https://github.com/sudoyasir/33-js-concepts) | +| Bengali (বাংলা) | Jisan Mia | [Link](https://github.com/Jisan-mia/33-js-concepts) | +| Gujarati (ગુજરાતી) | Vatsal Bhuva | [Link](https://github.com/VatsalBhuva11/33-js-concepts) | +| Sindhi (سنڌي) | Sunny Gandhwani | [Link](https://github.com/Sunny-unik/33-js-concepts) | +| Bhojpuri (भोजपुरी) | Pronay Debnath | [Link](https://github.com/debnath003/33-js-concepts) | +| Punjabi (ਪੰਜਾਬੀ) | Harsh Dev Pathak | [Link](https://github.com/Harshdev098/33-js-concepts) | +| Malayalam (മലയാളം) | Akshay Manoj | [Link](https://github.com/Stark-Akshay/33-js-concepts) | +| Yoruba (Yorùbá) | Ayomide Bajulaye | [Link](https://github.com/ayobaj/33-js-concepts) | +| Hebrew (עברית‎) | Refael Yzgea | [Link](https://github.com/rafyzg/33-js-concepts) | +| Dutch (Nederlands) | Dave Visser | [Link](https://github.com/dlvisser/33-js-concepts) | +| Tamil (தமிழ்) | Udaya Krishnan M | [Link](https://github.com/UdayaKrishnanM/33-js-concepts) | + +## Create Your Own Translation + +Want to help make JavaScript concepts accessible in your language? + + + Follow our contribution guidelines to create a translation + From d2b08424a4af9736a4543fd46663957ddc603cbf Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 18:21:31 -0300 Subject: [PATCH 004/128] feat(call-stack): add comprehensive beginner-friendly explanation with examples - Add real-world analogies (stack of plates) - Add step-by-step execution walkthroughs with visual diagrams - Add execution context deep-dive with accordions - Add stack overflow explanation and prevention tips - Add debugging guide with Chrome DevTools - Add quiz section to test understanding - Enhance all resource links with descriptive summaries - Link to related concepts (Event Loop, Recursion, Closures) --- docs/concepts/call-stack.mdx | 741 +++++++++++++++++++++++++++++++++-- 1 file changed, 719 insertions(+), 22 deletions(-) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 77ee577a..769579f8 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -3,15 +3,690 @@ title: "Call Stack" description: "Understanding how JavaScript tracks function execution" --- -## Overview +## The Stack of Plates: A Real-World Analogy -The call stack is a mechanism that the JavaScript interpreter uses to keep track of function execution within a program. In JavaScript, functions are executed in the order they are called. The call stack follows the **Last In, First Out (LIFO)** principle, meaning that the last function pushed onto the stack is the first one to be executed. +Imagine you're working in a restaurant kitchen, washing dishes. As clean plates come out, you stack them one on top of another. When a server needs a plate, they always take the one from the **top** of the stack — not from the middle or bottom. + +``` + ┌───────────┐ + │ Plate 3 │ ← You add here (top) + ├───────────┤ + │ Plate 2 │ + ├───────────┤ + │ Plate 1 │ ← First plate (bottom) + └───────────┘ +``` + +This is exactly how JavaScript keeps track of your functions! When you call a function, JavaScript puts it on top of a "stack." When that function finishes, JavaScript removes it from the top and goes back to whatever was underneath. + +This simple concept — **adding to the top and removing from the top** — is the foundation of how JavaScript executes your code. + + +**What you'll learn in this guide:** +- What the call stack is and why JavaScript needs it +- How functions are added and removed from the stack +- What happens step-by-step when your code runs +- Why you sometimes see "Maximum call stack size exceeded" errors +- How to debug call stack issues like a pro + + +--- + +## What is the Call Stack? + +The **call stack** is a mechanism that JavaScript uses to keep track of where it is in your code. Think of it as JavaScript's "to-do list" for function calls — but one where it can only work on the item at the top. + +### The LIFO Principle + +The call stack follows a principle called **LIFO** — **Last In, First Out**. + +- **Last In**: The most recent function call goes on top +- **First Out**: The function on top must finish before we can get to the ones below + +``` +LIFO = Last In, First Out + +┌─────────────────┐ +│ function C │ ← Last in (most recent call) +├─────────────────┤ First to finish and leave +│ function B │ +├─────────────────┤ +│ function A │ ← First in (earliest call) +└─────────────────┘ Last to finish +``` + +### Why Does JavaScript Need a Call Stack? + +JavaScript is **single-threaded**, meaning it can only do **one thing at a time**. The call stack helps JavaScript: + +1. **Remember where it is** — Which function is currently running? +2. **Know where to go back** — When a function finishes, where should execution continue? +3. **Keep track of local variables** — Each function has its own variables that shouldn't interfere with others + + +**ECMAScript Specification**: According to the official JavaScript specification, the call stack is implemented through "execution contexts." Each function call creates a new execution context that gets pushed onto the stack. + + +--- + +## How the Call Stack Works: Step-by-Step + +Let's trace through a simple example to see the call stack in action. + +### A Simple Example + +```javascript +function greet(name) { + const greeting = createGreeting(name); + console.log(greeting); +} + +function createGreeting(name) { + return "Hello, " + name + "!"; +} + +// Start here +greet("Alice"); +console.log("Done!"); +``` + +### Step-by-Step Execution + + + + JavaScript begins executing your code from top to bottom. The call stack is empty. + + ``` + Call Stack: [ empty ] + ``` + + + + JavaScript sees `greet("Alice")` and pushes `greet` onto the call stack. + + ``` + Call Stack: [ greet ] + ``` + + Now JavaScript enters the `greet` function and starts executing its code. + + + + Inside `greet`, JavaScript encounters `createGreeting(name)`. It pushes `createGreeting` onto the stack. + + ``` + Call Stack: [ createGreeting, greet ] + ``` + + Notice: `greet` is **paused** while `createGreeting` runs. JavaScript can only do one thing at a time! + + + + `createGreeting` finishes and returns `"Hello, Alice!"`. JavaScript pops it off the stack. + + ``` + Call Stack: [ greet ] + ``` + + The return value (`"Hello, Alice!"`) is passed back to `greet`. + + + + Back in `greet`, the returned value is stored in `greeting`, then `console.log` runs. Finally, `greet` finishes and is popped off. + + ``` + Call Stack: [ empty ] + ``` + + + + With the stack empty, JavaScript continues to the next line: `console.log("Done!")`. + + **Output:** + ``` + Hello, Alice! + Done! + ``` + + + +### Visual Summary + + + + ``` + Step 1: Step 2: Step 3: Step 4: Step 5: + + ┌─────────┐ ┌─────────┐ ┌────────────────┐ ┌─────────┐ ┌─────────┐ + │ (empty) │ → │ greet │ → │createGreeting │ → │ greet │ → │ (empty) │ + └─────────┘ └─────────┘ ├────────────────┤ └─────────┘ └─────────┘ + │ greet │ + └────────────────┘ + + Program greet() createGreeting() createGreeting greet() + starts called called returns returns + ``` + + + | Step | Action | Stack (top → bottom) | What's Happening | + |------|--------|---------------------|------------------| + | 1 | Start | `[]` | Program begins | + | 2 | Call `greet("Alice")` | `[greet]` | Enter greet function | + | 3 | Call `createGreeting("Alice")` | `[createGreeting, greet]` | greet pauses, enter createGreeting | + | 4 | Return from createGreeting | `[greet]` | createGreeting done, back to greet | + | 5 | Return from greet | `[]` | greet done, continue program | + | 6 | `console.log("Done!")` | `[]` | Print "Done!" | + + + +--- + +## Execution Context: What's Actually on the Stack? + +When we say a function is "on the stack," what does that actually mean? Each entry on the call stack is called an **execution context** (or **stack frame**). It contains everything JavaScript needs to execute that function. + + + + The values passed to the function when it was called. + + ```javascript + function greet(name, age) { + // Arguments: { name: "Alice", age: 25 } + } + greet("Alice", 25); + ``` + + + + Variables declared inside the function with `var`, `let`, or `const`. + + ```javascript + function calculate() { + const x = 10; // Local variable + let y = 20; // Local variable + var z = 30; // Local variable + // These only exist inside this function + } + ``` + + + + The value of `this` inside the function, which depends on how the function was called. + + ```javascript + const person = { + name: "Alice", + greet() { + console.log(this.name); // 'this' refers to person + } + }; + ``` + + + + Where JavaScript should continue executing after this function returns. This is how JavaScript knows to go back to the right place in your code. + + + + Access to variables from outer (parent) functions. This is how closures work! + + ```javascript + function outer() { + const message = "Hello"; + + function inner() { + console.log(message); // Can access 'message' from outer + } + + inner(); + } + ``` + + + +### Visualizing an Execution Context + +``` +┌─────────────────────────────────────────┐ +│ EXECUTION CONTEXT │ +│ Function: greet │ +├─────────────────────────────────────────┤ +│ Arguments: { name: "Alice" } │ +│ Local Vars: { greeting: undefined } │ +│ this: window (or undefined) │ +│ Return to: line 12, main program │ +│ Outer Scope: [global scope] │ +└─────────────────────────────────────────┘ +``` + +--- + +## Nested Function Calls: A Deeper Example + +Let's look at a more complex example with multiple levels of function calls. + +```javascript +function multiply(x, y) { + return x * y; +} + +function square(n) { + return multiply(n, n); +} + +function printSquare(n) { + const result = square(n); + console.log(result); +} + +printSquare(4); +``` + +### Tracing the Execution + + + + **Step 1: Call printSquare(4)** + ``` + Stack: [ printSquare ] + ``` + + **Step 2: printSquare calls square(4)** + ``` + Stack: [ square, printSquare ] + ``` + + **Step 3: square calls multiply(4, 4)** + ``` + Stack: [ multiply, square, printSquare ] + ``` + This is the **maximum stack depth** for this program: 3 frames. + + **Step 4: multiply returns 16** + ``` + Stack: [ square, printSquare ] + ``` + + **Step 5: square returns 16** + ``` + Stack: [ printSquare ] + ``` + + **Step 6: printSquare logs 16 and returns** + ``` + Stack: [ empty ] + ``` + + **Output: `16`** + + + ``` + printSquare(4) square(4) multiply(4,4) multiply square printSquare + called called called returns 16 returns 16 returns + + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐ + │printSquare │ → │ square │ → │ multiply │ → │ square │ → │printSquare │ → │ (empty) │ + └─────────────┘ ├─────────────┤ ├─────────────┤ ├─────────────┤ └─────────────┘ └─────────┘ + │printSquare │ │ square │ │printSquare │ + └─────────────┘ ├─────────────┤ └─────────────┘ + │printSquare │ + └─────────────┘ + + Depth: 1 Depth: 2 Depth: 3 Depth: 2 Depth: 1 Depth: 0 + ``` + + + + +**Understanding the flow**: Each function must completely finish before the function that called it can continue. This is why `printSquare` has to wait for `square`, and `square` has to wait for `multiply`. + + +--- + +## Stack Overflow: When Things Go Wrong + +The call stack has a **limited size**. If you keep adding functions without removing them, eventually you'll run out of space. This is called a **stack overflow**. + + +**Stack overflow** happens when the call stack exceeds its maximum size. The most common cause is **infinite recursion** — a function that calls itself forever. + + +### The Classic Mistake: Missing Base Case + +```javascript +// ❌ BAD: This will crash! +function countdown(n) { + console.log(n); + countdown(n - 1); // Calls itself forever! +} + +countdown(5); +``` + +**What happens:** +``` +Stack: [ countdown(5) ] +Stack: [ countdown(4), countdown(5) ] +Stack: [ countdown(3), countdown(4), countdown(5) ] +Stack: [ countdown(2), countdown(3), countdown(4), countdown(5) ] +... keeps growing forever ... +💥 CRASH: Maximum call stack size exceeded +``` + +### The Fix: Add a Base Case + +```javascript +// ✅ GOOD: This works correctly +function countdown(n) { + if (n <= 0) { + console.log("Done!"); + return; // ← BASE CASE: Stop here! + } + console.log(n); + countdown(n - 1); +} + +countdown(5); +// Output: 5, 4, 3, 2, 1, Done! +``` + +**What happens now:** +``` +Stack: [ countdown(5) ] +Stack: [ countdown(4), countdown(5) ] +Stack: [ countdown(3), countdown(4), countdown(5) ] +Stack: [ countdown(2), countdown(3), ..., countdown(5) ] +Stack: [ countdown(1), countdown(2), ..., countdown(5) ] +Stack: [ countdown(0), countdown(1), ..., countdown(5) ] + ↑ Base case reached! Start returning. +Stack: [ countdown(1), ..., countdown(5) ] +Stack: [ countdown(2), ..., countdown(5) ] +... stack unwinds ... +Stack: [ countdown(5) ] +Stack: [ empty ] +✅ Program completes successfully +``` + +### Error Messages by Browser + +| Browser | Error Message | +|---------|---------------| +| Chrome | `RangeError: Maximum call stack size exceeded` | +| Firefox | `InternalError: too much recursion` | +| Safari | `RangeError: Maximum call stack size exceeded` | + +### Common Causes of Stack Overflow + + + + ```javascript + // Missing the stopping condition + function loop() { + loop(); + } + loop(); // 💥 Crash! + ``` + + + + ```javascript + function countUp(n) { + if (n >= 1000000000000) return; // Too far away! + countUp(n + 1); + } + countUp(0); // 💥 Crash before reaching base case + ``` + + + + ```javascript + class Person { + set name(value) { + this.name = value; // Calls the setter again! Infinite loop! + } + } + + const p = new Person(); + p.name = "Alice"; // 💥 Crash! + + // Fix: Use a different property name + class PersonFixed { + set name(value) { + this._name = value; // Use _name instead + } + } + ``` + + + + ```javascript + function a() { b(); } + function b() { a(); } // a calls b, b calls a, forever! + + a(); // 💥 Crash! + ``` + + + + +**Prevention tips:** +1. Always define a clear **base case** for recursive functions +2. Make sure each recursive call moves **toward** the base case +3. Consider using **iteration** (loops) instead of recursion for simple cases +4. Be careful with property setters — use different internal property names + + +--- + +## Debugging the Call Stack + +When something goes wrong, the call stack is your best friend for figuring out what happened. + +### Reading a Stack Trace + +When an error occurs, JavaScript gives you a **stack trace** — a snapshot of the call stack at the moment of the error. + +```javascript +function a() { b(); } +function b() { c(); } +function c() { + throw new Error('Something went wrong!'); +} + +a(); +``` + +**Output:** +``` +Error: Something went wrong! + at c (script.js:4:9) + at b (script.js:2:14) + at a (script.js:1:14) + at script.js:7:1 +``` + +**How to read it:** +- Read from **top to bottom** = most recent call to oldest +- `at c (script.js:4:9)` = Error occurred in function `c`, file `script.js`, line 4, column 9 +- The trace shows you exactly how the program got to the error + +### Using Browser DevTools + + + + Press `F12` or `Cmd+Option+I` (Mac) / `Ctrl+Shift+I` (Windows) + + + + Click on the "Sources" tab (Chrome) or "Debugger" tab (Firefox) + + + + Click on a line number in your code to set a breakpoint. Execution will pause there. + + + + When paused, look at the "Call Stack" panel on the right. It shows all the functions currently on the stack. + + + + Use the step buttons to execute one line at a time and watch the stack change. + + + + +**Pro debugging tip:** If you're dealing with recursion, add a `console.log` at the start of your function to see how many times it's being called: + +```javascript +function factorial(n) { + console.log('factorial called with n =', n); + if (n <= 1) return 1; + return n * factorial(n - 1); +} +``` + + +--- + +## The Call Stack and Asynchronous Code + +You might be wondering: "If JavaScript can only do one thing at a time, how does it handle things like `setTimeout` or fetching data from a server?" + +Great question! The call stack is only **part** of the picture. + + +When you use asynchronous functions like `setTimeout`, `fetch`, or event listeners, JavaScript doesn't put them on the call stack immediately. Instead, they go through a different system involving the **Event Loop** and **Callback Queue**. + +This is covered in detail in the [Event Loop](/concepts/event-loop) section. + + +Here's a sneak peek: + +```javascript +console.log('First'); + +setTimeout(() => { + console.log('Second'); +}, 0); + +console.log('Third'); + +// Output: +// First +// Third +// Second ← Even with 0ms delay, this runs last! +``` + +The `setTimeout` callback doesn't go directly on the call stack — it waits in a queue until the stack is empty. This is why "Third" prints before "Second" even though the timeout is 0 milliseconds. + + + + Learn how JavaScript handles asynchronous operations + + + Modern way to handle async code + + + +--- + +## Key Takeaways -According to the ECMAScript specification, the call stack is defined as part of the execution context. Whenever a function is called, a new execution context is created and placed at the top of the stack. Once the function completes, its execution context is removed from the stack, and control returns to the previous context. +**Remember these essential points about the Call Stack:** + +1. **JavaScript is single-threaded** — It has ONE call stack and can only do one thing at a time + +2. **LIFO principle** — Last In, First Out. The most recent function call finishes first + +3. **Execution contexts** — Each function call creates a "frame" containing arguments, local variables, and return address + +4. **Synchronous execution** — Functions must complete before their callers can continue + +5. **Stack overflow** — Happens when the stack gets too deep, usually from infinite recursion + +6. **Always have a base case** — Recursive functions need a stopping condition + +7. **Stack traces are your friend** — They show you exactly how your program got to an error -This helps manage synchronous code execution, as each function call must complete before the next one can begin. +--- + +## Test Your Knowledge + + + + **Answer:** LIFO stands for **Last In, First Out**. + + It's important because it determines the order in which functions execute and return. The most recently called function must complete before the function that called it can continue. This is how JavaScript keeps track of nested function calls and knows where to return when a function finishes. + + + + ```javascript + function a() { b(); } + function b() { c(); } + function c() { d(); } + function d() { console.log('done'); } + a(); + ``` + + **Answer:** The maximum stack depth is **4 frames**. + + ``` + Stack at deepest point: [ d, c, b, a ] + ``` + + When `d()` is executing, all four functions are on the stack. After `d()` logs "done" and returns, the stack starts unwinding. + + + + ```javascript + function greet() { + greet(); + } + greet(); + ``` + + **Answer:** This code causes a stack overflow because there's **no base case** to stop the recursion. + + - `greet()` is called + - `greet()` calls `greet()` again + - That `greet()` calls `greet()` again + - This continues forever, adding new frames to the stack + - Eventually the stack runs out of space → **Maximum call stack size exceeded** + + **Fix:** Add a condition to stop the recursion: + ```javascript + function greet(times) { + if (times <= 0) return; // Base case + console.log('Hello!'); + greet(times - 1); + } + greet(3); + ``` + + + +--- + +## Related Concepts + + + + How async code works with the call stack + + + Functions that call themselves + + + How functions remember their scope + + + Understanding variable accessibility + + + +--- ## Reference @@ -23,34 +698,56 @@ This helps manage synchronous code execution, as each function call must complet - By Gaurav Pandvia + The complete picture: how the Call Stack, Heap, Event Loop, and Web APIs work together. Great starting point for understanding JavaScript's runtime. - By Charles Freeborn + Beginner-friendly freeCodeCamp tutorial covering LIFO, stack traces, and stack overflow with clear code examples. + + + Go deeper into how the JS engine creates execution contexts and manages the Global Memory. Perfect for interview prep. + + + 🎨 Beautiful ASCII art visualization showing step-by-step how setTimeout interacts with the Call Stack and Event Loop. + + + Advanced deep-dive into Creation vs Execution phases, Lexical Environment, and why `let`/`const` behave differently than `var`. + + + Explore the JS Engine architecture: V8, memory heap, and call stack from a systems perspective. -- [Javascript: What Is The Execution Context? What Is The Call Stack? — Valentino Gagliardi](https://medium.com/@valentinog/javascript-what-is-the-execution-context-what-is-the-call-stack-bd23c78f10d1) -- [What is the JS Event Loop and Call Stack? — Jess Telford](https://gist.github.com/jesstelford/9a35d20a2aa044df8bf241e00d7bc2d0) -- [Understanding Execution Context and Execution Stack in Javascript — Sukhjinder Arora](https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0) -- [How JavaScript Works Under The Hood: An Overview of JavaScript Engine, Heap and, Call Stack — Bipin Rajbhar](https://dev.to/bipinrajbhar/how-javascript-works-under-the-hood-an-overview-of-javascript-engine-heap-and-call-stack-1j5o) - ## Videos - By Philip Roberts — JSConf EU + 🏆 The legendary JSConf talk that made mass developers finally "get" the event loop. Amazing visualizations — a must watch! - By Colt Steele + Short, sweet, and beginner-friendly. Colt Steele breaks down the call stack with practical examples. + + + Part of the popular "Namaste JavaScript" series. Akshay Saini explains execution with great visuals and examples. + + + Codesmith's in-depth walkthrough of JavaScript execution. Part of a larger playlist on JS fundamentals. + + + Coding Blocks India provides a clear, methodical explanation with step-by-step code walkthroughs. + + + Eric Traub gives a concise explanation of call stack fundamentals in an easy-to-follow format. + + + Kevin Drumm explains the call stack concept with clear diagrams and simple examples. + + + Harvard's CS50 explains call stacks from a computer science perspective — great for understanding the theory. + + + codecupdev walks through the call stack with beginner-friendly explanations and live coding. + + + Chidre's Tech Tutorials covers how functions interact with the call stack in detail. - -- [Javascript: the Call Stack explained — Coding Blocks India](https://www.youtube.com/watch?v=w6QGEiQceOM) -- [What is the Call Stack? — Eric Traub](https://www.youtube.com/watch?v=w7QWQlkLY_s) -- [The Call Stack — Kevin Drumm](https://www.youtube.com/watch?v=Q2sFmqvpBe0) -- [Understanding JavaScript Execution — Codesmith](https://www.youtube.com/watch?v=Z6a1cLyq7Ac&list=PLWrQZnG8l0E4kd1T_nyuVoxQUaYEWFgcD) -- [How JavaScript Code is executed? & Call Stack — Akshay Saini](https://www.youtube.com/watch?v=iLWTnMzWtj4&list=PLlasXeu85E9cQ32gLCvAvr9vNaUccPVNP) -- [Call Stacks - CS50](https://www.youtube.com/watch?v=aCPkszeKRa4) -- [Learn the JavaScript Call Stack - codecupdev](https://www.youtube.com/watch?v=HXqXPGS96rw) -- [JavaScript Functions and the Call Stack - Chidre'sTechTutorials](https://www.youtube.com/watch?v=P6H-T4cUDR4) From f271e1ddc89e6a11f0b7ce74d9aaae7f4330756e Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 19:23:54 -0300 Subject: [PATCH 005/128] feat(primitive-types): add comprehensive beginner-friendly explanation with examples - Add real-world analogy (atoms vs molecules) - Add TL;DR summary box for quick readers - Add deep-dive sections for all 7 primitive types - Add typeof operator guide with quirks table - Add immutability explanation with visual diagrams - Add autoboxing section explaining wrapper objects - Add null vs undefined comparison with tabs - Add JavaScript quirks section (typeof null, NaN, 0.1+0.2) - Add Intl.NumberFormat for currency formatting - Add quiz section with 5 questions - Add fun moments and simplified technical jargon - Curate resources: 5 articles, 1 book, 3 courses, 3 videos - Add Piccalilli and Anthony Alicea courses --- docs/concepts/primitive-types.mdx | 975 +++++++++++++++++++++++++++++- 1 file changed, 950 insertions(+), 25 deletions(-) diff --git a/docs/concepts/primitive-types.mdx b/docs/concepts/primitive-types.mdx index f023c57d..b120945b 100644 --- a/docs/concepts/primitive-types.mdx +++ b/docs/concepts/primitive-types.mdx @@ -3,54 +3,979 @@ title: "Primitive Types" description: "Understanding JavaScript's fundamental data types" --- -## Overview +## Atoms vs Molecules: A Real-World Analogy -According to the ECMAScript specification, JavaScript has six primitive data types: **string**, **number**, **bigint**, **boolean**, **undefined**, and **symbol**. These types are immutable, meaning their values cannot be altered. There is also a special primitive type called **null**, which represents the intentional absence of any object value. +Think of data in JavaScript like chemistry class (but way more fun, and no lab goggles required). **Primitives** are like atoms — the fundamental, indivisible building blocks that cannot be broken down further. **Objects** are like molecules — complex structures made up of multiple atoms combined together. + +``` + PRIMITIVES (Atoms) OBJECTS (Molecules) + + ┌───┐ ┌─────┐ ┌──────┐ ┌────────────────────────────┐ + │ 5 │ │"hi" │ │ true │ │ { name: "Alice", age: 25 } │ + └───┘ └─────┘ └──────┘ └────────────────────────────┘ + + Simple, indivisible Complex, contains multiple values + Stored directly Stored as reference + Compared by value Compared by reference +``` + +Just like atoms are the foundation of all matter, primitives are the foundation of all data in JavaScript. Every complex data structure you create — arrays, objects, functions — is ultimately built on top of these simple primitive values. + + +**TL;DR:** JavaScript has 7 primitive types: string, number, bigint, boolean, undefined, null, and symbol. They're simple, unchangeable values. Everything else is an object. + -Primitive values are directly assigned to a variable, and when you manipulate a primitive type, you're working directly on the value. Unlike objects, primitives do not have properties or methods, but JavaScript automatically wraps primitive values with object counterparts when necessary (e.g., when calling methods on strings). +**What you'll learn in this guide:** +- The 7 primitive types in JavaScript and when to use each +- How `typeof` works (and its famous quirks) +- Why primitives are "immutable" and what that means +- The magic of autoboxing — how `"hello".toUpperCase()` works +- The difference between `null` and `undefined` +- Famous JavaScript gotchas every developer should know +--- + +## What Are Primitive Types? + +In JavaScript, a **primitive** is data that is not an object and has no methods of its own. JavaScript has exactly **7 primitive types**: + +| Type | Example | Description | +|------|---------|-------------| +| `string` | `"hello"` | Text data | +| `number` | `42`, `3.14` | Numeric data (integers and decimals) | +| `bigint` | `9007199254740993n` | Very large integers | +| `boolean` | `true`, `false` | Logical values | +| `undefined` | `undefined` | No value assigned | +| `null` | `null` | Intentional absence of value | +| `symbol` | `Symbol("id")` | Unique identifier | + +### Three Key Characteristics + +All primitives share these fundamental traits: + + + + Once a primitive value is created, it cannot be altered. When you "change" a string, you're actually creating a new string. + + ```javascript + let name = "Alice"; + name.toUpperCase(); // Creates "ALICE" but doesn't change 'name' + console.log(name); // Still "Alice" + ``` + + + + When you compare two primitives, JavaScript compares their actual values, not where they're stored in memory. + + ```javascript + let a = "hello"; + let b = "hello"; + console.log(a === b); // true - same value + + let obj1 = { text: "hello" }; + let obj2 = { text: "hello" }; + console.log(obj1 === obj2); // false - different objects! + ``` + + + + Primitives don't have methods, but JavaScript automatically wraps them in objects when you try to call methods. This is called "autoboxing." + + ```javascript + "hello".toUpperCase(); // Works! JS wraps "hello" in a String object + ``` + + We'll explore this magic in detail later. + + + +--- + +## The 7 Primitive Types: Deep Dive + +Let's explore each primitive type in detail. + +--- + +### String + +A **string** represents text data — a sequence of characters. + +```javascript +// Three ways to create strings +let single = 'Hello'; // Single quotes +let double = "World"; // Double quotes +let backtick = `Hello World`; // Template literal (ES6) +``` + +#### Template Literals + +Template literals (backticks) offer special features: + +```javascript +let name = "Alice"; +let age = 25; + +// String interpolation - embed expressions +let greeting = `Hello, ${name}! You are ${age} years old.`; +console.log(greeting); // "Hello, Alice! You are 25 years old." + +// Multi-line strings +let multiLine = ` + This is line 1 + This is line 2 +`; +``` + +#### Strings Are Immutable + +You cannot change individual characters in a string: + +```javascript +let str = "hello"; +str[0] = "H"; // Does nothing! No error, but no change +console.log(str); // Still "hello" + +// To "change" a string, create a new one +str = "H" + str.slice(1); +console.log(str); // "Hello" +``` + + +String methods like `toUpperCase()`, `slice()`, `replace()` always return **new strings** — they never modify the original. + + +--- + +### Number + +JavaScript has only **one number type** for both integers and decimals. All numbers are stored as 64-bit floating-point (a standard way computers store decimals). + +```javascript +let integer = 42; // Integer +let decimal = 3.14; // Decimal +let negative = -10; // Negative +let scientific = 2.5e6; // 2,500,000 (scientific notation) +``` + +#### Special Number Values + +```javascript +console.log(1 / 0); // Infinity +console.log(-1 / 0); // -Infinity +console.log("hello" * 2); // NaN (Not a Number) +``` + +#### The Famous Floating-Point Problem + +```javascript +console.log(0.1 + 0.2); // 0.30000000000000004 +console.log(0.1 + 0.2 === 0.3); // false! Welcome to JavaScript! +``` + +This isn't a JavaScript bug — it's how floating-point math works in all programming languages. The decimal `0.1` cannot be perfectly represented in binary. + + +**Working with money?** Never use floating-point for calculations! Store amounts in cents as integers, then use JavaScript's built-in `Intl.NumberFormat` for display. + +```javascript +// Bad: floating-point errors in calculations +let price = 0.1 + 0.2; // 0.30000000000000004 + +// Good: calculate in cents, format for display +let priceInCents = 10 + 20; // 30 (calculation is accurate!) + +// Use Intl.NumberFormat to display as currency +const formatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', +}); +console.log(formatter.format(priceInCents / 100)); // "$0.30" + +// Works for any locale and currency! +const euroFormatter = new Intl.NumberFormat('de-DE', { + style: 'currency', + currency: 'EUR', +}); +console.log(euroFormatter.format(1234.56)); // "1.234,56 €" +``` + + + +`Intl.NumberFormat` is built into JavaScript — no external libraries needed! It handles currency symbols, decimal separators, and locale-specific formatting automatically. + + +#### Safe Integer Range + +JavaScript can only safely represent integers up to a certain size: + +```javascript +console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1) +console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991 + +// Beyond this range, precision is lost +console.log(9007199254740992 === 9007199254740993); // true! (wrong!) +``` + +For larger integers, use `BigInt`. + +--- + +### BigInt + +**BigInt** (ES2020) represents integers larger than `Number.MAX_SAFE_INTEGER`. + +```javascript +// Add 'n' suffix to create a BigInt +let big = 9007199254740993n; +let alsoBig = BigInt("9007199254740993"); + +console.log(big + 1n); // 9007199254740994n (correct!) +``` + +#### BigInt Rules + +```javascript +// Cannot mix BigInt and Number +let big = 10n; +let regular = 5; +// console.log(big + regular); // TypeError! + +// Must convert explicitly +console.log(big + BigInt(regular)); // 15n +console.log(Number(big) + regular); // 15 +``` + + +**When to use BigInt:** Cryptography, precise timestamps, database IDs, any calculation requiring integers larger than 9 quadrillion. + + +--- + +### Boolean + +**Boolean** has exactly two values: `true` and `false`. + +```javascript +let isLoggedIn = true; +let hasPermission = false; + +// From comparisons +let isAdult = age >= 18; // true or false +let isEqual = name === "Alice"; // true or false +``` + +#### Truthy and Falsy + +When used in boolean contexts (like `if` statements), all values are either "truthy" or "falsy": + +```javascript +// Falsy values (only 8!) +false +0 +-0 +0n // BigInt zero +"" // Empty string +null +undefined +NaN + +// Everything else is truthy +"hello" // truthy +42 // truthy +[] // truthy (empty array!) +{} // truthy (empty object!) +``` + +```javascript +// Convert any value to boolean +let value = "hello"; +let bool = Boolean(value); // true +let shortcut = !!value; // true (double negation trick) +``` + + +Learn more about how JavaScript converts between types in the [Type Coercion](/concepts/type-coercion) section. + + +--- + +### undefined + +**`undefined`** means "no value has been assigned." JavaScript uses it automatically in several situations: + +```javascript +// 1. Declared but not assigned +let x; +console.log(x); // undefined + +// 2. Missing function parameters +function greet(name) { + console.log(name); // undefined if called without argument +} +greet(); + +// 3. Function with no return statement +function doNothing() { + // no return +} +console.log(doNothing()); // undefined + +// 4. Accessing non-existent object property +let person = { name: "Alice" }; +console.log(person.age); // undefined +``` + + +Don't explicitly assign `undefined` to variables. Use `null` instead to indicate "intentionally empty." + + +--- + +### null + +**`null`** means "intentionally empty" — you're explicitly saying "this has no value." + +```javascript +// Intentionally clearing a variable +let user = { name: "Alice" }; +user = null; // User logged out, clearing the reference + +// Indicating no result +function findUser(id) { + // ... search logic ... + return null; // User not found +} +``` + +#### The Famous typeof Bug + +```javascript +console.log(typeof null); // "object" — Wait, what?! +``` + +Yes, really. This is one of JavaScript's most famous quirks! It's a historical mistake from JavaScript's first implementation in 1995. It was never fixed because too much existing code depends on it. + +```javascript +// How to properly check for null +let value = null; +console.log(value === null); // true (use strict equality) +``` + +--- + +### Symbol + +**Symbol** (ES6) creates unique identifiers. Even symbols with the same description are different. + +```javascript +let id1 = Symbol("id"); +let id2 = Symbol("id"); + +console.log(id1 === id2); // false — always unique! +console.log(id1.description); // "id" (the description) +``` + +#### Use Case: Unique Object Keys + +```javascript +const ID = Symbol("id"); +const user = { + name: "Alice", + [ID]: 12345 // Symbol as property key +}; + +console.log(user.name); // "Alice" +console.log(user[ID]); // 12345 + +// Symbol keys don't appear in normal iteration +console.log(Object.keys(user)); // ["name"] — ID not included +``` + +#### Well-Known Symbols + +JavaScript has built-in symbols for customizing object behavior: + +```javascript +// Symbol.iterator - make an object iterable +// Symbol.toStringTag - customize Object.prototype.toString +// Symbol.toPrimitive - customize type conversion +``` + + +Symbols are an advanced feature. As a beginner, focus on understanding that they exist and create unique values. You'll encounter them when diving into advanced patterns and library code. + + +--- + +## The typeof Operator + +The `typeof` operator returns a string indicating the type of a value. + +```javascript +console.log(typeof "hello"); // "string" +console.log(typeof 42); // "number" +console.log(typeof 42n); // "bigint" +console.log(typeof true); // "boolean" +console.log(typeof undefined); // "undefined" +console.log(typeof Symbol()); // "symbol" +console.log(typeof null); // "object" ⚠️ (bug!) +console.log(typeof {}); // "object" +console.log(typeof []); // "object" +console.log(typeof function(){}); // "function" +``` + +### typeof Results Table + +| Value | typeof Result | Notes | +|-------|---------------|-------| +| `"hello"` | `"string"` | | +| `42` | `"number"` | | +| `42n` | `"bigint"` | | +| `true` / `false` | `"boolean"` | | +| `undefined` | `"undefined"` | | +| `Symbol()` | `"symbol"` | | +| `null` | `"object"` | Historical bug! | +| `{}` | `"object"` | | +| `[]` | `"object"` | Arrays are objects | +| `function(){}` | `"function"` | Functions are special | + +### Better Type Checking + +Since `typeof` has quirks, here are more reliable alternatives: + +```javascript +// Check for null specifically +let value = null; +if (value === null) { + console.log("It's null"); +} + +// Check for arrays +Array.isArray([1, 2, 3]); // true +Array.isArray("hello"); // false + +// Get precise type with Object.prototype.toString +Object.prototype.toString.call(null); // "[object Null]" +Object.prototype.toString.call([]); // "[object Array]" +Object.prototype.toString.call(new Date()); // "[object Date]" +``` + +--- + +## Immutability Explained + +**Immutable** means "cannot be changed." Primitive values are immutable — you cannot alter the value itself. + +### Seeing Immutability in Action + +```javascript +let str = "hello"; + +// These methods don't change 'str' — they return NEW strings +str.toUpperCase(); // Returns "HELLO" +console.log(str); // Still "hello"! + +// To capture the new value, you must reassign +str = str.toUpperCase(); +console.log(str); // Now "HELLO" +``` + +### Visual: What Happens in Memory + +``` +BEFORE str.toUpperCase(): +┌─────────────────┐ +│ str → "hello" │ (original string) +└─────────────────┘ + +AFTER str.toUpperCase() (without reassignment): +┌─────────────────┐ +│ str → "hello" │ (unchanged!) +└─────────────────┘ +┌─────────────────┐ +│ "HELLO" │ (new string, not captured, garbage collected) +└─────────────────┘ + +AFTER str = str.toUpperCase(): +┌─────────────────┐ +│ str → "HELLO" │ (str now points to new string) +└─────────────────┘ +``` + +### Common Misconception: const vs Immutability + +`const` prevents **reassignment**, not mutation. These are different concepts! + +```javascript +// const prevents reassignment +const name = "Alice"; +// name = "Bob"; // Error! Cannot reassign const + +// But const doesn't make objects immutable +const person = { name: "Alice" }; +person.name = "Bob"; // Works! Mutating the object +person.age = 25; // Works! Adding a property +// person = {}; // Error! Cannot reassign const + +// Primitives are immutable regardless of const/let +let str = "hello"; +str[0] = "H"; // Silently fails — can't mutate primitive +``` + + +Think of it this way: `const` protects the **variable** (the container). Immutability protects the **value** (the content). + + +--- + +## Autoboxing: The Secret Life of Primitives + +If primitives have no methods, how does `"hello".toUpperCase()` work? + +### The Magic Behind the Scenes + +When you access a property or method on a primitive, JavaScript temporarily wraps it in an object: + + + + ```javascript + "hello".toUpperCase() + ``` + + + + Behind the scenes, JavaScript does something like: + ```javascript + (new String("hello")).toUpperCase() + ``` + + + + The `toUpperCase()` method runs and returns `"HELLO"`. + + + + The temporary `String` object is thrown away. The original primitive `"hello"` is unchanged. + + + +### Wrapper Objects + +Each primitive type (except `null` and `undefined`) has a corresponding wrapper object: + +| Primitive | Wrapper Object | +|-----------|----------------| +| `string` | `String` | +| `number` | `Number` | +| `boolean` | `Boolean` | +| `bigint` | `BigInt` | +| `symbol` | `Symbol` | + +### Don't Use new String() etc. + +You can create wrapper objects manually, but **don't**: + +```javascript +// Don't do this! +let strObj = new String("hello"); +console.log(typeof strObj); // "object" (not "string"!) +console.log(strObj === "hello"); // false (object vs primitive) + +// Do this instead +let str = "hello"; +console.log(typeof str); // "string" +``` + + +Using `new String()`, `new Number()`, or `new Boolean()` creates **objects**, not primitives. This can cause confusing bugs with equality checks and typeof. + + +--- + +## null vs undefined + +These two "empty" values confuse many developers. Here's how they differ: + + + + | Aspect | `undefined` | `null` | + |--------|-------------|--------| + | **Meaning** | "No value assigned yet" | "Intentionally empty" | + | **Set by** | JavaScript automatically | Developer explicitly | + | **typeof** | `"undefined"` | `"object"` (bug) | + | **In JSON** | Omitted from output | Preserved as `null` | + | **Default params** | Triggers default | Doesn't trigger default | + | **Loose equality** | `null == undefined` is `true` | | + | **Strict equality** | `null === undefined` is `false` | | + + + ```javascript + // 1. Uninitialized variables + let x; + console.log(x); // undefined + + // 2. Missing function arguments + function greet(name) { + console.log(name); + } + greet(); // undefined + + // 3. No return statement + function noReturn() {} + console.log(noReturn()); // undefined + + // 4. Non-existent properties + let obj = {}; + console.log(obj.missing); // undefined + + // 5. Array holes + let arr = [1, , 3]; + console.log(arr[1]); // undefined + ``` + + + ```javascript + // 1. Explicitly "clearing" a value + let user = { name: "Alice" }; + user = null; // User logged out + + // 2. Function returning "no result" + function findUser(id) { + // Search logic... + return null; // Not found + } + + // 3. Optional object properties + let config = { + cache: true, + timeout: null // Explicitly no timeout + }; + + // 4. Resetting references + let timer = setTimeout(callback, 1000); + clearTimeout(timer); + timer = null; // Clear reference + ``` + + + +### Best Practices + +```javascript +// Check for either null or undefined (loose equality) +if (value == null) { + console.log("Value is null or undefined"); +} + +// Check for specifically undefined +if (value === undefined) { + console.log("Value is undefined"); +} + +// Check for specifically null +if (value === null) { + console.log("Value is null"); +} + +// Check for "has a value" (not null/undefined) +if (value != null) { + console.log("Value exists"); +} +``` + +--- + +## JavaScript Quirks & Gotchas + +JavaScript has some famous "weird parts" that every developer should know. Most relate to primitives and type coercion. + + + + ```javascript + console.log(typeof null); // "object" + ``` + + **Why?** This is a bug from JavaScript's first implementation in 1995. In the original code, values had a small label to identify their type. Objects had the label `000`, and `null` was represented as the NULL pointer (`0x00`), which also had `000`. + + **Why not fixed?** A proposal to fix it was rejected because too much existing code checks `typeof x === "object"` and expects `null` to pass. + + **Workaround:** + ```javascript + // Always check for null explicitly + if (value !== null && typeof value === "object") { + // It's a real object + } + ``` + + + + ```javascript + console.log(NaN === NaN); // false! + console.log(NaN !== NaN); // true! + ``` + + NaN is so confused about its identity that it doesn't even equal itself! + + **Why?** By the IEEE 754 specification, NaN represents "Not a Number" — an undefined or unrepresentable result. Since it's not a specific number, it can't equal anything, including itself. + + **How to check for NaN:** + ```javascript + // Don't do this + if (value === NaN) { } // Never true! + + // Do this instead + if (Number.isNaN(value)) { } // ES6, recommended + if (isNaN(value)) { } // Older, has quirks + ``` + + + `isNaN()` converts the value first, so `isNaN("hello")` is `true`. `Number.isNaN()` only returns `true` for actual `NaN`. + + + + + ```javascript + console.log(0.1 + 0.2); // 0.30000000000000004 + console.log(0.1 + 0.2 === 0.3); // false + ``` + + **Why?** Computers store numbers in binary. Just like 1/3 can't be perfectly represented in decimal (0.333...), 0.1 can't be perfectly represented in binary. + + **Solutions:** + ```javascript + // 1. Work in integers (cents, not dollars) — RECOMMENDED + let totalCents = 10 + 20; // 30 (accurate!) + let dollars = totalCents / 100; // 0.3 + + // 2. Use Intl.NumberFormat for display + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(0.30); // "$0.30" + + // 3. Compare with tolerance for equality checks + Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true + + // 4. Use toFixed() for simple rounding + (0.1 + 0.2).toFixed(2); // "0.30" + ``` + + + + ```javascript + console.log(Boolean("")); // false (empty string is falsy) + console.log(Boolean(" ")); // true (space is truthy!) + console.log(Boolean("0")); // true (string "0" is truthy!) + console.log(Boolean(0)); // false (number 0 is falsy) + + console.log("" == false); // true (coercion) + console.log("" === false); // false (different types) + ``` + + **The lesson:** Be careful with truthy/falsy checks on strings. An empty string `""` is falsy, but a string with just whitespace `" "` is truthy. + + ```javascript + // Check for empty or whitespace-only string + if (str.trim() === "") { + console.log("String is empty or whitespace"); + } + ``` + + + + ```javascript + console.log(1 + 2); // 3 (number addition) + console.log("1" + "2"); // "12" (string concatenation) + console.log(1 + "2"); // "12" (number converted to string!) + console.log("1" + 2); // "12" (number converted to string!) + console.log(1 + 2 + "3"); // "33" (left to right: 1+2=3, then 3+"3"="33") + console.log("1" + 2 + 3); // "123" (left to right: "1"+2="12", "12"+3="123") + ``` + + **Why?** The `+` operator does addition for numbers, but concatenation for strings. When mixed, JavaScript converts numbers to strings. + + **Be explicit:** + ```javascript + // Force number addition + Number("1") + Number("2"); // 3 + parseInt("1") + parseInt("2"); // 3 + + // Force string concatenation + String(1) + String(2); // "12" + `${1}${2}`; // "12" + ``` + + + + +**Want to go deeper?** Kyle Simpson's book "You Don't Know JS: Types & Grammar" is the definitive guide to understanding these quirks. It's free to read online! + + +--- + +## Test Your Knowledge + + + + **Answer:** + 1. `string` - text data + 2. `number` - integers and decimals + 3. `bigint` - large integers + 4. `boolean` - true/false + 5. `undefined` - no value assigned + 6. `null` - intentionally empty + 7. `symbol` - unique identifier + + Remember: Everything else is an object (arrays, functions, dates, etc.). + + + + **Answer:** `typeof null` returns `"object"`. + + This is a bug from JavaScript's original implementation in 1995. Values were stored with type tags, and both objects and `null` had the same `000` tag. The bug was never fixed because too much existing code depends on this behavior. + + To check for null, use `value === null` instead. + + + + **Answer:** Because JavaScript (like all languages) uses binary floating-point (IEEE 754) to store numbers. + + Just like 1/3 can't be perfectly represented in decimal (0.333...), 0.1 can't be perfectly represented in binary. The tiny rounding errors accumulate, giving us `0.30000000000000004` instead of `0.3`. + + Solutions: Use integers (work in cents), use `toFixed()` for display, compare with tolerance, or use a decimal math library. + + + + **Answer:** + - **`undefined`**: Means "no value has been assigned." JavaScript sets this automatically for uninitialized variables, missing function arguments, and non-existent properties. + + - **`null`**: Means "intentionally empty." Developers use this explicitly to indicate "this has no value on purpose." + + Key difference: `undefined` is the *default* empty value; `null` is the *intentional* empty value. + + ```javascript + let x; // undefined (automatic) + let y = null; // null (explicit) + ``` + + + + **Answer:** Through **autoboxing** (also called "auto-wrapping"). + + When you call a method on a primitive: + 1. JavaScript temporarily wraps it in a wrapper object (`String`, `Number`, etc.) + 2. The method is called on the wrapper object + 3. The result is returned + 4. The wrapper object is discarded + + So `"hello".toUpperCase()` becomes `(new String("hello")).toUpperCase()` behind the scenes. The original primitive `"hello"` is never changed. + + + +--- + +## Key Takeaways + + +**Remember these essential points about Primitive Types:** + +1. **7 primitives**: string, number, bigint, boolean, undefined, null, symbol + +2. **Primitives are immutable** — you can't change the value itself, only create new values + +3. **Compared by value** — `"hello" === "hello"` is true because the values match + +4. **typeof works for most types** — except `typeof null` returns `"object"` (historical bug) + +5. **Autoboxing** allows primitives to use methods — JavaScript wraps them temporarily + +6. **undefined vs null** — undefined is "not assigned," null is "intentionally empty" + +7. **Be aware of gotchas** — `NaN !== NaN`, `0.1 + 0.2 !== 0.3`, falsy values + +8. **Don't use `new String()` etc.** — creates objects, not primitives + + +--- + +## Related Concepts + + + + How primitives and objects are stored differently in memory + + + How JavaScript converts between types automatically + + + Understanding equality operators and type checking + + + How variables and their values are accessed + + + +--- + ## Reference - Official MDN documentation on primitive values + Official MDN documentation on primitive values and JavaScript's type system ## Articles - By GeeksforGeeks + Beginner-friendly overview covering all 7 primitive types with a comparison table showing the differences between primitives and objects. - By Dr. Axel Rauschmayer + Expert deep-dive by Dr. Axel Rauschmayer into IEEE 754 floating-point representation, explaining why 0.1 + 0.2 !== 0.3 and how JavaScript stores numbers internally. + + + Debunks the common myth that "everything in JS is an object." Excellent explanation of primitives, autoboxing, and wrapper objects with clear examples and a helpful summary. + + + Official-quality explanation from javascript.info on how autoboxing works and why primitives can use methods. Clear, up-to-date, and includes practice exercises. + + + Clears up the common confusion between const (prevents reassignment) and immutability (prevents mutation). Short and beginner-friendly. -- [What Every JavaScript Developer Should Know About Floating Point Numbers — Chewxy](https://blog.chewxy.com/2014/02/24/what-every-javascript-developer-should-know-about-floating-point-numbers/) -- [The Secret Life of JavaScript Primitives — Angus Croll](https://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/) -- [Primitive Types — Flow](https://flow.org/en/docs/types/primitives/) -- [(Not) Everything in JavaScript is an Object — Daniel Li](https://dev.to/d4nyll/not-everything-in-javascript-is-an-object) -- [Diving Deeper in JavaScripts Objects — Arfat Salman](https://blog.bitsrc.io/diving-deeper-in-javascripts-objects-318b1e13dc12) -- [The differences between Object.freeze() vs Const in JavaScript — Bolaji Ayodeji](https://medium.com/@bolajiayodeji/the-differences-between-object-freeze-vs-const-in-javascript-4eacea534d7c) -- [Object to primitive conversion — JavaScript.Info](https://javascript.info/object-toprimitive) -- [Methods of primitives - Javascript.info](https://javascript.info/primitives-methods) +## Books -## Videos + + The definitive deep-dive into JavaScript types. Free to read online. Covers primitives, coercion, and the "weird parts" that trip up developers. Essential reading for understanding JavaScript's type system. + + +## Courses - - By Academind + + Free preview of one of the most acclaimed JavaScript courses ever made. Covers types, coercion, and the "weird parts" that confuse developers. Perfect starting point before buying the full course. + + + The complete 12-hour course that has helped mass developers truly understand JavaScript. Deep coverage of types, operators, objects, and the engine internals. Worth every penny. - - By Simon Sez IT + + Part of the "JavaScript for Everyone" course by Mat Marquis. This module covers all 7 primitive types with dedicated lessons for Numbers, Strings, Booleans, null/undefined, BigInt, and Symbol. Beautifully written with a fun narrative style. -- [Value Types and Reference Types in JavaScript — Programming with Mosh](https://www.youtube.com/watch?v=e-_mDyqm2oU) -- [JavaScript Primitive Data Types — Avelx](https://www.youtube.com/watch?v=qw3j0A3DIzQ) -- [Everything you never wanted to know about JavaScript numbers — Bartek Szopka](https://www.youtube.com/watch?v=MqHDDtVYJRI) -- [What are variables in Javascript? — JS For Everyone](https://www.youtube.com/watch?v=B4Bbmei_thw) -- [TIPOS DE DATOS PRIMITIVOS en JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=cC65D2q5f8I) -- [Data Type in JavaScript - ScholarHat](https://www.youtube.com/watch?v=aFDvBjVjCh8) +## Videos + + + + Clear comparison of how primitives and reference types behave differently when assigned and passed to functions. Great visual explanations. + + + Mosh Hamedani's clear teaching style makes this complex topic easy to understand. Includes practical examples showing memory behavior. + + + JSConf talk by Bartek Szopka diving deep into the quirks of JavaScript numbers. Covers IEEE 754, precision issues, and edge cases. + + From 97dab8c24913fdcf1e80c912dd48057d54a16743 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 19:24:40 -0300 Subject: [PATCH 006/128] feat(call-stack): add Courses section with Piccalilli lesson - Add Courses section with Introduction to Asynchronous JavaScript - Piccalilli lesson covers single-threaded nature and call stack execution --- docs/concepts/call-stack.mdx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 769579f8..33b17bbd 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -707,7 +707,7 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in Go deeper into how the JS engine creates execution contexts and manages the Global Memory. Perfect for interview prep. - 🎨 Beautiful ASCII art visualization showing step-by-step how setTimeout interacts with the Call Stack and Event Loop. + Beautiful ASCII art visualization showing step-by-step how setTimeout interacts with the Call Stack and Event Loop. Advanced deep-dive into Creation vs Execution phases, Lexical Environment, and why `let`/`const` behave differently than `var`. @@ -717,6 +717,12 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in +## Courses + + + Part of the "JavaScript for Everyone" course by Mat Marquis. This free lesson explains why JavaScript is single-threaded, how the call stack manages execution contexts, and introduces the event loop and concurrency model. Beautifully written with a fun narrative style. + + ## Videos From 36f4f7c94bf8ea3b4d181a196f20c07aa6787ba7 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 19:25:33 -0300 Subject: [PATCH 007/128] feat(type-coercion): add Object to Primitive Conversion article - Add javascript.info article covering Symbol.toPrimitive, toString, valueOf - Moved from primitive-types page as it better fits type coercion topic --- docs/concepts/type-coercion.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index 4ae3cbc2..5a0f4c70 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -34,6 +34,9 @@ The ECMAScript specification defines JavaScript as a dynamically typed language, By Alexey Samoshkin + + Deep-dive into how JavaScript converts objects to primitives using Symbol.toPrimitive, toString, and valueOf. Essential for understanding type coercion. + - [Javascript Coercion Explained — Ben Garrison](https://hackernoon.com/javascript-coercion-explained-545c895213d3) From 278385fdd1f404a585169f95030a120c1135048f Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 19:44:12 -0300 Subject: [PATCH 008/128] feat(type-coercion): add comprehensive beginner-friendly explanation with examples - Add real-world 'Shapeshifter' analogy with ASCII diagram - Add explicit vs implicit coercion comparison with Tabs - Add deep-dive sections for String, Number, Boolean conversion - Add conversion rules tables with examples and gotchas - Add Object to Primitive conversion with ToPrimitive algorithm - Add == algorithm explanation with step-by-step examples - Add operators & coercion cheat sheet - Add JavaScript WAT moments section (8 famous weird parts) - Add best practices and anti-patterns - Add quiz section with 6 questions - Curate resources: 1 MDN reference, 3 articles, 1 book chapter, 3 videos - Link to YDKJS Chapter 4 specifically for coercion --- docs/concepts/type-coercion.mdx | 972 ++++++++++++++++++++++++++++++-- 1 file changed, 936 insertions(+), 36 deletions(-) diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index 5a0f4c70..b740c03b 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -1,66 +1,966 @@ --- title: "Type Coercion" -description: "Implicit, Explicit, Nominal, Structuring and Duck Typing in JavaScript" +description: "Understanding implicit and explicit type conversion in JavaScript" --- -## Overview +## The Shapeshifter: A Real-World Analogy -The ECMAScript specification defines JavaScript as a dynamically typed language, meaning that types are associated with values rather than variables, and type checking occurs at runtime. There are various ways JavaScript manages types: +Imagine JavaScript as an overly helpful translator. When you give it values of different types, it tries to "help" by converting them — sometimes correctly, sometimes... creatively. + +``` +YOU: "Hey JavaScript, add 5 and '3' together" + +JAVASCRIPT (thinking): "Hmm, one's a number, one's a string... + I'll just convert the number to a string! + '5' + '3' = '53'. You're welcome!" + +YOU: "That's... not what I meant." + +JAVASCRIPT: "¯\_(ツ)_/¯" +``` + +This "helpful" behavior is called **type coercion** — JavaScript automatically converting values from one type to another. Sometimes it's useful, sometimes it creates bugs that will haunt your dreams. + +``` + TYPE COERCION: THE SHAPESHIFTER + + ┌─────────┐ ┌─────────┐ + │ "5" │ ──── + 3 ────────► │ "53" │ String won! + │ string │ │ string │ + └─────────┘ └─────────┘ + + ┌─────────┐ ┌─────────┐ + │ "5" │ ──── - 3 ────────► │ 2 │ Number won! + │ string │ │ number │ + └─────────┘ └─────────┘ + + Same values, different operators, different results! +``` + + +**TL;DR:** Type coercion is JavaScript automatically converting values between types. It can be **implicit** (automatic) or **explicit** (you control it). Understanding the rules helps you avoid bugs and write cleaner code. + + + +**What you'll learn in this guide:** +- The difference between implicit and explicit coercion +- How JavaScript converts to strings, numbers, and booleans +- The 8 falsy values every developer must memorize +- How objects convert to primitives +- The famous JavaScript "WAT" moments explained +- Best practices for avoiding coercion bugs + + +--- + +## What Is Type Coercion? + +**Type coercion** is the process of converting a value from one type to another. In JavaScript, this happens constantly — sometimes because you asked for it, sometimes because JavaScript decided to "help." + +### Explicit vs Implicit Coercion + +There are two ways coercion happens: + + + + **You** control the conversion using built-in functions. This is predictable and intentional. + + ```javascript + // YOU decide when and how to convert + Number("42") // 42 + String(42) // "42" + Boolean(1) // true + + parseInt("42px") // 42 + parseFloat("3.14") // 3.14 + ``` + + This is the **safe** way — you know exactly what's happening. + + + **JavaScript** automatically converts types when operators or functions expect a different type. + + ```javascript + // JavaScript "helps" without asking + "5" + 3 // "53" (number became string) + "5" - 3 // 2 (string became number) + + if ("hello") {} // string became boolean (true) + + 5 == "5" // true (types were coerced) + ``` + + This is where bugs hide. Learn the rules or suffer the consequences! + + + +### Why Does JavaScript Do This? + +JavaScript is a **dynamically typed** language — variables don't have fixed types. This flexibility means JavaScript needs to figure out what to do when types don't match. + +```javascript +// In JavaScript, variables can hold any type +let x = 42; // x is a number +x = "hello"; // now x is a string +x = true; // now x is a boolean + +// So what happens here? +let result = x + 10; // JavaScript must decide how to handle this +``` + +Other languages would throw an error. JavaScript tries to make it work. Whether that's a feature or a bug... depends on who you ask! + +--- + +## The Three Types of Conversion + +Here's the most important rule: **JavaScript can only convert to THREE types:** + +| Target Type | Explicit Method | Common Implicit Triggers | +|-------------|-----------------|--------------------------| +| **String** | `String(value)` | `+` with a string, template literals | +| **Number** | `Number(value)` | Math operators (`- * / %`), comparisons | +| **Boolean** | `Boolean(value)` | `if`, `while`, `!`, `&&`, `\|\|`, `? :` | + +That's it. No matter how complex the coercion seems, the end result is always a string, number, or boolean. + +``` + ┌──────────────────────┐ + │ ANY VALUE │ + │ (string, number, │ + │ object, array...) │ + └──────────┬───────────┘ + │ + ┌────────────────┼────────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ String │ │ Number │ │ Boolean │ + │ "42" │ │ 42 │ │ true │ + └──────────┘ └──────────┘ └──────────┘ + + These are the ONLY three possible destinations! +``` + +--- + +## String Conversion + +String conversion is the most straightforward — almost anything can become a string. + +### When Does It Happen? + +```javascript +// Explicit conversion +String(123) // "123" +String(true) // "true" +(123).toString() // "123" + +// Implicit conversion +123 + "" // "123" (concatenation with empty string) +`Value: ${123}` // "Value: 123" (template literal) +"Hello " + 123 // "Hello 123" (+ with a string) +``` + +### String Conversion Rules + +| Value | Result | Notes | +|-------|--------|-------| +| `123` | `"123"` | Numbers become digit strings | +| `-12.34` | `"-12.34"` | Decimals and negatives work too | +| `true` | `"true"` | Booleans become their word | +| `false` | `"false"` | | +| `null` | `"null"` | | +| `undefined` | `"undefined"` | | +| `[1, 2, 3]` | `"1,2,3"` | Arrays join with commas | +| `[]` | `""` | Empty array becomes empty string | +| `{}` | `"[object Object]"` | Objects become this (usually useless) | +| `Symbol("id")` | Throws TypeError! | Symbols can't implicitly convert | + +### The + Operator's Split Personality + +The `+` operator is special — it does **both** addition and concatenation: + +```javascript +// With two numbers: addition +5 + 3 // 8 + +// With any string involved: concatenation +"5" + 3 // "53" (3 becomes "3") +5 + "3" // "53" (5 becomes "5") +"Hello" + " World" // "Hello World" + +// Order matters with multiple operands! +1 + 2 + "3" // "33" (1+2=3, then 3+"3"="33") +"1" + 2 + 3 // "123" (all become strings left-to-right) +``` + + +**Common gotcha:** The `+` operator with strings catches many developers off guard. If you're doing math and get unexpected string concatenation, check if any value might be a string! + +```javascript +// Dangerous: user input is always a string! +const userInput = "5"; +const result = userInput + 10; // "510", not 15! + +// Safe: convert first +const result = Number(userInput) + 10; // 15 +``` + + +--- + +## Number Conversion + +Number conversion has more triggers than string conversion, and more edge cases to memorize. + +### When Does It Happen? + +```javascript +// Explicit conversion +Number("42") // 42 +parseInt("42px") // 42 (stops at non-digit) +parseFloat("3.14") // 3.14 ++"42" // 42 (unary plus trick) + +// Implicit conversion +"6" - 2 // 4 (subtraction) +"6" * 2 // 12 (multiplication) +"6" / 2 // 3 (division) +"6" % 4 // 2 (modulo) +"10" > 5 // true (comparison) ++"42" // 42 (unary plus) +``` + +### Number Conversion Rules + +| Value | Result | Notes | +|-------|--------|-------| +| `"123"` | `123` | Numeric strings work | +| `" 123 "` | `123` | Whitespace is trimmed | +| `"123abc"` | `NaN` | Any non-numeric char → NaN | +| `""` | `0` | Empty string becomes 0 | +| `" "` | `0` | Whitespace-only becomes 0 | +| `true` | `1` | | +| `false` | `0` | | +| `null` | `0` | null → 0 | +| `undefined` | `NaN` | undefined → NaN (different!) | +| `[]` | `0` | Empty array → "" → 0 | +| `[1]` | `1` | Single element array | +| `[1, 2]` | `NaN` | Multiple elements → NaN | +| `{}` | `NaN` | Objects → NaN | + + +**null vs undefined:** Notice that `Number(null)` is `0` but `Number(undefined)` is `NaN`. This inconsistency trips up many developers! + +```javascript +Number(null) // 0 +Number(undefined) // NaN + +null + 5 // 5 +undefined + 5 // NaN +``` + + +### Math Operators Always Convert to Numbers + +Unlike `+`, the other math operators (`-`, `*`, `/`, `%`) **only** do math — they always convert to numbers: + +```javascript +"6" - "2" // 4 (both become numbers) +"6" * "2" // 12 +"6" / "2" // 3 +"10" % "3" // 1 + +// This is why - and + behave differently! +"5" + 3 // "53" (concatenation) +"5" - 3 // 2 (math) +``` + +### The Unary + Trick + +The unary `+` (plus sign before a value) is a quick way to convert to a number: + +```javascript ++"42" // 42 ++true // 1 ++false // 0 ++null // 0 ++undefined // NaN ++"hello" // NaN ++"" // 0 +``` + +--- + +## Boolean Conversion + +Boolean conversion is actually the simplest — every value is either **truthy** or **falsy**. + +### When Does It Happen? + +```javascript +// Explicit conversion +Boolean(1) // true +Boolean(0) // false +!!value // double negation trick + +// Implicit conversion +if (value) { } // condition check +while (value) { } // loop condition +value ? "yes" : "no" // ternary operator +value && doSomething() // logical AND +value || defaultValue // logical OR +!value // logical NOT +``` + +### The 8 Falsy Values (Memorize These!) + +There are exactly **8 values** that convert to `false`. Everything else is `true`. + +```javascript +// THE FALSY EIGHT +Boolean(false) // false (obviously) +Boolean(0) // false +Boolean(-0) // false (yes, -0 exists) +Boolean(0n) // false (BigInt zero) +Boolean("") // false (empty string) +Boolean(null) // false +Boolean(undefined) // false +Boolean(NaN) // false +``` + +### Everything Else Is Truthy! + +This includes some surprises: + +```javascript +// These are all TRUE! +Boolean(true) // true (obviously) +Boolean(1) // true +Boolean(-1) // true (negative numbers!) +Boolean("hello") // true +Boolean("0") // true (non-empty string!) +Boolean("false") // true (non-empty string!) +Boolean([]) // true (empty array!) +Boolean({}) // true (empty object!) +Boolean(function(){}) // true +Boolean(new Date()) // true +Boolean(Infinity) // true +Boolean(-Infinity) // true +``` + + +**Common gotchas:** + +```javascript +// These catch people ALL the time: +Boolean("0") // true (it's a non-empty string!) +Boolean("false") // true (it's a non-empty string!) +Boolean([]) // true (arrays are objects, objects are truthy) +Boolean({}) // true (even empty objects) + +// If checking for empty array, do this: +if (arr.length) { } // checks if array has items +if (arr.length === 0) { } // checks if array is empty +``` + + +### Logical Operators Don't Return Booleans! + +A common misconception: `&&` and `||` don't necessarily return `true` or `false` — they return one of the **original values**: + +```javascript +// || returns the FIRST truthy value (or the last value) +"hello" || "world" // "hello" +"" || "world" // "world" +"" || 0 || null || "yes" // "yes" + +// && returns the FIRST falsy value (or the last value) +"hello" && "world" // "world" +"" && "world" // "" +1 && 2 && 3 // 3 + +// This is useful for defaults! +const name = userInput || "Anonymous"; +const display = user && user.name; +``` + +--- + +## Object to Primitive Conversion + +When JavaScript needs to convert an object (including arrays) to a primitive, it follows a specific algorithm. + +### The ToPrimitive Algorithm + + + + If the value is already a primitive (string, number, boolean, etc.), return it as-is. + + + + JavaScript decides whether it wants a "string" or "number" based on context: + - **String hint:** `String()`, template literals, property keys + - **Number hint:** `Number()`, math operators, comparisons + - **Default hint:** `+` operator, `==` (usually treated as number) + + + + - For **number** hint: try `valueOf()` first, then `toString()` + - For **string** hint: try `toString()` first, then `valueOf()` + + + + If a primitive is returned, use it. Otherwise, throw `TypeError`. + + + +### How Built-in Objects Convert + +```javascript +// Arrays - toString() returns joined elements +[1, 2, 3].toString() // "1,2,3" +[1, 2, 3] + "" // "1,2,3" +[1, 2, 3] - 0 // NaN (can't convert "1,2,3" to number) + +[].toString() // "" +[] + "" // "" +[] - 0 // 0 (empty string → 0) + +[1].toString() // "1" +[1] - 0 // 1 + +// Plain objects - toString() returns "[object Object]" +({}).toString() // "[object Object]" +({}) + "" // "[object Object]" + +// Dates - special case, prefers string for + operator +const date = new Date(0); +date.toString() // "Thu Jan 01 1970 ..." +date.valueOf() // 0 (timestamp in ms) + +date + "" // "Thu Jan 01 1970 ..." (uses toString) +date - 0 // 0 (uses valueOf) +``` + +### Custom Conversion with valueOf and toString + +You can control how your objects convert: + +```javascript +const price = { + amount: 99.99, + currency: "USD", + + valueOf() { + return this.amount; + }, + + toString() { + return `${this.currency} ${this.amount}`; + } +}; + +// Number conversion uses valueOf() +price - 0 // 99.99 +price * 2 // 199.98 ++price // 99.99 + +// String conversion uses toString() +String(price) // "USD 99.99" +`Price: ${price}` // "Price: USD 99.99" + +// + is ambiguous, uses valueOf() if it returns primitive +price + "" // "99.99" (valueOf returned number, then → string) +``` + +### ES6 Symbol.toPrimitive + +ES6 introduced a cleaner way to control conversion — `Symbol.toPrimitive`: + +```javascript +const obj = { + [Symbol.toPrimitive](hint) { + console.log(`Converting with hint: ${hint}`); + + if (hint === "number") { + return 42; + } + if (hint === "string") { + return "forty-two"; + } + // hint === "default" + return "default value"; + } +}; + ++obj // 42 (hint: "number") +`${obj}` // "forty-two" (hint: "string") +obj + "" // "default value" (hint: "default") +``` + +--- + +## The == Algorithm Explained + +The loose equality operator `==` is where type coercion gets wild. Here's how it actually works: + +### Simplified == Rules + + + + Compare directly (like `===`). + ```javascript + 5 == 5 // true + "hello" == "hello" // true + ``` + + + + `null == undefined` is `true`. Neither equals anything else. + ```javascript + null == undefined // true + null == null // true + null == 0 // false (special rule!) + null == "" // false + ``` + + + + Convert the string to a number. + ```javascript + 5 == "5" + // becomes: 5 == 5 + // result: true + ``` + + + + Convert the boolean to a number FIRST. + ```javascript + true == "1" + // step 1: 1 == "1" (true → 1) + // step 2: 1 == 1 (string → number) + // result: true + + true == "true" + // step 1: 1 == "true" (true → 1) + // step 2: 1 == NaN ("true" → NaN) + // result: false (surprise!) + ``` + + + + Convert the object to a primitive. + ```javascript + [1] == 1 + // step 1: "1" == 1 (array → string "1") + // step 2: 1 == 1 (string → number) + // result: true + ``` + + + +### Step-by-Step Examples + +```javascript +// Example 1: "5" == 5 +"5" == 5 +// String vs Number → convert string to number +// 5 == 5 +// Result: true + +// Example 2: true == "1" +true == "1" +// Boolean involved → convert boolean to number first +// 1 == "1" +// Number vs String → convert string to number +// 1 == 1 +// Result: true + +// Example 3: [] == false +[] == false +// Boolean involved → convert boolean to number first +// [] == 0 +// Object vs Number → convert object to primitive +// "" == 0 (empty array → empty string) +// String vs Number → convert string to number +// 0 == 0 +// Result: true + +// Example 4: [] == ![] +[] == ![] +// First, evaluate ![] → false (arrays are truthy) +// [] == false +// Boolean involved → false becomes 0 +// [] == 0 +// Object vs Number → [] becomes "" +// "" == 0 +// String vs Number → "" becomes 0 +// 0 == 0 +// Result: true (yes, really!) +``` + + +**Just use `===`!** The triple equals operator never coerces types. If the types are different, it returns `false` immediately. This is almost always what you want. + +```javascript +5 === "5" // false (different types) +5 == "5" // true (coerced) + +null === undefined // false +null == undefined // true +``` + + +--- + +## Operators & Coercion Cheat Sheet + +Quick reference for which operators trigger which coercion: + +| Operator | Coercion Type | Example | Result | +|----------|---------------|---------|--------| +| `+` (with string) | String | `"5" + 3` | `"53"` | +| `+` (unary) | Number | `+"5"` | `5` | +| `-` `*` `/` `%` | Number | `"5" - 3` | `2` | +| `++` `--` | Number | `let x = "5"; x++` | `6` | +| `>` `<` `>=` `<=` | Number | `"10" > 5` | `true` | +| `==` `!=` | Complex | `"5" == 5` | `true` | +| `===` `!==` | None | `"5" === 5` | `false` | +| `&&` `\|\|` | Boolean (internal) | `"hi" \|\| "bye"` | `"hi"` | +| `!` | Boolean | `!"hello"` | `false` | +| `if` `while` `? :` | Boolean | `if ("hello")` | `true` | +| `&` `\|` `^` `~` | Number (32-bit int) | `"5" \| 0` | `5` | + +--- + +## JavaScript WAT Moments + +Let's explore the famous "weird parts" that make JavaScript... special. - - This occurs when JavaScript automatically converts one data type to another when required. For instance, JavaScript might convert a string to a number during an arithmetic operation. While this can simplify some code, it can also lead to unexpected results if not handled carefully. + + ```javascript + "5" + 3 // "53" (string concatenation) + "5" - 3 // 2 (math!) + + // Why? + does both addition AND concatenation + // If either operand is a string, it concatenates + // - only does subtraction, so it converts to numbers + ``` + + + + ```javascript + [] + [] // "" + // Both arrays → "", then "" + "" = "" + + [] + {} // "[object Object]" + // [] → "", {} → "[object Object]" + + {} + [] // 0 (in browser console!) + // {} is parsed as empty block, then +[] = 0 + // Wrap in parens to fix: ({}) + [] = "[object Object]" + ``` + + + + ```javascript + true + true // 2 (1 + 1) + true + false // 1 (1 + 0) + true - true // 0 (1 - 1) + + // Booleans convert to 1 (true) or 0 (false) + ``` + + + + ```javascript + [] == ![] // true + + // Step by step: + // 1. ![] → false (arrays are truthy, negated = false) + // 2. [] == false + // 3. [] == 0 (boolean → number) + // 4. "" == 0 (array → string) + // 5. 0 == 0 (string → number) + // 6. true! + + // Meanwhile... + [] === ![] // false (different types, no coercion) + ``` - - Unlike implicit typing, explicit typing involves manually converting a value from one type to another using functions like `Number()`, `String()`, or `Boolean()`. + + + ```javascript + "foo" + + "bar" // "fooNaN" + + // Step by step: + // 1. +"bar" is evaluated first (unary +) + // 2. +"bar" → NaN (can't convert "bar" to number) + // 3. "foo" + NaN → "fooNaN" + ``` - - JavaScript doesn't natively support nominal typing, where types are explicitly declared and checked. However, TypeScript, a superset of JavaScript, brings this feature to help catch type errors during development. + + + ```javascript + NaN === NaN // false + NaN == NaN // false + + // NaN is the only value in JavaScript not equal to itself! + // This is by design (IEEE 754 spec) + + // How to check for NaN: + Number.isNaN(NaN) // true (correct way) + isNaN(NaN) // true + isNaN("hello") // true (wrong! it converts first) + Number.isNaN("hello") // false (correct) + ``` - - In this type system, types are based on the structure or properties of the data. JavaScript is a structurally typed language where objects are compatible if they share the same structure (i.e., the same set of properties and methods). + + + ```javascript + typeof NaN // "number" (wat) + typeof null // "object" (historical bug) + typeof [] // "object" (arrays are objects) + typeof function(){} // "function" (special case) + ``` - - This is a concept where an object's suitability is determined by the presence of certain properties and methods, rather than by the actual type of the object. JavaScript relies heavily on duck typing, where behavior is inferred from an object's properties rather than its declared type. + + + ```javascript + [1, 2] + [3, 4] // "1,23,4" + + // Arrays convert to strings: + // [1, 2] → "1,2" + // [3, 4] → "3,4" + // "1,2" + "3,4" → "1,23,4" + + // To actually combine arrays: + [...[1, 2], ...[3, 4]] // [1, 2, 3, 4] + [1, 2].concat([3, 4]) // [1, 2, 3, 4] + ``` -## Articles +--- + +## Best Practices + + +**How to avoid coercion bugs:** + +1. **Use `===` instead of `==`** — No surprises, no coercion +2. **Be explicit** — Use `Number()`, `String()`, `Boolean()` when converting +3. **Validate input** — Don't assume types, especially from user input +4. **Use `Number.isNaN()`** — Not `isNaN()` or `=== NaN` +5. **Be careful with `+`** — Remember it concatenates if any operand is a string + + +### When Implicit Coercion IS Useful + +Despite the gotchas, some implicit coercion patterns are actually helpful: + +```javascript +// 1. Checking for null OR undefined in one shot +if (value == null) { + // This catches BOTH null and undefined + // Much cleaner than: if (value === null || value === undefined) +} + +// 2. Boolean context is natural and readable +if (user) { + // Truthy check - totally fine +} + +if (items.length) { + // Checking if array has items - totally fine +} + +// 3. Quick string conversion +const str = value + ""; +// or +const str = String(value); +// or +const str = `${value}`; + +// 4. Quick number conversion +const num = +value; +// or +const num = Number(value); +``` + +### Anti-Patterns to Avoid + +```javascript +// BAD: Relying on == for type-unsafe comparisons +if (x == true) { } // Don't do this! +if (x) { } // Do this instead + +// BAD: Using == with 0 or "" +if (x == 0) { } // Might match "", null behavior is weird +if (x === 0) { } // Clear intent + +// BAD: Truthy check when you need specific type +function process(count) { + if (!count) return; // Fails for count = 0! + // ... +} + +function process(count) { + if (typeof count !== "number") return; // Better + // ... +} +``` + +--- + +## Test Your Knowledge + + + + **Answer:** `"53"` (string) + + The `+` operator, when one operand is a string, performs string concatenation. The number `3` is converted to `"3"`, resulting in `"5" + "3" = "53"`. + + + + **Answer:** + 1. `false` + 2. `0` + 3. `-0` + 4. `0n` (BigInt zero) + 5. `""` (empty string) + 6. `null` + 7. `undefined` + 8. `NaN` + + Everything else is truthy, including `[]`, `{}`, `"0"`, and `"false"`. + + + + **Answer:** This is a multi-step coercion: + + 1. `![]` evaluates first: arrays are truthy, so `![]` = `false` + 2. Now we have `[] == false` + 3. Boolean converts to number: `[] == 0` + 4. Array converts to primitive: `"" == 0` + 5. String converts to number: `0 == 0` + 6. Result: `true` + + + + **Answer:** + + - `===` (strict equality) **never** coerces. If types differ, it returns `false` immediately. + - `==` (loose equality) **coerces** values to the same type before comparing, following a complex algorithm. + + ```javascript + 5 === "5" // false (different types) + 5 == "5" // true (string coerced to number) + ``` + + Best practice: Use `===` unless you specifically need coercion. + + + + **Answer:** + + ```javascript + Number(null) // 0 + Number(undefined) // NaN + ``` + + This inconsistency is a common source of bugs. `null` converts to `0` (like "nothing" = zero), while `undefined` converts to `NaN` (like "no value" = not a number). + + + + **Answer:** `"1hello"` + + Step by step: + 1. `true + false` = `1 + 0` = `1` (booleans → numbers) + 2. `1 + "hello"` = `"1hello"` (number → string for concatenation) + + + +--- + +## Key Takeaways + + +**Remember these essentials about Type Coercion:** + +1. **Three conversions only:** JavaScript converts to String, Number, or Boolean — nothing else + +2. **Implicit vs Explicit:** Know when JS converts automatically vs when you control it + +3. **The 8 falsy values:** `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy + +4. **+ is special:** It prefers string concatenation if ANY operand is a string + +5. **- * / % are consistent:** They ALWAYS convert to numbers + +6. **== coerces, === doesn't:** Use `===` by default to avoid surprises + +7. **null == undefined:** This is true, but neither equals anything else with `==` + +8. **Objects convert via:** `valueOf()` and `toString()` methods + +9. **When in doubt, be explicit:** Use `Number()`, `String()`, `Boolean()` + + +--- + +## Related Concepts - - By Promise Tochi + + Understanding the basic data types that coercion converts between - - By Alexey Samoshkin - - - Deep-dive into how JavaScript converts objects to primitives using Symbol.toPrimitive, toString, and valueOf. Essential for understanding type coercion. + + Deep dive into equality operators and type checking -- [Javascript Coercion Explained — Ben Garrison](https://hackernoon.com/javascript-coercion-explained-545c895213d3) -- [What exactly is Type Coercion in Javascript? - Stack Overflow](https://stackoverflow.com/questions/19915688/what-exactly-is-type-coercion-in-javascript) +--- -## Videos +## Reference + + + Official MDN documentation on type coercion in JavaScript + + +## Articles - - By Shirmung Bielefeld + + Comprehensive freeCodeCamp article by Alexey Samoshkin covering all coercion rules with tons of examples and quiz questions. One of the best resources available. - - By Hitesh Choudhary + + Practical guide by Promise Tochi covering implicit coercion patterns, valueOf/toString, and common gotchas with clear examples. + + + Deep-dive from javascript.info into how objects convert to primitives using Symbol.toPrimitive, toString, and valueOf. Essential for advanced understanding. -- [JavaScript Questions: What is Coercion? - Steven Hancock](https://www.youtube.com/watch?v=z4-8wMSPJyI) -- [Typing: Static vs Dynamic, Weak vs. Strong - Codexpanse](https://www.youtube.com/watch?v=C5fr0LZLMAs) -- [EL SISTEMA de TIPOS DE JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=0ei4nb49GKo) -- [Duck Typing in Javascript - Techmaker Studio](https://www.youtube.com/watch?v=oEpgyoMEkrM) -- [Duck Typing in Javascript - Programming with Kartik](https://youtu.be/e4X1KAuk6Bs?si=krZKbsM2i3tmIl2G) - ## Books - - By Kyle Simpson + + The definitive deep-dive into JavaScript coercion. Kyle Simpson explains abstract operations, ToString, ToNumber, ToBoolean, and the "why" behind every coercion rule. Free to read online. + +## Videos + + + + Entertaining JSConf talk by Shirmung Bielefeld exploring the chaos of JavaScript equality operators with live examples and audience participation. + + + Clear explanation of type coercion basics from popular JavaScript educator Hitesh Choudhary. Great for visual learners. + + + Beginner-friendly introduction to coercion concepts with practical examples and clear explanations. + + From 50d1a2efff00b7f721e4ab8322677a641c029eb1 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 20:27:58 -0300 Subject: [PATCH 009/128] feat(scope-and-closures): merge scope and closures into comprehensive guide - Combine concepts #6 (Scope) and #21 (Closures) into single document - Add real-world analogy and detailed explanations - Include code examples, ASCII diagrams, and test yourself section - Curate resources: 6 articles, 1 course, 4 videos - Update navigation in docs.json - Update cross-references in call-stack.mdx and primitive-types.mdx - Remove old scope.mdx and closures.mdx files --- docs/concepts/call-stack.mdx | 7 +- docs/concepts/closures.mdx | 69 -- docs/concepts/primitive-types.mdx | 4 +- docs/concepts/scope-and-closures.mdx | 1133 ++++++++++++++++++++++++++ docs/concepts/scope.mdx | 67 -- docs/docs.json | 3 +- 6 files changed, 1138 insertions(+), 145 deletions(-) delete mode 100644 docs/concepts/closures.mdx create mode 100644 docs/concepts/scope-and-closures.mdx delete mode 100644 docs/concepts/scope.mdx diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 33b17bbd..91bd79a1 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -678,11 +678,8 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in Functions that call themselves - - How functions remember their scope - - - Understanding variable accessibility + + Understanding variable visibility and how functions remember their environment diff --git a/docs/concepts/closures.mdx b/docs/concepts/closures.mdx deleted file mode 100644 index 5078f79a..00000000 --- a/docs/concepts/closures.mdx +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "Closures" -description: "Understanding how functions remember their scope" ---- - -## Overview - -A **closure** is the combination of a function bundled together with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created. - - -Closures are one of the most powerful features in JavaScript. They enable data privacy, function factories, and are fundamental to many JavaScript patterns. - - -## Reference - - - - MDN documentation - - - JavaScript.Info - - - -## Articles - - - - By Olivier De Meulder - - - By Richard Bovell - - - -- [Understanding JavaScript Closures — Codesmith](https://codeburst.io/understanding-javascript-closures-da6aab330302) -- [Understand Closures in JavaScript — Brandon Morelli](https://codeburst.io/understand-closures-in-javascript-d07852fa51e7) -- [A simple guide to help you understand closures in JavaScript — Prashant Ram](https://medium.freecodecamp.org/javascript-closures-simplified-d0d23fa06ba4) -- [Understanding JavaScript Closures: A Practical Approach — Paul Upendo](https://scotch.io/tutorials/understanding-javascript-closures-a-practical-approach) -- [Understanding JavaScript: Closures — Alexander Kondov](https://hackernoon.com/understanding-javascript-closures-4188edf5ea1b) -- [How to use JavaScript closures with confidence — Léna Faure](https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b) -- [JavaScript closures by example — tyler](https://howchoo.com/g/mge2mji2mtq/javascript-closures-by-example) -- [JavaScript — Closures and Scope — Alex Aitken](https://codeburst.io/javascript-closures-and-scope-3784c75b9290) -- [Discover the power of closures in JavaScript — Cristi Salcescu](https://medium.freecodecamp.org/discover-the-power-of-closures-in-javascript-5c472a7765d7) -- [Closure, Currying and IIFE in JavaScript — Ritik](https://dev.to/ritik_dev_js/what-the-hack-is-closure-currying-and-iife-in-javascript-32m9) -- [Understanding Closures in JavaScript — Sukhjinder Arora](https://blog.bitsrc.io/a-beginners-guide-to-closures-in-javascript-97d372284dda) -- [Closures: Using Memoization — Brian Barbour](https://dev.to/steelvoltage/closures-using-memoization-3597) -- [whatthefuck.is · A Closure - Dan Abramov](https://whatthefuck.is/closure) -- [Do you know Closures - Mohamed Khaled](https://dev.to/this_mkhy/do-you-know-es6-part-3-advanced-3fcl#Closures-2) - -## Videos - - - - By Codesmith - - - By Akshay Saini — Namaste Javascript - - - -- [Javascript Closure — techsith](https://www.youtube.com/watch?v=71AtaJpJHw0) -- [Closures — Fun Fun Function](https://www.youtube.com/watch?v=CQqwU2Ixu-U) -- [Closures in JavaScript — techsith](https://www.youtube.com/watch?v=-xqJo5VRP4A) -- [JavaScript Closures 101: What is a closure? — JavaScript Tutorials](https://www.youtube.com/watch?v=yiEeiMN2Khs) -- [Closures — freeCodeCamp](https://www.youtube.com/watch?v=1JsJx1x35c0) -- [JavaScript Closures — CodeWorkr](https://www.youtube.com/watch?v=-rLrGAXK8WE) -- [CLOSURES en JavaScript: Qué son y cómo funcionan - Carlos Azaustre](https://youtu.be/xa8lhVwQBw4) -- [Learn Closures In 7 Minutes - Web Dev Simplified](https://www.youtube.com/watch?v=3a0I8ICR1Vg) diff --git a/docs/concepts/primitive-types.mdx b/docs/concepts/primitive-types.mdx index b120945b..73cdd407 100644 --- a/docs/concepts/primitive-types.mdx +++ b/docs/concepts/primitive-types.mdx @@ -913,8 +913,8 @@ JavaScript has some famous "weird parts" that every developer should know. Most Understanding equality operators and type checking - - How variables and their values are accessed + + How variables are accessed and how functions remember their environment diff --git a/docs/concepts/scope-and-closures.mdx b/docs/concepts/scope-and-closures.mdx new file mode 100644 index 00000000..f4c70f20 --- /dev/null +++ b/docs/concepts/scope-and-closures.mdx @@ -0,0 +1,1133 @@ +--- +title: "Scope & Closures" +description: "Understanding variable visibility and how functions remember their environment" +--- + +## Sneaking Through an Office Building + +Imagine it's after hours and you're wandering through your office building (legally — you work there, promise). You notice something interesting about what you can and can't see: + +- **Inside your private office**, you can see everything on your desk, peek into the hallway through your door, and even see the lobby through the glass walls +- **In the hallway**, you can see the lobby clearly, but those private offices? Their blinds are shut — no peeking allowed +- **In the lobby**, you're limited to just what's there — the reception desk, some chairs, maybe a sad-looking plant + +``` +┌─────────────────────────────────────────────────────────────┐ +│ LOBBY (Global Scope) │ +│ reception = "Welcome Desk" │ +│ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ HALLWAY (Function Scope) │ │ +│ │ hallwayPlant = "Fern" │ │ +│ │ │ │ +│ │ ┌───────────────────────────────────────┐ │ │ +│ │ │ PRIVATE OFFICE (Block Scope) │ │ │ +│ │ │ secretDocs = "Confidential" │ │ │ +│ │ │ │ │ │ +│ │ │ Can see: secretDocs ✓ │ │ │ +│ │ │ Can see: hallwayPlant ✓ │ │ │ +│ │ │ Can see: reception ✓ │ │ │ +│ │ └───────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ Cannot see: secretDocs ✗ │ │ +│ └───────────────────────────────────────────────────┘ │ +│ │ +│ Cannot see: hallwayPlant, secretDocs ✗ │ +└─────────────────────────────────────────────────────────────┘ +``` + +This is exactly how **scope** works in JavaScript! Code in inner scopes can "look out" and access variables from outer scopes, but outer scopes can never "look in" to inner scopes. + +And here's where it gets really interesting: imagine someone who worked in that private office quits and leaves the building. But they took a mental snapshot of everything in there — the passwords on sticky notes, the secret project plans, the snack drawer location. Even though they've left, they still *remember* everything. That's essentially what a **closure** is: a function that "remembers" the scope where it was created, even after that scope is gone. + + +**TL;DR:** Scope determines where variables are accessible in your code. Closures are functions that remember and can access variables from their birth scope, even after that scope has finished executing. + + + +**What you'll learn in this guide:** +- The 3 types of scope: global, function, and block +- How `var`, `let`, and `const` behave differently +- What lexical scope means and how the scope chain works +- What closures are and why every JavaScript developer must understand them +- Practical patterns: data privacy, factories, and memoization +- The classic closure gotchas and how to avoid them + + +--- + +## Scope: Who Can See What? + +**Scope** is the set of rules that determines where and how variables can be accessed in your code. Think of it as the "visibility" or "reach" of a variable. + +### Why Does Scope Exist? + +Scope exists for three critical reasons: + + + + Without scope, every variable would be global. Imagine the chaos if every `i` in every `for` loop had to have a unique name! + + ```javascript + function countApples() { + let count = 0; // This 'count' is separate... + // ... + } + + function countOranges() { + let count = 0; // ...from this 'count' + // ... + } + ``` + + + + When a scope ends, variables declared in that scope can be garbage collected (cleaned up from memory). This keeps your program efficient. + + ```javascript + function processData() { + let hugeArray = new Array(1000000); // Takes memory + // ... process it + } // hugeArray can now be garbage collected + ``` + + + + Scope allows you to hide implementation details and protect data from being accidentally modified. + + ```javascript + function createBankAccount() { + let balance = 0; // Private! Can't be accessed directly + + return { + deposit(amount) { balance += amount; }, + getBalance() { return balance; } + }; + } + ``` + + + +--- + +## The Three Types of Scope + +JavaScript has three main types of scope. Understanding each one is fundamental to writing predictable code. + +### 1. Global Scope + +Variables declared outside of any function or block are in the **global scope**. They're accessible from anywhere in your code. + +```javascript +// Global scope +const appName = "MyApp"; +let userCount = 0; + +function greet() { + console.log(appName); // ✓ Can access global variable + userCount++; // ✓ Can modify global variable +} + +if (true) { + console.log(appName); // ✓ Can access global variable +} +``` + +#### The Global Object + +In browsers, global variables become properties of the `window` object. In Node.js, they attach to `global`. The modern, universal way to access the global object is `globalThis`. + +```javascript +var oldSchool = "I'm on window"; // window.oldSchool (var only) +let modern = "I'm NOT on window"; // NOT on window + +console.log(window.oldSchool); // "I'm on window" +console.log(window.modern); // undefined +console.log(globalThis); // Works everywhere +``` + + +**Avoid Global Pollution!** Too many global variables lead to naming conflicts, hard-to-track bugs, and code that's difficult to maintain. Keep your global scope clean. + +```javascript +// Bad: Polluting global scope +var userData = {}; +var settings = {}; +var helpers = {}; + +// Good: Use a single namespace +const MyApp = { + userData: {}, + settings: {}, + helpers: {} +}; +``` + + +--- + +### 2. Function Scope + +Variables declared with `var` inside a function are **function-scoped** — they're only accessible within that function. + +```javascript +function calculateTotal() { + var subtotal = 100; + var tax = 10; + var total = subtotal + tax; + + console.log(total); // ✓ 110 +} + +calculateTotal(); +// console.log(subtotal); // ✗ ReferenceError: subtotal is not defined +``` + +#### var Hoisting + +Variables declared with `var` are "hoisted" to the top of their function. This means JavaScript knows about them before the code runs, but they're initialized as `undefined` until the actual declaration line. + +```javascript +function example() { + console.log(message); // undefined (not an error!) + var message = "Hello"; + console.log(message); // "Hello" +} + +// JavaScript interprets this as: +function exampleHoisted() { + var message; // Declaration hoisted to top + console.log(message); // undefined + message = "Hello"; // Assignment stays in place + console.log(message); // "Hello" +} +``` + + +**Hoisting Visualization:** + +``` +Your code: How JS sees it: +┌─────────────────────┐ ┌─────────────────────┐ +│ function foo() { │ │ function foo() { │ +│ │ │ var x; // hoisted│ +│ console.log(x); │ ──► │ console.log(x); │ +│ var x = 5; │ │ x = 5; │ +│ } │ │ } │ +└─────────────────────┘ └─────────────────────┘ +``` + + +--- + +### 3. Block Scope + +Variables declared with `let` and `const` are **block-scoped**. A block is any code within curly braces `{}`: if statements, for loops, while loops, or just standalone blocks. + +```javascript +if (true) { + let blockLet = "I'm block-scoped"; + const blockConst = "Me too"; + var functionVar = "I escape the block!"; +} + +// console.log(blockLet); // ✗ ReferenceError +// console.log(blockConst); // ✗ ReferenceError +console.log(functionVar); // ✓ "I escape the block!" +``` + +#### The Temporal Dead Zone (TDZ) + +Unlike `var`, variables declared with `let` and `const` are not initialized until their declaration is evaluated. Accessing them before declaration causes a `ReferenceError`. This period is called the **Temporal Dead Zone**. + +```javascript +function demo() { + // TDZ for 'name' starts here + + console.log(name); // ReferenceError: Cannot access 'name' before initialization + + let name = "Alice"; // TDZ ends here + + console.log(name); // "Alice" +} +``` + +``` +┌────────────────────────────────────────────────────────────┐ +│ │ +│ function demo() { │ +│ │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ TEMPORAL DEAD ZONE │ │ +│ │ │ │ +│ │ 'name' exists but cannot be accessed yet! │ │ +│ │ │ │ +│ │ console.log(name); // ReferenceError │ │ +│ │ │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ let name = "Alice"; // TDZ ends here │ +│ │ +│ console.log(name); // "Alice" - works fine! │ +│ │ +│ } │ +│ │ +└────────────────────────────────────────────────────────────┘ +``` + + +The TDZ exists to catch programming errors. It's actually a good thing! It prevents you from accidentally using variables before they're ready. + + +--- + +## var vs let vs const + +Here's a comprehensive comparison of the three variable declaration keywords: + +| Feature | `var` | `let` | `const` | +|---------|-------|-------|---------| +| **Scope** | Function | Block | Block | +| **Hoisting** | Yes (initialized as `undefined`) | Yes (but TDZ) | Yes (but TDZ) | +| **Redeclaration** | ✓ Allowed | ✗ Error | ✗ Error | +| **Reassignment** | ✓ Allowed | ✓ Allowed | ✗ Error | +| **Must Initialize** | No | No | Yes | + + + + ```javascript + // var allows redeclaration (can cause bugs!) + var name = "Alice"; + var name = "Bob"; // No error, silently overwrites + console.log(name); // "Bob" + + // let and const prevent redeclaration + let age = 25; + // let age = 30; // SyntaxError: 'age' has already been declared + + const PI = 3.14; + // const PI = 3.14159; // SyntaxError + ``` + + + ```javascript + // var and let allow reassignment + var count = 1; + count = 2; // ✓ Fine + + let score = 100; + score = 200; // ✓ Fine + + // const prevents reassignment + const API_KEY = "abc123"; + // API_KEY = "xyz789"; // TypeError: Assignment to constant variable + + // BUT: const objects/arrays CAN be mutated! + const user = { name: "Alice" }; + user.name = "Bob"; // ✓ This works! + user.age = 25; // ✓ This works too! + // user = {}; // ✗ This fails (reassignment) + ``` + + + ```javascript + function hoistingDemo() { + // var: hoisted and initialized as undefined + console.log(a); // undefined + var a = 1; + + // let: hoisted but NOT initialized (TDZ) + // console.log(b); // ReferenceError! + let b = 2; + + // const: same as let + // console.log(c); // ReferenceError! + const c = 3; + } + ``` + + + +### The Classic for-loop Problem + +This is one of the most common JavaScript gotchas, and it perfectly illustrates why `let` is preferred over `var`: + +```javascript +// The Problem: var is function-scoped +for (var i = 0; i < 3; i++) { + setTimeout(() => { + console.log(i); + }, 100); +} +// Output: 3, 3, 3 (not 0, 1, 2!) + +// Why? There's only ONE 'i' variable shared across all iterations. +// By the time the setTimeout callbacks run, the loop has finished and i === 3. +``` + +```javascript +// The Solution: let is block-scoped +for (let i = 0; i < 3; i++) { + setTimeout(() => { + console.log(i); + }, 100); +} +// Output: 0, 1, 2 (correct!) + +// Why? Each iteration gets its OWN 'i' variable. +// Each setTimeout callback closes over a different 'i'. +``` + + +**Modern Best Practice:** +1. Use `const` by default +2. Use `let` when you need to reassign +3. Avoid `var` entirely (legacy code only) + +This approach catches bugs at compile time and makes your intent clear. + + +--- + +## Lexical Scope + +**Lexical scope** (also called **static scope**) means that the scope of a variable is determined by its position in the source code, not by how functions are called at runtime. + +```javascript +const outer = "I'm outside!"; + +function outerFunction() { + const middle = "I'm in the middle!"; + + function innerFunction() { + const inner = "I'm inside!"; + + // innerFunction can access all three variables + console.log(inner); // ✓ Own scope + console.log(middle); // ✓ Parent scope + console.log(outer); // ✓ Global scope + } + + innerFunction(); + // console.log(inner); // ✗ ReferenceError +} + +outerFunction(); +// console.log(middle); // ✗ ReferenceError +``` + +### The Scope Chain + +When JavaScript needs to find a variable, it walks up the **scope chain** — starting from the current scope and moving outward until it finds the variable or reaches the global scope. + + + + JavaScript first checks if the variable exists in the current function/block scope. + + + + If not found, it checks the enclosing (parent) scope. + + + + This process continues up through all ancestor scopes. + + + + Finally, it checks the global scope. If still not found, a `ReferenceError` is thrown. + + + +``` +Variable Lookup: Where is 'x'? + +┌─────────────────────────────────────────────────┐ +│ Global Scope │ +│ x = "global" │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ outer() Scope │ │ +│ │ x = "outer" │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────┐ │ │ +│ │ │ inner() Scope │ │ │ +│ │ │ │ │ │ +│ │ │ console.log(x); │ │ │ +│ │ │ │ │ │ │ +│ │ │ ▼ │ │ │ +│ │ │ 1. Check inner() → not found │ │ │ +│ │ │ │ │ │ │ +│ │ └─────────│───────────────────────┘ │ │ +│ │ ▼ │ │ +│ │ 2. Check outer() → FOUND! "outer" │ │ +│ │ │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────┘ + +Result: "outer" +``` + +### Variable Shadowing + +When an inner scope declares a variable with the same name as an outer scope, the inner variable "shadows" the outer one: + +```javascript +const name = "Global"; + +function greet() { + const name = "Function"; // Shadows global 'name' + + if (true) { + const name = "Block"; // Shadows function 'name' + console.log(name); // "Block" + } + + console.log(name); // "Function" +} + +greet(); +console.log(name); // "Global" +``` + + +Shadowing can be confusing. While sometimes intentional, accidental shadowing is a common source of bugs. Many linters warn about this. + + +--- + +## Closures: Functions with Memories + +Now we arrive at one of JavaScript's most powerful features. A **closure** is created when a function "remembers" the variables from its lexical scope, even when that function is executed outside that scope. + +Remember our office building analogy? A closure is like someone who worked in the private office, left the building, but still remembers exactly where everything was — and can still use that knowledge! + + +**The simplest definition:** A closure is a function that has access to variables from its outer (enclosing) scope, even after the outer function has returned. + + +### Every Function Creates a Closure + +In JavaScript, closures are created automatically every time you create a function. The function maintains a reference to its lexical environment. + +```javascript +function createGreeter(greeting) { + // 'greeting' is in createGreeter's scope + + return function(name) { + // This inner function is a closure! + // It "closes over" the 'greeting' variable + console.log(`${greeting}, ${name}!`); + }; +} + +const sayHello = createGreeter("Hello"); +const sayHola = createGreeter("Hola"); + +// createGreeter has finished executing, but... +sayHello("Alice"); // "Hello, Alice!" +sayHola("Bob"); // "Hola, Bob!" + +// The inner functions still remember their 'greeting' values! +``` + +### How Closures Work: Step by Step + + + + `createGreeter("Hello")` is called. A new execution context is created with `greeting = "Hello"`. + + + + The inner function is created. It captures a reference to the current lexical environment (which includes `greeting`). + + + + `createGreeter` returns the inner function and its execution context is (normally) cleaned up. + + + + Because the inner function holds a reference to the lexical environment, the `greeting` variable is NOT garbage collected. It survives! + + + + When `sayHello("Alice")` is called, the function can still access `greeting` through its closure. + + + +``` +After createGreeter("Hello") returns: + +┌──────────────────────────────────────┐ +│ sayHello (Function) │ +├──────────────────────────────────────┤ +│ [[Code]]: function(name) {...} │ +│ │ +│ [[Environment]]: ────────────────────────┐ +└──────────────────────────────────────┘ │ + ▼ + ┌────────────────────────────┐ + │ Lexical Environment │ + │ (Kept alive by closure!) │ + ├────────────────────────────┤ + │ greeting: "Hello" │ + └────────────────────────────┘ +``` + +--- + +## Closures in the Wild + +Closures aren't just a theoretical concept — you'll use them every day. Here are the patterns that make closures so powerful. + +### 1. Data Privacy & Encapsulation + +Closures let you create truly private variables in JavaScript: + +```javascript +function createCounter() { + let count = 0; // Private variable - no way to access directly! + + return { + increment() { + count++; + return count; + }, + decrement() { + count--; + return count; + }, + getCount() { + return count; + } + }; +} + +const counter = createCounter(); + +console.log(counter.getCount()); // 0 +console.log(counter.increment()); // 1 +console.log(counter.increment()); // 2 +console.log(counter.decrement()); // 1 + +// There's NO way to access 'count' directly! +console.log(counter.count); // undefined +``` + + +This pattern is the foundation of the **Module Pattern**, widely used before ES6 modules became available. It's still useful for creating private state. + + +### 2. Function Factories + +Closures let you create specialized functions on the fly: + +```javascript +function createMultiplier(multiplier) { + return function(number) { + return number * multiplier; + }; +} + +const double = createMultiplier(2); +const triple = createMultiplier(3); +const tenX = createMultiplier(10); + +console.log(double(5)); // 10 +console.log(triple(5)); // 15 +console.log(tenX(5)); // 50 + +// Each function "remembers" its own multiplier +``` + +```javascript +// Real-world example: API request factories +function createApiClient(baseUrl) { + return { + get(endpoint) { + return fetch(`${baseUrl}${endpoint}`); + }, + post(endpoint, data) { + return fetch(`${baseUrl}${endpoint}`, { + method: 'POST', + body: JSON.stringify(data) + }); + } + }; +} + +const githubApi = createApiClient('https://api.github.com'); +const myApi = createApiClient('https://myapp.com/api'); + +// Each client remembers its baseUrl +githubApi.get('/users/leonardomso'); +myApi.get('/users/1'); +``` + +### 3. Preserving State in Callbacks & Event Handlers + +Closures are essential for maintaining state in asynchronous code: + +```javascript +function setupClickCounter(buttonId) { + let clicks = 0; // This variable persists across clicks! + + const button = document.getElementById(buttonId); + + button.addEventListener('click', function() { + clicks++; + console.log(`Button clicked ${clicks} time${clicks === 1 ? '' : 's'}`); + }); +} + +setupClickCounter('myButton'); +// Each click increments the same 'clicks' variable +// Click 1: "Button clicked 1 time" +// Click 2: "Button clicked 2 times" +// Click 3: "Button clicked 3 times" +``` + +### 4. Memoization (Caching Results) + +Closures enable efficient caching of expensive computations: + +```javascript +function createMemoizedFunction(fn) { + const cache = {}; // Cache persists across calls! + + return function(arg) { + if (arg in cache) { + console.log('Returning cached result'); + return cache[arg]; + } + + console.log('Computing result'); + const result = fn(arg); + cache[arg] = result; + return result; + }; +} + +// Expensive operation: calculate factorial +function factorial(n) { + if (n <= 1) return 1; + return n * factorial(n - 1); +} + +const memoizedFactorial = createMemoizedFunction(factorial); + +console.log(memoizedFactorial(5)); // Computing result → 120 +console.log(memoizedFactorial(5)); // Returning cached result → 120 +console.log(memoizedFactorial(5)); // Returning cached result → 120 +``` + +--- + +## The Trap Everyone Falls Into + +We mentioned this earlier, but it's so important it deserves its own section. This is the #1 closure interview question — and almost everyone gets it wrong the first time: + +### The Problem + +```javascript +// What does this print? +for (var i = 0; i < 3; i++) { + setTimeout(function() { + console.log(i); + }, 1000); +} + +// Most people expect: 0, 1, 2 +// Actual output: 3, 3, 3 +``` + +### Why Does This Happen? + +``` +What actually happens: + + TIME ════════════════════════════════════════════════════► + + ┌─────────────────────────────────────────────────────────┐ + │ IMMEDIATELY (milliseconds): │ + │ │ + │ Loop iteration 1: i = 0, schedule callback │ + │ Loop iteration 2: i = 1, schedule callback │ + │ Loop iteration 3: i = 2, schedule callback │ + │ Loop ends: i = 3 │ + │ │ + │ All 3 callbacks point to the SAME 'i' variable ──┐ │ + └─────────────────────────────────────────────────────│───┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────┐ + │ ~1 SECOND LATER: │ + │ │ + │ callback1 runs: "What's i?" → i is 3 → prints 3 │ + │ callback2 runs: "What's i?" → i is 3 → prints 3 │ + │ callback3 runs: "What's i?" → i is 3 → prints 3 │ + │ │ + └─────────────────────────────────────────────────────────┘ + + Result: 3, 3, 3 (not 0, 1, 2!) +``` + +### The Solutions + + + + The simplest modern solution — `let` creates a new binding for each iteration: + + ```javascript + for (let i = 0; i < 3; i++) { + setTimeout(function() { + console.log(i); + }, 1000); + } + // Output: 0, 1, 2 ✓ + ``` + + + Pre-ES6 solution using an Immediately Invoked Function Expression: + + ```javascript + for (var i = 0; i < 3; i++) { + (function(j) { + setTimeout(function() { + console.log(j); + }, 1000); + })(i); // Pass i as argument, creating a new 'j' each time + } + // Output: 0, 1, 2 ✓ + ``` + + + Using array methods, which naturally create new scope per iteration: + + ```javascript + [0, 1, 2].forEach(function(i) { + setTimeout(function() { + console.log(i); + }, 1000); + }); + // Output: 0, 1, 2 ✓ + ``` + + + +--- + +## With Great Power Comes Great Responsibility + +Closures are powerful, but they come with responsibility. Since closures keep references to their outer scope variables, those variables can't be garbage collected. + +### Potential Memory Leaks + +```javascript +function createHeavyClosure() { + const hugeData = new Array(1000000).fill('x'); // Large data + + return function() { + // Even if we don't use hugeData, it's still retained! + console.log('Hello'); + }; +} + +const leakyFunction = createHeavyClosure(); +// hugeData is still in memory because the closure might reference it +``` + +### Breaking Closure References + +When you're done with a closure, explicitly break the reference: + +```javascript +function setupHandler(element) { + // Imagine this returns a large dataset + const largeData = { users: new Array(10000).fill({ name: 'User' }) }; + + const handler = function() { + console.log(`Processing ${largeData.users.length} users`); + }; + + element.addEventListener('click', handler); + + // Return a cleanup function + return function cleanup() { + element.removeEventListener('click', handler); + // Now handler and largeData can be garbage collected + }; +} + +const button = document.getElementById('myButton'); +const cleanup = setupHandler(button); + +// Later, when you're done with this functionality: +cleanup(); // Removes listener, allows memory to be freed +``` + + +**Best Practices:** +1. Don't capture more than you need in closures +2. Set closure references to `null` when done +3. Remove event listeners when components unmount +4. Be especially careful in loops and long-lived applications + + +--- + +## Key Takeaways + + +**Remember these essential points about Scope & Closures:** + +1. **Scope = Variable Visibility** — It determines where variables can be accessed + +2. **Three types of scope**: Global (everywhere), Function (`var`), Block (`let`/`const`) + +3. **Lexical scope is static** — Determined by code position, not runtime behavior + +4. **Scope chain** — JavaScript looks up variables from inner to outer scope + +5. **`let` and `const` are block-scoped** — Prefer them over `var` + +6. **Temporal Dead Zone** — `let`/`const` can't be accessed before declaration + +7. **Closure = Function + Its Lexical Environment** — Functions "remember" where they were created + +8. **Closures enable**: Data privacy, function factories, stateful callbacks, memoization + +9. **Watch for the loop gotcha** — Use `let` instead of `var` in loops with async callbacks + +10. **Mind memory** — Closures keep references alive; clean up when done + + +--- + +## Test Your Knowledge + + + + **Answer:** + + 1. **Global Scope** — Variables declared outside any function or block; accessible everywhere + 2. **Function Scope** — Variables declared with `var` inside a function; accessible only within that function + 3. **Block Scope** — Variables declared with `let` or `const` inside a block `{}`; accessible only within that block + + ```javascript + const global = "everywhere"; // Global scope + + function example() { + var functionScoped = "function"; // Function scope + + if (true) { + let blockScoped = "block"; // Block scope + } + } + ``` + + + + **Answer:** The Temporal Dead Zone (TDZ) is the period between entering a scope and the actual declaration of a `let` or `const` variable. During this time, the variable exists but cannot be accessed — doing so throws a `ReferenceError`. + + ```javascript + function example() { + // TDZ starts for 'x' + console.log(x); // ReferenceError! + // TDZ continues... + let x = 10; // TDZ ends + console.log(x); // 10 ✓ + } + ``` + + The TDZ helps catch bugs where you accidentally use variables before they're initialized. + + + + **Answer:** Lexical scope (also called static scope) means that the accessibility of variables is determined by their physical position in the source code at write time, not by how or where functions are called at runtime. + + Inner functions have access to variables declared in their outer functions because of where they are written, not because of when they're invoked. + + ```javascript + function outer() { + const message = "Hello"; + + function inner() { + console.log(message); // Can access 'message' because of lexical scope + } + + return inner; + } + + const fn = outer(); + fn(); // "Hello" — still works even though outer() has returned + ``` + + + + **Answer:** A closure is a function combined with references to its surrounding lexical environment. In simpler terms, a closure is a function that "remembers" the variables from the scope where it was created, even when executed outside that scope. + + ```javascript + function createCounter() { + let count = 0; // This variable is "enclosed" in the closure + + return function() { + count++; + return count; + }; + } + + const counter = createCounter(); + console.log(counter()); // 1 + console.log(counter()); // 2 + // 'count' persists because of the closure + ``` + + Every function in JavaScript creates a closure. The term usually refers to situations where this behavior is notably useful or surprising. + + + + ```javascript + for (var i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); + } + ``` + + **Answer:** It outputs `3, 3, 3`. + + **Why?** Because `var` is function-scoped (not block-scoped), there's only ONE `i` variable shared across all iterations. By the time the `setTimeout` callbacks execute (after ~100ms), the loop has already completed and `i` equals `3`. + + **Fix:** Use `let` instead of `var`: + ```javascript + for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); + } + // Outputs: 0, 1, 2 + ``` + + With `let`, each iteration gets its own `i` variable, and each callback closes over a different value. + + + + **Answer:** Common practical uses for closures include: + + 1. **Data Privacy** — Creating private variables that can't be accessed directly: + ```javascript + function createWallet(initial) { + let balance = initial; + return { + spend(amount) { balance -= amount; }, + getBalance() { return balance; } + }; + } + ``` + + 2. **Function Factories** — Creating specialized functions: + ```javascript + function createTaxCalculator(rate) { + return (amount) => amount * rate; + } + const calculateVAT = createTaxCalculator(0.20); + ``` + + 3. **Maintaining State in Callbacks** — Event handlers, timers, API calls: + ```javascript + function setupLogger(prefix) { + return (message) => console.log(`[${prefix}] ${message}`); + } + ``` + + 4. **Memoization/Caching** — Storing computed results: + ```javascript + function memoize(fn) { + const cache = {}; + return (arg) => cache[arg] ?? (cache[arg] = fn(arg)); + } + ``` + + + +--- + +## Related Concepts + + + + Patterns that leverage scope for encapsulation + + + Understanding execution context alongside scope + + + Functions that return functions often create closures + + + Advanced patterns built on closures + + + +--- + +## Reference + + + + Official MDN documentation on closures + + + In-depth tutorial on closures and lexical environment + + + +## Books + + + The definitive deep-dive into JavaScript scope and closures. Free to read online. This book will transform your understanding of how JavaScript really works. + + +## Articles + + + + Clear FreeCodeCamp guide comparing the three variable declaration keywords with practical examples. + + + Zell Liew's comprehensive CSS-Tricks article covering both scope and closures in one excellent resource. + + + Dan Abramov's clear, concise explanation of closures. Perfect for the "aha moment." + + + Olivier De Meulder's article that has helped countless developers finally understand closures. + + + Joseph Cardillo's focused explanation of how var differs from let and const in terms of scope. + + + Brian Barbour's practical guide showing how closures enable powerful caching patterns. + + + +## Courses + + + Free preview of Anthony Alicea's acclaimed course. Excellent coverage of scope, closures, and execution contexts. + + +## Videos + + + + Codesmith's in-depth exploration of how scope and closures really work under the hood. + + + Akshay Saini's popular Namaste JavaScript episode with clear visual explanations. + + + Mattias Petter Johansson's entertaining and educational take on closures. + + + Web Dev Simplified's concise, beginner-friendly closure explanation. + + diff --git a/docs/concepts/scope.mdx b/docs/concepts/scope.mdx deleted file mode 100644 index aba7ef13..00000000 --- a/docs/concepts/scope.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Function Scope, Block Scope and Lexical Scope" -description: "Understanding variable scope in JavaScript" ---- - -## Overview - -The ECMAScript specification outlines three key types of scope: - - - - Variables declared within a function using `var` are only accessible within that function. This scope isolates variables from being accessed outside of the function where they are declared. - - - Introduced with ES6, variables declared with `let` and `const` are block-scoped. This means they are only accessible within the specific block `{}` in which they are defined, such as inside loops or conditionals. - - - Refers to how variable access is determined based on the physical location of the variables in the code. Functions are lexically scoped, meaning that they can access variables from their parent scope. - - - -## Books - - - By Kyle Simpson - - -## Articles - - - - By Brandon Morelli - - - By FreeCodeCamp - - - -- [Functions in JavaScript - Deepa Pandey](https://www.scaler.com/topics/javascript/javascript-functions/) -- [Emulating Block Scope in JavaScript — Josh Clanton](http://adripofjavascript.com/blog/drips/emulating-block-scope-in-javascript.html) -- [The Difference Between Function and Block Scope in JavaScript — Joseph Cardillo](https://medium.com/@josephcardillo/the-difference-between-function-and-block-scope-in-javascript-4296b2322abe) -- [Understanding Scope and Context in JavaScript — Ryan Morr](http://ryanmorr.com/understanding-scope-and-context-in-javascript/) -- [JavaScript Scope and Closures — Zell Liew](https://css-tricks.com/javascript-scope-closures/) -- [Understanding Scope in JavaScript — Wissam Abirached](https://developer.telerik.com/topics/web-development/understanding-scope-in-javascript/) -- [Understanding Scope in JavaScript ― Hammad Ahmed](https://scotch.io/tutorials/understanding-scope-in-javascript) -- [When to use a function declaration vs. a function expression ― Amber Wilkie](https://medium.freecodecamp.org/when-to-use-a-function-declarations-vs-a-function-expression-70f15152a0a0) -- [A JavaScript Fundamentals Cheat Sheet: Scope, Context, and "this" ― Alexandra Fren](https://dev.to/alexandrafren/a-javascript-fundamentals-cheat-sheet-scope-context-and-this-28ai) -- [Functions / Function scope ― MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#function_scope) - -## Videos - - - - By LearnCode.academy - - - By Kirupa Chinnathambi - - - -- [JavaScript Block Scope and Function Scope — mmtuts](https://www.youtube.com/watch?v=aK_nuUAdr8E) -- [What the Heck is Lexical Scope? — NWCalvank](https://www.youtube.com/watch?v=GhNA0r10MmA) -- [Variable Scope — Steve Griffith](https://www.youtube.com/watch?v=FyWdrCZZavQ) -- [Javascript Tutorials for Beginners — Mosh Hemadani](https://www.youtube.com/watch?v=W6NZfCO5SIk) -- [JavaScript Block scope vs Function scope - nivek](https://www.youtube.com/watch?v=IaTztAtoNEY) -- [Lexical scoping in javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=qT5S7GgIioE) -- [Modern Scope Handling in JavaScript (ES6 and Beyond) - Prashant Dewangan](https://www.youtube.com/watch?v=zMseUdOR7z8) diff --git a/docs/docs.json b/docs/docs.json index 96847e98..f632d670 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -39,7 +39,7 @@ "concepts/value-reference-types", "concepts/type-coercion", "concepts/equality-operators", - "concepts/scope" + "concepts/scope-and-closures" ] }, { @@ -78,7 +78,6 @@ "pages": [ "concepts/map-reduce-filter", "concepts/pure-functions", - "concepts/closures", "concepts/higher-order-functions", "concepts/recursion" ] From 1148fbbd01823a8eb1cb42a65a90a6a3cd3bcb3a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 20:30:51 -0300 Subject: [PATCH 010/128] feat(equality-operators): add comprehensive guide with simplified language - Rename title to 'Equality and Type Checking' - Replace bouncer analogy with universal teacher grading analogy - Add complete Abstract Equality Algorithm with step-by-step traces - Cover ==, ===, Object.is(), and typeof in depth - Include surprising results gallery with coercion examples - Add decision flowchart and best practices guide - Simplify language for global accessibility - Remove culturally-specific idioms and colloquialisms - Add curated articles, videos, and references - Include test your knowledge section with 7 questions --- docs/concepts/equality-operators.mdx | 1448 +++++++++++++++++++++++++- 1 file changed, 1417 insertions(+), 31 deletions(-) diff --git a/docs/concepts/equality-operators.mdx b/docs/concepts/equality-operators.mdx index db345897..0fad546b 100644 --- a/docs/concepts/equality-operators.mdx +++ b/docs/concepts/equality-operators.mdx @@ -1,56 +1,1442 @@ --- -title: "== vs === vs typeof" -description: "Understanding equality operators and type checking in JavaScript" +title: "Equality and Type Checking" +description: "How JavaScript compares values — and why it sometimes gets confused" --- -## Overview +## The Teacher Grading Papers: A Real-World Analogy -According to the ECMAScript specification, JavaScript includes both strict (`===`) and loose (`==`) equality operators, which behave differently when comparing values. +Imagine a teacher grading a math test. The question asks: "What is 2 + 2?" + +One student writes: `4` +Another student writes: `"4"` (as text) +A third student writes: `4.0` + +How strict should the teacher be when grading? + +``` + RELAXED GRADING (==) STRICT GRADING (===) + "Is the answer correct?" "Is it exactly right?" + + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ + │ 4 │ = │ "4" │ │ 4 │ ≠ │ "4" │ + │ (number) │ │ (string) │ │ (number) │ │ (string) │ + └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ │ + └────────┬────────┘ └────────┬────────┘ + ▼ ▼ + "Close enough!" ✓ "Different types!" ✗ +``` + +JavaScript gives you both types of teachers: + +- **Loose equality (`==`)** — The relaxed teacher. Accepts `4` and `"4"` as the same answer because the *meaning* is similar. Converts values to match before comparing. +- **Strict equality (`===`)** — The strict teacher. Only accepts the *exact* answer in the *exact* format. The number `4` and the string `"4"` are different answers. +- **`typeof`** — Asks "What kind of answer is this?" Is it a number? A string? Something else? +- **`Object.is()`** — The most precise teacher. Even stricter than `===` — can spot tiny differences that others miss. + +Understanding these comparison tools is important because comparison bugs are very common in JavaScript. This guide will help you write code that works as expected and debug problems faster. + + +**What you'll learn in this guide:** +- The difference between `==` and `===` (and when to use each) +- How JavaScript converts values when comparing them +- The `typeof` operator and its quirks +- When to use `Object.is()` for special cases +- Common mistakes and how to avoid them + + +--- + +## The Three Equality Operators: Overview + +JavaScript provides three ways to compare values for equality. Here's the quick summary: + +| Operator | Name | Type Coercion | Best For | +|----------|------|---------------|----------| +| `==` | Loose (Abstract) Equality | Yes | Checking `null`/`undefined` only | +| `===` | Strict Equality | No | **Default choice for everything** | +| `Object.is()` | Same-Value Equality | No | Edge cases (`NaN`, `±0`) | + +```javascript +// The same comparison, three different results +const num = 1; +const str = "1"; + +console.log(num == str); // true (coerces string to number) +console.log(num === str); // false (different types) +console.log(Object.is(num, str)); // false (different types) +``` + + +**The simple rule:** Always use `===` for comparisons. The only exception: use `== null` to check if a value is empty (null or undefined). You'll rarely need `Object.is()` — it's for special cases we'll cover later. + + +--- + +## Loose Equality (`==`): The Relaxed Comparison + +The `==` operator tries to be helpful. Before comparing two values, it converts them to the same type. This automatic conversion is called **type coercion**. + +For example, if you compare the number `5` with the string `"5"`, JavaScript thinks: "These look similar. Let me convert them and check." So `5 == "5"` returns `true`. + +### How It Works + +When you write `x == y`, JavaScript asks: + +1. Are `x` and `y` the same type? → Compare them directly +2. Are they different types? → Convert one or both to match, then compare + +This automatic conversion can be helpful, but it can also cause unexpected results. + +### The Abstract Equality Comparison Algorithm + +Here's the complete algorithm from the ECMAScript specification. When comparing `x == y`: + + + + If `x` and `y` are the same type, perform strict equality comparison (`===`). + + ```javascript + 5 == 5 // Same type (number), compare directly → true + "hello" == "hello" // Same type (string), compare directly → true + ``` + + + + If `x` is `null` and `y` is `undefined` (or vice versa), return `true`. + + ```javascript + null == undefined // true (special case!) + undefined == null // true + ``` + + + + If one is a Number and the other is a String, convert the String to a Number. + + ```javascript + 5 == "5" // "5" → 5, then 5 == 5 → true + 0 == "" // "" → 0, then 0 == 0 → true + 42 == "42" // "42" → 42, then 42 == 42 → true + ``` + + + + If one is a BigInt and the other is a String, convert the String to a BigInt. + + ```javascript + 10n == "10" // "10" → 10n, then 10n == 10n → true + ``` + + + + If either value is a Boolean, convert it to a Number (`true` → `1`, `false` → `0`). + + ```javascript + true == 1 // true → 1, then 1 == 1 → true + false == 0 // false → 0, then 0 == 0 → true + true == "1" // true → 1, then 1 == "1" → 1 == 1 → true + ``` + + + + If one is an Object and the other is a String, Number, BigInt, or Symbol, convert the Object to a primitive using `ToPrimitive`. + + ```javascript + [1] == 1 // [1] → "1" → 1, then 1 == 1 → true + [""] == 0 // [""] → "" → 0, then 0 == 0 → true + ``` + + + + If one is a BigInt and the other is a Number, compare their mathematical values. + + ```javascript + 10n == 10 // Compare values: 10 == 10 → true + 10n == 10.5 // 10 !== 10.5 → false + ``` + + + + If none of the above rules apply, return `false`. + + ```javascript + null == 0 // false (null only equals undefined) + undefined == 0 // false + Symbol() == Symbol() // false (symbols are unique) + ``` + + + +### Visual: The Coercion Decision Tree + +``` + x == y + │ + ┌────────────┴────────────┐ + ▼ ▼ + Same type? Different types? + │ │ + YES YES + │ │ + ▼ ▼ + Compare values ┌────────┴────────┐ + (like ===) │ │ + ▼ ▼ + null == undefined? Apply coercion + │ rules above + YES │ + │ ▼ + ▼ Convert types + true then compare + again +``` + +### The Complete Coercion Rules Table + +| Type of x | Type of y | Coercion Applied | +|-----------|-----------|------------------| +| Number | String | `ToNumber(y)` — String becomes Number | +| String | Number | `ToNumber(x)` — String becomes Number | +| BigInt | String | `ToBigInt(y)` — String becomes BigInt | +| String | BigInt | `ToBigInt(x)` — String becomes BigInt | +| Boolean | Any | `ToNumber(x)` — Boolean becomes Number (0 or 1) | +| Any | Boolean | `ToNumber(y)` — Boolean becomes Number (0 or 1) | +| Object | String/Number/BigInt/Symbol | `ToPrimitive(x)` — Object becomes primitive | +| String/Number/BigInt/Symbol | Object | `ToPrimitive(y)` — Object becomes primitive | +| BigInt | Number | Compare mathematical values directly | +| Number | BigInt | Compare mathematical values directly | +| null | undefined | `true` (special case) | +| undefined | null | `true` (special case) | +| null | Any (except undefined) | `false` | +| undefined | Any (except null) | `false` | + +### Surprising Results Gallery + +Here are some comparison results that surprise most developers. Understanding *why* these happen will help you avoid bugs in your code: + + + + ```javascript + // String converted to Number + 1 == "1" // true ("1" → 1) + 0 == "" // true ("" → 0) + 0 == "0" // true ("0" → 0) + 100 == "1e2" // true ("1e2" → 100) + + // But string-to-string is direct comparison + "" == "0" // false (both strings, different values) + + // NaN conversions + NaN == "NaN" // false (NaN ≠ anything, including itself) + 0 == "hello" // false ("hello" → NaN, 0 ≠ NaN) + ``` + + + + ```javascript + // Booleans become 0 or 1 FIRST + true == 1 // true (true → 1) + false == 0 // true (false → 0) + true == "1" // true (true → 1, "1" → 1) + false == "" // true (false → 0, "" → 0) + + // This is why these are confusing: + true == "true" // false! (true → 1, "true" → NaN) + false == "false" // false! (false → 0, "false" → NaN) + + // And these seem wrong: + true == 2 // false (true → 1, 1 ≠ 2) + true == "2" // false (true → 1, "2" → 2, 1 ≠ 2) + ``` + + + **Common trap:** `true == "true"` is `false`! The boolean `true` becomes `1`, and the string `"true"` becomes `NaN`. Since `1 ≠ NaN`, the result is `false`. + + + + + ```javascript + // The special relationship + null == undefined // true (special rule!) + undefined == null // true + + // But they don't equal anything else + null == 0 // false + null == false // false + null == "" // false + undefined == 0 // false + undefined == false // false + undefined == "" // false + + // This is actually useful! + let value = null; + if (value == null) { + // Catches both null AND undefined + console.log("Value is nullish"); + } + ``` + + + This is the ONE legitimate use case for `==`. Using `value == null` checks for both `null` and `undefined` in a single comparison. + + + + + ```javascript + // Arrays convert via ToPrimitive (usually toString) + [] == false // true ([] → "" → 0, false → 0) + [] == 0 // true ([] → "" → 0) + [] == "" // true ([] → "") + [1] == 1 // true ([1] → "1" → 1) + [1] == "1" // true ([1] → "1") + [1,2] == "1,2" // true ([1,2] → "1,2") + + // Empty array gotcha + [] == ![] // true! (see explanation below) + + // Objects with valueOf/toString + let obj = { valueOf: () => 42 }; + obj == 42 // true (obj.valueOf() → 42) + ``` + + + +### Step-by-Step Trace: `[] == ![]` + +This is one of JavaScript's most surprising results. An empty array `[]` equals `![]`? Let's break down why this happens step by step: + + + + First, JavaScript evaluates `![]`. + - `[]` is truthy (all objects are truthy) + - `![]` therefore equals `false` + - Now we have: `[] == false` + + + + One side is a Boolean, so convert it to a Number. + - `false` → `0` + - Now we have: `[] == 0` + + + + One side is an Object, so convert it via ToPrimitive. + - `[]` → `""` (empty array's toString returns empty string) + - Now we have: `"" == 0` + + + + One side is a String and one is a Number, so convert the String. + - `""` → `0` (empty string becomes 0) + - Now we have: `0 == 0` + + + + Both sides are Numbers with the same value. + - `0 == 0` → `true` + + + +```javascript +// The chain of conversions: +[] == ![] +[] == false // ![] → false +[] == 0 // false → 0 +"" == 0 // [] → "" +0 == 0 // "" → 0 +true // 0 equals 0! +``` + + +This example shows why `==` can produce unexpected results. An empty array appears to equal its own negation! This isn't a bug — it's how JavaScript's conversion rules work. This is why most developers prefer `===`. + + +### When `==` Might Be Useful + +Despite its quirks, there's one legitimate use case for loose equality: + +```javascript +// Checking for null OR undefined in one comparison +function greet(name) { + // Using == (the one acceptable use case!) + if (name == null) { + return "Hello, stranger!"; + } + return `Hello, ${name}!`; +} + +// Both null and undefined are caught +greet(null); // "Hello, stranger!" +greet(undefined); // "Hello, stranger!" +greet("Alice"); // "Hello, Alice!" +greet(""); // "Hello, !" (empty string is NOT null) +greet(0); // "Hello, 0!" (0 is NOT null) +``` + +This is equivalent to the more verbose: +```javascript +function greet(name) { + if (name === null || name === undefined) { + return "Hello, stranger!"; + } + return `Hello, ${name}!`; +} +``` + + +Many style guides (including those from Airbnb and StandardJS) make an exception for `value == null` because it's a clean way to check for "nullish" values. However, you can also use the nullish coalescing operator (`??`) or optional chaining (`?.`) introduced in ES2020. + + +--- + +## Strict Equality (`===`): The Reliable Choice + +The strict equality operator compares two values **without** any conversion. If the types are different, it immediately returns `false`. + +This is the operator you should use almost always. It's simple and predictable: the number `1` and the string `"1"` are different types, so `1 === "1"` returns `false`. No surprises. + +### How It Works + +When you write `x === y`, JavaScript asks: + +1. Are `x` and `y` the same type? No → return `false` +2. Same type? → Compare their values + +That's it. No conversions, no surprises (well, *almost* — there's one special case with `NaN`). + +### The Strict Equality Comparison Algorithm + + + + If `x` and `y` are different types, return `false` immediately. + + ```javascript + 1 === "1" // false (number vs string) + true === 1 // false (boolean vs number) + null === undefined // false (null vs undefined) + ``` + + + + If both are Numbers: + - If either is `NaN`, return `false` + - If both are the same numeric value, return `true` + - `+0` and `-0` are considered equal + + ```javascript + 42 === 42 // true + NaN === NaN // false (!) + +0 === -0 // true + Infinity === Infinity // true + ``` + + + + If both are Strings, return `true` if they have the same characters in the same order. + + ```javascript + "hello" === "hello" // true + "hello" === "Hello" // false (case sensitive) + "hello" === "hello " // false (different length) + ``` + + + + If both are Booleans, return `true` if they're both `true` or both `false`. + + ```javascript + true === true // true + false === false // true + true === false // false + ``` + + + + If both are BigInts, return `true` if they have the same mathematical value. + + ```javascript + 10n === 10n // true + 10n === 20n // false + ``` + + + + If both are Symbols, return `true` only if they are the exact same Symbol. + + ```javascript + const sym = Symbol("id"); + sym === sym // true + Symbol("id") === Symbol("id") // false (different symbols!) + ``` + + + + If both are Objects (including Arrays and Functions), return `true` only if they are the **same object** (same reference in memory). + + ```javascript + const obj = { a: 1 }; + obj === obj // true (same reference) + { a: 1 } === { a: 1 } // false (different objects!) + [] === [] // false (different arrays!) + ``` + + + + `null === null` returns `true`. `undefined === undefined` returns `true`. But `null === undefined` returns `false` (different types). + + ```javascript + null === null // true + undefined === undefined // true + null === undefined // false + ``` + + + +### Visual: Strict Equality Flowchart + +``` + x === y + │ + ┌───────────────┴───────────────┐ + │ Same type? │ + └───────────────┬───────────────┘ + │ │ + NO YES + │ │ + ▼ ▼ + false Both NaN? + │ + ┌───────┴───────┐ + YES NO + │ │ + ▼ ▼ + false Same value? + (NaN never equals │ + anything!) ┌───────┴───────┐ + YES NO + │ │ + ▼ ▼ + true false +``` + +### The Predictable Results + +With `===`, what you see is what you get: + +```javascript +// All of these are false (different types) +1 === "1" // false +0 === "" // false +true === 1 // false +false === 0 // false +null === undefined // false +[] === "" // false + +// All of these are true (same type, same value) +1 === 1 // true +"hello" === "hello" // true +true === true // true +null === null // true +undefined === undefined // true +``` + +### Special Cases: Two Exceptions to Know + +Even `===` has two edge cases that might surprise you: + + + + ```javascript + // NaN is the only value that is not equal to itself + NaN === NaN // false! + + // NaN doesn't equal anything — not even itself! + // This is part of how numbers work in all programming languages + + // This is by design (IEEE 754 specification) + // NaN represents "Not a Number" - an undefined result + // Since it's not a specific number, it can't equal anything + + // How to check for NaN: + Number.isNaN(NaN) // true (recommended) + isNaN(NaN) // true (but has quirks) + Object.is(NaN, NaN) // true (ES6) + + // The isNaN() quirk: + isNaN("hello") // true! (converts to NaN first) + Number.isNaN("hello") // false (no conversion) + ``` + + + Always use `Number.isNaN()` instead of `isNaN()`. The global `isNaN()` function converts its argument to a Number first, which means `isNaN("hello")` returns `true`. That's rarely what you want. + + + + + ```javascript + // Positive zero and negative zero are considered equal + +0 === -0 // true + -0 === 0 // true + + // But they ARE different! Watch this: + 1 / +0 // Infinity + 1 / -0 // -Infinity + + // Two zeros, two different infinities. Math is wild. + + // How to distinguish them: + Object.is(+0, -0) // false (ES6) + 1 / +0 === 1 / -0 // false (Infinity vs -Infinity) + + // When does -0 appear? + 0 * -1 // -0 + Math.sign(-0) // -0 + JSON.parse("-0") // -0 + ``` + + You'll rarely need to tell `+0` and `-0` apart unless you're doing advanced math or physics calculations. + + + +### Object Comparison: Reference vs Value + +This is one of the most important concepts to understand: + +```javascript +// Objects are compared by REFERENCE, not by value +const obj1 = { name: "Alice" }; +const obj2 = { name: "Alice" }; +const obj3 = obj1; + +obj1 === obj2 // false (different objects in memory) +obj1 === obj3 // true (same reference) + +// Same with arrays +const arr1 = [1, 2, 3]; +const arr2 = [1, 2, 3]; +const arr3 = arr1; + +arr1 === arr2 // false (different arrays) +arr1 === arr3 // true (same reference) + +// And functions +const fn1 = () => {}; +const fn2 = () => {}; +const fn3 = fn1; + +fn1 === fn2 // false (different functions) +fn1 === fn3 // true (same reference) +``` + +``` +MEMORY VISUALIZATION: + +obj1 ──────┐ + ├──► { name: "Alice" } (Object A) +obj3 ──────┘ + +obj2 ──────────► { name: "Alice" } (Object B) + +obj1 === obj3 → true (both point to Object A) +obj1 === obj2 → false (different objects, even with same content) +``` + + +To compare objects by their content (deep equality), you need to: +- Use `JSON.stringify()` for simple objects (has limitations) +- Write a recursive comparison function +- Use a library like Lodash's `_.isEqual()` + + +--- + +## `Object.is()`: Same-Value Equality + +ES6 introduced `Object.is()` to fix the two edge cases where `===` gives unexpected results. It works exactly like `===`, but handles `NaN` and `-0` correctly. + +### Why It Exists + +```javascript +// The two cases where === is "wrong" +NaN === NaN // false (but NaN IS NaN!) ++0 === -0 // true (but they ARE different!) + +// Object.is() fixes both +Object.is(NaN, NaN) // true ✓ +Object.is(+0, -0) // false ✓ +``` + +### How It Differs from `===` + +`Object.is()` behaves exactly like `===` except for these two cases: + +| Expression | `===` | `Object.is()` | +|------------|-------|---------------| +| `NaN, NaN` | `false` | `true` | +| `+0, -0` | `true` | `false` | +| `-0, 0` | `true` | `false` | +| `1, 1` | `true` | `true` | +| `"a", "a"` | `true` | `true` | +| `null, null` | `true` | `true` | +| `{}, {}` | `false` | `false` | + +### Complete Comparison Table + +| Values | `==` | `===` | `Object.is()` | +|--------|------|-------|---------------| +| `1, "1"` | `true` | `false` | `false` | +| `0, false` | `true` | `false` | `false` | +| `null, undefined` | `true` | `false` | `false` | +| `NaN, NaN` | `false` | `false` | `true` | +| `+0, -0` | `true` | `true` | `false` | +| `[], []` | `false` | `false` | `false` | +| `{}, {}` | `false` | `false` | `false` | + +### When to Use `Object.is()` + +```javascript +// 1. Checking for NaN (alternative to Number.isNaN) +function isReallyNaN(value) { + return Object.is(value, NaN); +} + +// 2. Distinguishing +0 from -0 (rare, but needed in math/physics) +function isNegativeZero(value) { + return Object.is(value, -0); +} + +// 3. Implementing SameValue comparison (like in Map/Set) +// Maps use SameValueZero (like Object.is but +0 === -0) +const map = new Map(); +map.set(NaN, "value"); +map.get(NaN); // "value" (NaN works as a key!) + +// 4. Library code and polyfills +// When you need exact specification compliance +``` + + +For most everyday code, you won't need `Object.is()`. Use `===` as your default, and reach for `Object.is()` only when you specifically need to handle `NaN` or `±0` edge cases. + + +--- + +## The `typeof` Operator + +The `typeof` operator tells you what type a value is. It returns a string like `"number"`, `"string"`, or `"boolean"`. It's very useful, but it has some famous quirks that surprise many developers. + +### How It Works + +```javascript +typeof operand +typeof(operand) // Both forms are valid +``` + +### Complete Results Table + +| Value | `typeof` Result | Notes | +|-------|-----------------|-------| +| `"hello"` | `"string"` | | +| `42` | `"number"` | Includes `Infinity`, `NaN` | +| `42n` | `"bigint"` | ES2020 | +| `true` / `false` | `"boolean"` | | +| `undefined` | `"undefined"` | | +| `Symbol()` | `"symbol"` | ES6 | +| `null` | `"object"` | **Famous bug!** | +| `{}` | `"object"` | | +| `[]` | `"object"` | Arrays are objects | +| `function(){}` | `"function"` | Special case | +| `class {}` | `"function"` | Classes are functions | +| `new Date()` | `"object"` | | +| `/regex/` | `"object"` | | + +### The Famous Quirks - - This operator performs type coercion before comparing two values. If the values are of different types, JavaScript will attempt to convert one or both values to a common type before comparison, which can lead to unexpected results. + + ```javascript + typeof null // "object" — Wait, what?! + ``` + + **Why?** This is a bug from JavaScript's first version in 1995. In the original code, values were stored with a type tag. Objects had the tag `000`, and `null` was represented as `0x00`, which also matched the object tag. + + **Why wasn't it fixed?** Too much existing code depends on this behavior. Changing it now would break millions of websites. So we have to work around it. + + **Workaround:** + ```javascript + // Always check for null explicitly + function getType(value) { + if (value === null) return "null"; + return typeof value; + } + + // Or check for "real" objects + if (value !== null && typeof value === "object") { + // It's definitely an object (not null) + } + ``` - - This operator compares both the value and the type without any type coercion. If the two values are not of the same type, the comparison will return false. + + + ```javascript + typeof [] // "object" + typeof [1, 2, 3] // "object" + typeof new Array() // "object" + ``` + + **Why?** Arrays ARE objects in JavaScript. They inherit from `Object.prototype` and have special behavior for numeric keys and the `length` property, but they're still objects. + + **How to check for arrays:** + ```javascript + Array.isArray([]) // true (recommended) + Array.isArray({}) // false + Array.isArray("hello") // false + + // Or using Object.prototype.toString + Object.prototype.toString.call([]) // "[object Array]" + ``` - - The `typeof` operator is used to check the data type of a variable. While it's generally reliable, there are certain quirks, like how `typeof null` returns "object" instead of "null", due to a long-standing behavior in JavaScript's implementation. + + + ```javascript + typeof function() {} // "function" + typeof (() => {}) // "function" + typeof class {} // "function" + typeof Math.sin // "function" + ``` + + **Why is this different?** Functions are technically objects too, but `typeof` treats them specially because checking for "callable" values is so common. This is actually convenient! + + ```javascript + // This makes checking for functions easy + if (typeof callback === "function") { + callback(); + } + ``` + + + + ```javascript + // Referencing an undeclared variable throws an error + console.log(undeclaredVar); // ReferenceError! + + // But typeof on an undeclared variable returns "undefined" + typeof undeclaredVar // "undefined" (no error!) + ``` + + **Why?** This was a design decision to allow safe feature detection: + + ```javascript + // Safe way to check if a global exists + if (typeof jQuery !== "undefined") { + // jQuery is available + } + + // vs. this would throw if jQuery doesn't exist + if (jQuery !== undefined) { + // ReferenceError if jQuery not defined! + } + ``` + + + In modern JavaScript with modules and bundlers, this pattern is less necessary. But it's still useful for checking global variables and browser features. + + + + + ```javascript + typeof NaN // "number" + ``` + + "Not a Number" has a typeof of `"number"`. This sounds strange, but `NaN` is actually a special value in the number system. It represents a calculation that doesn't have a valid result, like `0 / 0`. + + ```javascript + // These all produce NaN + 0 / 0 // NaN + parseInt("hello") // NaN + Math.sqrt(-1) // NaN + + // Check for NaN properly + Number.isNaN(NaN) // true + ``` -## Articles +### Better Alternatives for Type Checking + +Since `typeof` has limitations, here are more reliable approaches: + + + + ```javascript + // Arrays + Array.isArray(value) // true for arrays only + + // NaN + Number.isNaN(value) // true for NaN only (no coercion) + + // Finite numbers + Number.isFinite(value) // true for finite numbers + + // Integers + Number.isInteger(value) // true for integers + + // Safe integers + Number.isSafeInteger(value) // true for safe integers + ``` + + + + ```javascript + // Check if an object is an instance of a constructor + [] instanceof Array // true + {} instanceof Object // true + new Date() instanceof Date // true + /regex/ instanceof RegExp // true + + // Works with custom classes + class Person {} + const p = new Person(); + p instanceof Person // true + + // Caveat: doesn't work across iframes/realms + // The Array in iframe A is different from Array in iframe B + ``` + + + + The most reliable method for getting precise type information: + + ```javascript + const getType = (value) => + Object.prototype.toString.call(value).slice(8, -1); + + getType(null) // "Null" + getType(undefined) // "Undefined" + getType([]) // "Array" + getType({}) // "Object" + getType(new Date()) // "Date" + getType(/regex/) // "RegExp" + getType(new Map()) // "Map" + getType(new Set()) // "Set" + getType(Promise.resolve()) // "Promise" + getType(function(){}) // "Function" + getType(42) // "Number" + getType("hello") // "String" + getType(Symbol()) // "Symbol" + getType(42n) // "BigInt" + ``` + + + + A comprehensive type-checking utility: + + ```javascript + function getType(value) { + // Handle null specially (typeof bug) + if (value === null) return "null"; + + // Handle primitives + const type = typeof value; + if (type !== "object" && type !== "function") { + return type; + } + + // Handle objects with Object.prototype.toString + const tag = Object.prototype.toString.call(value); + return tag.slice(8, -1).toLowerCase(); + } + + // Usage + getType(null) // "null" + getType([]) // "array" + getType({}) // "object" + getType(new Date()) // "date" + getType(/regex/) // "regexp" + getType(new Map()) // "map" + getType(Promise.resolve()) // "promise" + ``` + + + +--- + +## Decision Guide: Which to Use? + +### The Simple Rule + + +**Default to `===`** for all comparisons. It's predictable, doesn't perform type coercion, and will save you from countless bugs. + +The only exception: Use `== null` to check for both `null` and `undefined` in one comparison. + + +### Decision Flowchart + +``` + Need to compare two values? + │ + ▼ + ┌───────────────────────────────┐ + │ Checking for null/undefined? │ + └───────────────────────────────┘ + │ │ + YES NO + │ │ + ▼ ▼ + ┌──────────┐ ┌───────────────────┐ + │ == null │ │ Need NaN or ±0? │ + └──────────┘ └───────────────────┘ + │ │ + YES NO + │ │ + ▼ ▼ + ┌──────────┐ ┌─────────┐ + │Object.is │ │ === │ + │ or │ └─────────┘ + │Number. │ + │ isNaN() │ + └──────────┘ +``` + +### Quick Reference + +| Scenario | Use | Example | +|----------|-----|---------| +| Default comparison | `===` | `if (x === 5)` | +| Check nullish | `== null` | `if (value == null)` | +| Check NaN | `Number.isNaN()` | `if (Number.isNaN(x))` | +| Check array | `Array.isArray()` | `if (Array.isArray(x))` | +| Check type | `typeof` | `if (typeof x === "string")` | +| Distinguish ±0 | `Object.is()` | `Object.is(x, -0)` | + +### ESLint Configuration + +Most style guides enforce `===` with an exception for null checks: + +```javascript +// .eslintrc.js +module.exports = { + rules: { + // Require === and !== except for null comparisons + "eqeqeq": ["error", "always", { "null": "ignore" }] + } +}; +``` + +This allows: +```javascript +// Allowed +if (value === 5) { } // Using === +if (value == null) { } // Exception for null + +// Error +if (value == 5) { } // Should use === +``` + +--- + +## Common Gotchas and Mistakes + +These common mistakes trip up many JavaScript developers. Learning about them now will save you debugging time later: + + + + **The mistake:** + ```javascript + const user1 = { name: "Alice" }; + const user2 = { name: "Alice" }; + + if (user1 === user2) { + console.log("Same user!"); // Never runs! + } + ``` + + **Why it's wrong:** Objects are compared by reference, not by value. Two objects with identical content are still different objects. + + **The fix:** + ```javascript + // Option 1: Compare specific properties + if (user1.name === user2.name) { + console.log("Same name!"); + } + + // Option 2: JSON.stringify (simple objects only) + if (JSON.stringify(user1) === JSON.stringify(user2)) { + console.log("Same content!"); + } + + // Option 3: Deep equality function or library + import { isEqual } from 'lodash'; + if (isEqual(user1, user2)) { + console.log("Same content!"); + } + ``` + + + + **The mistake:** + ```javascript + // These all behave unexpectedly + if ([] == false) { } // true! (but [] is truthy) + if ("0" == false) { } // true! (but "0" is truthy) + if (" " == false) { } // false (but " " is truthy) + ``` + + **Why it's confusing:** The `==` operator doesn't check truthiness — it performs type coercion according to specific rules. + + **The fix:** + ```javascript + // Use === for explicit comparisons + if (value === false) { } // Only true for actual false + + // Or check truthiness directly + if (!value) { } // Falsy check + if (value) { } // Truthy check + + // For explicit boolean conversion + if (Boolean(value) === false) { } + ``` + + + + **The mistake:** + ```javascript + const result = parseInt("hello"); + + if (result === NaN) { + console.log("Not a number!"); // Never runs! + } + ``` + + **Why it's wrong:** `NaN` is never equal to anything, including itself. + + **The fix:** + ```javascript + // Use Number.isNaN() + if (Number.isNaN(result)) { + console.log("Not a number!"); // Works! + } + + // Or Object.is() + if (Object.is(result, NaN)) { + console.log("Not a number!"); // Works! + } + + // Avoid isNaN() - it coerces first + isNaN("hello") // true (coerces to NaN) + Number.isNaN("hello") // false (no coercion) + ``` + + + + **The mistake:** + ```javascript + function processObject(obj) { + if (typeof obj === "object") { + // Might be null! + console.log(obj.property); // TypeError if null! + } + } + + processObject(null); // Crashes! + ``` + + **Why it's wrong:** `typeof null === "object"` is true due to a historical bug. + + **The fix:** + ```javascript + function processObject(obj) { + // Check for null AND typeof + if (obj !== null && typeof obj === "object") { + console.log(obj.property); + } + + // Or use optional chaining (ES2020) + console.log(obj?.property); + } + ``` + + + + **The mistake:** + ```javascript + // Comparing numbers as strings + console.log("10" > "9"); // false! (string comparison) + + // Why? Strings compare character by character + // "1" (code 49) < "9" (code 57) + ``` + + **Why it's wrong:** String comparison uses lexicographic order (like a dictionary), not numeric value. + + **The fix:** + ```javascript + // Convert to numbers first + console.log(Number("10") > Number("9")); // true + console.log(+"10" > +"9"); // true (unary +) + console.log(parseInt("10") > parseInt("9")); // true + + // For sorting arrays of number strings + ["10", "9", "2"].sort((a, b) => a - b); // ["2", "9", "10"] + ``` + + + + **The mistake:** + ```javascript + const arr = []; + + // These seem contradictory + console.log(arr == false); // true + console.log(arr ? "yes" : "no"); // "yes" + + // So arr equals false but is truthy?! + ``` + + **Why it's confusing:** `==` uses type coercion (`[] → "" → 0`), but truthiness just checks if the value is truthy (all objects are truthy). + + **The fix:** + ```javascript + // Check array length for "emptiness" + if (arr.length === 0) { + console.log("Array is empty"); + } + + // Or use the array itself as a boolean + // (but remember, empty array is truthy!) + if (!arr.length) { + console.log("Array is empty"); + } + ``` + + + +--- + +## Key Takeaways + + +**Key Points to Remember:** + +1. **Use `===` by default** — It's predictable and doesn't convert types + +2. **`==` converts types first** — This leads to unexpected results like `"0" == false` being `true` + +3. **Only use `==` for null checks** — `value == null` checks for both `null` and `undefined` + +4. **`NaN !== NaN`** — NaN doesn't equal anything, not even itself. Use `Number.isNaN()` to check for it + +5. **Objects compare by reference** — `{} === {}` is `false` because they're different objects in memory + +6. **`typeof null === "object"`** — This is a bug that can't be fixed. Always check for `null` directly + +7. **`Object.is()` for edge cases** — Use it when you need to check for `NaN` or distinguish `+0` from `-0` + +8. **Arrays return `"object"` from typeof** — Use `Array.isArray()` to check for arrays + +9. **These rules are commonly asked in interviews** — Now you're prepared! + +10. **Configure ESLint** — Use the `eqeqeq` rule to enforce `===` in your projects + + +--- + +## Test Your Knowledge + +Try to answer each question before revealing the solution: + + + + **Answer:** `true` + + **Step-by-step:** + 1. `![]` → `false` (arrays are truthy, so negation makes false) + 2. `[] == false` → `[] == 0` (boolean converts to number) + 3. `[] == 0` → `"" == 0` (array converts to empty string) + 4. `"" == 0` → `0 == 0` (string converts to number) + 5. `0 == 0` → `true` + + This is why understanding type conversion is so important! + + + + **Answer:** This is a bug from JavaScript's original implementation in 1995. + + In the original C code, values were represented with a type tag. Objects had the tag `000`, and `null` was represented as the NULL pointer (`0x00`), which also matched the `000` tag for objects. + + This bug was never fixed because too much existing code depends on this behavior. A proposal to fix it was rejected for backward compatibility reasons. + + **Workaround:** Always check for null explicitly: `value === null` + + + + **Answer:** Use `Number.isNaN()`: + + ```javascript + Number.isNaN(NaN) // true + Number.isNaN("hello") // false (no coercion) + Number.isNaN(undefined) // false + ``` + + **Avoid** the global `isNaN()` because it coerces its argument first: + + ```javascript + isNaN("hello") // true (coerces to NaN) + isNaN(undefined) // true (coerces to NaN) + ``` + + You can also use `Object.is(value, NaN)` which returns `true` for `NaN`. + + + + **Answer:** Checking for both `null` and `undefined` in a single comparison: + + ```javascript + // Using == + if (value == null) { + // value is null OR undefined + } + + // Equivalent to: + if (value === null || value === undefined) { + // value is null OR undefined + } + ``` + + This works because `null == undefined` is `true` (special case in the spec), but `null` and `undefined` don't loosely equal anything else (not even `0`, `""`, or `false`). + + + + **Answer:** Objects are compared by **reference**, not by **value**. + + When you write `{}`, JavaScript creates a new object in memory. When you write another `{}`, it creates a completely different object. Even though they have the same content (both are empty objects), they are stored at different memory locations. + + ```javascript + const a = {}; + const b = {}; + const c = a; + + a === b // false (different objects) + a === c // true (same reference) + ``` + + To compare objects by content, you need to compare their properties or use a deep equality function. + + + + **Answer:** They behave identically except for two edge cases: + + | Expression | `===` | `Object.is()` | + |------------|-------|---------------| + | `NaN, NaN` | `false` | `true` | + | `+0, -0` | `true` | `false` | + + ```javascript + NaN === NaN // false + Object.is(NaN, NaN) // true + + +0 === -0 // true + Object.is(+0, -0) // false + ``` + + Use `===` for everyday comparisons. Use `Object.is()` when you specifically need to check for `NaN` equality or distinguish positive from negative zero. + + + + **Answer:** Use `Array.isArray()`: + + ```javascript + Array.isArray([]) // true + Array.isArray([1, 2, 3]) // true + Array.isArray(new Array()) // true + + Array.isArray({}) // false + Array.isArray("hello") // false + Array.isArray(null) // false + ``` + + **Why not `typeof`?** Because `typeof [] === "object"` — arrays are objects in JavaScript. + + **Why not `instanceof Array`?** It works in most cases, but can fail across different JavaScript realms (like iframes) where each realm has its own `Array` constructor. + + + +--- + +## Related Concepts + + + + Deep dive into how JavaScript converts between types automatically + + + Understanding JavaScript's fundamental data types + + + How primitives and objects are stored differently in memory + + + Understanding where variables are accessible in your code + + + +--- + +## Reference - - By Brandon Morelli + + Comprehensive official documentation covering ==, ===, Object.is(), and SameValue - - By Panu Pitkamaki + + Official documentation on the typeof operator and its behavior -- [Why Use the Triple-Equals Operator in JavaScript? — Louis Lazaris](https://www.impressivewebs.com/why-use-triple-equals-javascipt/) -- [What is the difference between == and === in JavaScript? — Craig Buckler](https://www.oreilly.com/learning/what-is-the-difference-between-and-in-javascript) -- [Why javascript's typeof always return "object"? — Stack Overflow](https://stackoverflow.com/questions/3787901/why-javascripts-typeof-always-return-object) -- [Checking Types in Javascript — Toby Ho](http://tobyho.com/2011/01/28/checking-types-in-javascript/) -- [How to better check data types in JavaScript — Webbjocke](https://webbjocke.com/javascript-check-data-types/) -- [Checking for the Absence of a Value in JavaScript — Tomer Aberbach](https://tomeraberba.ch/html/post/checking-for-the-absence-of-a-value-in-javascript.html) -- [Difference Between == and === in Javascript — Scaler](https://www.scaler.com/topics/javascript/difference-between-double-equals-and-triple-equals-in-javascript/) -- [Difference between == and === in JavaScript — GeeksforGeeks](https://www.geeksforgeeks.org/difference-between-double-equal-vs-triple-equal-javascript/) -- [=== vs == Comparision in JavaScript — FreeCodeCamp](https://www.freecodecamp.org/news/javascript-triple-equals-sign-vs-double-equals-sign-comparison-operators-explained-with-examples/) +## Articles + + + + Beginner-friendly explanation of the differences between == and === with clear examples and use cases. + + + Authoritative O'Reilly article explaining equality operators with practical guidance on when to use each. + + + Comprehensive guide with numerous examples showing how both operators behave in different scenarios. + + + In-depth exploration of typeof, instanceof, and alternative type-checking strategies. + + + Modern approaches to type checking with practical utility functions you can use in your projects. + + + Visual comparison table showing the results of == and === for all type combinations. Essential reference! + + ## Videos - - By Java Brains + + Clear, visual explanation of equality operators with practical examples. Perfect for visual learners. - - By Web Dev Simplified + + Comprehensive coverage of the typeof operator, its quirks, and when to use alternatives. + + + Popular explanation of equality operators with real-world coding examples. + + + Conference talk diving deep into JavaScript's equality quirks and type coercion weirdness. -- [Javascript typeof operator — DevDelight](https://www.youtube.com/watch?v=qPYhTPt_SbQ) -- [=== vs == in javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=a0S1iG3TgP0) -- [The typeof operator in JS - CodeVault](https://www.youtube.com/watch?v=NSS5WRcv7yM) +## Books + + + The definitive deep-dive into JavaScript types, coercion, and equality. Free to read online. Essential reading for truly understanding how JavaScript handles comparisons. + From 85ef042e7d197a855469d00f898c6187b7a9d7f6 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 20:47:14 -0300 Subject: [PATCH 011/128] feat(value-reference-types): add comprehensive guide with Object.freeze and memory management - Add sticky note vs map analogy with ASCII diagrams - Add detailed Stack vs Heap memory explanation - Add copying behavior section with visual diagrams - Add comparison behavior (by value vs by reference) - Add functions/parameters section (pass by value of reference) - Add mutation vs reassignment section with const trap - Add Object.freeze() section with shallow/deep freeze examples - Add related methods comparison (freeze vs seal vs preventExtensions) - Add shallow vs deep copy with structuredClone() - Fix structuredClone() limitations (remove incorrect Error mention) - Add WeakMap/memory leaks accordion (Advanced) - Add common bugs and pitfalls (7 items) - Add quiz section with 8 questions - Curate resources: 3 articles, 1 book, 3 videos --- docs/concepts/value-reference-types.mdx | 1229 ++++++++++++++++++++++- 1 file changed, 1206 insertions(+), 23 deletions(-) diff --git a/docs/concepts/value-reference-types.mdx b/docs/concepts/value-reference-types.mdx index 0a94dc9e..054f700e 100644 --- a/docs/concepts/value-reference-types.mdx +++ b/docs/concepts/value-reference-types.mdx @@ -1,47 +1,1230 @@ --- title: "Value Types and Reference Types" -description: "Understanding how data is stored and passed in JavaScript" +description: "Understanding how JavaScript stores and passes data in memory" --- -## Overview +## The Sticky Note vs The Map: A Real-World Analogy -According to the ECMAScript specification, **value types** are stored directly in the location that the variable accesses. These include types like number, string, boolean, undefined, bigint, symbol, and null. When you assign a value type to a variable, the value itself is stored. +Imagine you have two ways to share information with a friend: + +**Sticky Note (Value Types):** You write "42" on a sticky note and hand it to your friend. They now have their own note with "42" on it. If they change their note to "100", your note still says "42". You each have independent copies. + +**Map to Treasure (Reference Types):** Instead of giving your friend the treasure itself, you give them a map to where the treasure is buried. Now you BOTH have maps pointing to the SAME treasure. If they dig it up and add more gold, you'll see the extra gold too — because you're both looking at the same treasure! + +``` +VALUE TYPE (Sticky Note) REFERENCE TYPE (Map to Treasure) + +┌─────────────┐ ┌─────────────┐ +│ x = 42 │ │ x = ────────────────┐ +└─────────────┘ └─────────────┘ │ + ▼ +┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ +│ y = 42 │ (independent copy) │ y = ────────────►│ { name: "Alice" } │ +└─────────────┘ └─────────────┘ └──────────────────┘ + +Change y? Change the object via y? +x stays the same! x sees the change too! +``` + +This difference between "storing the value itself" vs "storing a map to the value" is one of the most important concepts in JavaScript — and the source of countless bugs for developers who don't understand it. + + +**TL;DR:** Primitives (numbers, strings, booleans, etc.) store the actual value — copying creates an independent copy. Objects and arrays store a *reference* (pointer) to the data — copying creates another pointer to the SAME data. + + + +**What you'll learn in this guide:** +- How JavaScript stores primitives vs objects in memory +- Why copying an object doesn't create a new object +- The difference between "pass by value" and "pass by reference" +- Why `{} === {}` returns `false` +- How to properly clone objects (shallow vs deep copy) +- Common bugs caused by reference sharing + + +--- + +## What Are Value Types and Reference Types? + +JavaScript has two categories of data types that behave very differently: + +### Value Types (Primitives) + +**The 7 primitive types** store their values directly: + +| Type | Example | Stored As | +|------|---------|-----------| +| `string` | `"hello"` | The actual characters | +| `number` | `42` | The actual number | +| `bigint` | `9007199254740993n` | The actual large integer | +| `boolean` | `true` | The actual boolean | +| `undefined` | `undefined` | The undefined value | +| `null` | `null` | The null value | +| `symbol` | `Symbol("id")` | The unique symbol | + +**Key characteristics:** +- Stored directly in the variable +- Immutable — you can't change them, only replace them +- Copied by value — copies are independent +- Compared by value — same value = equal + +### Reference Types + +**Everything else** is a reference type: + +| Type | Example | Stored As | +|------|---------|-----------| +| Object | `{ name: "Alice" }` | Reference to object | +| Array | `[1, 2, 3]` | Reference to array | +| Function | `function() {}` | Reference to function | +| Date | `new Date()` | Reference to date | +| RegExp | `/pattern/` | Reference to regex | +| Map | `new Map()` | Reference to map | +| Set | `new Set()` | Reference to set | + +**Key characteristics:** +- Variable stores a *reference* (pointer) to the actual data +- Mutable — you CAN change their contents +- Copied by reference — copies point to the SAME object +- Compared by reference — same reference = equal (not same contents!) + +--- + +## How Memory Works: Stack vs Heap + +To truly understand the difference, you need to know where JavaScript stores data. + +### The Stack: Home of Primitives + +The **stack** is a fast, organized region of memory. It stores: +- Primitive values +- References (pointers) to objects +- Function call information + +``` +THE STACK +┌────────────────────────────┐ +│ name = "Alice" │ ← Actual string value +├────────────────────────────┤ +│ age = 25 │ ← Actual number value +├────────────────────────────┤ +│ isActive = true │ ← Actual boolean value +├────────────────────────────┤ +│ user = 0x7F3A ──────┼──── Points to heap +└────────────────────────────┘ + Fixed size, fast access +``` + +### The Heap: Home of Objects + +The **heap** is a larger, less organized region for dynamic data: +- Objects +- Arrays +- Functions +- Anything that can grow or change size + +``` +THE HEAP +┌─────────────────────────────────────────────────┐ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 0x7F3A: │ │ +│ │ { │ │ +│ │ name: "Alice", │ │ +│ │ age: 25, │ │ +│ │ hobbies: ["reading", "gaming"] │ │ +│ │ } │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 0x8B2C: │ │ +│ │ [1, 2, 3, 4, 5] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────┘ + Dynamic size, slower access +``` + +### Putting It All Together + +When you create variables, here's what happens: + +```javascript +let name = "Alice"; // String stored on stack +let age = 25; // Number stored on stack +let user = { name: "Alice" }; // Reference on stack, object on heap +let scores = [95, 87, 92]; // Reference on stack, array on heap +``` + +``` +STACK HEAP +┌─────────────────────┐ ┌────────────────────────────┐ +│ name = "Alice" │ │ │ +├─────────────────────┤ │ ┌──────────────────────┐ │ +│ age = 25 │ │ │ { name: "Alice" } │ │ +├─────────────────────┤ │ └──────────────────────┘ │ +│ user = 0x001 ─────┼───────────┼──────────▲ │ +├─────────────────────┤ │ │ +│ scores = 0x002 ─────┼───────────┼───┐ ┌──────────────────┐ │ +└─────────────────────┘ │ └─►│ [95, 87, 92] │ │ + │ └──────────────────┘ │ + └────────────────────────────┘ +``` + +--- + +## Copying Behavior: The Critical Difference + +This is where things get interesting — and where bugs love to hide. + +### Copying Primitives: Independent Copies + +When you copy a primitive, you get a completely independent value: + +```javascript +let a = 10; +let b = a; // b gets a COPY of the value 10 + +b = 20; // changing b has NO effect on a + +console.log(a); // 10 (unchanged!) +console.log(b); // 20 +``` + +**What happens in memory:** + +``` +STEP 1: let a = 10 STEP 2: let b = a STEP 3: b = 20 + +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ a = 10 │ │ a = 10 │ │ a = 10 │ +└──────────────┘ ├──────────────┤ ├──────────────┤ + │ b = 10 │ (copy!) │ b = 20 │ + └──────────────┘ └──────────────┘ + + Two independent Changing b + copies of 10 doesn't touch a +``` + +### Copying Objects: Shared References + +When you copy an object, you copy the *reference* — both variables now point to the SAME object: + +```javascript +let obj1 = { name: "Alice" }; +let obj2 = obj1; // obj2 gets a COPY of the REFERENCE + +obj2.name = "Bob"; // modifies the SAME object! + +console.log(obj1.name); // "Bob" (changed!) +console.log(obj2.name); // "Bob" +``` + +**What happens in memory:** + +``` +STEP 1: Create obj1 STEP 2: let obj2 = obj1 + +STACK HEAP STACK HEAP +┌────────────┐ ┌──────────────────┐ ┌────────────┐ ┌──────────────────┐ +│obj1 = 0x01─┼─►│ { name: "Alice" }│ │obj1 = 0x01─┼──►│ { name: "Alice" }│ +└────────────┘ └──────────────────┘ ├────────────┤ │ │ + │obj2 = 0x01─┼──► (same object!) │ + └────────────┘ └──────────────────┘ + +STEP 3: obj2.name = "Bob" + +STACK HEAP +┌────────────┐ ┌──────────────────┐ +│obj1 = 0x01─┼──►│ { name: "Bob" } │ ← Both see this change! +├────────────┤ │ │ +│obj2 = 0x01─┼──► (same object!) │ +└────────────┘ └──────────────────┘ +``` + +### The Array Surprise + +Arrays are objects too, so they behave the same way: + +```javascript +let arr1 = [1, 2, 3]; +let arr2 = arr1; // arr2 points to the SAME array + +arr2.push(4); // modifies the shared array + +console.log(arr1); // [1, 2, 3, 4] — Wait, what?! +console.log(arr2); // [1, 2, 3, 4] +``` + + +**This catches EVERYONE at first!** When you write `let arr2 = arr1`, you're NOT creating a new array. You're creating a second variable that points to the same array. Any changes through either variable affect both. + + +--- + +## Comparison Behavior + +### Primitives: Compared by Value + +Two primitives are equal if they have the same value: + +```javascript +let a = "hello"; +let b = "hello"; +console.log(a === b); // true — same value + +let x = 42; +let y = 42; +console.log(x === y); // true — same value +``` + +### Objects: Compared by Reference + +Two objects are equal only if they are the SAME object (same reference): + +```javascript +let obj1 = { name: "Alice" }; +let obj2 = { name: "Alice" }; +console.log(obj1 === obj2); // false — different objects! + +let obj3 = obj1; +console.log(obj1 === obj3); // true — same reference +``` + +**Visual explanation:** + +``` +obj1 and obj2: Different objects, same contents + +STACK HEAP +┌────────────┐ ┌──────────────────┐ +│obj1 = 0x01─┼─►│ { name: "Alice" }│ ← Object A +├────────────┤ └──────────────────┘ +│obj2 = 0x02─┼─►┌──────────────────┐ +└────────────┘ │ { name: "Alice" }│ ← Object B (different!) + └──────────────────┘ + +obj1 === obj2? 0x01 === 0x02? FALSE! +``` + +``` +obj1 and obj3: Same object + +STACK HEAP +┌────────────┐ ┌──────────────────┐ +│obj1 = 0x01─┼──►│ { name: "Alice" }│ +├────────────┤ │ │ +│obj3 = 0x01─┼──► (same object!) │ +└────────────┘ └──────────────────┘ + +obj1 === obj3? 0x01 === 0x01? TRUE! +``` + +### The Empty Object/Array Trap + +```javascript +console.log({} === {}); // false — two different empty objects +console.log([] === []); // false — two different empty arrays +console.log([1,2] === [1,2]); // false — two different arrays +``` + + +**How to compare objects/arrays by content:** + +```javascript +// Simple (but limited) approach +JSON.stringify(obj1) === JSON.stringify(obj2) + +// For arrays of primitives +arr1.length === arr2.length && arr1.every((v, i) => v === arr2[i]) + +// For complex cases, use a library like Lodash +_.isEqual(obj1, obj2) +``` + + +--- + +## Functions and Parameters + +Here's a topic that confuses even experienced developers: + +**JavaScript is ALWAYS "pass by value"** — but when passing objects, the *value being passed is a reference*. + +### Passing Primitives + +When you pass a primitive to a function, the function receives a copy: + +```javascript +function double(num) { + num = num * 2; // changes the LOCAL copy only + return num; +} + +let x = 10; +let result = double(x); + +console.log(x); // 10 (unchanged!) +console.log(result); // 20 +``` + +**What happens:** + +``` +BEFORE double(x): INSIDE double(x): AFTER double(x): + +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ x = 10 │ │ x = 10 │ │ x = 10 │ +└──────────────┘ ├──────────────┤ └──────────────┘ + │ num = 10 │ (copy) + └──────────────┘ + ↓ + ┌──────────────┐ + │ num = 20 │ (modified copy) + └──────────────┘ +``` + +### Passing Objects: Mutation WORKS + +When you pass an object, the function receives a copy of the reference — both point to the same object: + +```javascript +function rename(person) { + person.name = "Bob"; // mutates the ORIGINAL object! +} + +let user = { name: "Alice" }; +rename(user); + +console.log(user.name); // "Bob" — changed! +``` + +**What happens:** + +``` +BEFORE rename(user): INSIDE rename(user): + +STACK HEAP STACK HEAP +┌────────────┐ ┌────────────┐ +│user = 0x01─┼─►{ name: │user = 0x01─┼──►{ name: "Bob" } +└────────────┘ "Alice" } ├────────────┤ ▲ + │person=0x01─┼───────┘ + └────────────┘ (same object!) +``` + +### Passing Objects: Reassignment DOESN'T Work + +But if you reassign the parameter, you're only changing the local copy of the reference: + +```javascript +function replace(person) { + person = { name: "Charlie" }; // creates NEW local object +} + +let user = { name: "Alice" }; +replace(user); + +console.log(user.name); // "Alice" — unchanged! +``` + +**What happens:** + +``` +INSIDE replace(user): + +STACK HEAP +┌────────────┐ ┌─────────────────┐ +│user = 0x01─┼─►│ { name: "Alice" }│ ← Original, unchanged +├────────────┤ └─────────────────┘ +│person=0x02─┼─►┌──────────────────┐ +└────────────┘ │ { name: "Charlie" }│ ← New object, discarded + └──────────────────┘ +``` -**Reference types**, on the other hand, are objects stored in the heap. Variables assigned to reference types actually store references (pointers) to the objects, not the objects themselves. When you assign a reference type to another variable, both variables reference the same object in memory. +**The key insight:** You can *mutate* an object through a function parameter, but you cannot *replace* the original object. Reassigning the parameter only changes where that local variable points. +--- + +## Mutation vs Reassignment + +Understanding this distinction is crucial for avoiding bugs. + +### Mutation: Changing the Contents + +Mutation modifies the existing object in place: + +```javascript +const arr = [1, 2, 3]; + +// These are all MUTATIONS: +arr.push(4); // [1, 2, 3, 4] +arr[0] = 99; // [99, 2, 3, 4] +arr.pop(); // [99, 2, 3] +arr.sort(); // modifies in place + +const obj = { name: "Alice" }; + +// These are all MUTATIONS: +obj.name = "Bob"; // changes property +obj.age = 25; // adds property +delete obj.age; // removes property +``` + +### Reassignment: Pointing to a New Value + +Reassignment makes the variable point to something else entirely: + +```javascript +let arr = [1, 2, 3]; +arr = [4, 5, 6]; // REASSIGNMENT — new array + +let obj = { name: "Alice" }; +obj = { name: "Bob" }; // REASSIGNMENT — new object +``` + +### The `const` Trap + +`const` prevents **reassignment** but NOT **mutation**: + +```javascript +const arr = [1, 2, 3]; + +// ✅ Mutations are ALLOWED: +arr.push(4); // works! +arr[0] = 99; // works! + +// ❌ Reassignment is BLOCKED: +arr = [4, 5, 6]; // TypeError: Assignment to constant variable + +const obj = { name: "Alice" }; + +// ✅ Mutations are ALLOWED: +obj.name = "Bob"; // works! +obj.age = 25; // works! + +// ❌ Reassignment is BLOCKED: +obj = { name: "Eve" }; // TypeError: Assignment to constant variable +``` + + +**Common misconception:** Many developers think `const` creates an "immutable" variable. It doesn't! It only prevents reassignment. The contents of objects and arrays declared with `const` can still be changed. + + +--- + +## True Immutability with Object.freeze() + +We learned that `const` doesn't make objects immutable — it only prevents reassignment. But what if you NEED a truly immutable object? + +### Object.freeze(): Shallow Immutability + +`Object.freeze()` prevents any changes to an object's properties: + +```javascript +const user = Object.freeze({ name: "Alice", age: 25 }); + +user.name = "Bob"; // Silently fails (or throws in strict mode) +user.email = "a@b.com"; // Can't add properties +delete user.age; // Can't delete properties + +console.log(user); // { name: "Alice", age: 25 } — unchanged! +``` + +**What Object.freeze() prevents:** +- Changing existing property values +- Adding new properties +- Deleting properties +- Changing property descriptors (like making a property writable) + +### Checking If an Object Is Frozen + +```javascript +const frozen = Object.freeze({ a: 1 }); +const normal = { a: 1 }; + +console.log(Object.isFrozen(frozen)); // true +console.log(Object.isFrozen(normal)); // false +``` + + +**Object.freeze() is shallow!** It only freezes the top level. Nested objects can still be modified: + +```javascript +const user = Object.freeze({ + name: "Alice", + address: { city: "NYC" } +}); + +user.name = "Bob"; // Blocked +user.address.city = "LA"; // Works! Nested object not frozen + +console.log(user.address.city); // "LA" +``` + + +### Deep Freeze: Making Everything Immutable + +To freeze nested objects too, you need a recursive "deep freeze" function: + +```javascript +function deepFreeze(obj) { + // Get all property names (including symbols) + const propNames = Reflect.ownKeys(obj); + + // Freeze nested objects first + for (const name of propNames) { + const value = obj[name]; + if (value && typeof value === "object") { + deepFreeze(value); + } + } + + // Then freeze the object itself + return Object.freeze(obj); +} + +const user = deepFreeze({ + name: "Alice", + address: { city: "NYC" } +}); + +user.address.city = "LA"; // Now this is blocked too! +console.log(user.address.city); // "NYC" +``` + +### Related Methods: freeze vs seal vs preventExtensions + +| Method | Add Properties | Delete Properties | Change Values | +|--------|:-------------:|:-----------------:|:-------------:| +| `Object.freeze()` | No | No | No | +| `Object.seal()` | No | No | **Yes** | +| `Object.preventExtensions()` | No | **Yes** | **Yes** | + +```javascript +// Object.seal() — can change values, but can't add/delete +const sealed = Object.seal({ name: "Alice" }); +sealed.name = "Bob"; // Works! +sealed.age = 25; // Fails — can't add +delete sealed.name; // Fails — can't delete + +// Object.preventExtensions() — can change/delete, but can't add +const noExtend = Object.preventExtensions({ name: "Alice" }); +noExtend.name = "Bob"; // Works! +delete noExtend.name; // Works! +noExtend.age = 25; // Fails — can't add +``` + + +**When to use Object.freeze():** +- Configuration objects that should never change +- Constants or enums in your application +- Protecting objects passed to untrusted code +- Debugging mutation bugs (freeze the object and see what breaks!) + + +--- + +## Shallow Copy vs Deep Copy + +When you need a truly independent copy of an object, you have two options. + +### Shallow Copy: One Level Deep + +A shallow copy creates a new object with copies of the top-level properties. But nested objects are still shared! + +**Shallow copy methods:** + +```javascript +const original = { + name: "Alice", + scores: [95, 87, 92], + address: { city: "NYC" } +}; + +// Method 1: Spread operator +const copy1 = { ...original }; + +// Method 2: Object.assign +const copy2 = Object.assign({}, original); + +// For arrays: +const arrCopy1 = [...originalArray]; +const arrCopy2 = originalArray.slice(); +const arrCopy3 = Array.from(originalArray); +``` + +**The problem with shallow copy:** + +```javascript +const original = { + name: "Alice", + address: { city: "NYC" } +}; + +const shallow = { ...original }; + +// Top-level changes are independent: +shallow.name = "Bob"; +console.log(original.name); // "Alice" ✅ + +// But nested objects are SHARED: +shallow.address.city = "LA"; +console.log(original.address.city); // "LA" 😱 +``` + +**Visual explanation:** + +``` +SHALLOW COPY + +original shallow +┌─────────────────┐ ┌─────────────────┐ +│ name: "Alice" │ │ name: "Alice" │ (independent copy) +│ address: 0x01 ──┼────┐ │ address: 0x01 ──┼────┐ +└─────────────────┘ │ └─────────────────┘ │ + │ │ + ▼ ▼ + ┌─────────────────┐ + │ { city: "NYC" } │ ← SHARED! Both point here + └─────────────────┘ +``` + +### Deep Copy: All Levels + +A deep copy creates completely independent copies at every level. + +**Method 1: `structuredClone()` (Recommended)** + +```javascript +const original = { + name: "Alice", + scores: [95, 87, 92], + address: { city: "NYC" }, + date: new Date() +}; + +const deep = structuredClone(original); + +// Now everything is independent: +deep.address.city = "LA"; +console.log(original.address.city); // "NYC" ✅ + +deep.scores.push(100); +console.log(original.scores); // [95, 87, 92] ✅ +``` + +**Method 2: JSON trick (has limitations)** + +```javascript +const deep = JSON.parse(JSON.stringify(original)); +``` + +### Comparing the Methods + + + + **Pros:** + - Handles most types correctly + - Preserves Dates, Maps, Sets, ArrayBuffers + - Handles circular references + - Built into JavaScript (ES2022+) + + **Cons:** + - Cannot clone functions + - Cannot clone DOM nodes + - Cannot clone property descriptors, getters/setters + - Cannot clone the prototype chain + - Symbol-keyed properties are ignored + - Not available in older browsers (pre-2022) + + ```javascript + const obj = { + date: new Date(), + set: new Set([1, 2, 3]), + map: new Map([["key", "value"]]) + }; + + const clone = structuredClone(obj); + // Works perfectly! ✅ + ``` + + + **Pros:** + - Works in all browsers + - Simple to use + + **Cons:** + - **Loses functions** — they disappear + - **Loses undefined** — properties with undefined are removed + - **Converts Dates to strings** — not Date objects + - **Cannot handle circular references** — throws error + - **Loses Symbol keys** — they're ignored + - **Loses Map/Set** — converted to empty objects + + ```javascript + const obj = { + fn: () => {}, // ❌ lost + date: new Date(), // ❌ becomes string + undef: undefined, // ❌ property removed + set: new Set([1, 2]) // ❌ becomes {} + }; + + const clone = JSON.parse(JSON.stringify(obj)); + // { date: "2025-12-29T..." } — Most data lost! + ``` + + + + +**Which to use:** +- **`structuredClone()`** — Use this for most cases (modern browsers) +- **JSON trick** — Only for simple objects with no functions, dates, or special types +- **Lodash `_.cloneDeep()`** — When you need maximum compatibility + + +--- + +## Common Bugs and Pitfalls + + + + ```javascript + // BUG: Modifying function parameter + function processUsers(users) { + users.push({ name: "New User" }); // Mutates original! + return users; + } + + const myUsers = [{ name: "Alice" }]; + processUsers(myUsers); + console.log(myUsers); // [{ name: "Alice" }, { name: "New User" }] + + // FIX: Create a copy first + function processUsers(users) { + const copy = [...users]; + copy.push({ name: "New User" }); + return copy; + } + ``` + + + + ```javascript + // These MUTATE the original array: + arr.push() arr.pop() + arr.shift() arr.unshift() + arr.splice() arr.sort() + arr.reverse() arr.fill() + + // These RETURN a new array (safe): + arr.map() arr.filter() + arr.slice() arr.concat() + arr.flat() arr.flatMap() + arr.toSorted() arr.toReversed() // ES2023 + arr.toSpliced() // ES2023 + + // GOTCHA: sort() mutates! + const nums = [3, 1, 2]; + const sorted = nums.sort(); // nums is NOW [1, 2, 3]! + + // FIX: Copy first, or use toSorted() + const sorted = [...nums].sort(); + const sorted = nums.toSorted(); // ES2023 + ``` + + + + ```javascript + // BUG: This will NEVER work + if (user1 === user2) { } // Compares references + if (arr1 === arr2) { } // Compares references + if (config === defaultConfig) { } // Compares references + + // Even these fail: + [] === [] // false + {} === {} // false + [1, 2] === [1, 2] // false + + // FIX: Compare contents + JSON.stringify(a) === JSON.stringify(b) // Simple but limited + + // Or use a deep equality function + function deepEqual(a, b) { + return JSON.stringify(a) === JSON.stringify(b); + } + ``` + + + + ```javascript + // BUG: Shallow copy doesn't clone nested objects + const user = { + name: "Alice", + settings: { theme: "dark" } + }; + + const copy = { ...user }; + copy.settings.theme = "light"; + + console.log(user.settings.theme); // "light" — Original changed! + + // FIX: Use deep copy + const copy = structuredClone(user); + ``` + + + + ```javascript + // BUG: Default object gets mutated across calls + function addItem(item, list = []) { + list.push(item); + return list; + } + + // This works fine with primitives, but with objects... + // Actually, this specific case is fine because default + // parameters are evaluated fresh each call. + + // But THIS is a problem: + const defaultList = []; + function addItem(item, list = defaultList) { + list.push(item); + return list; + } + + addItem("a"); // ["a"] + addItem("b"); // ["a", "b"] — defaultList was mutated! + ``` + + + + ```javascript + // BUG: Thinking you have two arrays + const original = [1, 2, 3]; + const backup = original; // NOT a backup! + + original.push(4); + console.log(backup); // [1, 2, 3, 4] — "backup" changed! + + // FIX: Actually copy the array + const backup = [...original]; + const backup = original.slice(); + ``` + + + + When you store objects in Maps or arrays, those references prevent garbage collection — even if you don't need the object anymore. + + ```javascript + // Potential memory leak: cache holds references forever + const cache = new Map(); + + function processUser(user) { + cache.set(user.id, user); // user can never be garbage collected + } + + // Even if user objects are no longer needed elsewhere, + // the cache keeps them alive in memory + ``` + + **Solutions:** + + - **WeakMap** — Keys are "weakly held" and can be garbage collected + - **Manual cleanup** — Delete entries when done + + ```javascript + // WeakMap: objects can be garbage collected when no other references exist + const cache = new WeakMap(); + + function processUser(user) { + cache.set(user, computeExpensiveData(user)); + } + // When 'user' object is no longer referenced elsewhere, + // it AND its cached data can be garbage collected + ``` + + + **WeakMap vs Map:** WeakMap keys must be objects (not primitives), and you can't iterate over a WeakMap. Use it when you want cached data to automatically disappear when the source object is gone. + + + + +--- + +## Best Practices + + +**Guidelines for working with reference types:** + +1. **Treat objects as immutable when possible** + ```javascript + // Instead of mutating: + user.name = "Bob"; + + // Create a new object: + const updatedUser = { ...user, name: "Bob" }; + ``` + +2. **Use `const` by default** — prevents accidental reassignment + +3. **Know which methods mutate** + - Mutating: `push`, `pop`, `sort`, `reverse`, `splice` + - Non-mutating: `map`, `filter`, `slice`, `concat`, `toSorted` + +4. **Use `structuredClone()` for deep copies** + ```javascript + const clone = structuredClone(original); + ``` + +5. **Be explicit about intent** — comment when mutating on purpose + ```javascript + // Intentionally mutating for performance + largeArray.sort((a, b) => a - b); + ``` + +6. **Clone function parameters if you need to modify them** + ```javascript + function processData(data) { + const copy = structuredClone(data); + // Now safe to modify copy + } + ``` + + +--- + +## Test Your Knowledge + + + + **Answer:** + + - **Value types (primitives)** store the actual value directly. Copying creates an independent copy, and comparison checks if values are the same. + + - **Reference types (objects, arrays, functions)** store a pointer/reference to the data. Copying creates another pointer to the SAME data, and comparison checks if both point to the same object. + + + + ```javascript + let a = { count: 1 }; + let b = a; + b.count = 5; + console.log(a.count); + ``` + + **Answer:** `5` + + Both `a` and `b` point to the same object. When you modify `b.count`, you're modifying the shared object, which `a` also sees. + + + + **Answer:** Because `===` compares references, not contents. + + Each `{}` creates a NEW empty object in memory with a different reference. Even though they have the same contents (both empty), they are different objects at different memory addresses. + + ```javascript + {} === {} // false (different objects) + + const a = {}; + const b = a; + a === b // true (same reference) + ``` + + + + **Answer:** + + - **Shallow copy** creates a new object and copies top-level properties. Nested objects are NOT copied — they're still shared references. + + - **Deep copy** creates a completely independent copy at ALL levels. Nested objects are also cloned. + + ```javascript + const original = { nested: { value: 1 } }; + + // Shallow: nested is shared + const shallow = { ...original }; + shallow.nested.value = 2; + console.log(original.nested.value); // 2 (affected!) + + // Deep: completely independent + const deep = structuredClone(original); + deep.nested.value = 3; + console.log(original.nested.value); // 2 (unchanged) + ``` + + + + **Answer:** No! + + `const` only prevents **reassignment** — you can't make the variable point to a different value. But you CAN still **mutate** the object's contents. + + ```javascript + const obj = { name: "Alice" }; + + obj.name = "Bob"; // ✅ Allowed (mutation) + obj.age = 25; // ✅ Allowed (mutation) + obj = {}; // ❌ Error (reassignment) + ``` + + + + **Answer:** The original object IS modified! + + When you pass an object to a function, the function receives a copy of the reference. Both the original variable and the function parameter point to the same object. Mutations through either will affect the shared object. + + ```javascript + function addAge(person) { + person.age = 30; // Mutates the original! + } + + const user = { name: "Alice" }; + addAge(user); + console.log(user.age); // 30 + ``` + + However, if you *reassign* the parameter inside the function, it only changes the local variable, not the original. + + + + **Answer:** No! `Object.freeze()` is shallow. + + It only freezes the top-level properties. Nested objects can still be modified: + + ```javascript + const user = Object.freeze({ + name: "Alice", + address: { city: "NYC" } + }); + + user.name = "Bob"; // Blocked (frozen) + user.address.city = "LA"; // Works! (nested object not frozen) + + console.log(user.address.city); // "LA" + ``` + + To freeze everything, you need a recursive "deep freeze" function that freezes all nested objects. + + + + **Answer:** They protect different things: + + - **`const`** prevents **reassignment** — you can't make the variable point to a different value + - **`Object.freeze()`** prevents **mutation** — you can't change the object's properties + + ```javascript + // const alone: can mutate, can't reassign + const obj1 = { a: 1 }; + obj1.a = 2; // ✅ Works + obj1 = {}; // ❌ Error + + // freeze alone: can reassign (if let), can't mutate + let obj2 = Object.freeze({ a: 1 }); + obj2.a = 2; // ❌ Fails silently + obj2 = {}; // ✅ Works (it's let) + + // Both together: can't do either + const obj3 = Object.freeze({ a: 1 }); + obj3.a = 2; // ❌ Fails silently + obj3 = {}; // ❌ Error + ``` + + + +--- + +## Key Takeaways + + +**Remember these essential points:** + +1. **Value types** (primitives) store values directly; **reference types** store pointers + +2. **Copying a primitive** creates an independent copy — changing one doesn't affect the other + +3. **Copying an object/array** copies the reference — both point to the SAME data + +4. **Comparison:** primitives compare by value, objects compare by reference + +5. **Functions:** JavaScript passes everything by value, but object values ARE references + +6. **Mutation ≠ Reassignment:** `const` only prevents reassignment, not mutation + +7. **Shallow copy** (spread, Object.assign) only copies one level — nested objects are shared + +8. **Deep copy** with `structuredClone()` creates completely independent copies + +9. **Know your array methods:** `push/pop/sort` mutate; `map/filter/slice` don't + +10. **True immutability** requires `Object.freeze()` — but it's shallow, so use deep freeze for nested objects + + +--- + +## Related Concepts + + + + Deep dive into the 7 primitive types and their characteristics + + + How JavaScript converts between types automatically + + + How closures capture references to variables + + + Understanding equality operators and type checking + + + Official documentation on freezing objects for immutability + + + Maps with weak references that allow garbage collection + + + +--- + +## Reference + + + Official MDN documentation on JavaScript's type system, including primitives and objects. + + ## Articles - By Arnav Aggarwal + Clear explanation by Arnav Aggarwal with visual diagrams showing how primitives and objects behave differently in memory. + + + + Comprehensive tutorial with excellent stack/heap diagrams and practical examples. One of the best visual explanations available. - - By Bran van der Meer + + + Deep dive by Miro Koczka into how JavaScript handles values and references, with clear explanations of common pitfalls. -- [Value Types, Reference Types and Scope in JavaScript — Ben Aston](https://medium.com/@benastontweet/lesson-1b-javascript-fundamentals-380f601ba851) -- [Back to roots: JavaScript Value vs Reference — Miro Koczka](https://medium.com/dailyjs/back-to-roots-javascript-value-vs-reference-8fb69d587a18) -- [Grasp "By Value" and "By Reference" in JavaScript — Léna Faure](https://hackernoon.com/grasp-by-value-and-by-reference-in-javascript-7ed75efa1293) -- [JavaScript Reference and Copy Variables — Vítor Capretz](https://hackernoon.com/javascript-reference-and-copy-variables-b0103074fdf0) -- [JavaScript Primitive vs Reference Values](http://www.javascripttutorial.net/javascript-primitive-vs-reference-values/) -- [JavaScript by Reference vs. by Value — nrabinowitz](https://stackoverflow.com/questions/6605640/javascript-by-reference-vs-by-value) -- [JavaScript Interview Prep: Primitive vs. Reference Types — Mike Cronin](https://dev.to/mostlyfocusedmike/javascript-interview-prep-primitive-vs-reference-types-3o4f) -- [JavaScript map vs. forEach: When to Use Each One - Sajal Soni](https://code.tutsplus.com/tutorials/javascript-map-vs-foreach-when-to-use-each-one--cms-38365) +## Books + + + The definitive guide to JavaScript's type system. Kyle Simpson explains value vs reference, coercion, and more. Free to read online. + ## Videos - - By techsith + + Clear, beginner-friendly explanation from one of the most popular JavaScript educators. Great visual demonstrations of how memory works. + + + Thorough walkthrough of how primitives and reference types differ, with live coding examples showing the implications for real code. - - By Programming with Mosh + + Focused explanation of how function parameters work with primitives vs objects. Great for understanding the "pass by value of the reference" concept. - -- [VALORES vs REFERENCIAS en JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=AvkyOrWkuQc) -- [JavaScript - Reference vs Primitive Values/ Types - Academind](https://www.youtube.com/watch?v=9ooYYRLdg_g) -- [Value Types and Reference Types in JavaScript - Programming with Mosh](https://www.youtube.com/watch?v=e-_mDyqm2oU) From 826bf6f591b5d64bb1e26abf131ec2ba20a63708 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 21:16:53 -0300 Subject: [PATCH 012/128] feat: add vitest test suite for fundamentals concepts - Install vitest 4.0.16 as test runner - Create vitest.config.js with test configuration - Add test scripts (test, test:watch, test:coverage) - Add docs scripts for mintlify (docs, docs:build) - Improve package.json description and keywords - Fix repository URLs to use 33-js-concepts Test coverage for 6 fundamentals concepts (288 tests): - call-stack (18 tests) - primitive-types (62 tests) - value-reference-types (43 tests) - type-coercion (60 tests) - equality-operators (68 tests) - scope-and-closures (37 tests) Update CONTRIBUTING.md with test guidelines --- CONTRIBUTING.md | 53 + package-lock.json | 1421 +++++++++++++++++ package.json | 55 +- tests/call-stack/call-stack.test.js | 254 +++ .../equality-operators.test.js | 646 ++++++++ tests/primitive-types/primitive-types.test.js | 452 ++++++ .../scope-and-closures.test.js | 657 ++++++++ tests/type-coercion/type-coercion.test.js | 506 ++++++ .../value-reference-types.test.js | 557 +++++++ vitest.config.js | 9 + 10 files changed, 4594 insertions(+), 16 deletions(-) create mode 100644 package-lock.json create mode 100644 tests/call-stack/call-stack.test.js create mode 100644 tests/equality-operators/equality-operators.test.js create mode 100644 tests/primitive-types/primitive-types.test.js create mode 100644 tests/scope-and-closures/scope-and-closures.test.js create mode 100644 tests/type-coercion/type-coercion.test.js create mode 100644 tests/value-reference-types/value-reference-types.test.js create mode 100644 vitest.config.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95a960d4..9cd591c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,59 @@ # Contribution This project would not be possible without your help and support, and we appreciate your willingness to contribute! +## Testing + +This project uses [Vitest](https://vitest.dev/) as the test runner to verify that code examples in the documentation work correctly. + +### Running Tests + +```bash +# Run all tests once +npm test + +# Run tests in watch mode (re-runs on file changes) +npm run test:watch + +# Run tests with coverage report +npm run test:coverage +``` + +### Test Structure + +Tests are organized by concept in the `tests/` directory: + +``` +tests/ +├── call-stack/ +│ └── call-stack.test.js +├── primitive-types/ +│ └── primitive-types.test.js +└── ... +``` + +### Writing Tests for Code Examples + +When adding new code examples to concept documentation, please include corresponding tests: + +1. **File naming**: Create `{concept-name}.test.js` in `tests/{concept-name}/` +2. **Use explicit imports**: + ```javascript + import { describe, it, expect } from 'vitest' + ``` +3. **Convert console.log examples to assertions**: + ```javascript + // Documentation example: + // console.log(typeof "hello") // "string" + + // Test: + it('should return string type', () => { + expect(typeof "hello").toBe("string") + }) + ``` +4. **Test error cases**: Use `expect(() => { ... }).toThrow()` for operations that should throw +5. **Skip browser-specific examples**: Tests run in Node.js, so skip DOM/window/document examples +6. **Note strict mode behavior**: Vitest runs in strict mode, so operations that "silently fail" in non-strict mode will throw `TypeError` + ### Creating a New Translation To create a new translation, please follow these steps: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..b6e9da74 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1421 @@ +{ + "name": "33-js-concepts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "33-js-concepts", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "vitest": "^4.0.16" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", + "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", + "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", + "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.16", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", + "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", + "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", + "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.16", + "@vitest/mocker": "4.0.16", + "@vitest/pretty-format": "4.0.16", + "@vitest/runner": "4.0.16", + "@vitest/snapshot": "4.0.16", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.16", + "@vitest/browser-preview": "4.0.16", + "@vitest/browser-webdriverio": "4.0.16", + "@vitest/ui": "4.0.16", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json index 588446b6..9c0668a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "33-js-concepts", "version": "1.0.0", - "description": "33 concepts every JavaScript developer should know.", + "description": "A curated collection of 33 essential JavaScript concepts every developer should master. Includes comprehensive learning resources, articles, videos, and interactive code examples covering everything from call stack and closures to async/await and design patterns.", "main": "index.js", "author": { "name": "Leonardo Maldonado", @@ -9,28 +9,51 @@ }, "license": "MIT", "bugs": { - "url": "https://github.com/leonardomso/33/issues" + "url": "https://github.com/leonardomso/33-js-concepts/issues" }, - "homepage": "https://github.com/leonardomso/33#readme", + "homepage": "https://github.com/leonardomso/33-js-concepts#readme", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "docs": "npx mintlify dev --dir docs", + "docs:build": "npx mintlify build --dir docs" }, "repository": { "type": "git", - "url": "git+https://github.com/leonardomso/33.git" + "url": "git+https://github.com/leonardomso/33-js-concepts.git" }, "keywords": [ - "JavaScript", "javascript", - "JS", - "programming", - "web", - "web dev", - "front end", - "front-end", + "javascript-concepts", + "javascript-learning", + "javascript-tutorial", + "learn-javascript", + "closures", + "promises", + "async-await", + "event-loop", + "prototypes", + "scope", + "hoisting", + "coercion", + "this-keyword", + "call-stack", + "higher-order-functions", + "functional-programming", + "design-patterns", + "data-structures", + "algorithms", + "es6", + "ecmascript", + "web-development", + "frontend", "nodejs", - "Node.js", - "NodeJS", - "Node" - ] + "programming", + "developer-resources", + "interview-preparation" + ], + "devDependencies": { + "vitest": "^4.0.16" + } } diff --git a/tests/call-stack/call-stack.test.js b/tests/call-stack/call-stack.test.js new file mode 100644 index 00000000..5cd05a26 --- /dev/null +++ b/tests/call-stack/call-stack.test.js @@ -0,0 +1,254 @@ +import { describe, it, expect } from 'vitest' + +describe('Call Stack', () => { + describe('Basic Function Calls', () => { + it('should execute nested function calls and return correct greeting', () => { + function createGreeting(name) { + return "Hello, " + name + "!" + } + + function greet(name) { + const greeting = createGreeting(name) + return greeting + } + + expect(greet("Alice")).toBe("Hello, Alice!") + }) + + it('should demonstrate function arguments in execution context', () => { + function greet(name, age) { + return { name, age } + } + + const result = greet("Alice", 25) + expect(result).toEqual({ name: "Alice", age: 25 }) + }) + + it('should demonstrate local variables in execution context', () => { + function calculate() { + const x = 10 + let y = 20 + var z = 30 + return x + y + z + } + + expect(calculate()).toBe(60) + }) + }) + + describe('Nested Function Calls', () => { + it('should execute multiply, square, and printSquare correctly', () => { + function multiply(x, y) { + return x * y + } + + function square(n) { + return multiply(n, n) + } + + function printSquare(n) { + const result = square(n) + return result + } + + expect(printSquare(4)).toBe(16) + }) + + it('should handle deep nesting of function calls', () => { + function a() { return b() } + function b() { return c() } + function c() { return d() } + function d() { return 'done' } + + expect(a()).toBe('done') + }) + + it('should calculate maximum stack depth correctly for nested calls', () => { + // This test verifies the example where max depth is 4 + let maxDepth = 0 + let currentDepth = 0 + + function a() { + currentDepth++ + maxDepth = Math.max(maxDepth, currentDepth) + const result = b() + currentDepth-- + return result + } + function b() { + currentDepth++ + maxDepth = Math.max(maxDepth, currentDepth) + const result = c() + currentDepth-- + return result + } + function c() { + currentDepth++ + maxDepth = Math.max(maxDepth, currentDepth) + const result = d() + currentDepth-- + return result + } + function d() { + currentDepth++ + maxDepth = Math.max(maxDepth, currentDepth) + currentDepth-- + return 'done' + } + + a() + expect(maxDepth).toBe(4) + }) + }) + + describe('Scope Chain in Execution Context', () => { + it('should access outer scope variables from inner function', () => { + function outer() { + const message = "Hello" + + function inner() { + return message + } + + return inner() + } + + expect(outer()).toBe("Hello") + }) + + it('should demonstrate this keyword context in objects', () => { + const person = { + name: "Alice", + greet() { + return this.name + } + } + + expect(person.greet()).toBe("Alice") + }) + }) + + describe('Stack Overflow', () => { + it('should throw RangeError for infinite recursion without base case', () => { + function countdown(n) { + countdown(n - 1) // No base case - infinite recursion + } + + expect(() => countdown(5)).toThrow(RangeError) + }) + + it('should work correctly with proper base case', () => { + const results = [] + + function countdown(n) { + if (n <= 0) { + results.push("Done!") + return + } + results.push(n) + countdown(n - 1) + } + + countdown(5) + expect(results).toEqual([5, 4, 3, 2, 1, "Done!"]) + }) + + it('should throw for infinite loop function', () => { + function loop() { + loop() + } + + expect(() => loop()).toThrow(RangeError) + }) + + it('should throw for base case that is never reached', () => { + function countUp(n, limit = 100) { + // Modified to have a safety limit for testing + if (n >= 1000000000000 || limit <= 0) return n + return countUp(n + 1, limit - 1) + } + + // This will return before hitting the impossible base case + expect(countUp(0)).toBe(100) + }) + + it('should throw for circular function calls', () => { + function a() { return b() } + function b() { return a() } + + expect(() => a()).toThrow(RangeError) + }) + }) + + describe('Recursion with Base Case', () => { + it('should calculate factorial correctly', () => { + function factorial(n) { + if (n <= 1) return 1 + return n * factorial(n - 1) + } + + expect(factorial(5)).toBe(120) + expect(factorial(1)).toBe(1) + expect(factorial(0)).toBe(1) + }) + + it('should demonstrate proper countdown with base case', () => { + function countdown(n) { + if (n <= 0) { + return "Done!" + } + return countdown(n - 1) + } + + expect(countdown(5)).toBe("Done!") + }) + }) + + describe('Error Stack Traces', () => { + it('should create error with proper stack trace', () => { + function a() { return b() } + function b() { return c() } + function c() { + throw new Error('Something went wrong!') + } + + expect(() => a()).toThrow('Something went wrong!') + }) + + it('should preserve call stack in error', () => { + function a() { return b() } + function b() { return c() } + function c() { + throw new Error('Test error') + } + + try { + a() + } catch (error) { + expect(error.stack).toContain('c') + expect(error.stack).toContain('b') + expect(error.stack).toContain('a') + } + }) + }) + + describe('Asynchronous Code Preview', () => { + it('should demonstrate setTimeout behavior with call stack', async () => { + const results = [] + + results.push('First') + + await new Promise(resolve => { + setTimeout(() => { + results.push('Second') + resolve() + }, 0) + + results.push('Third') + }) + + // Even with 0ms delay, 'Third' runs before 'Second' + expect(results).toEqual(['First', 'Third', 'Second']) + }) + }) +}) diff --git a/tests/equality-operators/equality-operators.test.js b/tests/equality-operators/equality-operators.test.js new file mode 100644 index 00000000..56c28749 --- /dev/null +++ b/tests/equality-operators/equality-operators.test.js @@ -0,0 +1,646 @@ +import { describe, it, expect } from 'vitest' + +describe('Equality and Type Checking', () => { + describe('Three Equality Operators Overview', () => { + it('should demonstrate different results for same comparison', () => { + const num = 1 + const str = "1" + + expect(num == str).toBe(true) // coerces string to number + expect(num === str).toBe(false) // different types + expect(Object.is(num, str)).toBe(false) // different types + }) + }) + + describe('Loose Equality (==)', () => { + describe('Same Type Comparison', () => { + it('should compare directly when same type', () => { + expect(5 == 5).toBe(true) + expect("hello" == "hello").toBe(true) + }) + }) + + describe('null and undefined', () => { + it('should return true for null == undefined', () => { + expect(null == undefined).toBe(true) + expect(undefined == null).toBe(true) + }) + }) + + describe('Number and String', () => { + it('should convert string to number', () => { + expect(5 == "5").toBe(true) + expect(0 == "").toBe(true) + expect(100 == "1e2").toBe(true) + }) + + it('should return false for different string comparison', () => { + expect("" == "0").toBe(false) // Both strings, different values + }) + + it('should handle NaN conversions', () => { + expect(NaN == "NaN").toBe(false) // NaN ≠ anything + expect(0 == "hello").toBe(false) // "hello" → NaN + }) + }) + + describe('BigInt and String', () => { + it('should convert string to BigInt', () => { + expect(10n == "10").toBe(true) + }) + }) + + describe('Boolean Coercion', () => { + it('should convert boolean to number first', () => { + expect(true == 1).toBe(true) + expect(false == 0).toBe(true) + expect(true == "1").toBe(true) + expect(false == "").toBe(true) + }) + + it('should demonstrate confusing boolean comparisons', () => { + expect(true == "true").toBe(false) // true → 1, "true" → NaN + expect(false == "false").toBe(false) // false → 0, "false" → NaN + expect(true == 2).toBe(false) // true → 1, 1 ≠ 2 + expect(true == "2").toBe(false) // true → 1, "2" → 2 + }) + }) + + describe('Object to Primitive', () => { + it('should convert object via ToPrimitive', () => { + expect([1] == 1).toBe(true) // [1] → "1" → 1 + expect([""] == 0).toBe(true) // [""] → "" → 0 + }) + }) + + describe('BigInt and Number', () => { + it('should compare mathematical values', () => { + expect(10n == 10).toBe(true) + expect(10n == 10.5).toBe(false) + }) + }) + + describe('Special Cases', () => { + it('should return false for null/undefined vs other values', () => { + expect(null == 0).toBe(false) + expect(undefined == 0).toBe(false) + expect(Symbol() == Symbol()).toBe(false) + }) + }) + + describe('Surprising Results', () => { + describe('String and Number', () => { + it('should demonstrate string to number conversions', () => { + expect(1 == "1").toBe(true) + expect(0 == "").toBe(true) + expect(0 == "0").toBe(true) + expect(100 == "1e2").toBe(true) + }) + }) + + describe('null and undefined', () => { + it('should demonstrate special null/undefined behavior', () => { + expect(null == undefined).toBe(true) + expect(null == 0).toBe(false) + expect(null == false).toBe(false) + expect(null == "").toBe(false) + expect(undefined == 0).toBe(false) + expect(undefined == false).toBe(false) + }) + + it('should catch both null and undefined with == null', () => { + function greet(name) { + if (name == null) { + return "Hello, stranger!" + } + return `Hello, ${name}!` + } + + expect(greet(null)).toBe("Hello, stranger!") + expect(greet(undefined)).toBe("Hello, stranger!") + expect(greet("Alice")).toBe("Hello, Alice!") + expect(greet("")).toBe("Hello, !") + expect(greet(0)).toBe("Hello, 0!") + }) + }) + + describe('Arrays and Objects', () => { + it('should convert arrays via ToPrimitive', () => { + expect([] == false).toBe(true) + expect([] == 0).toBe(true) + expect([] == "").toBe(true) + expect([1] == 1).toBe(true) + expect([1] == "1").toBe(true) + expect([1, 2] == "1,2").toBe(true) + }) + + it('should use valueOf on objects with custom valueOf', () => { + let obj = { valueOf: () => 42 } + expect(obj == 42).toBe(true) + }) + }) + }) + + describe('Step-by-Step Trace: [] == ![]', () => { + it('should demonstrate [] == ![] is true', () => { + // Step 1: Evaluate ![] + // [] is truthy, so ![] = false + const step1 = ![] + expect(step1).toBe(false) + + // Step 2: Now we have [] == false + // Boolean → Number: false → 0 + // [] == 0 + + // Step 3: Object → Primitive + // [].toString() → "" + // "" == 0 + + // Step 4: String → Number + // "" → 0 + // 0 == 0 → true + + const emptyArray = [] + expect(emptyArray == step1).toBe(true) + }) + }) + }) + + describe('Strict Equality (===)', () => { + describe('Type Check', () => { + it('should return false for different types immediately', () => { + expect(1 === "1").toBe(false) + expect(true === 1).toBe(false) + expect(null === undefined).toBe(false) + }) + }) + + describe('Number Comparison', () => { + it('should compare numeric values', () => { + expect(42 === 42).toBe(true) + expect(Infinity === Infinity).toBe(true) + }) + + it('should return false for NaN === NaN', () => { + expect(NaN === NaN).toBe(false) + }) + + it('should return true for +0 === -0', () => { + expect(+0 === -0).toBe(true) + }) + }) + + describe('String Comparison', () => { + it('should compare string characters', () => { + expect("hello" === "hello").toBe(true) + expect("hello" === "Hello").toBe(false) // Case sensitive + expect("hello" === "hello ").toBe(false) // Different length + }) + }) + + describe('Boolean Comparison', () => { + it('should compare boolean values', () => { + expect(true === true).toBe(true) + expect(false === false).toBe(true) + expect(true === false).toBe(false) + }) + }) + + describe('BigInt Comparison', () => { + it('should compare BigInt values', () => { + expect(10n === 10n).toBe(true) + expect(10n === 20n).toBe(false) + }) + }) + + describe('Symbol Comparison', () => { + it('should return false for different symbols', () => { + const sym = Symbol("id") + expect(sym === sym).toBe(true) + expect(Symbol("id") === Symbol("id")).toBe(false) + }) + }) + + describe('Object Comparison (Reference)', () => { + it('should compare by reference, not value', () => { + const obj = { a: 1 } + expect(obj === obj).toBe(true) + + const obj1 = { a: 1 } + const obj2 = { a: 1 } + expect(obj1 === obj2).toBe(false) // Different objects! + }) + + it('should return false for different arrays', () => { + const arr1 = [1, 2, 3] + const arr2 = [1, 2, 3] + const arr3 = arr1 + + expect(arr1 === arr2).toBe(false) + expect(arr1 === arr3).toBe(true) + }) + + it('should return false for different functions', () => { + const fn1 = () => {} + const fn2 = () => {} + const fn3 = fn1 + + expect(fn1 === fn2).toBe(false) + expect(fn1 === fn3).toBe(true) + }) + }) + + describe('null and undefined', () => { + it('should compare null and undefined correctly', () => { + expect(null === null).toBe(true) + expect(undefined === undefined).toBe(true) + expect(null === undefined).toBe(false) + }) + }) + + describe('Predictable Results', () => { + it('should return false for different types', () => { + expect(1 === "1").toBe(false) + expect(0 === "").toBe(false) + expect(true === 1).toBe(false) + expect(false === 0).toBe(false) + expect(null === undefined).toBe(false) + }) + + it('should return true for same type and value', () => { + expect(1 === 1).toBe(true) + expect("hello" === "hello").toBe(true) + expect(true === true).toBe(true) + expect(null === null).toBe(true) + expect(undefined === undefined).toBe(true) + }) + }) + + describe('Special Cases: NaN and ±0', () => { + it('should demonstrate NaN !== NaN', () => { + expect(NaN === NaN).toBe(false) + expect(Number.isNaN(NaN)).toBe(true) + expect(isNaN(NaN)).toBe(true) + expect(isNaN("hello")).toBe(true) // Converts to NaN first + expect(Number.isNaN("hello")).toBe(false) // No conversion + }) + + it('should demonstrate +0 === -0', () => { + expect(+0 === -0).toBe(true) + expect(1 / +0).toBe(Infinity) + expect(1 / -0).toBe(-Infinity) + expect(Object.is(+0, -0)).toBe(false) + }) + + it('should detect -0', () => { + expect(0 * -1).toBe(-0) + expect(Object.is(0 * -1, -0)).toBe(true) + }) + }) + }) + + describe('Object.is()', () => { + describe('Comparison with ===', () => { + it('should behave like === for most cases', () => { + expect(Object.is(1, 1)).toBe(true) + expect(Object.is("a", "a")).toBe(true) + expect(Object.is(null, null)).toBe(true) + + const obj1 = {} + const obj2 = {} + expect(Object.is(obj1, obj2)).toBe(false) + }) + }) + + describe('NaN Equality', () => { + it('should return true for NaN === NaN', () => { + expect(Object.is(NaN, NaN)).toBe(true) + }) + }) + + describe('±0 Distinction', () => { + it('should distinguish +0 from -0', () => { + expect(Object.is(+0, -0)).toBe(false) + expect(Object.is(-0, 0)).toBe(false) + }) + }) + + describe('Practical Uses', () => { + it('should check for NaN', () => { + function isReallyNaN(value) { + return Object.is(value, NaN) + } + expect(isReallyNaN(NaN)).toBe(true) + expect(isReallyNaN("hello")).toBe(false) + }) + + it('should check for negative zero', () => { + function isNegativeZero(value) { + return Object.is(value, -0) + } + expect(isNegativeZero(-0)).toBe(true) + expect(isNegativeZero(0)).toBe(false) + }) + }) + + describe('Complete Comparison Table', () => { + it('should show differences between ==, ===, and Object.is()', () => { + // 1, "1" + expect(1 == "1").toBe(true) + expect(1 === "1").toBe(false) + expect(Object.is(1, "1")).toBe(false) + + // 0, false + expect(0 == false).toBe(true) + expect(0 === false).toBe(false) + expect(Object.is(0, false)).toBe(false) + + // null, undefined + expect(null == undefined).toBe(true) + expect(null === undefined).toBe(false) + expect(Object.is(null, undefined)).toBe(false) + + // NaN, NaN + expect(NaN == NaN).toBe(false) + expect(NaN === NaN).toBe(false) + expect(Object.is(NaN, NaN)).toBe(true) + + // +0, -0 + expect(+0 == -0).toBe(true) + expect(+0 === -0).toBe(true) + expect(Object.is(+0, -0)).toBe(false) + }) + }) + }) + + describe('typeof Operator', () => { + describe('Correct Results', () => { + it('should return correct types for primitives', () => { + expect(typeof "hello").toBe("string") + expect(typeof 42).toBe("number") + expect(typeof 42n).toBe("bigint") + expect(typeof true).toBe("boolean") + expect(typeof undefined).toBe("undefined") + expect(typeof Symbol()).toBe("symbol") + }) + + it('should return "object" for objects and arrays', () => { + expect(typeof {}).toBe("object") + expect(typeof []).toBe("object") + expect(typeof new Date()).toBe("object") + expect(typeof /regex/).toBe("object") + }) + + it('should return "function" for functions', () => { + expect(typeof function(){}).toBe("function") + expect(typeof (() => {})).toBe("function") + expect(typeof class {}).toBe("function") + expect(typeof Math.sin).toBe("function") + }) + }) + + describe('Famous Quirks', () => { + it('should return "object" for null (bug)', () => { + expect(typeof null).toBe("object") + }) + + it('should return "object" for arrays', () => { + expect(typeof []).toBe("object") + expect(typeof [1, 2, 3]).toBe("object") + expect(typeof new Array()).toBe("object") + }) + + it('should return "number" for NaN', () => { + expect(typeof NaN).toBe("number") + }) + + it('should return "undefined" for undeclared variables', () => { + expect(typeof undeclaredVariable).toBe("undefined") + }) + }) + + describe('Workarounds', () => { + it('should check for null explicitly', () => { + function getType(value) { + if (value === null) return "null" + return typeof value + } + + expect(getType(null)).toBe("null") + expect(getType(undefined)).toBe("undefined") + expect(getType(42)).toBe("number") + }) + + it('should check for "real" objects', () => { + function isRealObject(value) { + return value !== null && typeof value === "object" + } + + expect(isRealObject({})).toBe(true) + expect(isRealObject([])).toBe(true) + expect(isRealObject(null)).toBe(false) + }) + }) + }) + + describe('Better Type Checking Alternatives', () => { + describe('Type-Specific Checks', () => { + it('should use Array.isArray for arrays', () => { + expect(Array.isArray([])).toBe(true) + expect(Array.isArray([1, 2, 3])).toBe(true) + expect(Array.isArray({})).toBe(false) + expect(Array.isArray("hello")).toBe(false) + expect(Array.isArray(null)).toBe(false) + }) + + it('should use Number.isNaN for NaN', () => { + expect(Number.isNaN(NaN)).toBe(true) + expect(Number.isNaN("hello")).toBe(false) + expect(Number.isNaN(undefined)).toBe(false) + }) + + it('should use Number.isFinite for finite numbers', () => { + expect(Number.isFinite(42)).toBe(true) + expect(Number.isFinite(Infinity)).toBe(false) + expect(Number.isFinite(NaN)).toBe(false) + }) + + it('should use Number.isInteger for integers', () => { + expect(Number.isInteger(42)).toBe(true) + expect(Number.isInteger(42.5)).toBe(false) + }) + }) + + describe('instanceof', () => { + it('should check instance of constructor', () => { + expect([] instanceof Array).toBe(true) + expect({} instanceof Object).toBe(true) + expect(new Date() instanceof Date).toBe(true) + expect(/regex/ instanceof RegExp).toBe(true) + }) + + it('should work with custom classes', () => { + class Person {} + const p = new Person() + expect(p instanceof Person).toBe(true) + }) + }) + + describe('Object.prototype.toString', () => { + it('should return precise type information', () => { + const getType = (value) => + Object.prototype.toString.call(value).slice(8, -1) + + expect(getType(null)).toBe("Null") + expect(getType(undefined)).toBe("Undefined") + expect(getType([])).toBe("Array") + expect(getType({})).toBe("Object") + expect(getType(new Date())).toBe("Date") + expect(getType(/regex/)).toBe("RegExp") + expect(getType(new Map())).toBe("Map") + expect(getType(new Set())).toBe("Set") + expect(getType(Promise.resolve())).toBe("Promise") + expect(getType(function(){})).toBe("Function") + expect(getType(42)).toBe("Number") + expect(getType("hello")).toBe("String") + expect(getType(Symbol())).toBe("Symbol") + expect(getType(42n)).toBe("BigInt") + }) + }) + + describe('Custom Type Checker', () => { + it('should create comprehensive type checker', () => { + function getType(value) { + if (value === null) return "null" + + const type = typeof value + if (type !== "object" && type !== "function") { + return type + } + + const tag = Object.prototype.toString.call(value) + return tag.slice(8, -1).toLowerCase() + } + + expect(getType(null)).toBe("null") + expect(getType([])).toBe("array") + expect(getType({})).toBe("object") + expect(getType(new Date())).toBe("date") + expect(getType(/regex/)).toBe("regexp") + expect(getType(new Map())).toBe("map") + expect(getType(Promise.resolve())).toBe("promise") + }) + }) + }) + + describe('Common Gotchas and Mistakes', () => { + describe('Comparing Objects by Value', () => { + it('should demonstrate object reference comparison', () => { + const user1 = { name: "Alice" } + const user2 = { name: "Alice" } + + expect(user1 === user2).toBe(false) // Never runs as equal! + + // Option 1: Compare specific properties + expect(user1.name === user2.name).toBe(true) + + // Option 2: JSON.stringify + expect(JSON.stringify(user1) === JSON.stringify(user2)).toBe(true) + }) + }) + + describe('NaN Comparisons', () => { + it('should never use === for NaN', () => { + const result = parseInt("hello") + + expect(result === NaN).toBe(false) // Never works! + expect(Number.isNaN(result)).toBe(true) // Correct way + expect(Object.is(result, NaN)).toBe(true) // Also works + }) + }) + + describe('typeof null Trap', () => { + it('should handle null separately from objects', () => { + function processObject(obj) { + if (obj !== null && typeof obj === "object") { + return "real object" + } + return "not an object" + } + + expect(processObject({})).toBe("real object") + expect(processObject(null)).toBe("not an object") + }) + }) + + describe('String Comparison Gotchas', () => { + it('should demonstrate string comparison issues', () => { + // Strings compare lexicographically + expect("10" > "9").toBe(false) // "1" < "9" + + // Convert to numbers for numeric comparison + expect(Number("10") > Number("9")).toBe(true) + expect(+"10" > +"9").toBe(true) + }) + }) + + describe('Empty Array Comparisons', () => { + it('should demonstrate array truthiness vs equality', () => { + const arr = [] + + // These seem contradictory + expect(arr == false).toBe(true) + expect(arr ? true : false).toBe(true) // arr is truthy! + + // Check array length instead + expect(arr.length === 0).toBe(true) + expect(!arr.length).toBe(true) + }) + }) + }) + + describe('Decision Guide', () => { + describe('Default to ===', () => { + it('should use === for predictable comparisons', () => { + expect(5 === 5).toBe(true) + expect(5 === "5").toBe(false) // No surprise + }) + }) + + describe('Use == null for Nullish Checks', () => { + it('should check for null or undefined', () => { + function isNullish(value) { + return value == null + } + + expect(isNullish(null)).toBe(true) + expect(isNullish(undefined)).toBe(true) + expect(isNullish(0)).toBe(false) + expect(isNullish("")).toBe(false) + expect(isNullish(false)).toBe(false) + }) + }) + + describe('Use Number.isNaN for NaN', () => { + it('should use Number.isNaN, not isNaN', () => { + expect(Number.isNaN(NaN)).toBe(true) + expect(Number.isNaN("hello")).toBe(false) // Correct + expect(isNaN("hello")).toBe(true) // Wrong! + }) + }) + + describe('Use Array.isArray for Arrays', () => { + it('should use Array.isArray, not typeof', () => { + expect(Array.isArray([])).toBe(true) + expect(typeof []).toBe("object") // Not helpful + }) + }) + + describe('Use Object.is for Edge Cases', () => { + it('should use Object.is for NaN and ±0', () => { + expect(Object.is(NaN, NaN)).toBe(true) + expect(Object.is(+0, -0)).toBe(false) + }) + }) + }) +}) diff --git a/tests/primitive-types/primitive-types.test.js b/tests/primitive-types/primitive-types.test.js new file mode 100644 index 00000000..73d44d50 --- /dev/null +++ b/tests/primitive-types/primitive-types.test.js @@ -0,0 +1,452 @@ +import { describe, it, expect } from 'vitest' + +describe('Primitive Types', () => { + describe('String', () => { + it('should create strings with single quotes, double quotes, and backticks', () => { + let single = 'Hello' + let double = "World" + let backtick = `Hello World` + + expect(single).toBe('Hello') + expect(double).toBe('World') + expect(backtick).toBe('Hello World') + }) + + it('should support template literal interpolation', () => { + let name = "Alice" + let age = 25 + + let greeting = `Hello, ${name}! You are ${age} years old.` + expect(greeting).toBe("Hello, Alice! You are 25 years old.") + }) + + it('should support multi-line strings with template literals', () => { + let multiLine = ` + This is line 1 + This is line 2 +` + expect(multiLine).toContain('This is line 1') + expect(multiLine).toContain('This is line 2') + }) + + it('should demonstrate string immutability - cannot change characters', () => { + let str = "hello" + // In strict mode, this throws TypeError + // In non-strict mode, it silently fails + expect(() => { + "use strict" + str[0] = "H" + }).toThrow(TypeError) + expect(str).toBe("hello") // Still "hello" + }) + + it('should create new string when "changing" with concatenation', () => { + let str = "hello" + str = "H" + str.slice(1) + expect(str).toBe("Hello") + }) + + it('should not modify original string with toUpperCase', () => { + let name = "Alice" + name.toUpperCase() // Creates "ALICE" but doesn't change 'name' + expect(name).toBe("Alice") // Still "Alice" + }) + }) + + describe('Number', () => { + it('should handle integers, decimals, negatives, and scientific notation', () => { + let integer = 42 + let decimal = 3.14 + let negative = -10 + let scientific = 2.5e6 + + expect(integer).toBe(42) + expect(decimal).toBe(3.14) + expect(negative).toBe(-10) + expect(scientific).toBe(2500000) + }) + + it('should return Infinity for division by zero', () => { + expect(1 / 0).toBe(Infinity) + expect(-1 / 0).toBe(-Infinity) + }) + + it('should return NaN for invalid operations', () => { + expect(Number.isNaN("hello" * 2)).toBe(true) + }) + + it('should demonstrate floating-point precision problem', () => { + expect(0.1 + 0.2).not.toBe(0.3) + expect(0.1 + 0.2).toBeCloseTo(0.3) + expect(0.1 + 0.2 === 0.3).toBe(false) + }) + + it('should have MAX_SAFE_INTEGER and MIN_SAFE_INTEGER', () => { + expect(Number.MAX_SAFE_INTEGER).toBe(9007199254740991) + expect(Number.MIN_SAFE_INTEGER).toBe(-9007199254740991) + }) + + it('should lose precision beyond safe integer range', () => { + // This demonstrates the precision loss + expect(9007199254740992 === 9007199254740993).toBe(true) // Wrong but expected + }) + }) + + describe('BigInt', () => { + it('should create BigInt with n suffix', () => { + let big = 9007199254740993n + expect(big).toBe(9007199254740993n) + }) + + it('should create BigInt from string', () => { + let alsoBig = BigInt("9007199254740993") + expect(alsoBig).toBe(9007199254740993n) + }) + + it('should perform accurate math with BigInt', () => { + let big = 9007199254740993n + expect(big + 1n).toBe(9007199254740994n) + }) + + it('should require explicit conversion between BigInt and Number', () => { + let big = 10n + let regular = 5 + + expect(big + BigInt(regular)).toBe(15n) + expect(Number(big) + regular).toBe(15) + }) + + it('should throw TypeError when mixing BigInt and Number without conversion', () => { + let big = 10n + let regular = 5 + + expect(() => big + regular).toThrow(TypeError) + }) + }) + + describe('Boolean', () => { + it('should have only two values: true and false', () => { + let isLoggedIn = true + let hasPermission = false + + expect(isLoggedIn).toBe(true) + expect(hasPermission).toBe(false) + }) + + it('should create boolean from comparisons', () => { + let age = 25 + let name = "Alice" + + let isAdult = age >= 18 + let isEqual = name === "Alice" + + expect(isAdult).toBe(true) + expect(isEqual).toBe(true) + }) + + describe('Falsy Values', () => { + it('should identify all 8 falsy values', () => { + expect(Boolean(false)).toBe(false) + expect(Boolean(0)).toBe(false) + expect(Boolean(-0)).toBe(false) + expect(Boolean(0n)).toBe(false) + expect(Boolean("")).toBe(false) + expect(Boolean(null)).toBe(false) + expect(Boolean(undefined)).toBe(false) + expect(Boolean(NaN)).toBe(false) + }) + }) + + describe('Truthy Values', () => { + it('should identify truthy values including surprises', () => { + expect(Boolean(true)).toBe(true) + expect(Boolean(1)).toBe(true) + expect(Boolean(-1)).toBe(true) + expect(Boolean("hello")).toBe(true) + expect(Boolean("0")).toBe(true) // Non-empty string is truthy! + expect(Boolean("false")).toBe(true) // Non-empty string is truthy! + expect(Boolean([])).toBe(true) // Empty array is truthy! + expect(Boolean({})).toBe(true) // Empty object is truthy! + expect(Boolean(function(){})).toBe(true) + expect(Boolean(Infinity)).toBe(true) + expect(Boolean(-Infinity)).toBe(true) + }) + }) + + it('should convert to boolean using Boolean() and double negation', () => { + let value = "hello" + let bool = Boolean(value) + let shortcut = !!value + + expect(bool).toBe(true) + expect(shortcut).toBe(true) + }) + }) + + describe('undefined', () => { + it('should be the default value for uninitialized variables', () => { + let x + expect(x).toBe(undefined) + }) + + it('should be the value for missing function parameters', () => { + function greet(name) { + return name + } + expect(greet()).toBe(undefined) + }) + + it('should be the return value of functions without return statement', () => { + function doNothing() { + // no return + } + expect(doNothing()).toBe(undefined) + }) + + it('should be the value for non-existent object properties', () => { + let person = { name: "Alice" } + expect(person.age).toBe(undefined) + }) + }) + + describe('null', () => { + it('should represent intentional absence of value', () => { + let user = { name: "Alice" } + user = null + expect(user).toBe(null) + }) + + it('should be used to indicate no result from functions', () => { + function findUser(id) { + // Simulating user not found + return null + } + expect(findUser(999)).toBe(null) + }) + + it('should have typeof return "object" (famous bug)', () => { + expect(typeof null).toBe("object") + }) + + it('should be checked with strict equality', () => { + let value = null + expect(value === null).toBe(true) + }) + }) + + describe('Symbol', () => { + it('should create unique symbols even with same description', () => { + let id1 = Symbol("id") + let id2 = Symbol("id") + + expect(id1 === id2).toBe(false) + }) + + it('should have accessible description', () => { + let id1 = Symbol("id") + expect(id1.description).toBe("id") + }) + + it('should work as unique object keys', () => { + const ID = Symbol("id") + const user = { + name: "Alice", + [ID]: 12345 + } + + expect(user.name).toBe("Alice") + expect(user[ID]).toBe(12345) + }) + + it('should not appear in Object.keys', () => { + const ID = Symbol("id") + const user = { + name: "Alice", + [ID]: 12345 + } + + expect(Object.keys(user)).toEqual(["name"]) + }) + }) + + describe('typeof Operator', () => { + it('should return correct types for primitives', () => { + expect(typeof "hello").toBe("string") + expect(typeof 42).toBe("number") + expect(typeof 42n).toBe("bigint") + expect(typeof true).toBe("boolean") + expect(typeof undefined).toBe("undefined") + expect(typeof Symbol()).toBe("symbol") + }) + + it('should return "object" for null (bug)', () => { + expect(typeof null).toBe("object") + }) + + it('should return "object" for objects and arrays', () => { + expect(typeof {}).toBe("object") + expect(typeof []).toBe("object") + }) + + it('should return "function" for functions', () => { + expect(typeof function(){}).toBe("function") + }) + }) + + describe('Immutability', () => { + it('should not modify original string with methods', () => { + let str = "hello" + + str.toUpperCase() // Returns "HELLO" + expect(str).toBe("hello") // Still "hello"! + }) + + it('should require reassignment to capture new value', () => { + let str = "hello" + str = str.toUpperCase() + expect(str).toBe("HELLO") + }) + }) + + describe('const vs Immutability', () => { + it('should prevent reassignment with const', () => { + const name = "Alice" + // name = "Bob" would throw TypeError + expect(name).toBe("Alice") + }) + + it('should allow mutation of const objects', () => { + const person = { name: "Alice" } + person.name = "Bob" // Works! + person.age = 25 // Works! + + expect(person.name).toBe("Bob") + expect(person.age).toBe(25) + }) + + it('should demonstrate primitives are immutable regardless of const/let', () => { + let str = "hello" + // In strict mode (which Vitest uses), this throws TypeError + // In non-strict mode, it silently fails + expect(() => { + str[0] = "H" + }).toThrow(TypeError) + expect(str).toBe("hello") + }) + }) + + describe('Autoboxing', () => { + it('should allow calling methods on primitive strings', () => { + expect("hello".toUpperCase()).toBe("HELLO") + }) + + it('should not modify the original primitive when calling methods', () => { + let str = "hello" + str.toUpperCase() + expect(str).toBe("hello") + }) + + it('should demonstrate wrapper objects are different from primitives', () => { + let strObj = new String("hello") + expect(typeof strObj).toBe("object") // Not "string"! + expect(strObj === "hello").toBe(false) // Object vs primitive + }) + + it('should create primitive strings, not wrapper objects', () => { + let str = "hello" + expect(typeof str).toBe("string") + }) + }) + + describe('null vs undefined Comparison', () => { + it('should show loose equality between null and undefined', () => { + expect(null == undefined).toBe(true) + }) + + it('should show strict inequality between null and undefined', () => { + expect(null === undefined).toBe(false) + }) + + it('should demonstrate checking for nullish values', () => { + let value = null + expect(value == null).toBe(true) + + value = undefined + expect(value == null).toBe(true) + }) + + it('should check for specific null', () => { + let value = null + expect(value === null).toBe(true) + }) + + it('should check for specific undefined', () => { + let value = undefined + expect(value === undefined).toBe(true) + }) + + it('should check for "has a value" (not null/undefined)', () => { + let value = "hello" + expect(value != null).toBe(true) + + value = 0 // 0 is a value, not nullish + expect(value != null).toBe(true) + }) + }) + + describe('JavaScript Quirks', () => { + it('should demonstrate NaN is not equal to itself', () => { + expect(NaN === NaN).toBe(false) + expect(NaN !== NaN).toBe(true) + }) + + it('should use Number.isNaN to check for NaN', () => { + expect(Number.isNaN(NaN)).toBe(true) + expect(Number.isNaN("hello")).toBe(false) + expect(isNaN("hello")).toBe(true) // Has quirks + }) + + it('should demonstrate empty string is falsy but whitespace is truthy', () => { + expect(Boolean("")).toBe(false) + expect(Boolean(" ")).toBe(true) + expect(Boolean("0")).toBe(true) + }) + + it('should demonstrate + operator string concatenation', () => { + expect(1 + 2).toBe(3) + expect("1" + "2").toBe("12") + expect(1 + "2").toBe("12") + expect("1" + 2).toBe("12") + expect(1 + 2 + "3").toBe("33") + expect("1" + 2 + 3).toBe("123") + }) + + it('should force number addition with explicit conversion', () => { + expect(Number("1") + Number("2")).toBe(3) + expect(parseInt("1") + parseInt("2")).toBe(3) + }) + + it('should force string concatenation with explicit conversion', () => { + expect(String(1) + String(2)).toBe("12") + expect(`${1}${2}`).toBe("12") + }) + }) + + describe('Type Checking Best Practices', () => { + it('should check for null explicitly', () => { + let value = null + expect(value === null).toBe(true) + }) + + it('should use Array.isArray for arrays', () => { + expect(Array.isArray([1, 2, 3])).toBe(true) + expect(Array.isArray("hello")).toBe(false) + }) + + it('should use Object.prototype.toString for precise type', () => { + expect(Object.prototype.toString.call(null)).toBe("[object Null]") + expect(Object.prototype.toString.call([])).toBe("[object Array]") + expect(Object.prototype.toString.call(new Date())).toBe("[object Date]") + }) + }) +}) diff --git a/tests/scope-and-closures/scope-and-closures.test.js b/tests/scope-and-closures/scope-and-closures.test.js new file mode 100644 index 00000000..ba680362 --- /dev/null +++ b/tests/scope-and-closures/scope-and-closures.test.js @@ -0,0 +1,657 @@ +import { describe, it, expect } from 'vitest' + +describe('Scope and Closures', () => { + describe('Scope Basics', () => { + describe('Preventing Naming Conflicts', () => { + it('should keep variables separate in different functions', () => { + function countApples() { + let count = 0 + count++ + return count + } + + function countOranges() { + let count = 0 + count++ + return count + } + + expect(countApples()).toBe(1) + expect(countOranges()).toBe(1) + }) + }) + + describe('Memory Management', () => { + it('should demonstrate scope cleanup concept', () => { + function processData() { + let hugeArray = new Array(1000).fill('x') + return hugeArray.length + } + + // hugeArray can be garbage collected after function returns + expect(processData()).toBe(1000) + }) + }) + + describe('Encapsulation', () => { + it('should hide implementation details', () => { + function createBankAccount() { + let balance = 0 + + return { + deposit(amount) { balance += amount }, + getBalance() { return balance } + } + } + + const account = createBankAccount() + account.deposit(100) + expect(account.getBalance()).toBe(100) + // balance is private - cannot access directly + }) + }) + }) + + describe('The Three Types of Scope', () => { + describe('Global Scope', () => { + it('should access global variables from anywhere', () => { + const appName = "MyApp" + let userCount = 0 + + function greet() { + userCount++ + return appName + } + + expect(greet()).toBe("MyApp") + expect(userCount).toBe(1) + }) + }) + + describe('Function Scope', () => { + it('should keep var variables within function', () => { + function calculateTotal() { + var subtotal = 100 + var tax = 10 + var total = subtotal + tax + return total + } + + expect(calculateTotal()).toBe(110) + // subtotal, tax, total are not accessible here + }) + + describe('var Hoisting', () => { + it('should demonstrate var hoisting behavior', () => { + function example() { + const first = message // undefined (not an error!) + var message = "Hello" + const second = message // "Hello" + return { first, second } + } + + const result = example() + expect(result.first).toBe(undefined) + expect(result.second).toBe("Hello") + }) + }) + }) + + describe('Block Scope', () => { + it('should keep let and const within blocks', () => { + let outsideBlock = "outside" + + if (true) { + let blockLet = "I'm block-scoped" + const blockConst = "Me too" + var functionVar = "I escape the block!" + outsideBlock = blockLet // Can access from inside + } + + expect(functionVar).toBe("I escape the block!") + expect(outsideBlock).toBe("I'm block-scoped") + // blockLet and blockConst are not accessible here + }) + + describe('Temporal Dead Zone', () => { + it('should throw ReferenceError when accessing let before declaration', () => { + function demo() { + // TDZ for 'name' starts here + const getName = () => name // This creates closure over TDZ variable + + let name = "Alice" // TDZ ends here + return name + } + + expect(demo()).toBe("Alice") + }) + + it('should demonstrate proper let declaration', () => { + function demo() { + let name = "Alice" + return name + } + + expect(demo()).toBe("Alice") + }) + }) + }) + }) + + describe('var vs let vs const', () => { + describe('Redeclaration', () => { + it('should allow var redeclaration', () => { + var name = "Alice" + var name = "Bob" // No error, silently overwrites + expect(name).toBe("Bob") + }) + + // Note: let and const redeclaration would cause SyntaxError + // which cannot be tested at runtime + }) + + describe('Reassignment', () => { + it('should allow var and let reassignment', () => { + var count = 1 + count = 2 + expect(count).toBe(2) + + let score = 100 + score = 200 + expect(score).toBe(200) + }) + + it('should allow const object mutation but not reassignment', () => { + const user = { name: "Alice" } + user.name = "Bob" // Works! + user.age = 25 // Works! + + expect(user.name).toBe("Bob") + expect(user.age).toBe(25) + // user = {} would throw TypeError + }) + }) + + describe('The Classic for-loop Problem', () => { + it('should demonstrate var problem with setTimeout', async () => { + const results = [] + + // Using var - only ONE i variable shared + for (var i = 0; i < 3; i++) { + // Capture current value using IIFE + ((j) => { + setTimeout(() => { + results.push(j) + }, 10) + })(i) + } + + await new Promise(resolve => setTimeout(resolve, 50)) + expect(results.sort()).toEqual([0, 1, 2]) + }) + + it('should demonstrate let solution with setTimeout', async () => { + const results = [] + + // Using let - each iteration gets its OWN i variable + for (let i = 0; i < 3; i++) { + setTimeout(() => { + results.push(i) + }, 10) + } + + await new Promise(resolve => setTimeout(resolve, 50)) + expect(results.sort()).toEqual([0, 1, 2]) + }) + }) + }) + + describe('Lexical Scope', () => { + it('should access variables from outer scopes', () => { + const outer = "I'm outside!" + + function outerFunction() { + const middle = "I'm in the middle!" + + function innerFunction() { + const inner = "I'm inside!" + return { inner, middle, outer } + } + + return innerFunction() + } + + const result = outerFunction() + expect(result.inner).toBe("I'm inside!") + expect(result.middle).toBe("I'm in the middle!") + expect(result.outer).toBe("I'm outside!") + }) + + describe('Scope Chain', () => { + it('should walk up the scope chain to find variables', () => { + const x = "global" + + function outer() { + const x = "outer" + + function inner() { + // Looks for x in inner scope first (not found) + // Then looks in outer scope (found!) + return x + } + + return inner() + } + + expect(outer()).toBe("outer") + }) + }) + + describe('Variable Shadowing', () => { + it('should shadow outer variables with inner declarations', () => { + const name = "Global" + + function greet() { + const name = "Function" // Shadows global 'name' + + function inner() { + const name = "Block" // Shadows function 'name' + return name + } + + return { inner: inner(), outer: name } + } + + const result = greet() + expect(result.inner).toBe("Block") + expect(result.outer).toBe("Function") + expect(name).toBe("Global") + }) + }) + }) + + describe('Closures', () => { + describe('Basic Closure', () => { + it('should remember variables from outer scope', () => { + function createGreeter(greeting) { + return function(name) { + return `${greeting}, ${name}!` + } + } + + const sayHello = createGreeter("Hello") + const sayHola = createGreeter("Hola") + + expect(sayHello("Alice")).toBe("Hello, Alice!") + expect(sayHola("Bob")).toBe("Hola, Bob!") + }) + }) + + describe('Data Privacy & Encapsulation', () => { + it('should create truly private variables', () => { + function createCounter() { + let count = 0 // Private variable! + + return { + increment() { + count++ + return count + }, + decrement() { + count-- + return count + }, + getCount() { + return count + } + } + } + + const counter = createCounter() + + expect(counter.getCount()).toBe(0) + expect(counter.increment()).toBe(1) + expect(counter.increment()).toBe(2) + expect(counter.decrement()).toBe(1) + expect(counter.count).toBe(undefined) // Cannot access directly! + }) + }) + + describe('Function Factories', () => { + it('should create specialized functions', () => { + function createMultiplier(multiplier) { + return function(number) { + return number * multiplier + } + } + + const double = createMultiplier(2) + const triple = createMultiplier(3) + const tenX = createMultiplier(10) + + expect(double(5)).toBe(10) + expect(triple(5)).toBe(15) + expect(tenX(5)).toBe(50) + }) + + it('should create API clients with base URL', () => { + function createApiClient(baseUrl) { + return { + getUrl(endpoint) { + return `${baseUrl}${endpoint}` + } + } + } + + const githubApi = createApiClient('https://api.github.com') + const myApi = createApiClient('https://myapp.com/api') + + expect(githubApi.getUrl('/users')).toBe('https://api.github.com/users') + expect(myApi.getUrl('/users')).toBe('https://myapp.com/api/users') + }) + }) + + describe('Preserving State in Callbacks', () => { + it('should maintain state across multiple calls', () => { + function setupClickCounter() { + let clicks = 0 + + return function handleClick() { + clicks++ + return clicks + } + } + + const handleClick = setupClickCounter() + + expect(handleClick()).toBe(1) + expect(handleClick()).toBe(2) + expect(handleClick()).toBe(3) + }) + }) + + describe('Memoization', () => { + it('should cache expensive computation results', () => { + function createMemoizedFunction(fn) { + const cache = {} + + return function(arg) { + if (arg in cache) { + return { value: cache[arg], cached: true } + } + + const result = fn(arg) + cache[arg] = result + return { value: result, cached: false } + } + } + + function factorial(n) { + if (n <= 1) return 1 + return n * factorial(n - 1) + } + + const memoizedFactorial = createMemoizedFunction(factorial) + + const first = memoizedFactorial(5) + expect(first.value).toBe(120) + expect(first.cached).toBe(false) + + const second = memoizedFactorial(5) + expect(second.value).toBe(120) + expect(second.cached).toBe(true) + }) + }) + }) + + describe('The Classic Closure Trap', () => { + describe('The Problem with var in Loops', () => { + it('should demonstrate the problem', () => { + const funcs = [] + + for (var i = 0; i < 3; i++) { + funcs.push(function() { + return i + }) + } + + // All functions return 3 because they share the same 'i' + expect(funcs[0]()).toBe(3) + expect(funcs[1]()).toBe(3) + expect(funcs[2]()).toBe(3) + }) + }) + + describe('Solution 1: Use let', () => { + it('should create new binding per iteration', () => { + const funcs = [] + + for (let i = 0; i < 3; i++) { + funcs.push(function() { + return i + }) + } + + expect(funcs[0]()).toBe(0) + expect(funcs[1]()).toBe(1) + expect(funcs[2]()).toBe(2) + }) + }) + + describe('Solution 2: IIFE', () => { + it('should capture value in IIFE scope', () => { + const funcs = [] + + for (var i = 0; i < 3; i++) { + (function(j) { + funcs.push(function() { + return j + }) + })(i) + } + + expect(funcs[0]()).toBe(0) + expect(funcs[1]()).toBe(1) + expect(funcs[2]()).toBe(2) + }) + }) + + describe('Solution 3: forEach', () => { + it('should create new scope per iteration', () => { + const funcs = [] + + ;[0, 1, 2].forEach(function(i) { + funcs.push(function() { + return i + }) + }) + + expect(funcs[0]()).toBe(0) + expect(funcs[1]()).toBe(1) + expect(funcs[2]()).toBe(2) + }) + }) + }) + + describe('Closure Memory Considerations', () => { + it('should demonstrate closure keeping references alive', () => { + function createClosure() { + const data = { value: 42 } + + return function() { + return data.value + } + } + + const getClosure = createClosure() + // data is kept alive because getClosure references it + expect(getClosure()).toBe(42) + }) + + it('should demonstrate cleanup with null assignment', () => { + function createHandler() { + let largeData = new Array(100).fill('data') + + const handler = function() { + return largeData.length + } + + return { + handler, + cleanup() { + largeData = null // Allow garbage collection + } + } + } + + const { handler, cleanup } = createHandler() + expect(handler()).toBe(100) + + cleanup() + // Now largeData can be garbage collected + }) + }) + + describe('Practical Closure Patterns', () => { + describe('Private State Pattern', () => { + it('should create objects with private state', () => { + function createWallet(initial) { + let balance = initial + + return { + spend(amount) { + if (amount <= balance) { + balance -= amount + return true + } + return false + }, + getBalance() { + return balance + } + } + } + + const wallet = createWallet(100) + expect(wallet.getBalance()).toBe(100) + expect(wallet.spend(30)).toBe(true) + expect(wallet.getBalance()).toBe(70) + expect(wallet.spend(100)).toBe(false) + expect(wallet.getBalance()).toBe(70) + }) + }) + + describe('Tax Calculator Factory', () => { + it('should create specialized tax calculators', () => { + function createTaxCalculator(rate) { + return (amount) => amount * rate + } + + const calculateVAT = createTaxCalculator(0.20) + const calculateGST = createTaxCalculator(0.10) + + expect(calculateVAT(100)).toBe(20) + expect(calculateGST(100)).toBe(10) + }) + }) + + describe('Logger Factory', () => { + it('should create prefixed loggers', () => { + function setupLogger(prefix) { + return (message) => `[${prefix}] ${message}` + } + + const errorLogger = setupLogger('ERROR') + const infoLogger = setupLogger('INFO') + + expect(errorLogger('Something went wrong')).toBe('[ERROR] Something went wrong') + expect(infoLogger('All good')).toBe('[INFO] All good') + }) + }) + + describe('Memoize Pattern', () => { + it('should cache results with closures', () => { + function memoize(fn) { + const cache = {} + return (arg) => cache[arg] ?? (cache[arg] = fn(arg)) + } + + let callCount = 0 + const expensive = (n) => { + callCount++ + return n * n + } + + const memoizedExpensive = memoize(expensive) + + expect(memoizedExpensive(5)).toBe(25) + expect(callCount).toBe(1) + + expect(memoizedExpensive(5)).toBe(25) + expect(callCount).toBe(1) // Still 1, used cache + + expect(memoizedExpensive(6)).toBe(36) + expect(callCount).toBe(2) + }) + }) + }) + + describe('Test Your Knowledge Examples', () => { + it('should identify all three scope types', () => { + const globalVar = "everywhere" // Global scope + + function example() { + var functionScoped = "function" // Function scope + + if (true) { + let blockScoped = "block" // Block scope + expect(blockScoped).toBe("block") + } + + expect(functionScoped).toBe("function") + } + + example() + expect(globalVar).toBe("everywhere") + }) + + it('should demonstrate closure definition', () => { + function createCounter() { + let count = 0 + + return function() { + count++ + return count + } + } + + const counter = createCounter() + expect(counter()).toBe(1) + expect(counter()).toBe(2) + }) + + it('should show var loop outputs 3,3,3 pattern', () => { + const results = [] + + for (var i = 0; i < 3; i++) { + results.push(() => i) + } + + expect(results[0]()).toBe(3) + expect(results[1]()).toBe(3) + expect(results[2]()).toBe(3) + }) + + it('should show let loop outputs 0,1,2 pattern', () => { + const results = [] + + for (let i = 0; i < 3; i++) { + results.push(() => i) + } + + expect(results[0]()).toBe(0) + expect(results[1]()).toBe(1) + expect(results[2]()).toBe(2) + }) + }) +}) diff --git a/tests/type-coercion/type-coercion.test.js b/tests/type-coercion/type-coercion.test.js new file mode 100644 index 00000000..aefc9b19 --- /dev/null +++ b/tests/type-coercion/type-coercion.test.js @@ -0,0 +1,506 @@ +import { describe, it, expect } from 'vitest' + +describe('Type Coercion', () => { + describe('Explicit vs Implicit Coercion', () => { + describe('Explicit Coercion', () => { + it('should convert to number explicitly', () => { + expect(Number("42")).toBe(42) + expect(parseInt("42px")).toBe(42) + expect(parseFloat("3.14")).toBe(3.14) + }) + + it('should convert to string explicitly', () => { + expect(String(42)).toBe("42") + expect((123).toString()).toBe("123") + }) + + it('should convert to boolean explicitly', () => { + expect(Boolean(1)).toBe(true) + expect(Boolean(0)).toBe(false) + }) + }) + + describe('Implicit Coercion', () => { + it('should demonstrate implicit string coercion with +', () => { + expect("5" + 3).toBe("53") + expect("5" - 3).toBe(2) + }) + + it('should demonstrate implicit boolean coercion in conditions', () => { + let result = null + if ("hello") { + result = "truthy" + } + expect(result).toBe("truthy") + }) + + it('should demonstrate loose equality coercion', () => { + expect(5 == "5").toBe(true) + }) + }) + }) + + describe('String Conversion', () => { + it('should convert various types to string explicitly', () => { + expect(String(123)).toBe("123") + expect(String(true)).toBe("true") + expect(String(false)).toBe("false") + expect(String(null)).toBe("null") + expect(String(undefined)).toBe("undefined") + }) + + it('should convert to string implicitly with + and empty string', () => { + expect(123 + "").toBe("123") + expect(`Value: ${123}`).toBe("Value: 123") + expect("Hello " + 123).toBe("Hello 123") + }) + + it('should convert arrays to comma-separated strings', () => { + expect([1, 2, 3].toString()).toBe("1,2,3") + expect([1, 2, 3] + "").toBe("1,2,3") + expect([].toString()).toBe("") + }) + + it('should convert objects to [object Object]', () => { + expect({}.toString()).toBe("[object Object]") + expect({} + "").toBe("[object Object]") + }) + + describe('The + Operator Split Personality', () => { + it('should add two numbers', () => { + expect(5 + 3).toBe(8) + }) + + it('should concatenate with any string involved', () => { + expect("5" + 3).toBe("53") + expect(5 + "3").toBe("53") + expect("Hello" + " World").toBe("Hello World") + }) + + it('should evaluate left to right with multiple operands', () => { + expect(1 + 2 + "3").toBe("33") // (1+2)=3, then 3+"3"="33" + expect("1" + 2 + 3).toBe("123") // all become strings left-to-right + }) + }) + + describe('User Input Gotcha', () => { + it('should demonstrate string concatenation instead of addition', () => { + const userInput = "5" + const wrongResult = userInput + 10 + expect(wrongResult).toBe("510") + + const correctResult = Number(userInput) + 10 + expect(correctResult).toBe(15) + }) + }) + }) + + describe('Number Conversion', () => { + it('should convert strings to numbers', () => { + expect(Number("42")).toBe(42) + expect(Number(" 123 ")).toBe(123) // Whitespace trimmed + expect(Number.isNaN(Number("123abc"))).toBe(true) // NaN + expect(Number("")).toBe(0) // Empty string → 0 + expect(Number(" ")).toBe(0) // Whitespace-only → 0 + }) + + it('should convert booleans to numbers', () => { + expect(Number(true)).toBe(1) + expect(Number(false)).toBe(0) + }) + + it('should demonstrate null vs undefined conversion difference', () => { + expect(Number(null)).toBe(0) + expect(Number.isNaN(Number(undefined))).toBe(true) + expect(null + 5).toBe(5) + expect(Number.isNaN(undefined + 5)).toBe(true) + }) + + it('should convert arrays to numbers', () => { + expect(Number([])).toBe(0) // [] → "" → 0 + expect(Number([1])).toBe(1) // [1] → "1" → 1 + expect(Number.isNaN(Number([1, 2]))).toBe(true) // [1,2] → "1,2" → NaN + }) + + it('should return NaN for objects', () => { + expect(Number.isNaN(Number({}))).toBe(true) + }) + + describe('Math Operators Always Convert to Numbers', () => { + it('should convert operands to numbers for -, *, /, %', () => { + expect("6" - "2").toBe(4) + expect("6" * "2").toBe(12) + expect("6" / "2").toBe(3) + expect("10" % "3").toBe(1) + }) + + it('should show why - and + behave differently', () => { + expect("5" + 3).toBe("53") // concatenation + expect("5" - 3).toBe(2) // math + }) + }) + + describe('Unary + Trick', () => { + it('should convert to number with unary +', () => { + expect(+"42").toBe(42) + expect(+true).toBe(1) + expect(+false).toBe(0) + expect(+null).toBe(0) + expect(Number.isNaN(+undefined)).toBe(true) + expect(Number.isNaN(+"hello")).toBe(true) + expect(+"").toBe(0) + }) + }) + }) + + describe('Boolean Conversion', () => { + describe('The 8 Falsy Values', () => { + it('should identify all 8 falsy values', () => { + expect(Boolean(false)).toBe(false) + expect(Boolean(0)).toBe(false) + expect(Boolean(-0)).toBe(false) + expect(Boolean(0n)).toBe(false) + expect(Boolean("")).toBe(false) + expect(Boolean(null)).toBe(false) + expect(Boolean(undefined)).toBe(false) + expect(Boolean(NaN)).toBe(false) + }) + }) + + describe('Truthy Values', () => { + it('should identify truthy values including surprises', () => { + expect(Boolean(true)).toBe(true) + expect(Boolean(1)).toBe(true) + expect(Boolean(-1)).toBe(true) + expect(Boolean("hello")).toBe(true) + expect(Boolean("0")).toBe(true) // Non-empty string! + expect(Boolean("false")).toBe(true) // Non-empty string! + expect(Boolean([])).toBe(true) // Empty array! + expect(Boolean({})).toBe(true) // Empty object! + expect(Boolean(function(){})).toBe(true) + expect(Boolean(new Date())).toBe(true) + expect(Boolean(Infinity)).toBe(true) + expect(Boolean(-Infinity)).toBe(true) + }) + }) + + describe('Common Gotchas', () => { + it('should demonstrate empty array checking', () => { + const arr = [] + expect(Boolean(arr)).toBe(true) // Array itself is truthy + expect(arr.length > 0).toBe(false) // Check length instead + }) + }) + + describe('Logical Operators Return Original Values', () => { + it('should return first truthy value with ||', () => { + expect("hello" || "world").toBe("hello") + expect("" || "world").toBe("world") + expect("" || 0 || null || "yes").toBe("yes") + }) + + it('should return first falsy value with &&', () => { + expect("hello" && "world").toBe("world") + expect("" && "world").toBe("") + expect(1 && 2 && 3).toBe(3) + }) + + it('should use || for defaults', () => { + const userInput = "" + const name = userInput || "Anonymous" + expect(name).toBe("Anonymous") + }) + }) + }) + + describe('Object to Primitive Conversion', () => { + describe('Built-in Object Conversion', () => { + it('should convert arrays via toString', () => { + expect([1, 2, 3].toString()).toBe("1,2,3") + expect([1, 2, 3] + "").toBe("1,2,3") + expect(Number.isNaN([1, 2, 3] - 0)).toBe(true) // "1,2,3" → NaN + + expect([].toString()).toBe("") + expect([] + "").toBe("") + expect([] - 0).toBe(0) // "" → 0 + + expect([1].toString()).toBe("1") + expect([1] - 0).toBe(1) + }) + + it('should convert plain objects to [object Object]', () => { + expect({}.toString()).toBe("[object Object]") + expect({} + "").toBe("[object Object]") + }) + }) + + describe('Custom valueOf and toString', () => { + it('should use valueOf for number conversion', () => { + const price = { + amount: 99.99, + currency: "USD", + valueOf() { + return this.amount + }, + toString() { + return `${this.currency} ${this.amount}` + } + } + + expect(price - 0).toBe(99.99) + expect(price * 2).toBe(199.98) + expect(+price).toBe(99.99) + }) + + it('should use toString for string conversion', () => { + const price = { + amount: 99.99, + currency: "USD", + valueOf() { + return this.amount + }, + toString() { + return `${this.currency} ${this.amount}` + } + } + + expect(String(price)).toBe("USD 99.99") + expect(`Price: ${price}`).toBe("Price: USD 99.99") + }) + }) + + describe('Symbol.toPrimitive', () => { + it('should use Symbol.toPrimitive for conversion hints', () => { + const obj = { + [Symbol.toPrimitive](hint) { + if (hint === "number") { + return 42 + } + if (hint === "string") { + return "forty-two" + } + return "default value" + } + } + + expect(+obj).toBe(42) // hint: "number" + expect(`${obj}`).toBe("forty-two") // hint: "string" + expect(obj + "").toBe("default value") // hint: "default" + }) + }) + }) + + describe('The == Algorithm', () => { + describe('Same Type Comparison', () => { + it('should compare directly when same type', () => { + expect(5 == 5).toBe(true) + expect("hello" == "hello").toBe(true) + }) + }) + + describe('null and undefined', () => { + it('should return true for null == undefined', () => { + expect(null == undefined).toBe(true) + expect(undefined == null).toBe(true) + }) + + it('should return false for null/undefined vs other values', () => { + expect(null == 0).toBe(false) + expect(null == "").toBe(false) + expect(undefined == 0).toBe(false) + expect(undefined == "").toBe(false) + }) + }) + + describe('Number and String', () => { + it('should convert string to number', () => { + expect(5 == "5").toBe(true) + expect(0 == "").toBe(true) + expect(42 == "42").toBe(true) + }) + }) + + describe('Boolean Conversion', () => { + it('should convert boolean to number first', () => { + expect(true == 1).toBe(true) + expect(false == 0).toBe(true) + expect(true == "1").toBe(true) + }) + + it('should demonstrate confusing boolean comparisons', () => { + expect(true == "true").toBe(false) // true → 1, "true" → NaN + expect(false == "false").toBe(false) // false → 0, "false" → NaN + expect(true == 2).toBe(false) // true → 1, 1 ≠ 2 + }) + }) + + describe('Object to Primitive', () => { + it('should convert object to primitive', () => { + expect([1] == 1).toBe(true) // [1] → "1" → 1 + expect([""] == 0).toBe(true) // [""] → "" → 0 + }) + }) + + describe('Step-by-Step Examples', () => { + it('should trace "5" == 5', () => { + // String vs Number → convert string to number + // 5 == 5 → true + expect("5" == 5).toBe(true) + }) + + it('should trace true == "1"', () => { + // Boolean → number: 1 == "1" + // Number vs String → 1 == 1 + // true + expect(true == "1").toBe(true) + }) + + it('should trace [] == false', () => { + // Boolean → number: [] == 0 + // Object → primitive: "" == 0 + // String → number: 0 == 0 + // true + expect([] == false).toBe(true) + }) + + it('should trace [] == ![]', () => { + // First, evaluate ![] → false (arrays are truthy) + // [] == false + // Boolean → number: [] == 0 + // Object → primitive: "" == 0 + // String → number: 0 == 0 + // true! + expect([] == ![]).toBe(true) + }) + }) + }) + + describe('JavaScript WAT Moments', () => { + describe('+ Operator Split Personality', () => { + it('should show string vs number behavior', () => { + expect("5" + 3).toBe("53") + expect("5" - 3).toBe(2) + }) + }) + + describe('Empty Array Weirdness', () => { + it('should demonstrate [] + [] behavior', () => { + expect([] + []).toBe("") // Both → "", then "" + "" = "" + }) + + it('should demonstrate [] + {} behavior', () => { + expect([] + {}).toBe("[object Object]") + }) + }) + + describe('Boolean Math', () => { + it('should add booleans as numbers', () => { + expect(true + true).toBe(2) + expect(true + false).toBe(1) + expect(true - true).toBe(0) + }) + }) + + describe('The Infamous [] == ![]', () => { + it('should return true for [] == ![]', () => { + const emptyArr = [] + const negatedArr = ![] + expect(emptyArr == negatedArr).toBe(true) + expect(emptyArr === negatedArr).toBe(false) + }) + }) + + describe('"foo" + + "bar"', () => { + it('should return "fooNaN"', () => { + // +"bar" is evaluated first → NaN + // "foo" + NaN → "fooNaN" + expect("foo" + + "bar").toBe("fooNaN") + }) + }) + + describe('NaN Equality', () => { + it('should never equal itself', () => { + expect(NaN === NaN).toBe(false) + expect(NaN == NaN).toBe(false) + }) + + it('should use Number.isNaN to check', () => { + expect(Number.isNaN(NaN)).toBe(true) + expect(isNaN(NaN)).toBe(true) + expect(isNaN("hello")).toBe(true) // Quirk: converts first + expect(Number.isNaN("hello")).toBe(false) // Correct + }) + }) + + describe('typeof Quirks', () => { + it('should demonstrate typeof oddities', () => { + expect(typeof NaN).toBe("number") // "Not a Number" is a number + expect(typeof null).toBe("object") // Historical bug + expect(typeof []).toBe("object") // Arrays are objects + expect(typeof function(){}).toBe("function") // Special case + }) + }) + + describe('Adding Arrays', () => { + it('should concatenate arrays as strings', () => { + expect([1, 2] + [3, 4]).toBe("1,23,4") + // [1, 2] → "1,2" + // [3, 4] → "3,4" + // "1,2" + "3,4" → "1,23,4" + }) + + it('should use spread or concat to combine arrays', () => { + expect([...[1, 2], ...[3, 4]]).toEqual([1, 2, 3, 4]) + expect([1, 2].concat([3, 4])).toEqual([1, 2, 3, 4]) + }) + }) + }) + + describe('Best Practices', () => { + describe('Use === instead of ==', () => { + it('should show predictable strict equality', () => { + expect(5 === "5").toBe(false) // No coercion + expect(5 == "5").toBe(true) // Coerced + }) + }) + + describe('When == IS Useful', () => { + it('should check for null or undefined in one shot', () => { + function checkNullish(value) { + return value == null + } + + expect(checkNullish(null)).toBe(true) + expect(checkNullish(undefined)).toBe(true) + expect(checkNullish(0)).toBe(false) + expect(checkNullish("")).toBe(false) + }) + }) + + describe('Be Explicit with Conversions', () => { + it('should convert explicitly for clarity', () => { + const str = "42" + + // Quick string conversion + expect(str + "").toBe("42") + expect(String(42)).toBe("42") + + // Quick number conversion + expect(+str).toBe(42) + expect(Number(str)).toBe(42) + }) + }) + }) + + describe('Test Your Knowledge Examples', () => { + it('should return "53" for "5" + 3', () => { + expect("5" + 3).toBe("53") + }) + + it('should return "1hello" for true + false + "hello"', () => { + // true + false = 1 + 0 = 1 + // 1 + "hello" = "1hello" + expect(true + false + "hello").toBe("1hello") + }) + }) +}) diff --git a/tests/value-reference-types/value-reference-types.test.js b/tests/value-reference-types/value-reference-types.test.js new file mode 100644 index 00000000..c3d87c84 --- /dev/null +++ b/tests/value-reference-types/value-reference-types.test.js @@ -0,0 +1,557 @@ +import { describe, it, expect } from 'vitest' + +describe('Value Types and Reference Types', () => { + describe('Copying Primitives', () => { + it('should create independent copies when copying primitives', () => { + let a = 10 + let b = a // b gets a COPY of the value 10 + + b = 20 // changing b has NO effect on a + + expect(a).toBe(10) // unchanged! + expect(b).toBe(20) + }) + + it('should demonstrate string variables are independent copies', () => { + let name = "Alice" + let age = 25 + let user = { name: "Alice" } // Reference on stack, object on heap + let scores = [95, 87, 92] // Reference on stack, array on heap + + expect(name).toBe("Alice") + expect(age).toBe(25) + expect(user).toEqual({ name: "Alice" }) + expect(scores).toEqual([95, 87, 92]) + }) + }) + + describe('Copying Objects', () => { + it('should share reference when copying objects', () => { + let obj1 = { name: "Alice" } + let obj2 = obj1 // obj2 gets a COPY of the REFERENCE + + obj2.name = "Bob" // modifies the SAME object! + + expect(obj1.name).toBe("Bob") // changed! + expect(obj2.name).toBe("Bob") + }) + + it('should share reference when copying arrays', () => { + let arr1 = [1, 2, 3] + let arr2 = arr1 // arr2 points to the SAME array + + arr2.push(4) // modifies the shared array + + expect(arr1).toEqual([1, 2, 3, 4]) // changed! + expect(arr2).toEqual([1, 2, 3, 4]) + }) + }) + + describe('Comparison Behavior', () => { + describe('Primitives: Compared by Value', () => { + it('should return true for equal primitive values', () => { + let a = "hello" + let b = "hello" + expect(a === b).toBe(true) + + let x = 42 + let y = 42 + expect(x === y).toBe(true) + }) + }) + + describe('Objects: Compared by Reference', () => { + it('should return false for different objects with same content', () => { + let obj1 = { name: "Alice" } + let obj2 = { name: "Alice" } + expect(obj1 === obj2).toBe(false) // different objects! + }) + + it('should return true for same reference', () => { + let obj1 = { name: "Alice" } + let obj3 = obj1 + expect(obj1 === obj3).toBe(true) // same reference + }) + + it('should return false for empty objects/arrays compared', () => { + // These tests intentionally demonstrate that objects compare by reference + const emptyObj1 = {} + const emptyObj2 = {} + expect(emptyObj1 === emptyObj2).toBe(false) + + const emptyArr1 = [] + const emptyArr2 = [] + expect(emptyArr1 === emptyArr2).toBe(false) + + const arr1 = [1, 2] + const arr2 = [1, 2] + expect(arr1 === arr2).toBe(false) + }) + }) + + describe('Comparing Objects by Content', () => { + it('should use JSON.stringify for simple comparison', () => { + let obj1 = { name: "Alice" } + let obj2 = { name: "Alice" } + expect(JSON.stringify(obj1) === JSON.stringify(obj2)).toBe(true) + }) + + it('should compare arrays of primitives with every()', () => { + let arr1 = [1, 2, 3] + let arr2 = [1, 2, 3] + expect(arr1.length === arr2.length && arr1.every((v, i) => v === arr2[i])).toBe(true) + }) + }) + }) + + describe('Functions and Parameters', () => { + describe('Passing Primitives', () => { + it('should not modify original when passing primitive to function', () => { + function double(num) { + num = num * 2 + return num + } + + let x = 10 + let result = double(x) + + expect(x).toBe(10) // unchanged! + expect(result).toBe(20) + }) + }) + + describe('Passing Objects', () => { + it('should modify original object when mutating through function parameter', () => { + function rename(person) { + person.name = "Bob" + } + + let user = { name: "Alice" } + rename(user) + + expect(user.name).toBe("Bob") // changed! + }) + + it('should not modify original when reassigning parameter', () => { + function replace(person) { + person = { name: "Charlie" } // creates NEW local object + } + + let user = { name: "Alice" } + replace(user) + + expect(user.name).toBe("Alice") // unchanged! + }) + }) + }) + + describe('Mutation vs Reassignment', () => { + describe('Mutation', () => { + it('should modify array with mutating methods', () => { + const arr = [1, 2, 3] + + arr.push(4) + expect(arr).toEqual([1, 2, 3, 4]) + + arr[0] = 99 + expect(arr).toEqual([99, 2, 3, 4]) + + arr.pop() + expect(arr).toEqual([99, 2, 3]) + }) + + it('should modify object properties', () => { + const obj = { name: "Alice" } + + obj.name = "Bob" + expect(obj.name).toBe("Bob") + + obj.age = 25 + expect(obj.age).toBe(25) + + delete obj.age + expect(obj.age).toBe(undefined) + }) + }) + + describe('Reassignment', () => { + it('should point to new value after reassignment', () => { + let arr = [1, 2, 3] + arr = [4, 5, 6] + expect(arr).toEqual([4, 5, 6]) + + let obj = { name: "Alice" } + obj = { name: "Bob" } + expect(obj).toEqual({ name: "Bob" }) + }) + }) + + describe('const with Objects', () => { + it('should allow mutations on const objects', () => { + const arr = [1, 2, 3] + + arr.push(4) + expect(arr).toEqual([1, 2, 3, 4]) + + arr[0] = 99 + expect(arr).toEqual([99, 2, 3, 4]) + }) + + it('should allow mutations on const object properties', () => { + const obj = { name: "Alice" } + + obj.name = "Bob" + expect(obj.name).toBe("Bob") + + obj.age = 25 + expect(obj.age).toBe(25) + }) + + it('should throw TypeError when reassigning const', () => { + expect(() => { + eval('const x = 1; x = 2') // Using eval to test const reassignment + }).toThrow() + }) + }) + }) + + describe('Object.freeze()', () => { + it('should throw TypeError when modifying frozen object in strict mode', () => { + const user = Object.freeze({ name: "Alice", age: 25 }) + + // In strict mode (which Vitest uses), modifications throw TypeError + expect(() => { user.name = "Bob" }).toThrow(TypeError) + expect(() => { user.email = "a@b.com" }).toThrow(TypeError) + expect(() => { delete user.age }).toThrow(TypeError) + + expect(user).toEqual({ name: "Alice", age: 25 }) // unchanged! + }) + + it('should check if object is frozen', () => { + const frozen = Object.freeze({ a: 1 }) + const normal = { a: 1 } + + expect(Object.isFrozen(frozen)).toBe(true) + expect(Object.isFrozen(normal)).toBe(false) + }) + + it('should only freeze shallow - nested objects can still be modified', () => { + const user = Object.freeze({ + name: "Alice", + address: { city: "NYC" } + }) + + // In strict mode, modifying frozen property throws TypeError + expect(() => { user.name = "Bob" }).toThrow(TypeError) + // But nested object is not frozen, so this works + user.address.city = "LA" + + expect(user.name).toBe("Alice") // unchanged + expect(user.address.city).toBe("LA") // changed! + }) + }) + + describe('Deep Freeze', () => { + it('should freeze nested objects with deep freeze function', () => { + function deepFreeze(obj) { + const propNames = Reflect.ownKeys(obj) + + for (const name of propNames) { + const value = obj[name] + if (value && typeof value === "object") { + deepFreeze(value) + } + } + + return Object.freeze(obj) + } + + const user = deepFreeze({ + name: "Alice", + address: { city: "NYC" } + }) + + // In strict mode, this throws TypeError since nested object is now frozen + expect(() => { user.address.city = "LA" }).toThrow(TypeError) + expect(user.address.city).toBe("NYC") // Now blocked! + }) + }) + + describe('Object.seal() and Object.preventExtensions()', () => { + it('should allow value changes but prevent add/delete with seal()', () => { + const sealed = Object.seal({ name: "Alice" }) + + sealed.name = "Bob" + expect(sealed.name).toBe("Bob") // Works! + + // In strict mode, these throw TypeError instead of failing silently + expect(() => { sealed.age = 25 }).toThrow(TypeError) + expect(sealed.age).toBe(undefined) + + expect(() => { delete sealed.name }).toThrow(TypeError) + expect(sealed.name).toBe("Bob") + }) + + it('should allow change/delete but prevent add with preventExtensions()', () => { + const noExtend = Object.preventExtensions({ name: "Alice" }) + + noExtend.name = "Bob" + expect(noExtend.name).toBe("Bob") // Works! + + delete noExtend.name + expect(noExtend.name).toBe(undefined) // Works! + + // In strict mode, adding properties throws TypeError + expect(() => { noExtend.age = 25 }).toThrow(TypeError) + expect(noExtend.age).toBe(undefined) + }) + }) + + describe('Shallow Copy', () => { + it('should create shallow copy with spread operator', () => { + const original = { + name: "Alice", + scores: [95, 87, 92], + address: { city: "NYC" } + } + + const copy1 = { ...original } + + expect(copy1.name).toBe("Alice") + expect(copy1).not.toBe(original) // Different objects + }) + + it('should create shallow copy with Object.assign', () => { + const original = { name: "Alice" } + const copy2 = Object.assign({}, original) + + expect(copy2.name).toBe("Alice") + expect(copy2).not.toBe(original) + }) + + it('should share nested objects in shallow copy', () => { + const original = { + name: "Alice", + address: { city: "NYC" } + } + + const shallow = { ...original } + + // Top-level changes are independent + shallow.name = "Bob" + expect(original.name).toBe("Alice") + + // But nested objects are SHARED + shallow.address.city = "LA" + expect(original.address.city).toBe("LA") // Original changed! + }) + + it('should create shallow copy of arrays', () => { + const originalArray = [1, 2, 3] + + const arrCopy1 = [...originalArray] + const arrCopy2 = originalArray.slice() + const arrCopy3 = Array.from(originalArray) + + expect(arrCopy1).toEqual([1, 2, 3]) + expect(arrCopy2).toEqual([1, 2, 3]) + expect(arrCopy3).toEqual([1, 2, 3]) + + expect(arrCopy1).not.toBe(originalArray) + expect(arrCopy2).not.toBe(originalArray) + expect(arrCopy3).not.toBe(originalArray) + }) + }) + + describe('Deep Copy', () => { + it('should create deep copy with structuredClone', () => { + const original = { + name: "Alice", + scores: [95, 87, 92], + address: { city: "NYC" }, + date: new Date() + } + + const deep = structuredClone(original) + + // Everything is independent + deep.address.city = "LA" + expect(original.address.city).toBe("NYC") // unchanged! + + deep.scores.push(100) + expect(original.scores).toEqual([95, 87, 92]) // unchanged! + }) + + it('should create deep copy with JSON trick (with limitations)', () => { + const original = { + name: "Alice", + address: { city: "NYC" } + } + + const deep = JSON.parse(JSON.stringify(original)) + + deep.address.city = "LA" + expect(original.address.city).toBe("NYC") // unchanged! + }) + + it('should demonstrate JSON trick limitations', () => { + const obj = { + fn: () => {}, + date: new Date('2025-01-01'), + undef: undefined, + set: new Set([1, 2]) + } + + const clone = JSON.parse(JSON.stringify(obj)) + + expect(clone.fn).toBe(undefined) // Functions lost + expect(typeof clone.date).toBe('string') // Date becomes string + expect(clone.undef).toBe(undefined) // Property removed + expect(clone.set).toEqual({}) // Set becomes empty object + }) + }) + + describe('Array Methods: Mutating vs Non-Mutating', () => { + describe('Mutating Methods', () => { + it('should mutate array with push, pop, shift, unshift', () => { + const arr = [1, 2, 3] + + arr.push(4) + expect(arr).toEqual([1, 2, 3, 4]) + + arr.pop() + expect(arr).toEqual([1, 2, 3]) + + arr.shift() + expect(arr).toEqual([2, 3]) + + arr.unshift(1) + expect(arr).toEqual([1, 2, 3]) + }) + + it('should mutate array with sort and reverse', () => { + const nums = [3, 1, 2] + nums.sort() + expect(nums).toEqual([1, 2, 3]) // Original mutated! + + nums.reverse() + expect(nums).toEqual([3, 2, 1]) // Original mutated! + }) + + it('should mutate array with splice', () => { + const arr = [1, 2, 3, 4, 5] + arr.splice(2, 1) // Remove 1 element at index 2 + expect(arr).toEqual([1, 2, 4, 5]) + }) + }) + + describe('Non-Mutating Methods', () => { + it('should not mutate with map, filter, slice, concat', () => { + const original = [1, 2, 3] + + const mapped = original.map(x => x * 2) + expect(original).toEqual([1, 2, 3]) + expect(mapped).toEqual([2, 4, 6]) + + const filtered = original.filter(x => x > 1) + expect(original).toEqual([1, 2, 3]) + expect(filtered).toEqual([2, 3]) + + const sliced = original.slice(1) + expect(original).toEqual([1, 2, 3]) + expect(sliced).toEqual([2, 3]) + + const concatenated = original.concat([4, 5]) + expect(original).toEqual([1, 2, 3]) + expect(concatenated).toEqual([1, 2, 3, 4, 5]) + }) + + it('should use toSorted and toReversed for non-mutating sort/reverse (ES2023)', () => { + const nums = [3, 1, 2] + + const sorted = nums.toSorted() + expect(nums).toEqual([3, 1, 2]) // Original unchanged + expect(sorted).toEqual([1, 2, 3]) + + const reversed = nums.toReversed() + expect(nums).toEqual([3, 1, 2]) // Original unchanged + expect(reversed).toEqual([2, 1, 3]) + }) + }) + + describe('Safe Sorting Pattern', () => { + it('should copy array before sorting to avoid mutation', () => { + const nums = [3, 1, 2] + const sorted = [...nums].sort() + + expect(nums).toEqual([3, 1, 2]) // Original unchanged + expect(sorted).toEqual([1, 2, 3]) + }) + }) + }) + + describe('Common Pitfalls', () => { + it('should demonstrate accidental array mutation in function', () => { + function processUsers(users) { + const copy = [...users] + copy.push({ name: "New User" }) + return copy + } + + const myUsers = [{ name: "Alice" }] + const result = processUsers(myUsers) + + expect(myUsers).toEqual([{ name: "Alice" }]) // Original unchanged + expect(result).toEqual([{ name: "Alice" }, { name: "New User" }]) + }) + + it('should demonstrate backup pattern failure', () => { + const original = [1, 2, 3] + const notABackup = original // NOT a backup! + + original.push(4) + expect(notABackup).toEqual([1, 2, 3, 4]) // "backup" changed! + + // Correct backup + const original2 = [1, 2, 3] + const backup = [...original2] + + original2.push(4) + expect(backup).toEqual([1, 2, 3]) // Real backup unchanged + }) + + it('should demonstrate deep equality comparison', () => { + function deepEqual(a, b) { + return JSON.stringify(a) === JSON.stringify(b) + } + + const obj1 = { name: "Alice", age: 25 } + const obj2 = { name: "Alice", age: 25 } + + expect(obj1 === obj2).toBe(false) + expect(deepEqual(obj1, obj2)).toBe(true) + }) + }) + + describe('Best Practices: Immutable Patterns', () => { + it('should create new object instead of mutating', () => { + const user = { name: "Alice", age: 25 } + + // Instead of: user.name = "Bob" + const updatedUser = { ...user, name: "Bob" } + + expect(user.name).toBe("Alice") // Original unchanged + expect(updatedUser.name).toBe("Bob") + }) + + it('should use non-mutating array methods', () => { + const numbers = [3, 1, 2] + + // Instead of: numbers.sort() + const sorted = [...numbers].sort((a, b) => a - b) + + expect(numbers).toEqual([3, 1, 2]) // Original unchanged + expect(sorted).toEqual([1, 2, 3]) + }) + }) +}) diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 00000000..3bf505d5 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['tests/**/*.test.js'], + globals: false, + environment: 'node' + } +}) From ce9fc4fe1ddf6a978f2880d3ddf5f6900a98d8a6 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 21:39:14 -0300 Subject: [PATCH 013/128] feat: expand test coverage and reorganize test structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move tests to tests/fundamentals/ for better organization - Add @vitest/coverage-v8 for test coverage support - Add 66 new tests across all 6 fundamentals concepts (288 → 354) New tests added: - call-stack: setter recursion bug and fix (+2) - primitive-types: Intl.NumberFormat, floating-point solutions, array holes, trim checks (+11) - value-reference-types: structuredClone with Map/Set, WeakMap, shared default pitfall (+11) - type-coercion: modulo, comparison operators, double negation, Date coercion (+14) - equality-operators: -0 edge cases, NaN Map key, isSafeInteger, string sorting (+19) - scope-and-closures: TDZ ReferenceError, hoisting comparison (+9) Update CLAUDE.md with new test counts and improved documentation --- CLAUDE.md | 228 +++++++++++--- package-lock.json | 283 ++++++++++++++++++ package.json | 1 + .../call-stack/call-stack.test.js | 26 ++ .../equality-operators.test.js | 132 ++++++++ .../primitive-types/primitive-types.test.js | 111 +++++++ .../scope-and-closures.test.js | 102 +++++++ .../type-coercion/type-coercion.test.js | 129 ++++++++ .../value-reference-types.test.js | 175 +++++++++++ 9 files changed, 1141 insertions(+), 46 deletions(-) rename tests/{ => fundamentals}/call-stack/call-stack.test.js (89%) rename tests/{ => fundamentals}/equality-operators/equality-operators.test.js (82%) rename tests/{ => fundamentals}/primitive-types/primitive-types.test.js (79%) rename tests/{ => fundamentals}/scope-and-closures/scope-and-closures.test.js (86%) rename tests/{ => fundamentals}/type-coercion/type-coercion.test.js (79%) rename tests/{ => fundamentals}/value-reference-types/value-reference-types.test.js (76%) diff --git a/CLAUDE.md b/CLAUDE.md index 4a9c8ee8..9b288e1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,15 @@ The project was recognized by GitHub as one of the **top open source projects of │ ├── call-stack.mdx │ ├── primitive-types.mdx │ └── ... (all 33 concepts) +├── tests/ # Vitest test suites +│ └── fundamentals/ # Tests for fundamental concepts (1-6) +│ ├── call-stack/ +│ ├── primitive-types/ +│ ├── value-reference-types/ +│ ├── type-coercion/ +│ ├── equality-operators/ +│ └── scope-and-closures/ +├── vitest.config.js # Vitest configuration ├── README.md # Main GitHub README ├── CONTRIBUTING.md # Guidelines for contributors ├── CODE_OF_CONDUCT.md # Community standards @@ -36,69 +45,122 @@ The project was recognized by GitHub as one of the **top open source projects of └── github-image.png # Project banner image ``` -## The 33 Concepts +## The 32 Concepts (33rd coming soon) +### Fundamentals (1-6) 1. Call Stack 2. Primitive Types 3. Value Types and Reference Types -4. Implicit, Explicit, Nominal, Structuring and Duck Typing -5. == vs === vs typeof -6. Function Scope, Block Scope and Lexical Scope +4. Type Coercion (Implicit, Explicit, Nominal, Structuring and Duck Typing) +5. Equality Operators (== vs === vs typeof) +6. Scope & Closures + +### Functions & Execution (7-10) 7. Expression vs Statement 8. IIFE, Modules and Namespaces -9. Message Queue and Event Loop -10. setTimeout, setInterval and requestAnimationFrame +9. Event Loop (Message Queue) +10. Timers (setTimeout, setInterval, requestAnimationFrame) + +### Under the Hood (11-13) 11. JavaScript Engines 12. Bitwise Operators, Type Arrays and Array Buffers 13. DOM and Layout Trees + +### Object-Oriented JS (14-18) 14. Factories and Classes 15. this, call, apply and bind 16. new, Constructor, instanceof and Instances 17. Prototype Inheritance and Prototype Chain 18. Object.create and Object.assign + +### Functional Programming (19-22) 19. map, reduce, filter 20. Pure Functions, Side Effects, State Mutation and Event Propagation -21. Closures -22. High Order Functions -23. Recursion -24. Collections and Generators -25. Promises -26. async/await -27. Data Structures -28. Expensive Operation and Big O Notation -29. Algorithms -30. Inheritance, Polymorphism and Code Reuse -31. Design Patterns -32. Partial Applications, Currying, Compose and Pipe -33. Clean Code +21. Higher-Order Functions +22. Recursion + +### Async JavaScript (23-25) +23. Collections and Generators +24. Promises +25. async/await + +### Advanced Topics (26-32) +26. Data Structures +27. Big O Notation (Expensive Operations) +28. Algorithms +29. Inheritance, Polymorphism and Code Reuse +30. Design Patterns +31. Partial Applications, Currying, Compose and Pipe +32. Clean Code ## Content Format -Each concept in the README follows this structure: +Each concept page in `/docs/concepts/` follows this structure: + +### 1. Frontmatter +```mdx +--- +title: "Concept Name" +description: "Brief description of the concept" +--- +``` + +### 2. Real-World Analogy +Start with an engaging analogy that makes the concept relatable. Include ASCII art diagrams when helpful. + +### 3. Info Box (What You'll Learn) +```mdx + +**What you'll learn in this guide:** +- Key point 1 +- Key point 2 +- Key point 3 + +``` + +### 4. Main Content Sections +- Use clear headings (`##`, `###`) to organize topics +- Include code examples with explanations +- Use Mintlify components (``, ``, ``, etc.) +- Add diagrams and visualizations where helpful + +### 5. Related Concepts +```mdx + + + Brief description of how it relates + + +``` + +### 6. Reference +```mdx + + Official MDN documentation + +``` -1. **Concept Title** - Numbered heading -2. **Description** - Brief ECMAScript-based explanation -3. **Reference** - MDN or official documentation links -4. **Articles** - Curated blog posts and tutorials -5. **Videos** - YouTube tutorials and conference talks -6. **Books** - Recommended reading (when applicable) +### 7. Articles +Curated blog posts and tutorials using `` with `icon="newspaper"`. + +### 8. Courses (optional) +Educational courses using `` with `icon="graduation-cap"`. + +### 9. Videos +YouTube tutorials and conference talks using `` with `icon="video"`. ## Contributing Guidelines ### Adding Resources - Resources should be high-quality and educational -- Include author name in the link text -- Follow the existing format for consistency - -### Creating Translations -1. Fork the repository -2. Translate content in your fork -3. Add translation link to the Community section in README -4. Submit a PR with title "Add [language] translation" +- Follow the existing Card format for consistency +- Include a brief description of what the resource covers ### Resource Format -```markdown -- [Article/Video Title — Author Name](URL) +```mdx + + Brief description of what the reader will learn from this resource. + ``` ## Git Commit Conventions @@ -172,6 +234,79 @@ This project has OpenCode configured with: 1. **Context7** - Documentation search (`use context7` in prompts) 2. **GitHub** - Repository management (`use github` in prompts) +## Testing + +This project uses [Vitest](https://vitest.dev/) as the test runner to verify that code examples in the documentation work correctly. + +### Running Tests + +```bash +# Run all tests once +npm test + +# Run tests in watch mode (re-runs on file changes) +npm run test:watch + +# Run tests with coverage report +npm run test:coverage +``` + +### Test Structure + +Tests are organized by concept category in the `tests/` directory: + +``` +tests/ +└── fundamentals/ # Concepts 1-6 + ├── call-stack/ + │ └── call-stack.test.js + ├── primitive-types/ + │ └── primitive-types.test.js + ├── value-reference-types/ + │ └── value-reference-types.test.js + ├── type-coercion/ + │ └── type-coercion.test.js + ├── equality-operators/ + │ └── equality-operators.test.js + └── scope-and-closures/ + └── scope-and-closures.test.js +``` + +### Writing Tests for Code Examples + +When adding new code examples to concept documentation, please include corresponding tests: + +1. **File naming**: Create `{concept-name}.test.js` in `tests/{category}/{concept-name}/` +2. **Use explicit imports**: + ```javascript + import { describe, it, expect } from 'vitest' + ``` +3. **Convert console.log examples to assertions**: + ```javascript + // Documentation example: + // console.log(typeof "hello") // "string" + + // Test: + it('should return string type', () => { + expect(typeof "hello").toBe("string") + }) + ``` +4. **Test error cases**: Use `expect(() => { ... }).toThrow()` for operations that should throw +5. **Skip browser-specific examples**: Tests run in Node.js, so skip DOM/window/document examples +6. **Note strict mode behavior**: Vitest runs in strict mode, so operations that "silently fail" in non-strict mode will throw `TypeError` + +### Current Test Coverage + +| Category | Concept | Tests | +|----------|---------|-------| +| Fundamentals | Call Stack | 20 | +| Fundamentals | Primitive Types | 73 | +| Fundamentals | Value vs Reference Types | 54 | +| Fundamentals | Type Coercion | 74 | +| Fundamentals | Equality Operators | 87 | +| Fundamentals | Scope and Closures | 46 | +| **Total** | | **354** | + ## Documentation Site (Mintlify) The project includes a Mintlify documentation site in the `/docs` directory. @@ -179,10 +314,11 @@ The project includes a Mintlify documentation site in the `/docs` directory. ### Local Development ```bash -# Install Mintlify CLI -npm i -g mint +# Using npm script +npm run docs -# Start dev server +# Or install Mintlify CLI globally +npm i -g mint cd docs mint dev ``` @@ -192,13 +328,13 @@ The site will be available at `http://localhost:3000`. ### Documentation Structure - **Getting Started**: Homepage and introduction -- **Fundamentals**: Concepts 1-6 (Call Stack through Scope) -- **Functions & Execution**: Concepts 7-10 -- **Under the Hood**: Concepts 11-13 -- **Object-Oriented JS**: Concepts 14-18 -- **Functional Programming**: Concepts 19-23 -- **Async JavaScript**: Concepts 24-26 -- **Advanced Topics**: Concepts 27-33 +- **Fundamentals**: Concepts 1-6 (Call Stack through Scope & Closures) +- **Functions & Execution**: Concepts 7-10 (Expression/Statement through Timers) +- **Under the Hood**: Concepts 11-13 (JS Engines through DOM) +- **Object-Oriented JS**: Concepts 14-18 (Factories through Object.create/assign) +- **Functional Programming**: Concepts 19-22 (map/reduce/filter through Recursion) +- **Async JavaScript**: Concepts 23-25 (Collections/Generators through async/await) +- **Advanced Topics**: Concepts 26-32 (Data Structures through Clean Code) ### Adding/Editing Concept Pages diff --git a/package-lock.json b/package-lock.json index b6e9da74..45a3426c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,70 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { + "@vitest/coverage-v8": "^4.0.16", "vitest": "^4.0.16" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -454,6 +515,16 @@ "node": ">=18" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -461,6 +532,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.54.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", @@ -801,6 +883,38 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", + "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.16", + "ast-v8-to-istanbul": "^0.3.8", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.16", + "vitest": "4.0.16" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", @@ -922,6 +1036,18 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -932,6 +1058,24 @@ "node": ">=18" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1034,6 +1178,84 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1044,6 +1266,41 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1172,6 +1429,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -1203,6 +1473,19 @@ "dev": true, "license": "MIT" }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", diff --git a/package.json b/package.json index 9c0668a1..ab805ca4 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "interview-preparation" ], "devDependencies": { + "@vitest/coverage-v8": "^4.0.16", "vitest": "^4.0.16" } } diff --git a/tests/call-stack/call-stack.test.js b/tests/fundamentals/call-stack/call-stack.test.js similarity index 89% rename from tests/call-stack/call-stack.test.js rename to tests/fundamentals/call-stack/call-stack.test.js index 5cd05a26..615ffbc4 100644 --- a/tests/call-stack/call-stack.test.js +++ b/tests/fundamentals/call-stack/call-stack.test.js @@ -178,6 +178,32 @@ describe('Call Stack', () => { expect(() => a()).toThrow(RangeError) }) + + it('should throw for accidental recursion in setters', () => { + class Person { + set name(value) { + this.name = value // Calls the setter again - infinite loop! + } + } + + const p = new Person() + expect(() => { p.name = "Alice" }).toThrow(RangeError) + }) + + it('should work correctly with proper setter implementation using different property', () => { + class PersonFixed { + set name(value) { + this._name = value // Use _name instead to avoid recursion + } + get name() { + return this._name + } + } + + const p = new PersonFixed() + p.name = "Alice" + expect(p.name).toBe("Alice") + }) }) describe('Recursion with Base Case', () => { diff --git a/tests/equality-operators/equality-operators.test.js b/tests/fundamentals/equality-operators/equality-operators.test.js similarity index 82% rename from tests/equality-operators/equality-operators.test.js rename to tests/fundamentals/equality-operators/equality-operators.test.js index 56c28749..652586db 100644 --- a/tests/equality-operators/equality-operators.test.js +++ b/tests/fundamentals/equality-operators/equality-operators.test.js @@ -643,4 +643,136 @@ describe('Equality and Type Checking', () => { }) }) }) + + describe('Additional Missing Examples', () => { + describe('More Loose Equality Examples', () => { + it('should coerce 42 == "42" to true', () => { + expect(42 == "42").toBe(true) + }) + + it('should return false for undefined == ""', () => { + expect(undefined == "").toBe(false) + }) + }) + + describe('More Strict Equality Examples', () => { + it('should return false for array === string', () => { + const arr = [] + const str = "" + expect(arr === str).toBe(false) + }) + + it('should demonstrate -0 === 0 is true', () => { + expect(-0 === 0).toBe(true) + expect(0 === -0).toBe(true) + }) + }) + + describe('Negative Zero Edge Cases', () => { + it('should demonstrate 1/+0 vs 1/-0', () => { + expect(1 / +0).toBe(Infinity) + expect(1 / -0).toBe(-Infinity) + expect((1 / +0) === (1 / -0)).toBe(false) + }) + + it('should demonstrate Math.sign with -0', () => { + expect(Object.is(Math.sign(-0), -0)).toBe(true) + expect(Math.sign(-0) === 0).toBe(true) // But === says it equals 0 + }) + + it('should parse -0 from JSON', () => { + const negZero = JSON.parse("-0") + expect(Object.is(negZero, -0)).toBe(true) + }) + + it('should create -0 through multiplication', () => { + expect(Object.is(0 * -1, -0)).toBe(true) + expect(Object.is(-0 * 1, -0)).toBe(true) + }) + }) + + describe('Map with NaN as Key', () => { + it('should use NaN as a Map key', () => { + const map = new Map() + + map.set(NaN, "value for NaN") + + // Map uses SameValueZero algorithm, which treats NaN === NaN + expect(map.get(NaN)).toBe("value for NaN") + expect(map.has(NaN)).toBe(true) + }) + + it('should only have one NaN key despite multiple sets', () => { + const map = new Map() + + map.set(NaN, "first") + map.set(NaN, "second") + + expect(map.size).toBe(1) + expect(map.get(NaN)).toBe("second") + }) + }) + + describe('Number.isSafeInteger', () => { + it('should identify safe integers', () => { + expect(Number.isSafeInteger(3)).toBe(true) + expect(Number.isSafeInteger(-3)).toBe(true) + expect(Number.isSafeInteger(0)).toBe(true) + expect(Number.isSafeInteger(Number.MAX_SAFE_INTEGER)).toBe(true) + expect(Number.isSafeInteger(Number.MIN_SAFE_INTEGER)).toBe(true) + }) + + it('should return false for unsafe integers', () => { + expect(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1)).toBe(false) + expect(Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1)).toBe(false) + }) + + it('should return false for non-integers', () => { + expect(Number.isSafeInteger(3.1)).toBe(false) + expect(Number.isSafeInteger(NaN)).toBe(false) + expect(Number.isSafeInteger(Infinity)).toBe(false) + expect(Number.isSafeInteger("3")).toBe(false) + }) + }) + + describe('NaN Creation Examples', () => { + it('should create NaN from 0/0', () => { + expect(Number.isNaN(0 / 0)).toBe(true) + }) + + it('should create NaN from Math.sqrt(-1)', () => { + expect(Number.isNaN(Math.sqrt(-1))).toBe(true) + }) + + it('should create NaN from invalid math operations', () => { + expect(Number.isNaN(Infinity - Infinity)).toBe(true) + expect(Number.isNaN(Infinity / Infinity)).toBe(true) + expect(Number.isNaN(0 * Infinity)).toBe(true) + }) + }) + + describe('Sorting Array of Number Strings', () => { + it('should sort incorrectly with default sort', () => { + const arr = ["10", "9", "2", "1", "100"] + const sorted = [...arr].sort() + + // Lexicographic sort - NOT numeric order! + expect(sorted).toEqual(["1", "10", "100", "2", "9"]) + }) + + it('should sort correctly with numeric comparison', () => { + const arr = ["10", "9", "2", "1", "100"] + const sorted = [...arr].sort((a, b) => Number(a) - Number(b)) + + expect(sorted).toEqual(["1", "2", "9", "10", "100"]) + }) + + it('should sort correctly using + for conversion', () => { + const arr = ["10", "9", "2", "1", "100"] + const sorted = [...arr].sort((a, b) => +a - +b) + + expect(sorted).toEqual(["1", "2", "9", "10", "100"]) + }) + }) + }) }) diff --git a/tests/primitive-types/primitive-types.test.js b/tests/fundamentals/primitive-types/primitive-types.test.js similarity index 79% rename from tests/primitive-types/primitive-types.test.js rename to tests/fundamentals/primitive-types/primitive-types.test.js index 73d44d50..3e1aea67 100644 --- a/tests/primitive-types/primitive-types.test.js +++ b/tests/fundamentals/primitive-types/primitive-types.test.js @@ -449,4 +449,115 @@ describe('Primitive Types', () => { expect(Object.prototype.toString.call(new Date())).toBe("[object Date]") }) }) + + describe('Intl.NumberFormat for Currency', () => { + it('should format currency correctly with Intl.NumberFormat', () => { + const formatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }) + + expect(formatter.format(0.30)).toBe("$0.30") + expect(formatter.format(19.99)).toBe("$19.99") + expect(formatter.format(1000)).toBe("$1,000.00") + }) + + it('should handle different locales', () => { + const euroFormatter = new Intl.NumberFormat('de-DE', { + style: 'currency', + currency: 'EUR', + }) + + // German locale uses comma for decimal and period for thousands + expect(euroFormatter.format(1234.56)).toContain("1.234,56") + }) + }) + + describe('Floating-Point Solutions', () => { + it('should use Number.EPSILON for floating-point comparison', () => { + const result = 0.1 + 0.2 + const expected = 0.3 + + // Using epsilon comparison for floating-point + expect(Math.abs(result - expected) < Number.EPSILON).toBe(true) + }) + + it('should use toFixed for rounding display', () => { + expect((0.1 + 0.2).toFixed(2)).toBe("0.30") + expect((0.1 + 0.2).toFixed(1)).toBe("0.3") + }) + + it('should use integers (cents) for precise money calculations', () => { + // Instead of 0.1 + 0.2, use cents + const price1 = 10 // 10 cents + const price2 = 20 // 20 cents + const total = price1 + price2 + + expect(total).toBe(30) // Exactly 30 cents + expect(total / 100).toBe(0.3) // $0.30 + }) + }) + + describe('Array Holes', () => { + it('should return undefined for array holes', () => { + let arr = [1, , 3] // Sparse array with hole at index 1 + + expect(arr[0]).toBe(1) + expect(arr[1]).toBe(undefined) + expect(arr[2]).toBe(3) + expect(arr.length).toBe(3) + }) + + it('should skip holes in forEach but include in map', () => { + let arr = [1, , 3] + let forEachCount = 0 + let mapResult + + arr.forEach(() => forEachCount++) + mapResult = arr.map(x => x * 2) + + expect(forEachCount).toBe(2) // Holes are skipped + expect(mapResult).toEqual([2, undefined, 6]) // Hole becomes undefined in map result + }) + }) + + describe('String Trim for Empty/Whitespace Check', () => { + it('should use trim to check for empty or whitespace-only strings', () => { + expect("".trim() === "").toBe(true) + expect(" ".trim() === "").toBe(true) + expect("\t\n".trim() === "").toBe(true) + expect("hello".trim() === "").toBe(false) + expect(" hello ".trim() === "").toBe(false) + }) + + it('should use trim with length check for validation', () => { + function isEmptyOrWhitespace(str) { + return str.trim().length === 0 + } + + expect(isEmptyOrWhitespace("")).toBe(true) + expect(isEmptyOrWhitespace(" ")).toBe(true) + expect(isEmptyOrWhitespace("hello")).toBe(false) + }) + }) + + describe('null vs undefined Patterns', () => { + it('should demonstrate clearing with null vs undefined', () => { + let user = { name: "Alice" } + + // Clear intentionally with null + user = null + expect(user).toBe(null) + }) + + it('should show function returning null for no result', () => { + function findUser(id) { + const users = [{ id: 1, name: "Alice" }] + return users.find(u => u.id === id) || null + } + + expect(findUser(1)).toEqual({ id: 1, name: "Alice" }) + expect(findUser(999)).toBe(null) + }) + }) }) diff --git a/tests/scope-and-closures/scope-and-closures.test.js b/tests/fundamentals/scope-and-closures/scope-and-closures.test.js similarity index 86% rename from tests/scope-and-closures/scope-and-closures.test.js rename to tests/fundamentals/scope-and-closures/scope-and-closures.test.js index ba680362..5d1719ab 100644 --- a/tests/scope-and-closures/scope-and-closures.test.js +++ b/tests/fundamentals/scope-and-closures/scope-and-closures.test.js @@ -654,4 +654,106 @@ describe('Scope and Closures', () => { expect(results[2]()).toBe(2) }) }) + + describe('Temporal Dead Zone (TDZ)', () => { + it('should throw ReferenceError when accessing let before declaration', () => { + expect(() => { + // Using eval to avoid syntax errors at parse time + eval(` + const before = x + let x = 10 + `) + }).toThrow(ReferenceError) + }) + + it('should throw ReferenceError when accessing const before declaration', () => { + expect(() => { + eval(` + const before = y + const y = 10 + `) + }).toThrow(ReferenceError) + }) + + it('should demonstrate TDZ exists from block start to declaration', () => { + let outsideValue = "outside" + + expect(() => { + eval(` + { + // TDZ starts here for 'name' + const beforeDeclaration = name // ReferenceError + let name = "Alice" + } + `) + }).toThrow(ReferenceError) + }) + + it('should not throw for var due to hoisting', () => { + // var is hoisted with undefined value, so no TDZ + function example() { + const before = message // undefined, not an error + var message = "Hello" + const after = message // "Hello" + return { before, after } + } + + const result = example() + expect(result.before).toBe(undefined) + expect(result.after).toBe("Hello") + }) + + it('should have TDZ in function parameters', () => { + // Default parameters can reference earlier parameters but not later ones + expect(() => { + eval(` + function test(a = b, b = 2) { + return a + b + } + test() + `) + }).toThrow(ReferenceError) + }) + + it('should allow later parameters to reference earlier ones', () => { + function test(a = 1, b = a + 1) { + return a + b + } + + expect(test()).toBe(3) // a=1, b=2 + expect(test(5)).toBe(11) // a=5, b=6 + expect(test(5, 10)).toBe(15) // a=5, b=10 + }) + }) + + describe('Hoisting Comparison: var vs let/const', () => { + it('should demonstrate var hoisting without TDZ', () => { + function example() { + // var is hoisted and initialized to undefined + expect(a).toBe(undefined) + var a = 1 + expect(a).toBe(1) + } + + example() + }) + + it('should demonstrate function declarations are fully hoisted', () => { + // Function can be called before its declaration + expect(hoistedFn()).toBe("I was hoisted!") + + function hoistedFn() { + return "I was hoisted!" + } + }) + + it('should demonstrate function expressions are not hoisted', () => { + expect(() => { + eval(` + notHoisted() + var notHoisted = function() { return "Not hoisted" } + `) + }).toThrow(TypeError) + }) + }) }) diff --git a/tests/type-coercion/type-coercion.test.js b/tests/fundamentals/type-coercion/type-coercion.test.js similarity index 79% rename from tests/type-coercion/type-coercion.test.js rename to tests/fundamentals/type-coercion/type-coercion.test.js index aefc9b19..757561e3 100644 --- a/tests/type-coercion/type-coercion.test.js +++ b/tests/fundamentals/type-coercion/type-coercion.test.js @@ -503,4 +503,133 @@ describe('Type Coercion', () => { expect(true + false + "hello").toBe("1hello") }) }) + + describe('Modulo Operator with Strings', () => { + it('should coerce strings to numbers for modulo', () => { + expect("6" % 4).toBe(2) + expect("10" % "3").toBe(1) + expect(17 % "5").toBe(2) + }) + + it('should return NaN for non-numeric strings', () => { + expect(Number.isNaN("hello" % 2)).toBe(true) + expect(Number.isNaN(10 % "abc")).toBe(true) + }) + }) + + describe('Comparison Operators with Coercion', () => { + it('should coerce strings to numbers in comparisons', () => { + expect("10" > 5).toBe(true) + expect("10" < 5).toBe(false) + expect("10" >= 10).toBe(true) + expect("10" <= 10).toBe(true) + }) + + it('should compare strings lexicographically when both are strings', () => { + // String comparison (lexicographic, not numeric) + expect("10" > "9").toBe(false) // "1" < "9" in char codes + expect("2" > "10").toBe(true) // "2" > "1" in char codes + }) + + it('should coerce null and undefined in comparisons', () => { + expect(null >= 0).toBe(true) // null coerces to 0 + expect(null > 0).toBe(false) + expect(null == 0).toBe(false) // Special case! + + // undefined always returns false in comparisons + expect(undefined > 0).toBe(false) + expect(undefined < 0).toBe(false) + expect(undefined >= 0).toBe(false) + }) + }) + + describe('Double Negation (!!)', () => { + it('should convert values to boolean with !!', () => { + // Truthy values + expect(!!"hello").toBe(true) + expect(!!1).toBe(true) + expect(!!{}).toBe(true) + expect(!![]).toBe(true) + expect(!!-1).toBe(true) + + // Falsy values + expect(!!"").toBe(false) + expect(!!0).toBe(false) + expect(!!null).toBe(false) + expect(!!undefined).toBe(false) + expect(!!NaN).toBe(false) + }) + + it('should be equivalent to Boolean()', () => { + const values = ["hello", "", 0, 1, null, undefined, {}, []] + + values.forEach(value => { + expect(!!value).toBe(Boolean(value)) + }) + }) + }) + + describe('Date Coercion', () => { + it('should coerce Date to number (timestamp) with + operator', () => { + const date = new Date("2025-01-01T00:00:00.000Z") + const timestamp = +date + + expect(typeof timestamp).toBe("number") + expect(timestamp).toBe(date.getTime()) + }) + + it('should coerce Date to string with String()', () => { + const date = new Date("2025-06-15T12:00:00.000Z") + const str = String(date) + + expect(typeof str).toBe("string") + // Use a mid-year date to avoid timezone edge cases + expect(str).toContain("2025") + }) + + it('should prefer string coercion with + operator and string', () => { + const date = new Date("2025-06-15T12:00:00.000Z") + const result = "Date: " + date + + expect(typeof result).toBe("string") + expect(result).toContain("Date:") + expect(result).toContain("2025") + }) + + it('should use valueOf for numeric context', () => { + const date = new Date("2025-01-01T00:00:00.000Z") + + // In numeric context, Date uses valueOf (returns timestamp) + expect(date - 0).toBe(date.getTime()) + expect(date * 1).toBe(date.getTime()) + }) + }) + + describe('Implicit Boolean Contexts', () => { + it('should coerce to boolean in if statements', () => { + let result = "" + + if ("hello") result += "truthy string " + if (0) result += "zero " + if ([]) result += "empty array " + if ({}) result += "empty object " + + expect(result).toBe("truthy string empty array empty object ") + }) + + it('should coerce to boolean in ternary operator', () => { + expect("hello" ? "yes" : "no").toBe("yes") + expect("" ? "yes" : "no").toBe("no") + expect(0 ? "yes" : "no").toBe("no") + expect(1 ? "yes" : "no").toBe("yes") + }) + + it('should coerce to boolean in logical NOT', () => { + expect(!0).toBe(true) + expect(!"").toBe(true) + expect(!null).toBe(true) + expect(!"hello").toBe(false) + expect(!1).toBe(false) + }) + }) }) diff --git a/tests/value-reference-types/value-reference-types.test.js b/tests/fundamentals/value-reference-types/value-reference-types.test.js similarity index 76% rename from tests/value-reference-types/value-reference-types.test.js rename to tests/fundamentals/value-reference-types/value-reference-types.test.js index c3d87c84..5d6c96c6 100644 --- a/tests/value-reference-types/value-reference-types.test.js +++ b/tests/fundamentals/value-reference-types/value-reference-types.test.js @@ -554,4 +554,179 @@ describe('Value Types and Reference Types', () => { expect(sorted).toEqual([1, 2, 3]) }) }) + + describe('structuredClone with Special Types', () => { + it('should deep clone objects with Map', () => { + const original = { + name: "Alice", + data: new Map([["key1", "value1"], ["key2", "value2"]]) + } + + const clone = structuredClone(original) + + // Modify the clone's Map + clone.data.set("key1", "modified") + clone.data.set("key3", "new value") + + // Original should be unchanged + expect(original.data.get("key1")).toBe("value1") + expect(original.data.has("key3")).toBe(false) + expect(clone.data.get("key1")).toBe("modified") + }) + + it('should deep clone objects with Set', () => { + const original = { + name: "Alice", + tags: new Set([1, 2, 3]) + } + + const clone = structuredClone(original) + + // Modify the clone's Set + clone.tags.add(4) + clone.tags.delete(1) + + // Original should be unchanged + expect(original.tags.has(1)).toBe(true) + expect(original.tags.has(4)).toBe(false) + expect(clone.tags.has(1)).toBe(false) + expect(clone.tags.has(4)).toBe(true) + }) + + it('should deep clone objects with Date', () => { + const original = { + name: "Event", + date: new Date("2025-01-01") + } + + const clone = structuredClone(original) + + expect(clone.date instanceof Date).toBe(true) + expect(clone.date.getTime()).toBe(original.date.getTime()) + expect(clone.date).not.toBe(original.date) // Different reference + }) + }) + + describe('Shared Default Object Reference Pitfall', () => { + it('should demonstrate shared default array problem', () => { + const defaultList = [] + + function addItem(item, list = defaultList) { + list.push(item) + return list + } + + const result1 = addItem("a") + const result2 = addItem("b") + + // Both calls modified the same defaultList! + expect(result1).toEqual(["a", "b"]) + expect(result2).toEqual(["a", "b"]) + expect(result1).toBe(result2) // Same reference! + }) + + it('should fix shared default with new array creation', () => { + function addItem(item, list = []) { + list.push(item) + return list + } + + const result1 = addItem("a") + const result2 = addItem("b") + + // Each call gets its own array + expect(result1).toEqual(["a"]) + expect(result2).toEqual(["b"]) + expect(result1).not.toBe(result2) + }) + }) + + describe('WeakMap vs Map Memory Behavior', () => { + it('should demonstrate Map holds strong references', () => { + const cache = new Map() + let user = { id: 1, name: "Alice" } + + cache.set(user.id, user) + + // Even if we clear user, the Map still holds the reference + const cachedUser = cache.get(1) + expect(cachedUser.name).toBe("Alice") + }) + + it('should demonstrate WeakMap allows garbage collection', () => { + const cache = new WeakMap() + let user = { id: 1, name: "Alice" } + + cache.set(user, { computed: "expensive data" }) + + // WeakMap uses the object itself as key + expect(cache.get(user)).toEqual({ computed: "expensive data" }) + + // WeakMap keys must be objects + expect(() => cache.set("string-key", "value")).toThrow(TypeError) + }) + + it('should show WeakMap cannot be iterated', () => { + const weakMap = new WeakMap() + const obj = { id: 1 } + weakMap.set(obj, "value") + + // WeakMap has no size property + expect(weakMap.size).toBe(undefined) + + // WeakMap is not iterable + expect(typeof weakMap[Symbol.iterator]).toBe("undefined") + }) + }) + + describe('Clone Function Parameters Pattern', () => { + it('should clone parameters before modification', () => { + function processData(data) { + // Clone to avoid modifying original + const copy = structuredClone(data) + copy.processed = true + copy.items.push("new item") + return copy + } + + const original = { + name: "data", + items: ["item1", "item2"] + } + + const result = processData(original) + + // Original is unchanged + expect(original.processed).toBe(undefined) + expect(original.items).toEqual(["item1", "item2"]) + + // Result has modifications + expect(result.processed).toBe(true) + expect(result.items).toEqual(["item1", "item2", "new item"]) + }) + }) + + describe('let with Object.freeze()', () => { + it('should allow reassignment of let variable holding frozen object', () => { + let obj = Object.freeze({ a: 1 }) + + // Cannot modify the frozen object + expect(() => { obj.a = 2 }).toThrow(TypeError) + + // But CAN reassign the variable to a new object + obj = { a: 2 } + expect(obj.a).toBe(2) + }) + + it('should demonstrate const + freeze for true immutability', () => { + const obj = Object.freeze({ a: 1 }) + + // Cannot modify the frozen object + expect(() => { obj.a = 2 }).toThrow(TypeError) + + // Cannot reassign const + // obj = { a: 2 } // Would throw TypeError + expect(obj.a).toBe(1) + }) + }) }) From 05b46ab69d47ba4e9c063e31d19f0f05ca9b5818 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 21:41:09 -0300 Subject: [PATCH 014/128] fix: correct docs script to run mintlify from docs directory --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ab805ca4..7c7de349 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", - "docs": "npx mintlify dev --dir docs", - "docs:build": "npx mintlify build --dir docs" + "docs": "cd docs && npx mintlify dev", + "docs:build": "cd docs && npx mintlify build" }, "repository": { "type": "git", From 8434ff4844159b506441427f18fad66567411038 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 21:49:38 -0300 Subject: [PATCH 015/128] refactor: reorder fundamentals concepts for better learning progression - Move from 'concrete to abstract' learning path - Start with Primitive Types (most tangible concept) - End with Call Stack (capstone tying everything together) New order: 1. Primitive Types 2. Value Types and Reference Types 3. Type Coercion 4. Equality Operators 5. Scope & Closures 6. Call Stack Update Related Concepts links to reflect new order: - call-stack.mdx: add link to Primitive Types - type-coercion.mdx: add link to Value vs Reference Types - scope-and-closures.mdx: add link to Call Stack --- CLAUDE.md | 12 ++++++------ docs/concepts/call-stack.mdx | 9 ++++++--- docs/concepts/scope-and-closures.mdx | 5 ++++- docs/concepts/type-coercion.mdx | 3 +++ docs/docs.json | 4 ++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9b288e1f..51f7648b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,12 +48,12 @@ The project was recognized by GitHub as one of the **top open source projects of ## The 32 Concepts (33rd coming soon) ### Fundamentals (1-6) -1. Call Stack -2. Primitive Types -3. Value Types and Reference Types -4. Type Coercion (Implicit, Explicit, Nominal, Structuring and Duck Typing) -5. Equality Operators (== vs === vs typeof) -6. Scope & Closures +1. Primitive Types +2. Value Types and Reference Types +3. Type Coercion (Implicit, Explicit, Nominal, Structuring and Duck Typing) +4. Equality Operators (== vs === vs typeof) +5. Scope & Closures +6. Call Stack ### Functions & Execution (7-10) 7. Expression vs Statement diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 91bd79a1..43df6400 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -672,15 +672,18 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in ## Related Concepts + + Understanding how primitives are stored in stack frames + + + Understanding variable visibility and how functions remember their environment + How async code works with the call stack Functions that call themselves - - Understanding variable visibility and how functions remember their environment - --- diff --git a/docs/concepts/scope-and-closures.mdx b/docs/concepts/scope-and-closures.mdx index f4c70f20..96279429 100644 --- a/docs/concepts/scope-and-closures.mdx +++ b/docs/concepts/scope-and-closures.mdx @@ -1053,13 +1053,16 @@ cleanup(); // Removes listener, allows memory to be freed ## Related Concepts + + How JavaScript tracks function execution and manages scope + Patterns that leverage scope for encapsulation Understanding execution context alongside scope - + Functions that return functions often create closures diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index b740c03b..9c3b1852 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -918,6 +918,9 @@ function process(count) { Understanding the basic data types that coercion converts between + + How primitives and objects behave differently during coercion + Deep dive into equality operators and type checking diff --git a/docs/docs.json b/docs/docs.json index f632d670..5b5b3df9 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -34,12 +34,12 @@ "group": "Fundamentals", "icon": "cube", "pages": [ - "concepts/call-stack", "concepts/primitive-types", "concepts/value-reference-types", "concepts/type-coercion", "concepts/equality-operators", - "concepts/scope-and-closures" + "concepts/scope-and-closures", + "concepts/call-stack" ] }, { From 46cb56a45eed6c80a6d6d3ba16f2f156da7e7dcb Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado Date: Mon, 29 Dec 2025 22:09:53 -0300 Subject: [PATCH 016/128] feat(iife-modules): add comprehensive guide with simplified language - Complete rewrite with 'Messy Desk' analogy for code organization - Cover IIFEs, Namespaces, and ES6 Modules in three structured parts - Include practical examples: barrel files, dynamic imports, real project structure - Add 6 quiz questions and curated resources (6 articles, 4 videos) - Use simple, accessible language for non-native English speakers --- docs/concepts/iife-modules.mdx | 1196 +++++++++++++++++++++++++++++++- 1 file changed, 1167 insertions(+), 29 deletions(-) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 997a923b..177bdad8 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -1,23 +1,1160 @@ --- title: "IIFE, Modules and Namespaces" -description: "Code organization patterns in JavaScript" +description: "How to organize your JavaScript code and keep it clean" --- -## Overview +## The Messy Desk Problem: A Real-World Analogy -With the introduction of ES6 modules, the role of IIFEs (Immediately Invoked Function Expressions) in scope isolation has diminished but they still remain relevant for certain use cases. +Imagine you're working at a desk covered with papers, pens, sticky notes, and coffee cups. Everything is mixed together. When you need to find something specific, you have to dig through the mess. And if someone else uses your desk? Chaos. + +Now imagine organizing that desk: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ THE MESSY DESK (No Organization) │ +│ │ +│ password = "123" userName = "Bob" calculate() │ +│ config = {} helpers = {} API_KEY = "secret" │ +│ utils = {} data = [] currentUser = null init() │ +│ │ +│ Everything is everywhere. Anyone can access anything. │ +│ Name conflicts are common. It's hard to find what you need. │ +└─────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────┐ +│ THE ORGANIZED DESK (With Modules) │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ auth.js │ │ api.js │ │ utils.js │ │ +│ │ │ │ │ │ │ │ +│ │ • login() │ │ • fetch() │ │ • format() │ │ +│ │ • logout() │ │ • post() │ │ • validate()│ │ +│ │ • user │ │ • API_KEY │ │ • helpers │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ +│ Each drawer has its own space. Take only what you need. │ +│ Private things stay private. Everything is easy to find. │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +This is the story of how JavaScript developers learned to organize their code: + +1. **First**, we had the messy desk — everything in the global scope +2. **Then**, we invented **IIFEs** — a clever trick to create private spaces +3. **Next**, we created **Namespaces** — grouping related things under one name +4. **Finally**, we got **Modules** — the modern, built-in solution + +Let's learn each approach and understand when to use them. + + +**What you'll learn in this guide:** +- What IIFEs are and why they were invented +- How to create private variables and avoid global pollution +- What namespaces are and how to use them +- Modern ES6 modules: import, export, and organizing large projects +- The evolution from IIFEs to modules and why it matters + + +--- + +## Part 1: IIFE — The Self-Running Function + +### What is an IIFE? + +**IIFE** stands for **Immediately Invoked Function Expression**. It's a function that runs as soon as it's defined. + +```javascript +// A normal function — you define it, then call it later +function greet() { + console.log("Hello!"); +} +greet(); // You have to call it + +// An IIFE — it runs immediately, no calling needed +(function() { + console.log("Hello!"); +})(); // Runs right away! +``` + +The name tells you exactly what it does: +- **Immediately** — runs right now +- **Invoked** — called/executed +- **Function Expression** — a function written as an expression (not a declaration) + +### The Anatomy of an IIFE + +Let's break down the syntax piece by piece: + +```javascript +(function() { + // your code here +})(); + +// Let's label each part: + +( function() { ... } ) (); +│ │ │ +│ │ └─── 3. Invoke (call) it immediately +│ │ +│ └─────── 2. Wrap in parentheses (makes it an expression) +│ +└──────────────────────────── 1. Define a function +``` + + +**Why the parentheses?** Without them, JavaScript thinks you're writing a function declaration, not an expression. The parentheses tell JavaScript: "This is a value (an expression), not a statement." + + +### IIFE Variations + +There are several ways to write an IIFE. They all do the same thing: + +```javascript +// Classic style +(function() { + console.log("Classic IIFE"); +})(); + +// Alternative parentheses placement +(function() { + console.log("Alternative style"); +}()); + +// Arrow function IIFE (modern) +(() => { + console.log("Arrow IIFE"); +})(); + +// With parameters +((name) => { + console.log(`Hello, ${name}!`); +})("Alice"); + +// Named IIFE (useful for debugging) +(function myIIFE() { + console.log("Named IIFE"); +})(); +``` + +### Why Were IIFEs Invented? + +Before ES6 modules, JavaScript had a big problem: **everything was global**. + +```javascript +// file1.js +var userName = "Alice"; +var count = 0; + +// file2.js (loaded after file1.js) +var userName = "Bob"; // Oops! Overwrites the first userName +var count = 100; // Oops! Overwrites the first count + +// Now file1.js's code is broken because its variables were replaced +``` + +IIFEs solved this by creating a **private scope**: + +```javascript +// file1.js — wrapped in an IIFE +(function() { + var userName = "Alice"; // Private to this IIFE + var count = 0; // Private to this IIFE + + // Your code here... +})(); + +// file2.js — also wrapped in an IIFE +(function() { + var userName = "Bob"; // Different variable, no conflict! + var count = 100; // Different variable, no conflict! + + // Your code here... +})(); +``` + +### Practical Example: Creating Private Variables + +One of the most powerful uses of IIFEs is creating **private variables** that can't be accessed from outside: + +```javascript +const counter = (function() { + // Private variable — can't be accessed directly + let count = 0; + + // Private function — also hidden + function log(message) { + console.log(`[Counter] ${message}`); + } + + // Return public interface + return { + increment() { + count++; + log(`Incremented to ${count}`); + }, + decrement() { + count--; + log(`Decremented to ${count}`); + }, + getCount() { + return count; + } + }; +})(); + +// Using the counter +counter.increment(); // [Counter] Incremented to 1 +counter.increment(); // [Counter] Incremented to 2 +console.log(counter.getCount()); // 2 + +// Trying to access private variables +console.log(counter.count); // undefined (it's private!) +counter.log("test"); // TypeError: counter.log is not a function +``` + +This pattern is called the **Module Pattern** — it was the standard way to create "modules" before ES6. + +### IIFE with Parameters + +You can pass values into an IIFE: + +```javascript +// Passing jQuery to ensure $ refers to jQuery +(function($) { + // Inside here, $ is definitely jQuery + $(".button").click(function() { + console.log("Clicked!"); + }); +})(jQuery); + +// Passing window and document for performance +(function(window, document) { + // Accessing window and document is slightly faster + // because they're local variables now + const body = document.body; + const location = window.location; +})(window, document); +``` + +### When to Use IIFEs Today + +With ES6 modules, IIFEs are less common. But they're still useful for: + + + + ```javascript + // Run setup code once without leaving variables behind + const config = (() => { + const env = process.env.NODE_ENV; + const apiUrl = env === 'production' + ? 'https://api.example.com' + : 'http://localhost:3000'; + + return { env, apiUrl }; + })(); + ``` + + + + ```javascript + // Top-level await isn't always available + // IIFE lets you use async/await anywhere + (async () => { + const response = await fetch('/api/data'); + const data = await response.json(); + console.log(data); + })(); + ``` + + + + ```javascript + // In a + + + +``` + +#### In Node.js + +```javascript +// Option 1: Use .mjs extension +// math.mjs +export function add(a, b) { return a + b; } + +// Option 2: Add "type": "module" to package.json +// Then use .js extension normally +``` + +--- + +## Exporting: Sharing Your Code + +There are two types of exports: **named exports** and **default exports**. + +### Named Exports + +Named exports let you export multiple things from a module. Each has a name. + +```javascript +// utils.js + +// Export as you declare +export const PI = 3.14159; + +export function square(x) { + return x * x; +} + +export class Calculator { + add(a, b) { return a + b; } +} + +// Or export at the end +const E = 2.71828; +function cube(x) { return x * x * x; } + +export { E, cube }; +``` + +### Default Export + +Each module can have ONE default export. It's the "main" thing the module provides. + +```javascript +// greeting.js + +// Default export — no name needed when importing +export default function greet(name) { + return `Hello, ${name}!`; +} + +// You can have named exports too +export const defaultName = "World"; +``` + +```javascript +// Another example — default exporting a class +// User.js + +export default class User { + constructor(name) { + this.name = name; + } + + greet() { + return `Hi, I'm ${this.name}`; + } +} +``` + +### When to Use Each + + + + **Use when:** + - You're exporting multiple things + - You want clear, explicit imports + - You want to enable tree-shaking + + ```javascript + // utils.js + export function formatDate(date) { /* ... */ } + export function formatCurrency(amount) { /* ... */ } + export function formatPhone(number) { /* ... */ } + + // Import only what you need + import { formatDate } from './utils.js'; + ``` + + + + **Use when:** + - The module has one main purpose + - You're exporting a class or component + - The import name doesn't need to match + + ```javascript + // Button.js — React component + export default function Button({ label }) { + return ; + } + + // Import with any name + import MyButton from './Button.js'; + ``` + + + +--- + +## Importing: Using Other People's Code + +### Named Imports + +Import specific things by name (must match the export names): + +```javascript +// Import specific items +import { PI, square } from './utils.js'; + +// Import with a different name (alias) +import { PI as pi, square as sq } from './utils.js'; + +// Import everything as a namespace object +import * as Utils from './utils.js'; +console.log(Utils.PI); +console.log(Utils.square(4)); +``` + +### Default Import + +Import the default export with any name you choose: + +```javascript +// The name doesn't have to match +import greet from './greeting.js'; +import sayHello from './greeting.js'; // Same thing, different name +import xyz from './greeting.js'; // Still works! + +// Combine default and named imports +import greet, { defaultName } from './greeting.js'; +``` + +### Side-Effect Imports + +Sometimes you just want to run a module's code without importing anything: + +```javascript +// This runs the module but imports nothing +import './polyfills.js'; +import './analytics.js'; + +// Useful for: +// - Polyfills that add global features +// - Initialization code +// - CSS (with bundlers) +``` + +### Import Syntax Summary + +```javascript +// Named imports +import { a, b, c } from './module.js'; + +// Named import with alias +import { reallyLongName as short } from './module.js'; + +// Default import +import myDefault from './module.js'; + +// Default + named imports +import myDefault, { a, b } from './module.js'; + +// Import all as namespace +import * as MyModule from './module.js'; + +// Side-effect import +import './module.js'; +``` + +--- + +## Organizing a Real Project + +Let's see how modules work in a realistic project structure: + +``` +my-app/ +├── index.html +├── src/ +│ ├── main.js # Entry point +│ ├── config.js # App configuration +│ ├── utils/ +│ │ ├── index.js # Re-exports from utils +│ │ ├── format.js +│ │ └── validate.js +│ ├── services/ +│ │ ├── index.js +│ │ ├── api.js +│ │ └── auth.js +│ └── components/ +│ ├── index.js +│ ├── Button.js +│ └── Modal.js +``` + +### The Index.js Pattern (Barrel Files) + +Use `index.js` to re-export from multiple files: + +```javascript +// utils/format.js +export function formatDate(date) { /* ... */ } +export function formatCurrency(amount) { /* ... */ } + +// utils/validate.js +export function isEmail(str) { /* ... */ } +export function isPhone(str) { /* ... */ } + +// utils/index.js — re-exports everything +export { formatDate, formatCurrency } from './format.js'; +export { isEmail, isPhone } from './validate.js'; + +// Now in main.js, you can import from the folder +import { formatDate, isEmail } from './utils/index.js'; +// Or even shorter (works with bundlers and Node.js, not native browser modules): +import { formatDate, isEmail } from './utils'; +``` + +### Real Example: A Simple App + +```javascript +// config.js +export const API_URL = 'https://api.example.com'; +export const APP_NAME = 'My App'; + +// services/api.js +import { API_URL } from '../config.js'; + +export async function fetchUsers() { + const response = await fetch(`${API_URL}/users`); + return response.json(); +} + +export async function fetchPosts() { + const response = await fetch(`${API_URL}/posts`); + return response.json(); +} + +// services/auth.js +import { API_URL } from '../config.js'; + +let currentUser = null; // Private to this module + +export async function login(email, password) { + const response = await fetch(`${API_URL}/login`, { + method: 'POST', + body: JSON.stringify({ email, password }) + }); + currentUser = await response.json(); + return currentUser; +} + +export function getCurrentUser() { + return currentUser; +} + +export function logout() { + currentUser = null; +} + +// main.js — Entry point +import { APP_NAME } from './config.js'; +import { fetchUsers } from './services/api.js'; +import { login, getCurrentUser } from './services/auth.js'; + +console.log(`Welcome to ${APP_NAME}`); + +async function init() { + await login('user@example.com', 'password'); + console.log('Logged in as:', getCurrentUser().name); + + const users = await fetchUsers(); + console.log('Users:', users); +} + +init(); +``` + +--- + +## Dynamic Imports + +Sometimes you don't want to load a module until it's needed. **Dynamic imports** load modules on demand: + +```javascript +// Static import — always loaded +import { bigFunction } from './heavy-module.js'; + +// Dynamic import — loaded only when needed +async function loadWhenNeeded() { + const module = await import('./heavy-module.js'); + module.bigFunction(); +} + +// Common use: Code splitting for routes +async function loadPage(pageName) { + switch (pageName) { + case 'home': + const home = await import('./pages/Home.js'); + return home.default; + case 'about': + const about = await import('./pages/About.js'); + return about.default; + case 'contact': + const contact = await import('./pages/Contact.js'); + return contact.default; + } +} + +// Common use: Conditional loading +if (userWantsCharts) { + const { renderChart } = await import('./chart-library.js'); + renderChart(data); +} +``` + + +**Performance tip:** Dynamic imports are great for loading heavy libraries only when needed. This makes your app's initial load faster. + + +--- + +## The Evolution: From IIFEs to Modules + +Here's how the same code would look in each era: + + + + ```javascript + // Everything pollutes global scope + var counter = 0; + + function increment() { + counter++; + } + + function getCount() { + return counter; + } + + // Problem: Anyone can do this + counter = 999; // Oops, state corrupted! + ``` + + + + ```javascript + // Uses closure to hide counter + var Counter = (function() { + var counter = 0; // Private! + + return { + increment: function() { + counter++; + }, + getCount: function() { + return counter; + } + }; + })(); + + Counter.increment(); + console.log(Counter.getCount()); // 1 + console.log(Counter.counter); // undefined (private!) + ``` + + + + ```javascript + // counter.js + let counter = 0; // Private (not exported) + + export function increment() { + counter++; + } + + export function getCount() { + return counter; + } + + // main.js + import { increment, getCount } from './counter.js'; + + increment(); + console.log(getCount()); // 1 + // counter variable is not accessible at all + ``` + + + +--- + +## Common Patterns and Best Practices + +### 1. One Thing Per Module + +Each module should do one thing well: + +```javascript +// ✗ Bad: One file does everything +// utils.js with 50 different functions + +// ✓ Good: Separate concerns +// formatters.js — formatting functions +// validators.js — validation functions +// api.js — API calls +``` + +### 2. Keep Related Things Together + +```javascript +// user/ +// ├── User.js # User class +// ├── userService.js # User API calls +// ├── userUtils.js # User-related utilities +// └── index.js # Re-exports public API +``` + +### 3. Avoid Circular Dependencies + +```javascript +// ✗ Bad: A imports B, B imports A +// a.js +import { fromB } from './b.js'; +export const fromA = "A"; + +// b.js +import { fromA } from './a.js'; // Circular! +export const fromB = "B"; + +// ✓ Good: Create a third module for shared code +// shared.js +export const sharedThing = "shared"; + +// a.js +import { sharedThing } from './shared.js'; + +// b.js +import { sharedThing } from './shared.js'; +``` + +### 4. Use Default Exports for Components/Classes + +```javascript +// Components are usually one-per-file +// Button.js +export default function Button({ label, onClick }) { + return ; +} + +// Usage is clean +import Button from './Button.js'; +``` + +### 5. Use Named Exports for Utilities + +```javascript +// Multiple utilities in one file +// stringUtils.js +export function capitalize(str) { /* ... */ } +export function truncate(str, length) { /* ... */ } +export function slugify(str) { /* ... */ } + +// Import only what you need +import { capitalize } from './stringUtils.js'; +``` + +--- + +## Key Takeaways + + +**Key Points to Remember:** + +1. **IIFEs** create private scope by running immediately — useful for initialization and avoiding globals + +2. **Namespaces** group related code under one object — reduces global pollution but isn't true encapsulation + +3. **ES6 Modules** are the modern solution — file-based, true privacy, and built into the language + +4. **Named exports** let you export multiple things — import what you need by name + +5. **Default exports** are for the main thing a module provides — one per file + +6. **Dynamic imports** load modules on demand — great for performance optimization + +7. **Each module has its own scope** — variables are private unless exported + +8. **Use modules for new projects** — IIFEs and namespaces are for legacy code or special cases + +9. **Organize by feature or type** — group related modules in folders with index.js barrel files + +10. **Avoid circular dependencies** — they cause confusing bugs and loading issues + + +--- + +## Test Your Knowledge + +Try to answer each question before revealing the solution: + + + + **Answer:** IIFE stands for **Immediately Invoked Function Expression**. + + It was invented to solve the problem of global scope pollution. Before ES6 modules, all JavaScript code shared the same global scope. Variables from different files could accidentally overwrite each other. IIFEs create a private scope where variables are protected from outside access. + + + + **Answer:** + + **Named exports:** + - Can have multiple per module + - Must be imported by exact name (or aliased) + - Use `export { name }` or `export function name()` + - Import with `import { name } from './module.js'` + + **Default exports:** + - Only one per module + - Can be imported with any name + - Use `export default` + - Import with `import anyName from './module.js'` + + ```javascript + // Named export + export const PI = 3.14; + import { PI } from './math.js'; + + // Default export + export default function add(a, b) { return a + b; } + import myAdd from './math.js'; // Any name works + ``` + + + + **Answer:** Declare the variable inside the IIFE. It won't be accessible from outside because it's in the function's local scope. + + ```javascript + const module = (function() { + // Private variable + let privateCounter = 0; + + // Return public methods that can access it + return { + increment() { privateCounter++; }, + getCount() { return privateCounter; } + }; + })(); + + module.increment(); + console.log(module.getCount()); // 1 + console.log(module.privateCounter); // undefined (private!) + ``` + + + + **Answer:** + + **Static imports:** + - Loaded at the top of the file + - Always loaded, even if not used + - Analyzed at build time + - Syntax: `import { x } from './module.js'` + + **Dynamic imports:** + - Can be loaded anywhere in the code + - Loaded only when the import() call runs + - Loaded at runtime, returns a Promise + - Syntax: `const module = await import('./module.js')` + + Use dynamic imports for code splitting and loading modules on demand. + + + + **Answer:** Circular dependencies occur when module A imports from module B, and module B imports from module A. + + Problems: + - **Loading issues:** When A loads, it needs B. But B needs A, which isn't fully loaded yet. + - **Undefined values:** You might get `undefined` for imports that should have values. + - **Confusing bugs:** Hard to track down because the error isn't where the bug is. + + Solution: Create a third module for shared code, or restructure your code to break the cycle. + + + + **Answer:** Even with ES6 modules, IIFEs are useful for: + + 1. **Async initialization:** + ```javascript + (async () => { + const data = await fetchData(); + init(data); + })(); + ``` + + 2. **One-time calculations:** + ```javascript + const config = (() => { + // Complex setup that runs once + return computedConfig; + })(); + ``` + + 3. **Scripts without modules:** When you're adding a ` +
Hidden
+``` + +``` +DOM Render Tree +┌─────────────────────┐ ┌─────────────────────┐ +│ │ │ │ +│ │ │ │ +│ │ │ <h1> │ +│ <body> │ │ "Hello" │ +│ <h1>Hello</h1> │ │ <p> │ +│ <p>World</p> │ │ "World" │ +│ <div hidden> │ │ │ +│ Secret! │ │ (no hidden div!) │ +│ </div> │ │ │ +└─────────────────────┘ └─────────────────────┘ +``` + +### The `document` Object: Your Entry Point + +The `document` object is your gateway to the DOM. It's automatically available in any browser JavaScript: + +```javascript +// document is the root of everything +console.log(document); // The entire document +console.log(document.documentElement); // <html> element +console.log(document.head); // <head> element +console.log(document.body); // <body> element +console.log(document.title); // Page title (getter/setter!) + +// You can modify the document +document.title = 'New Title'; // Changes browser tab title +``` + +--- + +## DOM Tree Structure & Node Types + +Everything in the DOM is a **node**. But not all nodes are created equal! + +### The Node Type Hierarchy + +``` + Node (base class) + │ + ┌─────────────────────┼─────────────────────┐ + │ │ │ + Document Element CharacterData + │ │ │ + HTMLDocument ┌────┴────┐ ┌─────┴─────┐ + │ │ │ │ + HTMLElement SVGElement Text Comment + │ + ┌────────────────┼────────────────┐ + │ │ │ + HTMLDivElement HTMLSpanElement HTMLInputElement + ... +``` + +### Node Types You'll Encounter + +| Node Type | `nodeType` | `nodeName` | Example | +|-----------|------------|------------|---------| +| Element | `1` | Tag name (uppercase) | `<div>`, `<p>`, `<span>` | +| Text | `3` | `#text` | Text inside elements | +| Comment | `8` | `#comment` | `<!-- comment -->` | +| Document | `9` | `#document` | The `document` object | +| DocumentFragment | `11` | `#document-fragment` | Virtual container | + +```javascript +const div = document.createElement('div'); +console.log(div.nodeType); // 1 (Element) +console.log(div.nodeName); // "DIV" + +const text = document.createTextNode('Hello'); +console.log(text.nodeType); // 3 (Text) +console.log(text.nodeName); // "#text" + +console.log(document.nodeType); // 9 (Document) +console.log(document.nodeName); // "#document" +``` + +### Node Type Constants + +Instead of remembering numbers, use the constants: + +```javascript +Node.ELEMENT_NODE // 1 +Node.TEXT_NODE // 3 +Node.COMMENT_NODE // 8 +Node.DOCUMENT_NODE // 9 +Node.DOCUMENT_FRAGMENT_NODE // 11 + +// Check if something is an element +if (node.nodeType === Node.ELEMENT_NODE) { + console.log('This is an element!'); +} +``` + +### Visualizing a Real DOM Tree + +Given this HTML: + +```html +<div id="container"> + <h1>Title</h1> + <!-- A comment --> + <p>Paragraph</p> +</div> +``` + +The actual DOM tree looks like this (including text nodes from whitespace!): + +``` +div#container +├── #text (newline + spaces) +├── h1 +│ └── #text "Title" +├── #text (newline + spaces) +├── #comment " A comment " +├── #text (newline + spaces) +├── p +│ └── #text "Paragraph" +└── #text (newline) +``` + +<Warning> +**The Whitespace Gotcha!** Line breaks and spaces between HTML tags create **text nodes**. This surprises many developers! We'll see how to handle this in the traversal section. +</Warning> + +--- + +## Selecting Elements + +Before you can manipulate an element, you need to find it. JavaScript provides several methods: + +### The getElementById() Classic + +The fastest way to select a single element by its unique ID: + +```javascript +// HTML: <div id="hero">Welcome!</div> + +const hero = document.getElementById('hero'); +console.log(hero); // <div id="hero">Welcome!</div> + +// Returns null if not found (not an error!) +const ghost = document.getElementById('nonexistent'); +console.log(ghost); // null +``` + +<Tip> +IDs must be unique in a document. If you have duplicate IDs, `getElementById` returns the first one. But don't do this—it's invalid HTML! +</Tip> + +### getElementsByClassName() and getElementsByTagName() + +Select multiple elements by class or tag name: + +```javascript +// HTML: +// <p class="intro">First</p> +// <p class="intro">Second</p> +// <p>Third</p> + +const intros = document.getElementsByClassName('intro'); +console.log(intros.length); // 2 +console.log(intros[0]); // <p class="intro">First</p> + +const allParagraphs = document.getElementsByTagName('p'); +console.log(allParagraphs.length); // 3 +``` + +### The Modern Way: querySelector() and querySelectorAll() + +Use CSS selectors to find elements—much more powerful! + +```javascript +// querySelector returns the FIRST match (or null) +const firstButton = document.querySelector('button'); +const submitBtn = document.querySelector('#submit'); +const firstCard = document.querySelector('.card'); +const navLink = document.querySelector('nav a.active'); +const dataItem = document.querySelector('[data-id="123"]'); + +// querySelectorAll returns ALL matches (NodeList) +const allButtons = document.querySelectorAll('button'); +const allCards = document.querySelectorAll('.card'); +const evenRows = document.querySelectorAll('tr:nth-child(even)'); +``` + +### Selector Examples + +```javascript +// By ID +document.querySelector('#main'); + +// By class +document.querySelector('.active'); +document.querySelectorAll('.btn.primary'); + +// By tag +document.querySelector('header'); +document.querySelectorAll('li'); + +// By attribute +document.querySelector('[type="submit"]'); +document.querySelector('[data-modal="login"]'); + +// Descendant selectors +document.querySelector('nav ul li a'); +document.querySelector('.sidebar .widget:first-child'); + +// Pseudo-selectors (limited support) +document.querySelectorAll('input:not([type="hidden"])'); +document.querySelector('p:first-of-type'); +``` + +### Live vs Static Collections + +This is a crucial difference that trips up many developers: + +```javascript +const liveList = document.getElementsByClassName('item'); // LIVE +const staticList = document.querySelectorAll('.item'); // STATIC + +// Start with 3 items +console.log(liveList.length); // 3 +console.log(staticList.length); // 3 + +// Add a new item to the DOM +const newItem = document.createElement('div'); +newItem.className = 'item'; +document.body.appendChild(newItem); + +// Check lengths again +console.log(liveList.length); // 4 (automatically updated!) +console.log(staticList.length); // 3 (still the old snapshot) +``` + +| Method | Returns | Live? | +|--------|---------|-------| +| `getElementById()` | Element or null | N/A | +| `getElementsByClassName()` | HTMLCollection | **Yes** (live) | +| `getElementsByTagName()` | HTMLCollection | **Yes** (live) | +| `querySelector()` | Element or null | N/A | +| `querySelectorAll()` | NodeList | **No** (static) | + +### Scoped Selection + +You can call selection methods on any element, not just `document`: + +```javascript +const nav = document.querySelector('nav'); + +// Find links ONLY inside nav +const navLinks = nav.querySelectorAll('a'); + +// Find the active link inside nav +const activeLink = nav.querySelector('.active'); +``` + +This is faster than searching the entire document and helps avoid selecting unintended elements. + +### Performance Comparison + +<AccordionGroup> + <Accordion title="Which selector method is fastest?"> + In order of speed (fastest first): + + 1. **`getElementById()`** - Direct hashtable lookup, O(1) + 2. **`getElementsByClassName()`** - Optimized internal lookup + 3. **`getElementsByTagName()`** - Optimized internal lookup + 4. **`querySelector()`** - Must parse CSS selector + 5. **`querySelectorAll()`** - Must parse and find all matches + + However, for most applications, **the difference is negligible**. Use `querySelector/querySelectorAll` for readability unless you're selecting thousands of elements in a loop. + + ```javascript + // Premature optimization - don't do this + const el1 = document.getElementById('myId'); + + // This is fine and more readable + const el2 = document.querySelector('#myId'); + ``` + </Accordion> +</AccordionGroup> + +--- + +## Traversing the DOM + +Once you have an element, you can navigate to related elements without querying the entire document. + +### Traversing Downwards (To Children) + +```javascript +const ul = document.querySelector('ul'); + +// Get ALL child nodes (including text nodes!) +const allChildNodes = ul.childNodes; // NodeList + +// Get only ELEMENT children (usually what you want) +const elementChildren = ul.children; // HTMLCollection + +// Get specific children +const firstChild = ul.firstChild; // First node (might be text!) +const firstElement = ul.firstElementChild; // First ELEMENT child +const lastChild = ul.lastChild; // Last node +const lastElement = ul.lastElementChild; // Last ELEMENT child +``` + +<Warning> +**The Text Node Trap!** Look at this HTML: + +```html +<ul> + <li>One</li> + <li>Two</li> +</ul> +``` + +What is `ul.firstChild`? It's NOT the first `<li>`! It's a **text node** containing the newline and spaces after `<ul>`. Use `firstElementChild` to get the actual `<li>` element. +</Warning> + +### Traversing Upwards (To Parents) + +```javascript +const li = document.querySelector('li'); + +// Direct parent +const parent = li.parentNode; // Usually same as parentElement +const parentEl = li.parentElement; // Guaranteed to be an Element (or null) + +// Find ancestor matching selector (very useful!) +const form = li.closest('form'); // Finds nearest ancestor <form> +const card = li.closest('.card'); // Finds nearest ancestor with class "card" + +// closest() includes the element itself +const self = li.closest('li'); // Returns li itself if it matches! +``` + +The `closest()` method is incredibly useful for event delegation: + +```javascript +// Handle clicks on any button inside a card +document.addEventListener('click', (e) => { + const card = e.target.closest('.card'); + if (card) { + console.log('Clicked inside card:', card); + } +}); +``` + +### Traversing Sideways (To Siblings) + +```javascript +const secondLi = document.querySelectorAll('li')[1]; + +// Previous/next nodes (might be text!) +const prevNode = secondLi.previousSibling; +const nextNode = secondLi.nextSibling; + +// Previous/next ELEMENTS (usually what you want) +const prevElement = secondLi.previousElementSibling; +const nextElement = secondLi.nextElementSibling; + +// Returns null at the boundaries +const firstLi = document.querySelector('li'); +console.log(firstLi.previousElementSibling); // null (no previous sibling) +``` + +### Node vs Element Properties Cheat Sheet + +| Get... | Node Property (includes text) | Element Property (elements only) | +|--------|-------------------------------|----------------------------------| +| Parent | `parentNode` | `parentElement` | +| Children | `childNodes` | `children` | +| First child | `firstChild` | `firstElementChild` | +| Last child | `lastChild` | `lastElementChild` | +| Previous sibling | `previousSibling` | `previousElementSibling` | +| Next sibling | `nextSibling` | `nextElementSibling` | + +<Tip> +**Rule of thumb:** Unless you specifically need text nodes, always use the Element variants (`children`, `firstElementChild`, `nextElementSibling`, etc.) +</Tip> + +### Practical Example: Building a Breadcrumb Trail + +```javascript +// Get all ancestors of an element +function getAncestors(element) { + const ancestors = []; + let current = element.parentElement; + + while (current && current !== document.body) { + ancestors.push(current); + current = current.parentElement; + } + + return ancestors; +} + +const deepElement = document.querySelector('.deeply-nested'); +console.log(getAncestors(deepElement)); +// [<div.parent>, <section>, <main>, ...] +``` + +--- + +## Creating and Manipulating Elements + +The real power of the DOM is the ability to create, modify, and remove elements dynamically. + +### Creating Elements + +```javascript +// Create a new element +const div = document.createElement('div'); +const span = document.createElement('span'); +const img = document.createElement('img'); + +// Create a text node +const text = document.createTextNode('Hello, world!'); + +// Create a comment node +const comment = document.createComment('This is a comment'); + +// Elements are created "detached" - not yet in the DOM! +console.log(div.parentNode); // null +``` + +### Adding Elements to the DOM + +There are many ways to add elements. Here's a comprehensive overview: + +<Tabs> + <Tab title="appendChild()"> + Adds a node as the **last child** of a parent: + + ```javascript + const ul = document.querySelector('ul'); + const li = document.createElement('li'); + li.textContent = 'New item'; + + ul.appendChild(li); + // <ul> + // <li>Existing</li> + // <li>New item</li> ← Added at the end + // </ul> + ``` + </Tab> + <Tab title="insertBefore()"> + Inserts a node **before** a reference node: + + ```javascript + const ul = document.querySelector('ul'); + const existingLi = ul.querySelector('li'); + const newLi = document.createElement('li'); + newLi.textContent = 'First!'; + + ul.insertBefore(newLi, existingLi); + // <ul> + // <li>First!</li> ← Inserted before + // <li>Existing</li> + // </ul> + ``` + </Tab> + <Tab title="append() / prepend()"> + Modern methods that accept multiple nodes AND strings: + + ```javascript + const div = document.querySelector('div'); + + // append() - adds to the END + div.append('Text', document.createElement('span'), 'More text'); + + // prepend() - adds to the START + div.prepend(document.createElement('strong')); + ``` + </Tab> + <Tab title="before() / after()"> + Insert as siblings (not children): + + ```javascript + const h1 = document.querySelector('h1'); + + // Insert BEFORE h1 (as previous sibling) + h1.before(document.createElement('nav')); + + // Insert AFTER h1 (as next sibling) + h1.after(document.createElement('p')); + ``` + </Tab> +</Tabs> + +### insertAdjacentHTML() - The Swiss Army Knife + +For inserting HTML strings, `insertAdjacentHTML()` is powerful and fast: + +```javascript +const div = document.querySelector('div'); + +// Four positions to insert: +div.insertAdjacentHTML('beforebegin', '<p>Before div</p>'); +div.insertAdjacentHTML('afterbegin', '<p>First child of div</p>'); +div.insertAdjacentHTML('beforeend', '<p>Last child of div</p>'); +div.insertAdjacentHTML('afterend', '<p>After div</p>'); +``` + +Visual representation: + +```html +<!-- beforebegin --> +<div> + <!-- afterbegin --> + existing content + <!-- beforeend --> +</div> +<!-- afterend --> +``` + +### Removing Elements + +<Tabs> + <Tab title="remove()"> + Modern and simple—element removes itself: + + ```javascript + const element = document.querySelector('.to-remove'); + element.remove(); // Gone! + ``` + </Tab> + <Tab title="removeChild()"> + Classic method—remove via parent: + + ```javascript + const parent = document.querySelector('ul'); + const child = parent.querySelector('li'); + parent.removeChild(child); + + // Or remove from any element + element.parentNode.removeChild(element); + ``` + </Tab> +</Tabs> + +### Cloning Elements + +```javascript +const original = document.querySelector('.card'); + +// Shallow clone (element only, no children) +const shallow = original.cloneNode(false); + +// Deep clone (element AND all descendants) +const deep = original.cloneNode(true); + +// Clones are detached - must add to DOM +document.body.appendChild(deep); +``` + +<Warning> +**ID Collision!** If you clone an element with an ID, you'll have duplicate IDs in your document (invalid HTML). Remove or change the ID after cloning: + +```javascript +const clone = original.cloneNode(true); +clone.id = ''; // Remove ID +// or +clone.id = 'new-unique-id'; +``` +</Warning> + +### DocumentFragment - Batch Operations + +When adding many elements, using a `DocumentFragment` is more efficient: + +```javascript +// Bad: Multiple reflows +const ul = document.querySelector('ul'); +for (let i = 0; i < 1000; i++) { + const li = document.createElement('li'); + li.textContent = `Item ${i}`; + ul.appendChild(li); // Triggers reflow each time! +} + +// Good: Single reflow +const ul = document.querySelector('ul'); +const fragment = document.createDocumentFragment(); + +for (let i = 0; i < 1000; i++) { + const li = document.createElement('li'); + li.textContent = `Item ${i}`; + fragment.appendChild(li); // No reflow (fragment is detached) +} + +ul.appendChild(fragment); // Single reflow! +``` + +A `DocumentFragment` is a lightweight container that: +- Is not part of the DOM tree +- Has no parent +- When appended, only its **children** are inserted (the fragment itself disappears) + +--- + +## Modifying Content + +Three properties let you read and write element content: + +### innerHTML - Parse and Insert HTML + +```javascript +const div = document.querySelector('div'); + +// Read HTML content +console.log(div.innerHTML); // "<p>Hello</p><span>World</span>" + +// Write HTML content (parses the string!) +div.innerHTML = '<h1>New Title</h1><p>New paragraph</p>'; + +// Clear all content +div.innerHTML = ''; +``` + +<Warning> +**Security Alert: XSS Vulnerability!** + +Never use `innerHTML` with user-provided content: + +```javascript +// DANGEROUS! User could inject: <img src=x onerror="stealCookies()"> +div.innerHTML = userInput; // NO! + +// Safe alternatives: +div.textContent = userInput; // Escapes HTML +// or sanitize the input first +``` +</Warning> + +### textContent - Plain Text Only + +```javascript +const div = document.querySelector('div'); + +// Read text (ignores HTML tags) +// <div><p>Hello</p><span>World</span></div> +console.log(div.textContent); // "HelloWorld" + +// Write text (HTML is escaped, not parsed) +div.textContent = '<script>alert("XSS")</script>'; +// Displays literally: <script>alert("XSS")</script> +// Safe from XSS! +``` + +### innerText - Rendered Text + +```javascript +const div = document.querySelector('div'); + +// innerText respects CSS visibility +// <div>Hello <span style="display:none">Hidden</span> World</div> + +console.log(div.textContent); // "Hello Hidden World" +console.log(div.innerText); // "Hello World" (Hidden is excluded!) +``` + +### When to Use Each + +| Property | Use Case | +|----------|----------| +| `innerHTML` | Inserting trusted HTML (never user input!) | +| `textContent` | Setting/getting plain text (safe, fast) | +| `innerText` | Getting text as user sees it (slower, respects CSS) | + +```javascript +// Performance: textContent is faster than innerText +// because innerText must calculate styles + +// Setting text content (both work, textContent is faster) +element.textContent = 'Hello'; // Preferred +element.innerText = 'Hello'; // Works but slower +``` + +--- + +## Working with Attributes + +HTML elements have attributes. JavaScript lets you read, write, and remove them. + +### Standard Attribute Methods + +```javascript +const link = document.querySelector('a'); + +// Get attribute value +const href = link.getAttribute('href'); +const target = link.getAttribute('target'); + +// Set attribute value +link.setAttribute('href', 'https://example.com'); +link.setAttribute('target', '_blank'); + +// Check if attribute exists +if (link.hasAttribute('target')) { + console.log('Link opens in new tab'); +} + +// Remove attribute +link.removeAttribute('target'); +``` + +### Properties vs Attributes: The Difference + +This confuses many developers! **Attributes** are in the HTML. **Properties** are on the DOM object. + +```html +<input type="text" value="initial"> +``` + +```javascript +const input = document.querySelector('input'); + +// ATTRIBUTE: The original HTML value +console.log(input.getAttribute('value')); // "initial" + +// PROPERTY: The current state +console.log(input.value); // "initial" + +// User types "new text"... +console.log(input.getAttribute('value')); // Still "initial"! +console.log(input.value); // "new text" + +// Reset to attribute value +input.value = input.getAttribute('value'); +``` + +Key differences: + +| Aspect | Attribute | Property | +|--------|-----------|----------| +| Source | HTML markup | DOM object | +| Access | `get/setAttribute()` | Direct property access | +| Updates | Manual only | Automatically with user interaction | +| Type | Always string | Can be any type | + +```javascript +// Attribute is always a string +checkbox.getAttribute('checked'); // "" or null + +// Property is a boolean +checkbox.checked; // true or false + +// Attribute (string) +input.getAttribute('maxlength'); // "10" + +// Property (number) +input.maxLength; // 10 +``` + +### Data Attributes and the dataset API + +Custom data attributes start with `data-` and are accessible via the `dataset` property: + +```html +<div id="user" + data-user-id="123" + data-role="admin" + data-is-active="true"> + John Doe +</div> +``` + +```javascript +const user = document.querySelector('#user'); + +// Read data attributes (camelCase!) +console.log(user.dataset.userId); // "123" +console.log(user.dataset.role); // "admin" +console.log(user.dataset.isActive); // "true" (string, not boolean!) + +// Write data attributes +user.dataset.lastLogin = '2024-01-15'; +// Creates: data-last-login="2024-01-15" + +// Delete data attributes +delete user.dataset.role; + +// Check if exists +if ('userId' in user.dataset) { + console.log('Has user ID'); +} +``` + +<Tip> +**Naming Convention:** HTML uses `kebab-case` (`data-user-id`), JavaScript uses `camelCase` (`dataset.userId`). The conversion is automatic! +</Tip> + +### Common Attribute Shortcuts + +Many attributes have direct property shortcuts: + +```javascript +// These pairs are equivalent: +element.id // element.getAttribute('id') +element.className // element.getAttribute('class') +element.href // element.getAttribute('href') +element.src // element.getAttribute('src') +element.title // element.getAttribute('title') + +// For class manipulation, use classList (covered next) +``` + +--- + +## Styling Elements + +JavaScript can modify element styles in several ways. + +### The style Property (Inline Styles) + +```javascript +const box = document.querySelector('.box'); + +// Set individual styles (camelCase!) +box.style.backgroundColor = 'blue'; +box.style.fontSize = '20px'; +box.style.marginTop = '10px'; + +// Read styles (only reads INLINE styles!) +console.log(box.style.backgroundColor); // "blue" +console.log(box.style.color); // "" (not inline, from stylesheet) + +// Set multiple styles at once +box.style.cssText = 'background: red; font-size: 16px; padding: 10px;'; + +// Remove an inline style +box.style.backgroundColor = ''; // Removes the style +``` + +<Warning> +`element.style` only reads/writes **inline** styles! To get computed styles (from stylesheets), use `getComputedStyle()`. +</Warning> + +### getComputedStyle() - Read Actual Styles + +```javascript +const box = document.querySelector('.box'); + +// Get all computed styles +const styles = getComputedStyle(box); + +console.log(styles.backgroundColor); // "rgb(0, 0, 255)" +console.log(styles.fontSize); // "16px" +console.log(styles.display); // "block" + +// Get pseudo-element styles +const beforeStyles = getComputedStyle(box, '::before'); +console.log(beforeStyles.content); // '"Hello"' +``` + +### classList - Manipulate CSS Classes + +The `classList` API is the modern way to add/remove/toggle classes: + +```javascript +const button = document.querySelector('button'); + +// Add classes +button.classList.add('active'); +button.classList.add('btn', 'btn-primary'); // Multiple at once + +// Remove classes +button.classList.remove('active'); +button.classList.remove('btn', 'btn-primary'); // Multiple at once + +// Toggle (add if missing, remove if present) +button.classList.toggle('active'); + +// Toggle with condition +button.classList.toggle('active', isActive); // Add if isActive is true + +// Check if class exists +if (button.classList.contains('active')) { + console.log('Button is active'); +} + +// Replace a class +button.classList.replace('btn-primary', 'btn-secondary'); + +// Iterate over classes +button.classList.forEach(cls => console.log(cls)); + +// Get number of classes +console.log(button.classList.length); // 2 +``` + +### className vs classList + +```javascript +// className is a string (old way) +element.className = 'btn btn-primary'; // Replaces ALL classes +element.className += ' active'; // Appending is clunky + +// classList is a DOMTokenList (modern way) +element.classList.add('active'); // Adds without affecting others +element.classList.remove('btn-primary'); // Removes specifically +``` + +--- + +## The Render Tree & Critical Rendering Path + +Understanding how browsers render pages helps you write performant code. + +### From HTML to Pixels + +When you load a webpage, the browser goes through these steps: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ THE CRITICAL RENDERING PATH │ +│ │ +│ 1. PARSE HTML 2. PARSE CSS 3. BUILD RENDER TREE │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ HTML bytes │ │ CSS bytes │ │ DOM + CSSOM │ │ +│ │ ↓ │ │ ↓ │ │ ↘ ↙ │ │ +│ │ Characters │ │ Characters │ │ RENDER TREE │ │ +│ │ ↓ │ │ ↓ │ │ (visible elements │ │ +│ │ Tokens │ │ Tokens │ │ + their styles) │ │ +│ │ ↓ │ │ ↓ │ └──────────────────────┘ │ +│ │ Nodes │ │ Rules │ │ │ +│ │ ↓ │ │ ↓ │ ▼ │ +│ │ DOM │ │ CSSOM │ 4. LAYOUT (Reflow) │ +│ └──────────────┘ └──────────────┘ ┌──────────────────────┐ │ +│ │ Calculate exact │ │ +│ │ position & size of │ │ +│ │ every element │ │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ▼ │ +│ 5. PAINT │ +│ ┌──────────────────────┐ │ +│ │ Fill in pixels: │ │ +│ │ colors, borders, │ │ +│ │ shadows, text │ │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ▼ │ +│ 6. COMPOSITE │ +│ ┌──────────────────────┐ │ +│ │ Combine layers into │ │ +│ │ final image (GPU) │ │ +│ └──────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ PIXELS! │ │ +│ └──────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### What's NOT in the Render Tree + +The Render Tree only contains visible elements: + +```html +<!-- NOT in Render Tree --> +<head>...</head> <!-- head is never rendered --> +<script>...</script> <!-- script tags aren't visible --> +<link rel="stylesheet"> <!-- link tags aren't visible --> +<meta> <!-- meta tags aren't visible --> +<div style="display: none">Hi</div> <!-- display:none excluded --> + +<!-- IN the Render Tree (even if not seen) --> +<div style="visibility: hidden">Hi</div> <!-- Takes up space --> +<div style="opacity: 0">Hi</div> <!-- Takes up space --> +``` + +### Layout (Reflow) - The Expensive Step + +Layout calculates the **geometry** of every element: position, size, margins, etc. + +**Reflow is triggered when:** +- Adding/removing elements +- Changing element dimensions (width, height, padding, margin) +- Changing font size +- Resizing the window +- Reading certain properties (more on this below!) + +### Paint - Drawing Pixels + +After layout, the browser paints the pixels: text, colors, images, borders, shadows. + +**Repaint (without reflow) happens when:** +- Changing colors +- Changing background-image +- Changing visibility +- Changing box-shadow (sometimes) + +### Composite - Layering + +Modern browsers separate content into layers and use the GPU to composite them. This is why some animations are smooth: + +```css +/* These properties can animate without reflow/repaint */ +transform: translateX(100px); /* GPU accelerated! */ +opacity: 0.5; /* GPU accelerated! */ + +/* These properties cause reflow */ +left: 100px; /* Avoid for animations! */ +width: 200px; /* Avoid for animations! */ +``` + +--- + +## Performance Best Practices + +DOM operations can be slow. Here's how to keep your pages fast. + +### Cache DOM References + +```javascript +// Bad: Queries the DOM every iteration +for (let i = 0; i < 1000; i++) { + document.querySelector('.result').textContent += i; +} + +// Good: Query once, reuse +const result = document.querySelector('.result'); +for (let i = 0; i < 1000; i++) { + result.textContent += i; +} + +// Even better: Build string, set once +const result = document.querySelector('.result'); +let text = ''; +for (let i = 0; i < 1000; i++) { + text += i; +} +result.textContent = text; +``` + +### Batch DOM Updates + +```javascript +// Bad: 3 separate reflows +element.style.width = '100px'; +element.style.height = '200px'; +element.style.margin = '10px'; + +// Good: Single reflow with cssText +element.style.cssText = 'width: 100px; height: 200px; margin: 10px;'; + +// Good: Single reflow with class +element.classList.add('my-styles'); + +// Good: DocumentFragment for multiple elements +const fragment = document.createDocumentFragment(); +items.forEach(item => { + const li = document.createElement('li'); + li.textContent = item; + fragment.appendChild(li); +}); +ul.appendChild(fragment); // Single DOM update +``` + +### Avoid Layout Thrashing + +**Layout thrashing** occurs when you alternate between reading and writing DOM properties: + +```javascript +// TERRIBLE: Forces layout on EVERY iteration +boxes.forEach(box => { + const width = box.offsetWidth; // Read (forces layout) + box.style.width = (width + 10) + 'px'; // Write (invalidates layout) +}); + +// GOOD: Batch reads, then batch writes +const widths = boxes.map(box => box.offsetWidth); // Read all +boxes.forEach((box, i) => { + box.style.width = (widths[i] + 10) + 'px'; // Write all +}); +``` + +**Properties that trigger layout when read:** + +```javascript +// Reading these forces the browser to calculate layout +element.offsetWidth element.offsetHeight +element.offsetTop element.offsetLeft +element.clientWidth element.clientHeight +element.scrollWidth element.scrollHeight +element.scrollTop element.scrollLeft +element.getBoundingClientRect() +getComputedStyle(element) +``` + +### Use requestAnimationFrame for Visual Changes + +```javascript +// Bad: DOM changes at unpredictable times +window.addEventListener('scroll', () => { + element.style.transform = `translateY(${window.scrollY}px)`; +}); + +// Good: Batch visual changes with next frame +let ticking = false; +window.addEventListener('scroll', () => { + if (!ticking) { + requestAnimationFrame(() => { + element.style.transform = `translateY(${window.scrollY}px)`; + ticking = false; + }); + ticking = true; + } +}); +``` + +--- + +## Common Patterns + +### Event Delegation + +Instead of adding listeners to many elements, add one to a parent: + +```javascript +// Bad: Many listeners +document.querySelectorAll('.btn').forEach(btn => { + btn.addEventListener('click', handleClick); +}); + +// Good: One listener with delegation +document.querySelector('.button-container').addEventListener('click', (e) => { + const btn = e.target.closest('.btn'); + if (btn) { + handleClick(e); + } +}); +``` + +Benefits: +- Works for dynamically added elements +- Less memory usage +- Easier cleanup + +### Checking if Element Exists + +```javascript +// Using querySelector (returns null if not found) +const element = document.querySelector('.maybe-exists'); +if (element) { + element.textContent = 'Found!'; +} + +// Optional chaining (modern) +document.querySelector('.maybe-exists')?.classList.add('active'); + +// With getElementById +const el = document.getElementById('myId'); +if (el !== null) { + // Element exists +} +``` + +### Waiting for DOM Ready + +```javascript +// Modern: DOMContentLoaded (DOM ready, images may still be loading) +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM is ready!'); + // Safe to query elements +}); + +// Full page load (including images, stylesheets) +window.addEventListener('load', () => { + console.log('Everything loaded!'); +}); + +// If script is at end of body, DOM is already ready +// <script src="app.js"></script> <!-- Just before </body> --> + +// Modern: defer attribute (script loads in parallel, runs after DOM ready) +// <script src="app.js" defer></script> +``` + +<Tip> +**Best practice:** Put your `<script>` tags just before `</body>` or use the `defer` attribute. Then you don't need to wait for DOMContentLoaded. +</Tip> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between childNodes and children?"> + **Answer:** + + - `childNodes` returns ALL child nodes, including **text nodes** (whitespace!) and **comment nodes** + - `children` returns only **element nodes** + + ```javascript + // <ul> + // <li>One</li> + // <li>Two</li> + // </ul> + + ul.childNodes.length; // 5 (text, li, text, li, text) + ul.children.length; // 2 (li, li) + ``` + + **Rule:** Use `children` unless you specifically need text/comment nodes. + </Accordion> + + <Accordion title="Question 2: Why is innerHTML dangerous with user input?"> + **Answer:** `innerHTML` parses strings as HTML, enabling **Cross-Site Scripting (XSS)** attacks: + + ```javascript + // User input: <img src=x onerror="stealCookies()"> + div.innerHTML = userInput; // Executes malicious code! + + // Safe: textContent escapes HTML + div.textContent = userInput; // Displays as plain text + ``` + + Always sanitize HTML or use `textContent` for user-provided content. + </Accordion> + + <Accordion title="Question 3: What's the difference between getAttribute('value') and .value on an input?"> + **Answer:** + + - `getAttribute('value')` returns the **original HTML attribute** (initial value) + - `.value` property returns the **current value** (what user typed) + + ```javascript + // <input value="initial"> + // User types "hello" + + input.getAttribute('value'); // "initial" + input.value; // "hello" + ``` + + Attributes are the HTML source. Properties are the live DOM state. + </Accordion> + + <Accordion title="Question 4: What does closest() do and why is it useful?"> + **Answer:** `closest()` finds the nearest **ancestor** (including the element itself) that matches a selector: + + ```javascript + // <div class="card"> + // <button class="btn">Click</button> + // </div> + + btn.closest('.card'); // Returns the parent div + btn.closest('button'); // Returns btn itself (it matches!) + btn.closest('.modal'); // null (no matching ancestor) + ``` + + **Super useful for event delegation:** + + ```javascript + document.addEventListener('click', (e) => { + const card = e.target.closest('.card'); + if (card) { + // Handle click inside any card + } + }); + ``` + </Accordion> + + <Accordion title="Question 5: What causes layout thrashing and how do you avoid it?"> + **Answer:** Layout thrashing happens when you **alternate reading and writing** layout-triggering properties: + + ```javascript + // BAD: Read-write-read-write pattern + boxes.forEach(box => { + const width = box.offsetWidth; // READ → forces layout + box.style.width = width + 10 + 'px'; // WRITE → invalidates layout + }); + // Each iteration forces a new layout calculation! + + // GOOD: Batch reads, then batch writes + const widths = boxes.map(b => b.offsetWidth); // All reads + boxes.forEach((box, i) => { + box.style.width = widths[i] + 10 + 'px'; // All writes + }); + // Only one layout calculation! + ``` + </Accordion> + + <Accordion title="Question 6: What's in the Render Tree vs the DOM?"> + **Answer:** The DOM contains **all nodes** from the HTML (plus JS modifications). The Render Tree contains only **visible elements** with their computed styles. + + **In DOM but NOT in Render Tree:** + - `<head>` and its contents + - `<script>`, `<link>`, `<meta>` tags + - Elements with `display: none` + + **In Render Tree:** + - Visible elements + - Elements with `visibility: hidden` (still take space) + - Elements with `opacity: 0` (still take space) + + Pseudo-elements (`::before`, `::after`) are in the Render Tree but NOT in the DOM. + </Accordion> + + <Accordion title="Question 7: getElementsByClassName vs querySelectorAll - what's different?"> + **Answer:** + + | Aspect | `getElementsByClassName` | `querySelectorAll` | + |--------|--------------------------|-------------------| + | Returns | HTMLCollection | NodeList | + | **Live** | **Yes** (updates automatically) | **No** (static snapshot) | + | Selector | Class name only | Any CSS selector | + | Speed | Slightly faster | Slightly slower | + + ```javascript + const live = document.getElementsByClassName('item'); + const staticList = document.querySelectorAll('.item'); + + // Add new element with class="item" + document.body.appendChild(newItem); + + live.length; // Increased (live collection) + staticList.length; // Same (static snapshot) + ``` + </Accordion> + + <Accordion title="Question 8: How do you safely add many elements to the DOM?"> + **Answer:** Use a **DocumentFragment** to batch insertions: + + ```javascript + const fragment = document.createDocumentFragment(); + + for (let i = 0; i < 1000; i++) { + const li = document.createElement('li'); + li.textContent = `Item ${i}`; + fragment.appendChild(li); // No reflow (fragment is detached) + } + + ul.appendChild(fragment); // Single reflow! + ``` + + A DocumentFragment is a virtual container. When appended, only its children are inserted—the fragment disappears. + + Alternative: Build HTML string and use `innerHTML` once (but sanitize if user input!). + </Accordion> +</AccordionGroup> + +--- + +## Key Takeaways + +<Info> +**Remember these essential points:** + +1. **The DOM is a tree** — Elements are nodes with parent, child, and sibling relationships + +2. **DOM ≠ HTML source** — The browser fixes errors and JavaScript modifies it + +3. **Use querySelector** — More flexible than getElementById, accepts any CSS selector + +4. **Element vs Node properties** — Use `children`, `firstElementChild`, etc. to skip text nodes + +5. **closest() is your friend** — Perfect for event delegation and finding ancestor elements + +6. **innerHTML is dangerous** — Never use with user input; use textContent instead + +7. **Attributes vs Properties** — Attributes are HTML source, properties are live DOM state + +8. **classList over className** — Use add/remove/toggle for cleaner class manipulation + +9. **Batch DOM operations** — Use DocumentFragment or build strings to minimize reflows + +10. **Avoid layout thrashing** — Don't alternate reading and writing layout properties +</Info> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + How JavaScript handles async operations and DOM events + </Card> + <Card title="JavaScript Engines" icon="gear" href="/concepts/javascript-engines"> + How V8 and other engines parse and execute your DOM code + </Card> + <Card title="Scope and Closures" icon="layer-group" href="/concepts/scope-and-closures"> + Understanding variable scope in event handlers and callbacks + </Card> + <Card title="Design Patterns" icon="puzzle-piece" href="/concepts/design-patterns"> + Patterns like Observer for reactive DOM updates + </Card> +</CardGroup> + +--- ## Reference <Card title="Document Object Model (DOM) — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model"> - Official MDN documentation + The comprehensive MDN reference for all DOM interfaces, methods, and properties. </Card> ## Books <Card title="Eloquent JavaScript, 3rd Edition: Ch. 14 - The Document Object Model" icon="book" href="https://eloquentjavascript.net/14_dom.html"> - By Marijn Haverbeke + Marijn Haverbeke's excellent free book chapter covering DOM fundamentals with clear explanations and exercises. </Card> ## Articles <CardGroup cols={2}> <Card title="How To Understand and Modify the DOM in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/introduction-to-the-dom"> - By Tania Rascia + Tania Rascia's beginner-friendly guide covering DOM basics, selection, traversal, and manipulation. </Card> <Card title="What's the Document Object Model, and why you should know how to use it" icon="newspaper" href="https://medium.freecodecamp.org/whats-the-document-object-model-and-why-you-should-know-how-to-use-it-1a2d0bc5429d"> - By Leonardo Maldonado + A practical introduction to the DOM with real-world examples. + </Card> + <Card title="What is the DOM?" icon="newspaper" href="https://css-tricks.com/dom/"> + Chris Coyier's clear explanation of what the DOM is and common misconceptions. + </Card> + <Card title="Traversing the DOM with JavaScript" icon="newspaper" href="https://zellwk.com/blog/dom-traversals/"> + Zell Liew's detailed guide to navigating parent, child, and sibling elements efficiently. + </Card> + <Card title="DOM Tree" icon="newspaper" href="https://javascript.info/dom-nodes"> + JavaScript.info's comprehensive tutorial on DOM structure, node types, and tree navigation. + </Card> + <Card title="How to traverse the DOM in JavaScript" icon="newspaper" href="https://medium.com/javascript-in-plain-english/how-to-traverse-the-dom-in-javascript-d6555c335b4e"> + Vojislav Grujic's in-depth guide to DOM traversal with practical examples. + </Card> + <Card title="Render Tree Construction" icon="newspaper" href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction"> + Google's authoritative guide to how browsers construct the render tree from DOM and CSSOM. + </Card> + <Card title="What, exactly, is the DOM?" icon="newspaper" href="https://bitsofco.de/what-exactly-is-the-dom/"> + Ire Aderinokun's excellent breakdown of what the DOM is and isn't, including how it differs from the render tree. + </Card> + <Card title="JavaScript DOM Tutorial" icon="newspaper" href="https://www.javascripttutorial.net/javascript-dom/"> + A comprehensive multi-part tutorial covering selection, traversal, manipulation, and events. </Card> </CardGroup> -- [JavaScript DOM Tutorial with Example — Guru99](https://www.guru99.com/how-to-use-dom-and-events-in-javascript.html) -- [What is the DOM? — Chris Coyier](https://css-tricks.com/dom/) -- [Traversing the DOM with JavaScript — Zell Liew](https://zellwk.com/blog/dom-traversals/) -- [DOM Tree](https://javascript.info/dom-nodes) -- [How to traverse the DOM in Javascript — Vojislav Grujić](https://medium.com/javascript-in-plain-english/how-to-traverse-the-dom-in-javascript-d6555c335b4e) -- [Render Tree Construction — Ilya Grigorik](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction) -- [What exactly is the DOM?](https://bitsofco.de/what-exactly-is-the-dom/) -- [JavaScript DOM](https://www.javascripttutorial.net/javascript-dom/) - ## Videos <CardGroup cols={2}> - <Card title="JavaScript DOM" icon="video" href="https://www.youtube.com/watch?v=FIORjGvT0kk"> - By The Net Ninja + <Card title="JavaScript DOM Tutorial" icon="video" href="https://www.youtube.com/watch?v=FIORjGvT0kk"> + The Net Ninja's popular playlist covering DOM fundamentals step by step. </Card> <Card title="JavaScript DOM Crash Course" icon="video" href="https://www.youtube.com/watch?v=0ik6X4DJKCc"> - By Traversy Media + Traversy Media's comprehensive crash course covering DOM manipulation in one video. + </Card> + <Card title="JavaScript DOM Manipulation Methods" icon="video" href="https://www.youtube.com/watch?v=y17RuWkWdn8"> + Web Dev Simplified explains createElement, appendChild, and other manipulation methods. + </Card> + <Card title="JavaScript DOM Traversal Methods" icon="video" href="https://www.youtube.com/watch?v=v7rSSy8CaYE"> + Web Dev Simplified covers parent, child, and sibling traversal methods. </Card> </CardGroup> - -- [JavaScript DOM Manipulation Methods — Web Dev Simplified](https://www.youtube.com/watch?v=y17RuWkWdn8) -- [JavaScript DOM Traversal Methods — Web Dev Simplified](https://www.youtube.com/watch?v=v7rSSy8CaYE) -- [Traversing the Dom with Javascript — Steve Griffith](https://www.youtube.com/watch?v=Pr4LLrmDLLo) diff --git a/package-lock.json b/package-lock.json index 45a3426c..9f2e8242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,52 @@ "license": "MIT", "devDependencies": { "@vitest/coverage-v8": "^4.0.16", + "jsdom": "^27.4.0", "vitest": "^4.0.16" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.30", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz", + "integrity": "sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", + "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.4" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", + "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.4" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -73,6 +116,141 @@ "node": ">=18" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz", + "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -515,6 +693,24 @@ "node": ">=18" } }, + "node_modules/@exodus/bytes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.7.0.tgz", + "integrity": "sha512-5i+BtvujK/vM07YCGDyz4C4AyDzLmhxHMtM5HpUyPRtJPBdFPsj290ffXW+UXY21/G7GtXeHD2nRmq0T1ShyQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@exodus/crypto": "^1.0.0-rc.4" + }, + "peerDependenciesMeta": { + "@exodus/crypto": { + "optional": true + } + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1026,6 +1222,16 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1048,6 +1254,16 @@ "js-tokens": "^9.0.1" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1058,6 +1274,49 @@ "node": ">=18" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.5.tgz", + "integrity": "sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1076,6 +1335,26 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1188,6 +1467,19 @@ "node": ">=8" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -1195,6 +1487,41 @@ "dev": true, "license": "MIT" }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -1256,6 +1583,56 @@ "dev": true, "license": "MIT" }, + "node_modules/jsdom": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1294,6 +1671,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1331,6 +1715,19 @@ ], "license": "MIT" }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1387,6 +1784,26 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.54.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", @@ -1429,6 +1846,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -1486,6 +1916,13 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -1530,6 +1967,52 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/vite": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", @@ -1683,6 +2166,53 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -1699,6 +2229,45 @@ "engines": { "node": ">=8" } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 7c7de349..350ba6c8 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ ], "devDependencies": { "@vitest/coverage-v8": "^4.0.16", + "jsdom": "^27.4.0", "vitest": "^4.0.16" } } diff --git a/tests/web-platform/dom/dom.test.js b/tests/web-platform/dom/dom.test.js new file mode 100644 index 00000000..17c1d280 --- /dev/null +++ b/tests/web-platform/dom/dom.test.js @@ -0,0 +1,901 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, it, expect, beforeEach } from 'vitest' + +// ============================================================================= +// DOM AND LAYOUT TREES - TEST SUITE +// Tests for code examples from docs/concepts/dom.mdx +// ============================================================================= + +describe('DOM and Layout Trees', () => { + // Reset document body before each test + beforeEach(() => { + document.body.innerHTML = '' + document.head.innerHTML = '' + }) + + // =========================================================================== + // NODE TYPES AND STRUCTURE + // =========================================================================== + describe('Node Types and Structure', () => { + it('should identify element node type', () => { + const div = document.createElement('div') + expect(div.nodeType).toBe(1) + expect(div.nodeType).toBe(Node.ELEMENT_NODE) + expect(div.nodeName).toBe('DIV') + }) + + it('should identify text node type', () => { + const text = document.createTextNode('Hello') + expect(text.nodeType).toBe(3) + expect(text.nodeType).toBe(Node.TEXT_NODE) + expect(text.nodeName).toBe('#text') + }) + + it('should identify comment node type', () => { + const comment = document.createComment('This is a comment') + expect(comment.nodeType).toBe(8) + expect(comment.nodeType).toBe(Node.COMMENT_NODE) + expect(comment.nodeName).toBe('#comment') + }) + + it('should identify document node type', () => { + expect(document.nodeType).toBe(9) + expect(document.nodeType).toBe(Node.DOCUMENT_NODE) + expect(document.nodeName).toBe('#document') + }) + + it('should identify document fragment node type', () => { + const fragment = document.createDocumentFragment() + expect(fragment.nodeType).toBe(11) + expect(fragment.nodeType).toBe(Node.DOCUMENT_FRAGMENT_NODE) + expect(fragment.nodeName).toBe('#document-fragment') + }) + + it('should have correct node type constants', () => { + expect(Node.ELEMENT_NODE).toBe(1) + expect(Node.TEXT_NODE).toBe(3) + expect(Node.COMMENT_NODE).toBe(8) + expect(Node.DOCUMENT_NODE).toBe(9) + expect(Node.DOCUMENT_FRAGMENT_NODE).toBe(11) + }) + + it('should access document properties', () => { + expect(document.documentElement.tagName).toBe('HTML') + expect(document.head).toBeTruthy() + expect(document.body).toBeTruthy() + }) + + it('should be able to set document title', () => { + document.title = 'New Title' + expect(document.title).toBe('New Title') + }) + }) + + // =========================================================================== + // SELECTING ELEMENTS + // =========================================================================== + describe('Selecting Elements', () => { + beforeEach(() => { + document.body.innerHTML = ` + <div id="hero">Welcome!</div> + <p class="intro">First</p> + <p class="intro">Second</p> + <p>Third</p> + <nav> + <a href="#" class="active">Home</a> + <a href="#">About</a> + </nav> + <input type="text" data-id="123"> + ` + }) + + describe('getElementById', () => { + it('should select element by id', () => { + const hero = document.getElementById('hero') + expect(hero).toBeTruthy() + expect(hero.textContent).toBe('Welcome!') + }) + + it('should return null for non-existent id', () => { + const ghost = document.getElementById('nonexistent') + expect(ghost).toBeNull() + }) + }) + + describe('getElementsByClassName', () => { + it('should select elements by class name', () => { + const intros = document.getElementsByClassName('intro') + expect(intros.length).toBe(2) + expect(intros[0].textContent).toBe('First') + }) + + it('should return empty collection for non-existent class', () => { + const ghosts = document.getElementsByClassName('nonexistent') + expect(ghosts.length).toBe(0) + }) + }) + + describe('getElementsByTagName', () => { + it('should select elements by tag name', () => { + const allParagraphs = document.getElementsByTagName('p') + expect(allParagraphs.length).toBe(3) + }) + }) + + describe('querySelector', () => { + it('should select first matching element', () => { + const firstButton = document.querySelector('a') + expect(firstButton.textContent).toBe('Home') + }) + + it('should select by id', () => { + const hero = document.querySelector('#hero') + expect(hero.textContent).toBe('Welcome!') + }) + + it('should select by class', () => { + const firstIntro = document.querySelector('.intro') + expect(firstIntro.textContent).toBe('First') + }) + + it('should select by complex selector', () => { + const navLink = document.querySelector('nav a.active') + expect(navLink.textContent).toBe('Home') + }) + + it('should select by attribute', () => { + const dataItem = document.querySelector('[data-id="123"]') + expect(dataItem.tagName).toBe('INPUT') + }) + + it('should return null for no match', () => { + const ghost = document.querySelector('.nonexistent') + expect(ghost).toBeNull() + }) + }) + + describe('querySelectorAll', () => { + it('should select all matching elements', () => { + const allCards = document.querySelectorAll('.intro') + expect(allCards.length).toBe(2) + }) + + it('should return empty NodeList for no matches', () => { + const ghosts = document.querySelectorAll('.nonexistent') + expect(ghosts.length).toBe(0) + }) + + it('should support complex selectors', () => { + const links = document.querySelectorAll('nav a') + expect(links.length).toBe(2) + }) + }) + + describe('Scoped Selection', () => { + it('should select within a parent element', () => { + const nav = document.querySelector('nav') + const navLinks = nav.querySelectorAll('a') + expect(navLinks.length).toBe(2) + }) + + it('should find specific child in parent', () => { + const nav = document.querySelector('nav') + const activeLink = nav.querySelector('.active') + expect(activeLink.textContent).toBe('Home') + }) + }) + }) + + // =========================================================================== + // LIVE VS STATIC COLLECTIONS + // =========================================================================== + describe('Live vs Static Collections', () => { + beforeEach(() => { + document.body.innerHTML = ` + <div class="item">One</div> + <div class="item">Two</div> + <div class="item">Three</div> + ` + }) + + it('should demonstrate live HTMLCollection updates', () => { + const liveList = document.getElementsByClassName('item') + expect(liveList.length).toBe(3) + + // Add a new item + const newItem = document.createElement('div') + newItem.className = 'item' + document.body.appendChild(newItem) + + // Live collection is automatically updated + expect(liveList.length).toBe(4) + }) + + it('should demonstrate static NodeList does not update', () => { + const staticList = document.querySelectorAll('.item') + expect(staticList.length).toBe(3) + + // Add a new item + const newItem = document.createElement('div') + newItem.className = 'item' + document.body.appendChild(newItem) + + // Static list is still the old snapshot + expect(staticList.length).toBe(3) + }) + }) + + // =========================================================================== + // DOM TRAVERSAL + // =========================================================================== + describe('DOM Traversal', () => { + beforeEach(() => { + document.body.innerHTML = ` + <ul id="list"> + <li id="first">One</li> + <li id="second">Two</li> + <li id="third">Three</li> + </ul> + ` + }) + + describe('Traversing Downwards', () => { + it('should get child nodes including text nodes', () => { + const ul = document.querySelector('ul') + // childNodes includes text nodes from whitespace + expect(ul.childNodes.length).toBeGreaterThan(3) + }) + + it('should get only element children', () => { + const ul = document.querySelector('ul') + expect(ul.children.length).toBe(3) + expect(ul.children[0].textContent).toBe('One') + }) + + it('should get first and last element children', () => { + const ul = document.querySelector('ul') + expect(ul.firstElementChild.textContent).toBe('One') + expect(ul.lastElementChild.textContent).toBe('Three') + }) + + it('should demonstrate firstChild vs firstElementChild', () => { + const ul = document.querySelector('ul') + // firstChild might be a text node (whitespace) + const firstChild = ul.firstChild + const firstElementChild = ul.firstElementChild + + // firstElementChild is always an element + expect(firstElementChild.tagName).toBe('LI') + // firstChild might be text node + expect(firstChild.nodeType === Node.TEXT_NODE || firstChild.nodeType === Node.ELEMENT_NODE).toBe(true) + }) + }) + + describe('Traversing Upwards', () => { + it('should get parent node', () => { + const li = document.querySelector('li') + expect(li.parentNode.tagName).toBe('UL') + }) + + it('should get parent element', () => { + const li = document.querySelector('li') + expect(li.parentElement.tagName).toBe('UL') + }) + + it('should find ancestor with closest()', () => { + const li = document.querySelector('li') + const ul = li.closest('ul') + expect(ul.id).toBe('list') + }) + + it('should return element itself if it matches closest()', () => { + const li = document.querySelector('li') + const self = li.closest('li') + expect(self).toBe(li) + }) + + it('should return null if no ancestor matches closest()', () => { + const li = document.querySelector('li') + const result = li.closest('.nonexistent') + expect(result).toBeNull() + }) + }) + + describe('Traversing Sideways', () => { + it('should get next element sibling', () => { + const first = document.querySelector('#first') + const second = first.nextElementSibling + expect(second.id).toBe('second') + }) + + it('should get previous element sibling', () => { + const second = document.querySelector('#second') + const first = second.previousElementSibling + expect(first.id).toBe('first') + }) + + it('should return null at boundaries', () => { + const first = document.querySelector('#first') + const third = document.querySelector('#third') + expect(first.previousElementSibling).toBeNull() + expect(third.nextElementSibling).toBeNull() + }) + }) + + describe('Building Ancestor Trail', () => { + it('should get all ancestors of an element', () => { + document.body.innerHTML = ` + <main> + <section> + <div class="deeply-nested">Content</div> + </section> + </main> + ` + + function getAncestors(element) { + const ancestors = [] + let current = element.parentElement + + while (current && current !== document.body) { + ancestors.push(current) + current = current.parentElement + } + + return ancestors + } + + const deepElement = document.querySelector('.deeply-nested') + const ancestors = getAncestors(deepElement) + + expect(ancestors.length).toBe(2) + expect(ancestors[0].tagName).toBe('SECTION') + expect(ancestors[1].tagName).toBe('MAIN') + }) + }) + }) + + // =========================================================================== + // CREATING AND MANIPULATING ELEMENTS + // =========================================================================== + describe('Creating and Manipulating Elements', () => { + describe('Creating Elements', () => { + it('should create a new element', () => { + const div = document.createElement('div') + expect(div.tagName).toBe('DIV') + expect(div.parentNode).toBeNull() // Not yet in DOM + }) + + it('should create a text node', () => { + const text = document.createTextNode('Hello, world!') + expect(text.nodeType).toBe(Node.TEXT_NODE) + expect(text.textContent).toBe('Hello, world!') + }) + + it('should create a comment node', () => { + const comment = document.createComment('This is a comment') + expect(comment.nodeType).toBe(Node.COMMENT_NODE) + expect(comment.textContent).toBe('This is a comment') + }) + }) + + describe('appendChild', () => { + it('should add element as last child', () => { + document.body.innerHTML = '<ul><li>Existing</li></ul>' + const ul = document.querySelector('ul') + const li = document.createElement('li') + li.textContent = 'New item' + + ul.appendChild(li) + + expect(ul.lastElementChild.textContent).toBe('New item') + expect(ul.children.length).toBe(2) + }) + }) + + describe('insertBefore', () => { + it('should insert element before reference node', () => { + document.body.innerHTML = '<ul><li>Existing</li></ul>' + const ul = document.querySelector('ul') + const existingLi = ul.querySelector('li') + const newLi = document.createElement('li') + newLi.textContent = 'First!' + + ul.insertBefore(newLi, existingLi) + + expect(ul.firstElementChild.textContent).toBe('First!') + expect(ul.children.length).toBe(2) + }) + }) + + describe('append and prepend', () => { + it('should append multiple items including strings', () => { + const div = document.createElement('div') + const span = document.createElement('span') + + div.append('Text', span, 'More text') + + expect(div.childNodes.length).toBe(3) + expect(div.textContent).toBe('TextMore text') + }) + + it('should prepend element to start', () => { + document.body.innerHTML = '<div>Existing</div>' + const div = document.querySelector('div') + const strong = document.createElement('strong') + strong.textContent = 'New' + + div.prepend(strong) + + expect(div.firstElementChild.tagName).toBe('STRONG') + }) + }) + + describe('before and after', () => { + it('should insert as previous sibling with before()', () => { + document.body.innerHTML = '<h1>Title</h1>' + const h1 = document.querySelector('h1') + const nav = document.createElement('nav') + + h1.before(nav) + + expect(h1.previousElementSibling.tagName).toBe('NAV') + }) + + it('should insert as next sibling with after()', () => { + document.body.innerHTML = '<h1>Title</h1>' + const h1 = document.querySelector('h1') + const p = document.createElement('p') + + h1.after(p) + + expect(h1.nextElementSibling.tagName).toBe('P') + }) + }) + + describe('insertAdjacentHTML', () => { + it('should insert at all four positions', () => { + document.body.innerHTML = '<div id="target">Content</div>' + const div = document.querySelector('#target') + + div.insertAdjacentHTML('beforebegin', '<p id="before">Before</p>') + div.insertAdjacentHTML('afterbegin', '<span id="first">First</span>') + div.insertAdjacentHTML('beforeend', '<span id="last">Last</span>') + div.insertAdjacentHTML('afterend', '<p id="after">After</p>') + + expect(div.previousElementSibling.id).toBe('before') + expect(div.firstElementChild.id).toBe('first') + expect(div.lastElementChild.id).toBe('last') + expect(div.nextElementSibling.id).toBe('after') + }) + }) + + describe('Removing Elements', () => { + it('should remove element with remove()', () => { + document.body.innerHTML = '<div class="to-remove">Gone</div>' + const element = document.querySelector('.to-remove') + + element.remove() + + expect(document.querySelector('.to-remove')).toBeNull() + }) + + it('should remove child with removeChild()', () => { + document.body.innerHTML = '<ul><li>Keep</li><li class="remove">Remove</li></ul>' + const ul = document.querySelector('ul') + const toRemove = ul.querySelector('.remove') + + ul.removeChild(toRemove) + + expect(ul.children.length).toBe(1) + expect(ul.querySelector('.remove')).toBeNull() + }) + }) + + describe('Cloning Elements', () => { + it('should shallow clone element only', () => { + document.body.innerHTML = '<div class="card"><p>Content</p></div>' + const original = document.querySelector('.card') + + const shallow = original.cloneNode(false) + + expect(shallow.className).toBe('card') + expect(shallow.children.length).toBe(0) // No children + }) + + it('should deep clone element with descendants', () => { + document.body.innerHTML = '<div class="card"><p>Content</p></div>' + const original = document.querySelector('.card') + + const deep = original.cloneNode(true) + + expect(deep.className).toBe('card') + expect(deep.children.length).toBe(1) + expect(deep.querySelector('p').textContent).toBe('Content') + }) + + it('should create detached clone', () => { + document.body.innerHTML = '<div class="card">Content</div>' + const original = document.querySelector('.card') + + const clone = original.cloneNode(true) + + expect(clone.parentNode).toBeNull() + }) + }) + + describe('DocumentFragment', () => { + it('should batch add elements with fragment', () => { + document.body.innerHTML = '<ul></ul>' + const ul = document.querySelector('ul') + const fragment = document.createDocumentFragment() + + for (let i = 0; i < 5; i++) { + const li = document.createElement('li') + li.textContent = `Item ${i}` + fragment.appendChild(li) + } + + ul.appendChild(fragment) + + expect(ul.children.length).toBe(5) + expect(ul.firstElementChild.textContent).toBe('Item 0') + expect(ul.lastElementChild.textContent).toBe('Item 4') + }) + + it('should have no parent', () => { + const fragment = document.createDocumentFragment() + expect(fragment.parentNode).toBeNull() + }) + }) + }) + + // =========================================================================== + // MODIFYING CONTENT + // =========================================================================== + describe('Modifying Content', () => { + describe('innerHTML', () => { + it('should read HTML content', () => { + document.body.innerHTML = '<div><p>Hello</p><span>World</span></div>' + const div = document.querySelector('div') + + expect(div.innerHTML).toBe('<p>Hello</p><span>World</span>') + }) + + it('should set HTML content', () => { + document.body.innerHTML = '<div></div>' + const div = document.querySelector('div') + + div.innerHTML = '<h1>New Title</h1><p>New paragraph</p>' + + expect(div.children.length).toBe(2) + expect(div.querySelector('h1').textContent).toBe('New Title') + }) + + it('should clear content with empty string', () => { + document.body.innerHTML = '<div><p>Content</p></div>' + const div = document.querySelector('div') + + div.innerHTML = '' + + expect(div.children.length).toBe(0) + }) + }) + + describe('textContent', () => { + it('should read text content ignoring HTML', () => { + document.body.innerHTML = '<div><p>Hello</p><span>World</span></div>' + const div = document.querySelector('div') + + expect(div.textContent).toBe('HelloWorld') + }) + + it('should set text content escaping HTML', () => { + document.body.innerHTML = '<div></div>' + const div = document.querySelector('div') + + div.textContent = '<script>alert("XSS")</script>' + + // HTML is escaped, not parsed + expect(div.children.length).toBe(0) + expect(div.textContent).toBe('<script>alert("XSS")</script>') + }) + }) + + describe('innerText vs textContent', () => { + it('textContent includes hidden text, innerText may not', () => { + document.body.innerHTML = '<div>Hello <span style="display:none">Hidden</span> World</div>' + const div = document.querySelector('div') + + // textContent includes all text + expect(div.textContent).toContain('Hidden') + + // Note: In jsdom, innerText may behave like textContent + // In real browsers, innerText would exclude display:none text + }) + }) + }) + + // =========================================================================== + // WORKING WITH ATTRIBUTES + // =========================================================================== + describe('Working with Attributes', () => { + describe('Standard Attribute Methods', () => { + it('should get attribute value', () => { + document.body.innerHTML = '<a href="https://example.com" target="_blank">Link</a>' + const link = document.querySelector('a') + + expect(link.getAttribute('href')).toBe('https://example.com') + expect(link.getAttribute('target')).toBe('_blank') + }) + + it('should set attribute value', () => { + document.body.innerHTML = '<a href="#">Link</a>' + const link = document.querySelector('a') + + link.setAttribute('href', 'https://newurl.com') + link.setAttribute('target', '_blank') + + expect(link.getAttribute('href')).toBe('https://newurl.com') + expect(link.getAttribute('target')).toBe('_blank') + }) + + it('should check if attribute exists', () => { + document.body.innerHTML = '<a href="#" target="_blank">Link</a>' + const link = document.querySelector('a') + + expect(link.hasAttribute('target')).toBe(true) + expect(link.hasAttribute('rel')).toBe(false) + }) + + it('should remove attribute', () => { + document.body.innerHTML = '<a href="#" target="_blank">Link</a>' + const link = document.querySelector('a') + + link.removeAttribute('target') + + expect(link.hasAttribute('target')).toBe(false) + }) + }) + + describe('Properties vs Attributes', () => { + it('should show difference between attribute and property', () => { + document.body.innerHTML = '<input type="text" value="initial">' + const input = document.querySelector('input') + + // Both start the same + expect(input.getAttribute('value')).toBe('initial') + expect(input.value).toBe('initial') + + // Change the property (simulating user input) + input.value = 'new text' + + // Attribute stays the same, property changes + expect(input.getAttribute('value')).toBe('initial') + expect(input.value).toBe('new text') + }) + + it('should show checkbox property vs attribute', () => { + document.body.innerHTML = '<input type="checkbox" checked>' + const checkbox = document.querySelector('input') + + // Attribute is string or null + expect(checkbox.getAttribute('checked')).toBe('') + + // Property is boolean + expect(checkbox.checked).toBe(true) + + // Toggle the property + checkbox.checked = false + expect(checkbox.checked).toBe(false) + // Attribute may still exist + }) + }) + + describe('Data Attributes and dataset API', () => { + it('should read data attributes via dataset', () => { + document.body.innerHTML = '<div id="user" data-user-id="123" data-role="admin"></div>' + const user = document.querySelector('#user') + + expect(user.dataset.userId).toBe('123') + expect(user.dataset.role).toBe('admin') + }) + + it('should write data attributes via dataset', () => { + document.body.innerHTML = '<div id="user"></div>' + const user = document.querySelector('#user') + + user.dataset.lastLogin = '2024-01-15' + + expect(user.getAttribute('data-last-login')).toBe('2024-01-15') + }) + + it('should delete data attributes', () => { + document.body.innerHTML = '<div data-role="admin"></div>' + const div = document.querySelector('div') + + delete div.dataset.role + + expect(div.hasAttribute('data-role')).toBe(false) + }) + + it('should check if data attribute exists', () => { + document.body.innerHTML = '<div data-user-id="123"></div>' + const div = document.querySelector('div') + + expect('userId' in div.dataset).toBe(true) + expect('role' in div.dataset).toBe(false) + }) + }) + }) + + // =========================================================================== + // STYLING ELEMENTS + // =========================================================================== + describe('Styling Elements', () => { + describe('style Property', () => { + it('should set inline styles', () => { + document.body.innerHTML = '<div></div>' + const box = document.querySelector('div') + + box.style.backgroundColor = 'blue' + box.style.fontSize = '20px' + + expect(box.style.backgroundColor).toBe('blue') + expect(box.style.fontSize).toBe('20px') + }) + + it('should remove inline style with empty string', () => { + document.body.innerHTML = '<div style="color: red;"></div>' + const box = document.querySelector('div') + + box.style.color = '' + + expect(box.style.color).toBe('') + }) + + it('should set multiple styles with cssText', () => { + document.body.innerHTML = '<div></div>' + const box = document.querySelector('div') + + box.style.cssText = 'background: red; font-size: 16px; padding: 10px;' + + expect(box.style.background).toContain('red') + expect(box.style.fontSize).toBe('16px') + expect(box.style.padding).toBe('10px') + }) + }) + + describe('getComputedStyle', () => { + it('should get computed styles', () => { + document.body.innerHTML = '<div style="display: block;"></div>' + const box = document.querySelector('div') + + const styles = getComputedStyle(box) + + expect(styles.display).toBe('block') + }) + }) + + describe('classList API', () => { + it('should add classes', () => { + document.body.innerHTML = '<button></button>' + const button = document.querySelector('button') + + button.classList.add('active') + button.classList.add('btn', 'btn-primary') + + expect(button.classList.contains('active')).toBe(true) + expect(button.classList.contains('btn')).toBe(true) + expect(button.classList.contains('btn-primary')).toBe(true) + }) + + it('should remove classes', () => { + document.body.innerHTML = '<button class="active btn btn-primary"></button>' + const button = document.querySelector('button') + + button.classList.remove('active') + button.classList.remove('btn', 'btn-primary') + + expect(button.classList.contains('active')).toBe(false) + expect(button.classList.contains('btn')).toBe(false) + }) + + it('should toggle classes', () => { + document.body.innerHTML = '<button></button>' + const button = document.querySelector('button') + + button.classList.toggle('active') + expect(button.classList.contains('active')).toBe(true) + + button.classList.toggle('active') + expect(button.classList.contains('active')).toBe(false) + }) + + it('should toggle with condition', () => { + document.body.innerHTML = '<button></button>' + const button = document.querySelector('button') + + button.classList.toggle('active', true) + expect(button.classList.contains('active')).toBe(true) + + button.classList.toggle('active', false) + expect(button.classList.contains('active')).toBe(false) + }) + + it('should replace class', () => { + document.body.innerHTML = '<button class="btn-primary"></button>' + const button = document.querySelector('button') + + button.classList.replace('btn-primary', 'btn-secondary') + + expect(button.classList.contains('btn-primary')).toBe(false) + expect(button.classList.contains('btn-secondary')).toBe(true) + }) + + it('should get class count', () => { + document.body.innerHTML = '<button class="btn btn-primary active"></button>' + const button = document.querySelector('button') + + expect(button.classList.length).toBe(3) + }) + }) + }) + + // =========================================================================== + // COMMON PATTERNS + // =========================================================================== + describe('Common Patterns', () => { + describe('Checking Element Existence', () => { + it('should check if element exists with querySelector', () => { + document.body.innerHTML = '<div class="exists">Found</div>' + + const element = document.querySelector('.exists') + if (element) { + element.textContent = 'Updated!' + } + + expect(document.querySelector('.exists').textContent).toBe('Updated!') + }) + + it('should handle non-existent element safely', () => { + document.body.innerHTML = '' + + const element = document.querySelector('.maybe-exists') + + // Using optional chaining (no error) + element?.classList.add('active') + + expect(element).toBeNull() + }) + }) + + describe('Event Delegation Pattern', () => { + it('should use closest() for event delegation', () => { + document.body.innerHTML = ` + <div class="card-container"> + <div class="card"> + <button class="btn">Click</button> + </div> + </div> + ` + + let clickedCard = null + const container = document.querySelector('.card-container') + + // Simulate event delegation + const btn = document.querySelector('.btn') + const card = btn.closest('.card') + + if (card) { + clickedCard = card + } + + expect(clickedCard).not.toBeNull() + expect(clickedCard.classList.contains('card')).toBe(true) + }) + }) + }) +}) From cc10cf6ac4088bc0753be211e556a60eeab3f674 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 08:36:05 -0300 Subject: [PATCH 025/128] refactor(dom): restructure intro to lead with simple explanation - Move DOM definition to the very beginning of the page - Add Info box with learning objectives after intro code - Reorganize Family Tree as a dedicated section after intro - Create 'What the DOM is NOT' as a proper section header - Improve table format for DOM relationship analogies --- docs/concepts/dom.mdx | 71 ++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 82d9b38c..dcbc4194 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -3,9 +3,31 @@ title: "DOM and Layout Trees" description: "Understanding the Document Object Model and how browsers render pages" --- -## The Family Tree: A Real-World Analogy +The **Document Object Model (DOM)** is a programming interface for web documents. It represents your HTML as a **tree of objects** that JavaScript can read and manipulate. + +```javascript +// The DOM lets you do things like this: +document.querySelector('h1').textContent = 'Hello, DOM!'; +document.body.style.backgroundColor = 'lightblue'; +document.getElementById('btn').addEventListener('click', handleClick); +``` + +<Info> +**What you'll learn in this guide:** +- What the DOM actually is (and what it's NOT) +- How to select elements like a pro (getElementById vs querySelector) +- How to traverse the tree (parent, children, siblings) +- How to create, modify, and remove elements +- The difference between properties and attributes +- How the browser turns DOM → pixels (the Critical Rendering Path) +- Performance best practices (avoid layout thrashing!) +</Info> + +--- + +## The Family Tree: Understanding DOM Structure -Imagine your family tree hanging on the wall. At the top, there's your great-grandmother. Below her are her children (your grandparents), then their children (your parents and aunts/uncles), and finally you and your cousins at the bottom. +Think of the DOM like a family tree. At the top sits `document` (the family historian who knows everyone). Below it is `<html>` (the matriarch), which has two children: `<head>` and `<body>`. Each of these has their own children, grandchildren, and so on. ``` THE DOM FAMILY TREE @@ -39,49 +61,22 @@ Imagine your family tree hanging on the wall. At the top, there's your great-gra <li> <li> <li> ← Siblings ``` -Here's how family relationships map to the DOM: +Just like navigating a family reunion, the DOM lets you: -| Family Term | DOM Relationship | Example | -|-------------|------------------|---------| -| **Parent** | `.parentNode` / `.parentElement` | `<body>` is parent of `<main>` | -| **Child** | `.children` / `.childNodes` | `<li>` elements are children of `<ul>` | -| **Sibling** | `.nextSibling` / `.previousSibling` | `<nav>`, `<main>`, `<footer>` are siblings | -| **Ancestor** | Any parent above | `<html>` is ancestor of all elements | -| **Descendant** | Any child below | All elements are descendants of `<html>` | - -Just like at a family reunion, you can: -- **Ask someone about their parent:** "Who's your mom?" → `element.parentNode` -- **Ask about their kids:** "Where are your children?" → `element.children` -- **Find their siblings:** "Who's your brother?" → `element.nextElementSibling` -- **Search the whole family:** "Where's cousin Bob?" → `document.querySelector('#bob')` +| Action | Family Analogy | DOM Method | +|--------|----------------|------------| +| Find your parent | "Who's your mom?" | `element.parentNode` | +| Find your kids | "Where are your children?" | `element.children` | +| Find your sibling | "Who's your brother?" | `element.nextElementSibling` | +| Search the whole family | "Where's cousin Bob?" | `document.querySelector('#bob')` | <Note> -**TL;DR:** The DOM is a tree structure representing your HTML document. Every element, text, and comment is a "node" in this tree. JavaScript lets you navigate this tree (like walking through a family reunion) and modify it (like updating the family photo album). +**Key insight:** Every element, text, and comment in your HTML becomes a "node" in this tree. JavaScript lets you navigate this tree and modify it — changing content, adding elements, or removing them entirely. </Note> -<Info> -**What you'll learn in this guide:** -- What the DOM actually is (and what it's NOT) -- How to select elements like a pro (getElementById vs querySelector) -- How to traverse the tree (parent, children, siblings) -- How to create, modify, and remove elements -- The difference between properties and attributes -- How the browser turns DOM → pixels (the Critical Rendering Path) -- Performance best practices (avoid layout thrashing!) -</Info> - --- -## What is the DOM? - -The **Document Object Model (DOM)** is a programming interface for web documents. It represents your HTML as a **tree of objects** that JavaScript can read and manipulate. - -```javascript -// The DOM lets you do things like this: -document.querySelector('h1').textContent = 'Hello, DOM!'; -document.body.style.backgroundColor = 'lightblue'; -document.getElementById('btn').addEventListener('click', handleClick); -``` +## What the DOM is NOT ### The DOM is NOT Your HTML Source Code From 5ea2f06461a07f3db3e33cdb19a3328f005849e9 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 08:36:06 -0300 Subject: [PATCH 026/128] fix(factories-classes): add contextual introduction before analogy - Add opening paragraph explaining why object creation matters - Define what Factory Functions are in simple terms - Define what Classes are and reference familiar OOP languages - Explain both achieve same goal with different approaches - Improve transition into video game analogy --- docs/concepts/factories-classes.mdx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx index 5e6e1d03..b1242be7 100644 --- a/docs/concepts/factories-classes.mdx +++ b/docs/concepts/factories-classes.mdx @@ -3,7 +3,21 @@ title: "Factories and Classes" description: "How to create objects in JavaScript — from factory functions to ES6 classes" --- -## The Video Game Character Problem: A Real-World Analogy +In JavaScript, objects are everywhere. User profiles, shopping carts, game characters, API responses — almost everything is an object. But when you need to create **many similar objects**, writing them one by one becomes a problem. + +**Factories** and **Classes** solve this problem. They are patterns for creating objects efficiently: + +- A **Factory Function** is a regular function that returns a new object each time you call it. It's simple, flexible, and doesn't require special syntax. + +- A **Class** is a blueprint for creating objects. It uses the `class` keyword (introduced in ES6) and the `new` operator to create instances. If you've used Java, C#, or Python, this will feel familiar. + +Both achieve the same goal — creating objects — but they work differently and have different strengths. This guide will teach you both approaches and help you decide which to use. + +--- + +## The Video Game Character Problem + +To understand why we need factories and classes, let's look at a common problem. Imagine you're building a video game. You need to create enemies — lots of them. Goblins, dragons, zombies, and bosses. Each enemy has a name, health points, attack power, and abilities. From 25f66a95d787c740baabb1efee39e66247aa18d2 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 08:43:22 -0300 Subject: [PATCH 027/128] feat(dom): add freeCodeCamp full course to resources --- docs/concepts/dom.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index dcbc4194..03360480 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -1572,6 +1572,12 @@ window.addEventListener('load', () => { </Card> </CardGroup> +## Courses + +<Card title="JavaScript DOM Manipulation – Full Course for Beginners" icon="graduation-cap" href="https://www.youtube.com/watch?v=5fb2aPlgoys"> + A comprehensive freeCodeCamp course covering DOM selection, traversal, manipulation, and events from the ground up. +</Card> + ## Videos <CardGroup cols={2}> From 263f4acea22c70347810da2db30e8791c734a045 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 08:49:12 -0300 Subject: [PATCH 028/128] feat(http-fetch): add comprehensive guide with 72 tests - Add HTTP & Fetch concept explaining the protocol and Fetch API - Cover HTTP methods, status codes, request/response cycle - Include error handling patterns (network vs HTTP errors) - Add AbortController section for request cancellation - Add URL/URLSearchParams section for building URLs - Include restaurant analogy and ASCII diagrams - Add 72 tests covering all code examples - Update CLAUDE.md with new concept and test counts --- CLAUDE.md | 52 +- docs/concepts/http-fetch.mdx | 983 ++++++++++++++++ docs/docs.json | 3 +- .../http-fetch/http-fetch.test.js | 1031 +++++++++++++++++ 4 files changed, 2044 insertions(+), 25 deletions(-) create mode 100644 docs/concepts/http-fetch.mdx create mode 100644 tests/web-platform/http-fetch/http-fetch.test.js diff --git a/CLAUDE.md b/CLAUDE.md index 4cb8ca94..dbd9b85f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,7 @@ The project was recognized by GitHub as one of the **top open source projects of └── github-image.png # Project banner image ``` -## The 30 Concepts (31st, 32nd, and 33rd coming soon) +## The 31 Concepts (32nd and 33rd coming soon) ### Fundamentals (1-6) 1. Primitive Types @@ -55,13 +55,13 @@ The project was recognized by GitHub as one of the **top open source projects of 5. Scope & Closures 6. Call Stack -### Functions & Execution (7-9) +### Functions & Execution (7-8) 7. Event Loop (Message Queue) -8. Expression vs Statement -9. IIFE, Modules and Namespaces +8. IIFE, Modules and Namespaces -### Web Platform (10) -10. DOM and Layout Trees +### Web Platform (9-10) +9. DOM and Layout Trees +10. HTTP & Fetch ### Object-Oriented JS (11-15) 11. Factories and Classes @@ -81,7 +81,7 @@ The project was recognized by GitHub as one of the **top open source projects of 21. Promises 22. async/await -### Advanced Topics (23-30) +### Advanced Topics (23-31) 23. JavaScript Engines 24. Data Structures 25. Big O Notation (Expensive Operations) @@ -255,19 +255,19 @@ Tests are organized by concept category in the `tests/` directory: ``` tests/ -└── fundamentals/ # Concepts 1-6 - ├── call-stack/ - │ └── call-stack.test.js - ├── primitive-types/ - │ └── primitive-types.test.js - ├── value-reference-types/ - │ └── value-reference-types.test.js - ├── type-coercion/ - │ └── type-coercion.test.js - ├── equality-operators/ - │ └── equality-operators.test.js - └── scope-and-closures/ - └── scope-and-closures.test.js +├── fundamentals/ # Concepts 1-6 +│ ├── call-stack/ +│ ├── primitive-types/ +│ ├── value-reference-types/ +│ ├── type-coercion/ +│ ├── equality-operators/ +│ └── scope-and-closures/ +├── functions-execution/ # Concepts 7-8 +│ ├── event-loop/ +│ └── iife-modules/ +└── web-platform/ # Concepts 9-10 + ├── dom/ + └── http-fetch/ ``` ### Writing Tests for Code Examples @@ -303,7 +303,11 @@ When adding new code examples to concept documentation, please include correspon | Fundamentals | Type Coercion | 74 | | Fundamentals | Equality Operators | 87 | | Fundamentals | Scope and Closures | 46 | -| **Total** | | **354** | +| Functions & Execution | Event Loop | 56 | +| Functions & Execution | IIFE & Modules | 61 | +| Web Platform | DOM | 85 | +| Web Platform | HTTP & Fetch | 72 | +| **Total** | | **628** | ## Documentation Site (Mintlify) @@ -327,12 +331,12 @@ The site will be available at `http://localhost:3000`. - **Getting Started**: Homepage and introduction - **Fundamentals**: Concepts 1-6 (Primitive Types through Call Stack) -- **Functions & Execution**: Concepts 7-9 (Event Loop through IIFE/Modules) -- **Web Platform**: Concept 10 (DOM and Layout Trees) +- **Functions & Execution**: Concepts 7-8 (Event Loop through IIFE/Modules) +- **Web Platform**: Concepts 9-10 (DOM and HTTP & Fetch) - **Object-Oriented JS**: Concepts 11-15 (Factories through Object.create/assign) - **Functional Programming**: Concepts 16-19 (map/reduce/filter through Recursion) - **Async JavaScript**: Concepts 20-22 (Collections/Generators through async/await) -- **Advanced Topics**: Concepts 23-30 (JavaScript Engines through Clean Code) +- **Advanced Topics**: Concepts 23-31 (JavaScript Engines through Clean Code) ### Adding/Editing Concept Pages diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx new file mode 100644 index 00000000..1cffc2bd --- /dev/null +++ b/docs/concepts/http-fetch.mdx @@ -0,0 +1,983 @@ +--- +title: "HTTP & Fetch" +description: "Making network requests in JavaScript" +--- + +How does JavaScript get data from a server? How do you load user profiles, submit forms, or fetch the latest posts from an API? The answer is the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** — JavaScript's modern way to make network requests. + +```javascript +// This is how you fetch data in JavaScript +const response = await fetch('https://api.example.com/users/1') +const user = await response.json() +console.log(user.name) // "Alice" +``` + +But to really understand Fetch, you need to understand what's happening underneath: **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)**. + +<Info> +**What you'll learn in this guide:** +- How HTTP requests and responses work +- The four main HTTP methods (GET, POST, PUT, DELETE) +- How to use the Fetch API to make requests +- Reading and parsing JSON responses +- The critical difference between network errors and HTTP errors +- Modern patterns with async/await +- How to cancel requests with AbortController +</Info> + +## What is HTTP? + +**[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)** (Hypertext Transfer Protocol) is the protocol that powers the web. It's not a JavaScript thing — it's a set of rules that *all* web communication follows, regardless of programming language. When your browser loads a webpage, it uses HTTP. When your phone app talks to a server, it uses HTTP. When JavaScript fetches data, it uses HTTP. + +<Warning> +**HTTP is not JavaScript.** HTTP is a language-agnostic protocol — Python, Ruby, Go, Java, and every other language uses it too. We cover HTTP basics in this guide because understanding the protocol is essential to using the Fetch API effectively. If you want to dive deeper into HTTP itself, check out the MDN resources below. +</Warning> + +<CardGroup cols={2}> + <Card title="HTTP — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/HTTP"> + Comprehensive guide to the HTTP protocol + </Card> + <Card title="An Overview of HTTP — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview"> + Understanding the fundamentals of HTTP + </Card> +</CardGroup> + +--- + +## The Restaurant Analogy + +HTTP follows a simple pattern called **request-response**. To understand it, imagine you're at a restaurant: + +1. **You place an order** (the request) — "I'd like the pasta, please" +2. **The waiter takes it to the kitchen** (the network) — your order travels to where the food is prepared +3. **The kitchen prepares your meal** (the server) — they process your request and make your food +4. **The waiter brings back your food** (the response) — you receive what you asked for (hopefully!) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE REQUEST-RESPONSE CYCLE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ YOU (Browser) KITCHEN (Server) │ +│ ┌──────────┐ ┌──────────────┐ │ +│ │ │ ──── "I'd like pasta" ────► │ │ │ +│ │ :) │ (REQUEST) │ [chef] │ │ +│ │ │ │ │ │ +│ │ │ ◄──── Here you go! ──────── │ │ │ +│ │ │ (RESPONSE) │ │ │ +│ └──────────┘ └──────────────┘ │ +│ │ +│ The waiter (HTTP) is the protocol that makes this exchange work! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Sometimes things go wrong: +- **The kitchen is closed** (server is down) — You can't even place an order +- **They're out of pasta** (404 Not Found) — The order was received, but they can't fulfill it +- **Something's wrong in the kitchen** (500 Server Error) — They tried but something broke + +This request-response cycle is the core of how the web works. The **Fetch API** is JavaScript's modern way to participate in this cycle programmatically. + +<Warning> +**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so those concepts are essential. If you're not comfortable with Promises yet, read that guide first! +</Warning> + +--- + +## HTTP Fundamentals + +Before diving into the Fetch API, let's understand the key concepts of HTTP itself. + +### The Request-Response Model + +Every HTTP interaction follows a simple pattern: + +<Steps> + <Step title="Client Sends Request"> + Your browser (the client) sends an HTTP request to a server. The request includes what you want (the URL), how you want it (the method), and any additional info (headers, body). + </Step> + + <Step title="Server Processes Request"> + The server receives the request, does whatever work is needed (database queries, calculations, etc.), and prepares a response. + </Step> + + <Step title="Server Sends Response"> + The server sends back an HTTP response containing a status code (success/failure), headers (metadata), and usually a body (the actual data). + </Step> + + <Step title="Client Handles Response"> + Your JavaScript code receives the response and does something with it — display data, show an error, redirect the user, etc. + </Step> +</Steps> + +### HTTP Methods: What Do You Want to Do? + +HTTP methods tell the server what action you want to perform. Think of them as verbs: + +| Method | Purpose | Restaurant Analogy | +|--------|---------|-------------------| +| **GET** | Retrieve data | "Can I see the menu?" | +| **POST** | Create new data | "I'd like to place an order" | +| **PUT** | Update/replace data | "Actually, change my order to pizza" | +| **PATCH** | Partially update data | "Add extra cheese to my order" | +| **DELETE** | Remove data | "Cancel my order" | + +```javascript +// GET - Retrieve a user +fetch('/api/users/123') + +// POST - Create a new user +fetch('/api/users', { + method: 'POST', + body: JSON.stringify({ name: 'Alice' }) +}) + +// PUT - Replace a user +fetch('/api/users/123', { + method: 'PUT', + body: JSON.stringify({ name: 'Alice Updated' }) +}) + +// DELETE - Remove a user +fetch('/api/users/123', { + method: 'DELETE' +}) +``` + +### HTTP Status Codes: What Happened? + +Status codes are three-digit numbers that tell you how the request went: + +<AccordionGroup> + <Accordion title="2xx - Success"> + The request was received, understood, and accepted. + + - **200 OK** — Standard success response + - **201 Created** — New resource was created (common after POST) + - **204 No Content** — Success, but nothing to return (common after DELETE) + + ```javascript + // 200 OK example + const response = await fetch('/api/users/123') + console.log(response.status) // 200 + console.log(response.ok) // true + ``` + </Accordion> + + <Accordion title="3xx - Redirection"> + The resource has moved somewhere else. + + - **301 Moved Permanently** — Resource has a new permanent URL + - **302 Found** — Temporary redirect + - **304 Not Modified** — Use your cached version + + Fetch follows redirects automatically by default. + </Accordion> + + <Accordion title="4xx - Client Errors"> + Something is wrong with your request. + + - **400 Bad Request** — Malformed request syntax + - **401 Unauthorized** — Authentication required + - **403 Forbidden** — You don't have permission + - **404 Not Found** — Resource doesn't exist + - **422 Unprocessable Entity** — Validation failed + + ```javascript + // 404 Not Found example + const response = await fetch('/api/users/999999') + console.log(response.status) // 404 + console.log(response.ok) // false + ``` + </Accordion> + + <Accordion title="5xx - Server Errors"> + Something went wrong on the server. + + - **500 Internal Server Error** — Generic server error + - **502 Bad Gateway** — Server got invalid response from upstream + - **503 Service Unavailable** — Server is overloaded or down for maintenance + + ```javascript + // 500 error example + const response = await fetch('/api/broken-endpoint') + console.log(response.status) // 500 + console.log(response.ok) // false + ``` + </Accordion> +</AccordionGroup> + +<Tip> +**Quick Rule of Thumb:** +- **2xx** = "Here's what you asked for" +- **3xx** = "Go look over there" +- **4xx** = "You messed up" +- **5xx** = "We messed up" +</Tip> + +--- + +## The Fetch API + +The **Fetch API** is JavaScript's modern way to make HTTP requests. It replaced the older `XMLHttpRequest` (XHR) with a cleaner, Promise-based interface. + +### Basic GET Request + +The simplest fetch call retrieves data from a URL: + +```javascript +// Basic fetch - returns a Promise +fetch('https://api.example.com/users') + .then(response => response.json()) + .then(data => console.log(data)) + .catch(error => console.error('Error:', error)) +``` + +Let's break this down step by step: + +```javascript +// Step 1: fetch() returns a Promise that resolves to a Response object +const responsePromise = fetch('https://api.example.com/users') + +// Step 2: When the response arrives, we get a Response object +responsePromise.then(response => { + console.log(response.status) // 200 + console.log(response.ok) // true + console.log(response.headers) // Headers object + + // Step 3: The body is a stream, we need to parse it + // .json() returns ANOTHER Promise + return response.json() +}) +.then(data => { + // Step 4: Now we have the actual data + console.log(data) // { users: [...] } +}) +``` + +### The Response Object + +When `fetch()` resolves, you get a `Response` object with useful properties: + +```javascript +const response = await fetch('https://api.example.com/users/1') + +// Status information +response.status // 200, 404, 500, etc. +response.statusText // "OK", "Not Found", "Internal Server Error" +response.ok // true if status is 200-299 + +// Response metadata +response.headers // Headers object +response.url // Final URL (after redirects) +response.type // "basic", "cors", etc. +response.redirected // true if response came from a redirect + +// Body methods (each returns a Promise) +response.json() // Parse body as JSON +response.text() // Parse body as plain text +response.blob() // Parse body as binary Blob +response.formData() // Parse body as FormData +response.arrayBuffer() // Parse body as ArrayBuffer +``` + +<Warning> +**Important:** The body can only be read once! If you call `response.json()`, you can't call `response.text()` afterward. If you need to read it multiple times, clone the response first with `response.clone()`. +</Warning> + +### Reading JSON Data + +Most APIs return JSON. Use `.json()` to parse it: + +```javascript +async function getUser(id) { + const response = await fetch(`https://api.example.com/users/${id}`) + const user = await response.json() + + console.log(user.name) // "Alice" + console.log(user.email) // "alice@example.com" + + return user +} +``` + +### Making POST Requests + +To send data to a server, use the POST method with a request body: + +```javascript +async function createUser(userData) { + const response = await fetch('https://api.example.com/users', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(userData) + }) + + const newUser = await response.json() + return newUser +} + +// Usage +const user = await createUser({ + name: 'Bob', + email: 'bob@example.com' +}) +console.log(user.id) // New user's ID from server +``` + +### Setting Headers + +Headers provide additional information about the request: + +```javascript +const response = await fetch('https://api.example.com/data', { + method: 'GET', + headers: { + // Tell server what format we want + 'Accept': 'application/json', + + // Authentication token + 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...', + + // Custom header + 'X-Custom-Header': 'custom-value' + } +}) +``` + +Common headers you'll use: + +| Header | Purpose | +|--------|---------| +| `Content-Type` | Format of data you're sending (e.g., `application/json`) | +| `Accept` | Format of data you want back | +| `Authorization` | Authentication credentials | +| `Cache-Control` | Caching instructions | + +### Building URLs with Query Parameters + +When fetching data, you often need to include query parameters (e.g., `/api/search?q=javascript&page=1`). Use the **[URL](https://developer.mozilla.org/en-US/docs/Web/API/URL)** and **[URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)** APIs to build URLs safely: + +```javascript +// Building a URL with query parameters +const url = new URL('https://api.example.com/search') +url.searchParams.set('q', 'javascript') +url.searchParams.set('page', '1') +url.searchParams.set('limit', '10') + +console.log(url.toString()) +// "https://api.example.com/search?q=javascript&page=1&limit=10" + +// Use with fetch +const response = await fetch(url) +``` + +You can also use `URLSearchParams` directly: + +```javascript +const params = new URLSearchParams({ + q: 'javascript', + page: '1' +}) + +// Append to a URL string +const response = await fetch(`/api/search?${params}`) +``` + +<Tip> +**Why use URL/URLSearchParams instead of string concatenation?** These APIs automatically handle URL encoding for special characters. If a user searches for "C++ tutorial", it becomes `q=C%2B%2B+tutorial` — something you'd have to handle manually with string concatenation. +</Tip> + +--- + +## Error Handling: The Critical Distinction + +This is the **most important section** of this guide. Many developers get this wrong, leading to bugs that are hard to track down. + +### Two Types of "Errors" + +When working with `fetch()`, there are two completely different types of failures: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ TWO TYPES OF FAILURES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. NETWORK ERRORS 2. HTTP ERROR RESPONSES │ +│ ──────────────────── ─────────────────────── │ +│ │ +│ • Server unreachable • Server responded with error │ +│ • DNS lookup failed • 404 Not Found │ +│ • No internet connection • 500 Internal Server Error │ +│ • Request timed out • 401 Unauthorized │ +│ • CORS blocked • 403 Forbidden │ +│ │ +│ Promise REJECTS ❌ Promise RESOLVES ✓ │ +│ Goes to .catch() response.ok is false │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Warning> +**The Trap:** `fetch()` only rejects its Promise for network errors. An HTTP 404 or 500 response is still a "successful" fetch — the network request completed! You must check `response.ok` to detect HTTP errors. +</Warning> + +### The Wrong Way + +```javascript +// ❌ WRONG - This misses HTTP errors! +try { + const response = await fetch('/api/users/999') + const data = await response.json() + console.log(data) // Might be an error object! +} catch (error) { + // Only catches NETWORK errors + // A 404 response WON'T end up here! + console.error('Error:', error) +} +``` + +### The Right Way + +```javascript +// ✓ CORRECT - Check response.ok +async function fetchUser(id) { + try { + const response = await fetch(`/api/users/${id}`) + + // Check if the HTTP response was successful + if (!response.ok) { + // HTTP error (4xx, 5xx) - throw to catch block + throw new Error(`HTTP error! Status: ${response.status}`) + } + + const data = await response.json() + return data + + } catch (error) { + // Now this catches BOTH network errors AND HTTP errors + console.error('Fetch failed:', error.message) + throw error + } +} +``` + +### A Reusable Fetch Wrapper + +Here's a pattern you can use in real projects: + +```javascript +async function fetchJSON(url, options = {}) { + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options.headers + }, + ...options + }) + + // Handle HTTP errors + if (!response.ok) { + const error = new Error(`HTTP ${response.status}: ${response.statusText}`) + error.status = response.status + error.response = response + throw error + } + + // Handle empty responses (like 204 No Content) + if (response.status === 204) { + return null + } + + return response.json() +} + +// Usage +try { + const user = await fetchJSON('/api/users/1') + console.log(user) +} catch (error) { + if (error.status === 404) { + console.log('User not found') + } else if (error.status >= 500) { + console.log('Server error, try again later') + } else { + console.log('Request failed:', error.message) + } +} +``` + +--- + +## Modern Patterns with async/await + +While `fetch()` works with `.then()` chains, modern JavaScript uses `async/await` for cleaner code. + +### Basic async/await Pattern + +```javascript +async function loadUserProfile(userId) { + try { + const response = await fetch(`/api/users/${userId}`) + + if (!response.ok) { + throw new Error(`Failed to load user: ${response.status}`) + } + + const user = await response.json() + return user + + } catch (error) { + console.error('Error loading profile:', error) + return null + } +} + +// Usage +const user = await loadUserProfile(123) +if (user) { + console.log(`Welcome, ${user.name}!`) +} +``` + +### Parallel Requests + +Need to fetch multiple resources? Don't await them one by one: + +```javascript +// ❌ SLOW - Sequential requests (one after another) +async function loadDashboardSlow() { + const user = await fetch('/api/user').then(r => r.json()) + const posts = await fetch('/api/posts').then(r => r.json()) + const notifications = await fetch('/api/notifications').then(r => r.json()) + // Total time: user + posts + notifications + return { user, posts, notifications } +} + +// ✓ FAST - Parallel requests (all at once) +async function loadDashboardFast() { + const [user, posts, notifications] = await Promise.all([ + fetch('/api/user').then(r => r.json()), + fetch('/api/posts').then(r => r.json()), + fetch('/api/notifications').then(r => r.json()) + ]) + // Total time: max(user, posts, notifications) + return { user, posts, notifications } +} +``` + +### Loading States Pattern + +In real applications, you need to track loading and error states: + +```javascript +async function fetchWithState(url) { + const state = { + data: null, + loading: true, + error: null + } + + try { + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + state.data = await response.json() + } catch (error) { + state.error = error.message + } finally { + state.loading = false + } + + return state +} + +// Usage +const result = await fetchWithState('/api/users') + +if (result.loading) { + console.log('Loading...') +} else if (result.error) { + console.log('Error:', result.error) +} else { + console.log('Data:', result.data) +} +``` + +--- + +## Cancelling Requests with AbortController + +The **[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)** API lets you cancel in-flight fetch requests. This is essential for: + +- **Timeouts** — Cancel requests that take too long +- **User navigation** — Cancel pending requests when user leaves a page +- **Search inputs** — Cancel the previous search when user types new characters +- **Component cleanup** — Cancel requests when a React/Vue component unmounts + +Without AbortController, abandoned requests continue running in the background, wasting bandwidth and potentially causing bugs when their responses arrive after you no longer need them. + +### How It Works + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ABORTCONTROLLER FLOW │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Create controller 2. Pass signal to fetch │ +│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ const controller = │ │ fetch(url, { │ │ +│ │ new AbortController│ ───► │ signal: controller.signal │ │ +│ └─────────────────────┘ │ }) │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ 3. Call abort() to cancel 4. Fetch rejects with AbortError │ +│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ controller.abort() │ ───► │ catch (error) { │ │ +│ └─────────────────────┘ │ error.name === 'AbortError' │ │ +│ │ } │ │ +│ └─────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Basic AbortController Usage + +```javascript +// Create a controller +const controller = new AbortController() + +// Pass its signal to fetch +fetch('/api/slow-endpoint', { + signal: controller.signal +}) + .then(response => response.json()) + .then(data => console.log(data)) + .catch(error => { + if (error.name === 'AbortError') { + console.log('Request was cancelled') + } else { + console.error('Request failed:', error) + } + }) + +// Cancel the request after 5 seconds +setTimeout(() => { + controller.abort() +}, 5000) +``` + +### Timeout Pattern + +Create a reusable timeout wrapper: + +```javascript +async function fetchWithTimeout(url, options = {}, timeout = 5000) { + const controller = new AbortController() + + // Set up timeout + const timeoutId = setTimeout(() => { + controller.abort() + }, timeout) + + try { + const response = await fetch(url, { + ...options, + signal: controller.signal + }) + + clearTimeout(timeoutId) + return response + + } catch (error) { + clearTimeout(timeoutId) + + if (error.name === 'AbortError') { + throw new Error(`Request timed out after ${timeout}ms`) + } + + throw error + } +} + +// Usage +try { + const response = await fetchWithTimeout('/api/data', {}, 3000) + const data = await response.json() +} catch (error) { + console.error(error.message) // "Request timed out after 3000ms" +} +``` + +### Search Input Pattern + +Cancel previous search when user types: + +```javascript +let currentController = null + +async function searchUsers(query) { + // Cancel any in-flight request + if (currentController) { + currentController.abort() + } + + // Create new controller for this request + currentController = new AbortController() + + try { + const response = await fetch(`/api/search?q=${query}`, { + signal: currentController.signal + }) + + if (!response.ok) throw new Error('Search failed') + + return await response.json() + + } catch (error) { + if (error.name === 'AbortError') { + // Ignore - we cancelled this on purpose + return null + } + throw error + } +} + +// As user types, only the last request matters +searchInput.addEventListener('input', async (e) => { + const results = await searchUsers(e.target.value) + if (results) { + displayResults(results) + } +}) +``` + +--- + +## Key Takeaways + +<Info> +**Remember these essential points about HTTP & Fetch:** + +1. **HTTP is request-response** — Client sends a request, server sends a response + +2. **HTTP methods are verbs** — GET (read), POST (create), PUT (update), DELETE (remove) + +3. **Status codes tell you what happened** — 2xx (success), 4xx (your fault), 5xx (server's fault) + +4. **Fetch returns a Promise** — It resolves to a Response object, not directly to data + +5. **Response.json() is also a Promise** — You need to await it too + +6. **Fetch only rejects on network errors** — HTTP 404/500 still "succeeds" — check `response.ok`! + +7. **Always check response.ok** — This is the most common fetch mistake + +8. **Use async/await** — It's cleaner than Promise chains + +9. **Use Promise.all for parallel requests** — Don't await sequentially when you don't have to + +10. **AbortController cancels requests** — Essential for search inputs and cleanup +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between a network error and an HTTP error in fetch?"> + **Answer:** + + - **Network errors** occur when the request can't be completed at all — server unreachable, DNS failure, no internet, CORS blocked, etc. These cause the fetch Promise to **reject**. + + - **HTTP errors** occur when the server responds with an error status code (4xx, 5xx). The request completed successfully (the network worked), so the Promise **resolves**. You must check `response.ok` to detect these. + + ```javascript + try { + const response = await fetch('/api/data') + + // This line runs even for 404, 500, etc.! + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + const data = await response.json() + } catch (error) { + // Now catches both types + } + ``` + </Accordion> + + <Accordion title="Question 2: Why does response.json() return a Promise?"> + **Answer:** The response body is a readable stream that might still be downloading when `fetch()` resolves. The `response.json()` method reads the entire stream and parses it as JSON, which is an asynchronous operation. + + This is why you need to `await` it: + + ```javascript + const response = await fetch('/api/data') // Response headers arrived + const data = await response.json() // Body fully downloaded & parsed + ``` + + The same applies to `response.text()`, `response.blob()`, etc. + </Accordion> + + <Accordion title="Question 3: How do you send JSON data in a POST request?"> + **Answer:** You need to: + 1. Set the method to 'POST' + 2. Set the Content-Type header to 'application/json' + 3. Stringify your data in the body + + ```javascript + const response = await fetch('/api/users', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: 'Alice', + email: 'alice@example.com' + }) + }) + ``` + </Accordion> + + <Accordion title="Question 4: What does response.ok mean?"> + **Answer:** `response.ok` is a boolean that's `true` if the HTTP status code is in the 200-299 range (success), and `false` otherwise. + + It's a convenient shorthand for checking if the request succeeded: + + ```javascript + // These are equivalent: + if (response.ok) { ... } + if (response.status >= 200 && response.status < 300) { ... } + ``` + + Common values: + - 200, 201, 204 → `ok` is `true` + - 400, 401, 404, 500 → `ok` is `false` + </Accordion> + + <Accordion title="Question 5: How do you cancel a fetch request?"> + **Answer:** Use an `AbortController`: + + ```javascript + // 1. Create controller + const controller = new AbortController() + + // 2. Pass its signal to fetch + fetch('/api/data', { signal: controller.signal }) + .then(r => r.json()) + .catch(error => { + if (error.name === 'AbortError') { + console.log('Cancelled!') + } + }) + + // 3. Call abort() to cancel + controller.abort() + ``` + + Common use cases: + - Timeout implementation + - Cancelling when user navigates away + - Cancelling previous search when user types new input + </Accordion> + + <Accordion title="Question 6: How do you make multiple fetch requests in parallel?"> + **Answer:** Use `Promise.all()` to run requests concurrently: + + ```javascript + // ✓ Parallel - fast + const [users, posts, comments] = await Promise.all([ + fetch('/api/users').then(r => r.json()), + fetch('/api/posts').then(r => r.json()), + fetch('/api/comments').then(r => r.json()) + ]) + + // ❌ Sequential - slow (each waits for the previous) + const users = await fetch('/api/users').then(r => r.json()) + const posts = await fetch('/api/posts').then(r => r.json()) + const comments = await fetch('/api/comments').then(r => r.json()) + ``` + + Parallel requests complete in the time of the slowest request, not the sum of all requests. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Promises" icon="handshake" href="/concepts/promises"> + Fetch is Promise-based — understanding Promises is essential + </Card> + <Card title="async/await" icon="hourglass" href="/concepts/async-await"> + Modern syntax for working with Promises and fetch + </Card> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + How JavaScript handles async operations like fetch + </Card> + <Card title="DOM" icon="sitemap" href="/concepts/dom"> + Often you'll fetch data and update the DOM with it + </Card> +</CardGroup> + +--- + +## Reference + +<CardGroup cols={2}> + <Card title="Fetch API — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"> + Official MDN documentation for the Fetch API + </Card> + <Card title="Using Fetch — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch"> + Comprehensive guide to using the Fetch API + </Card> + <Card title="Response — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Response"> + Documentation for the Response object + </Card> + <Card title="AbortController — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController"> + Documentation for cancelling fetch requests + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="How to Use Fetch with async/await" icon="newspaper" href="https://dmitripavlutin.com/javascript-fetch-async-await/"> + Dmitri Pavlutin's clear guide to modern fetch patterns with practical examples. + </Card> + <Card title="JavaScript Fetch API Ultimate Guide" icon="newspaper" href="https://blog.webdevsimplified.com/2022-01/js-fetch-api/"> + Web Dev Simplified's comprehensive tutorial covering all fetch fundamentals. + </Card> + <Card title="Fetch API Error Handling" icon="newspaper" href="https://www.tjvantoll.com/2015/09/13/fetch-and-errors/"> + TJ VanToll's essential article on the fetch error handling gotcha that trips up everyone. + </Card> + <Card title="Abort Controller: Why and How" icon="newspaper" href="https://blog.openreplay.com/abort-controller--why-and-how-to-use-it/"> + OpenReplay's guide to cancelling fetch requests with AbortController. + </Card> +</CardGroup> + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript Fetch API" icon="video" href="https://www.youtube.com/watch?v=cuEtnrL9-H0"> + Traversy Media's beginner-friendly introduction to the Fetch API. + </Card> + <Card title="Learn Fetch API in 6 Minutes" icon="video" href="https://www.youtube.com/watch?v=37vxWr0WgQk"> + Web Dev Simplified's quick and clear fetch tutorial. + </Card> + <Card title="Async JS Crash Course - Callbacks, Promises, Async/Await" icon="video" href="https://www.youtube.com/watch?v=PoRJizFvM7s"> + Traversy Media's comprehensive async JavaScript guide including fetch. + </Card> + <Card title="JavaScript Fetch with Request and Headers Objects" icon="video" href="https://www.youtube.com/watch?v=CVZhFBjn8Rw"> + Steve Griffith's detailed walkthrough of fetch's Request and Headers APIs. + </Card> +</CardGroup> diff --git a/docs/docs.json b/docs/docs.json index 6c1ec3bd..6ff7944d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -54,7 +54,8 @@ "group": "Web Platform", "icon": "browser", "pages": [ - "concepts/dom" + "concepts/dom", + "concepts/http-fetch" ] }, { diff --git a/tests/web-platform/http-fetch/http-fetch.test.js b/tests/web-platform/http-fetch/http-fetch.test.js new file mode 100644 index 00000000..37d5a692 --- /dev/null +++ b/tests/web-platform/http-fetch/http-fetch.test.js @@ -0,0 +1,1031 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +// ============================================================================= +// HTTP & FETCH - TEST SUITE +// Tests for code examples from docs/concepts/http-fetch.mdx +// Uses Node 20+ native fetch with Vitest mocking +// ============================================================================= + +describe('HTTP & Fetch', () => { + // Store original fetch + const originalFetch = global.fetch + + beforeEach(() => { + // Reset fetch mock before each test + vi.restoreAllMocks() + }) + + afterEach(() => { + // Restore original fetch after each test + global.fetch = originalFetch + }) + + // =========================================================================== + // RESPONSE OBJECT BASICS + // =========================================================================== + describe('Response Object', () => { + it('should have status property', () => { + const response = new Response('OK', { status: 200 }) + expect(response.status).toBe(200) + }) + + it('should have statusText property', () => { + const response = new Response('OK', { status: 200, statusText: 'OK' }) + expect(response.statusText).toBe('OK') + }) + + it('should have ok property true for 2xx status', () => { + const response200 = new Response('OK', { status: 200 }) + const response201 = new Response('Created', { status: 201 }) + const response204 = new Response(null, { status: 204 }) + + expect(response200.ok).toBe(true) + expect(response201.ok).toBe(true) + expect(response204.ok).toBe(true) + }) + + it('should have ok property false for non-2xx status', () => { + const response400 = new Response('Bad Request', { status: 400 }) + const response404 = new Response('Not Found', { status: 404 }) + const response500 = new Response('Server Error', { status: 500 }) + + expect(response400.ok).toBe(false) + expect(response404.ok).toBe(false) + expect(response500.ok).toBe(false) + }) + + it('should have headers object', () => { + const response = new Response('OK', { + headers: { + 'Content-Type': 'application/json', + 'X-Custom-Header': 'custom-value' + } + }) + + expect(response.headers.get('Content-Type')).toBe('application/json') + expect(response.headers.get('X-Custom-Header')).toBe('custom-value') + }) + + it('should parse JSON body', async () => { + const data = { name: 'Alice', age: 30 } + const response = new Response(JSON.stringify(data), { + headers: { 'Content-Type': 'application/json' } + }) + + const parsed = await response.json() + expect(parsed).toEqual(data) + }) + + it('should parse text body', async () => { + const response = new Response('Hello, World!') + const text = await response.text() + expect(text).toBe('Hello, World!') + }) + + it('should only allow body to be read once', async () => { + const response = new Response('Hello') + + await response.text() // First read + + // Second read should throw + await expect(response.text()).rejects.toThrow() + }) + + it('should clone response for multiple reads', async () => { + const response = new Response('Hello') + const clone = response.clone() + + const text1 = await response.text() + const text2 = await clone.text() + + expect(text1).toBe('Hello') + expect(text2).toBe('Hello') + }) + }) + + // =========================================================================== + // STATUS CODE RANGES + // =========================================================================== + describe('HTTP Status Codes', () => { + describe('2xx Success', () => { + it('200 OK should be successful', () => { + const response = new Response('OK', { status: 200 }) + expect(response.ok).toBe(true) + expect(response.status).toBe(200) + }) + + it('201 Created should be successful', () => { + const response = new Response('Created', { status: 201 }) + expect(response.ok).toBe(true) + expect(response.status).toBe(201) + }) + + it('204 No Content should be successful', () => { + const response = new Response(null, { status: 204 }) + expect(response.ok).toBe(true) + expect(response.status).toBe(204) + }) + + it('299 should still be ok', () => { + const response = new Response('OK', { status: 299 }) + expect(response.ok).toBe(true) + }) + }) + + describe('3xx Redirection', () => { + it('301 Moved Permanently should not be ok', () => { + const response = new Response('Moved', { status: 301 }) + expect(response.ok).toBe(false) + }) + + it('302 Found should not be ok', () => { + const response = new Response('Found', { status: 302 }) + expect(response.ok).toBe(false) + }) + + it('304 Not Modified should not be ok', () => { + // 304 is a "null body status" so we use null body + const response = new Response(null, { status: 304 }) + expect(response.ok).toBe(false) + }) + }) + + describe('4xx Client Errors', () => { + it('400 Bad Request should not be ok', () => { + const response = new Response('Bad Request', { status: 400 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(400) + }) + + it('401 Unauthorized should not be ok', () => { + const response = new Response('Unauthorized', { status: 401 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(401) + }) + + it('403 Forbidden should not be ok', () => { + const response = new Response('Forbidden', { status: 403 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(403) + }) + + it('404 Not Found should not be ok', () => { + const response = new Response('Not Found', { status: 404 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(404) + }) + + it('422 Unprocessable Entity should not be ok', () => { + const response = new Response('Unprocessable Entity', { status: 422 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(422) + }) + }) + + describe('5xx Server Errors', () => { + it('500 Internal Server Error should not be ok', () => { + const response = new Response('Internal Server Error', { status: 500 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(500) + }) + + it('502 Bad Gateway should not be ok', () => { + const response = new Response('Bad Gateway', { status: 502 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(502) + }) + + it('503 Service Unavailable should not be ok', () => { + const response = new Response('Service Unavailable', { status: 503 }) + expect(response.ok).toBe(false) + expect(response.status).toBe(503) + }) + }) + }) + + // =========================================================================== + // HEADERS API + // =========================================================================== + describe('Headers API', () => { + it('should create headers from object', () => { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + expect(headers.get('Content-Type')).toBe('application/json') + expect(headers.get('Accept')).toBe('application/json') + }) + + it('should append headers', () => { + const headers = new Headers() + headers.append('Accept', 'application/json') + headers.append('Accept', 'text/plain') + + expect(headers.get('Accept')).toBe('application/json, text/plain') + }) + + it('should set headers (overwrite)', () => { + const headers = new Headers() + headers.set('Accept', 'application/json') + headers.set('Accept', 'text/plain') + + expect(headers.get('Accept')).toBe('text/plain') + }) + + it('should check if header exists', () => { + const headers = new Headers({ + 'Content-Type': 'application/json' + }) + + expect(headers.has('Content-Type')).toBe(true) + expect(headers.has('Authorization')).toBe(false) + }) + + it('should delete headers', () => { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + headers.delete('Accept') + + expect(headers.has('Accept')).toBe(false) + expect(headers.has('Content-Type')).toBe(true) + }) + + it('should iterate over headers', () => { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + const entries = [] + for (const [key, value] of headers) { + entries.push([key, value]) + } + + expect(entries).toContainEqual(['content-type', 'application/json']) + expect(entries).toContainEqual(['accept', 'application/json']) + }) + + it('should be case-insensitive for header names', () => { + const headers = new Headers({ + 'Content-Type': 'application/json' + }) + + expect(headers.get('content-type')).toBe('application/json') + expect(headers.get('CONTENT-TYPE')).toBe('application/json') + expect(headers.get('Content-Type')).toBe('application/json') + }) + }) + + // =========================================================================== + // REQUEST OBJECT + // =========================================================================== + describe('Request Object', () => { + it('should create a basic request', () => { + const request = new Request('https://api.example.com/users') + + expect(request.url).toBe('https://api.example.com/users') + expect(request.method).toBe('GET') + }) + + it('should create request with method', () => { + const request = new Request('https://api.example.com/users', { + method: 'POST' + }) + + expect(request.method).toBe('POST') + }) + + it('should create request with headers', () => { + const request = new Request('https://api.example.com/users', { + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer token123' + } + }) + + expect(request.headers.get('Content-Type')).toBe('application/json') + expect(request.headers.get('Authorization')).toBe('Bearer token123') + }) + + it('should create request with body', async () => { + const body = JSON.stringify({ name: 'Alice' }) + const request = new Request('https://api.example.com/users', { + method: 'POST', + body: body + }) + + const requestBody = await request.text() + expect(requestBody).toBe(body) + }) + + it('should clone request', () => { + const request = new Request('https://api.example.com/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }) + + const clone = request.clone() + + expect(clone.url).toBe(request.url) + expect(clone.method).toBe(request.method) + expect(clone.headers.get('Content-Type')).toBe('application/json') + }) + }) + + // =========================================================================== + // FETCH WITH MOCKING + // =========================================================================== + describe('Fetch API (Mocked)', () => { + it('should make a GET request', async () => { + const mockData = { id: 1, name: 'Alice' } + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(mockData), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) + ) + + const response = await fetch('https://api.example.com/users/1') + const data = await response.json() + + expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1') + expect(data).toEqual(mockData) + }) + + it('should make a POST request with body', async () => { + const mockResponse = { id: 1, name: 'Alice' } + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify(mockResponse), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }) + ) + + const userData = { name: 'Alice', email: 'alice@example.com' } + const response = await fetch('https://api.example.com/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(userData) + }) + + expect(fetch).toHaveBeenCalledWith('https://api.example.com/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(userData) + }) + expect(response.status).toBe(201) + }) + + it('should handle 404 response (not rejection)', async () => { + global.fetch = vi.fn().mockResolvedValue( + new Response('Not Found', { status: 404 }) + ) + + // Fetch resolves even for 404! + const response = await fetch('https://api.example.com/users/999') + + expect(response.ok).toBe(false) + expect(response.status).toBe(404) + }) + + it('should handle 500 response (not rejection)', async () => { + global.fetch = vi.fn().mockResolvedValue( + new Response('Internal Server Error', { status: 500 }) + ) + + // Fetch resolves even for 500! + const response = await fetch('https://api.example.com/broken') + + expect(response.ok).toBe(false) + expect(response.status).toBe(500) + }) + + it('should reject on network error', async () => { + global.fetch = vi.fn().mockRejectedValue( + new TypeError('Failed to fetch') + ) + + await expect(fetch('https://api.example.com/unreachable')) + .rejects.toThrow('Failed to fetch') + }) + }) + + // =========================================================================== + // ERROR HANDLING PATTERNS + // =========================================================================== + describe('Error Handling Patterns', () => { + it('should properly check response.ok', async () => { + global.fetch = vi.fn().mockResolvedValue( + new Response('Not Found', { status: 404 }) + ) + + const response = await fetch('/api/users/999') + + // The correct pattern + if (!response.ok) { + const error = new Error(`HTTP ${response.status}`) + expect(error.message).toBe('HTTP 404') + } + }) + + it('should demonstrate fetchJSON wrapper pattern', async () => { + // Reusable fetch wrapper + async function fetchJSON(url, options = {}) { + const response = await fetch(url, options) + + if (!response.ok) { + const error = new Error(`HTTP ${response.status}`) + error.status = response.status + throw error + } + + if (response.status === 204) { + return null + } + + return response.json() + } + + // Test successful response + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ id: 1 }), { status: 200 }) + ) + + const data = await fetchJSON('/api/users/1') + expect(data).toEqual({ id: 1 }) + + // Test 404 error + global.fetch = vi.fn().mockResolvedValue( + new Response('Not Found', { status: 404 }) + ) + + try { + await fetchJSON('/api/users/999') + expect.fail('Should have thrown') + } catch (error) { + expect(error.message).toBe('HTTP 404') + expect(error.status).toBe(404) + } + + // Test 204 No Content + global.fetch = vi.fn().mockResolvedValue( + new Response(null, { status: 204 }) + ) + + const noContent = await fetchJSON('/api/users/1', { method: 'DELETE' }) + expect(noContent).toBeNull() + }) + + it('should differentiate network vs HTTP errors', async () => { + let errorType = null + + async function safeFetch(url) { + try { + const response = await fetch(url) + + if (!response.ok) { + errorType = 'http' + throw new Error(`HTTP ${response.status}`) + } + + return await response.json() + } catch (error) { + if (error.message.startsWith('HTTP')) { + // Already handled HTTP error + throw error + } + // Network error + errorType = 'network' + throw new Error('Network error: ' + error.message) + } + } + + // Test HTTP error (404) + global.fetch = vi.fn().mockResolvedValue( + new Response('Not Found', { status: 404 }) + ) + + try { + await safeFetch('/api/missing') + } catch (e) { + expect(errorType).toBe('http') + } + + // Test network error + global.fetch = vi.fn().mockRejectedValue( + new TypeError('Failed to fetch') + ) + + try { + await safeFetch('/api/unreachable') + } catch (e) { + expect(errorType).toBe('network') + } + }) + }) + + // =========================================================================== + // ABORT CONTROLLER + // =========================================================================== + describe('AbortController', () => { + it('should create an AbortController', () => { + const controller = new AbortController() + + expect(controller).toBeDefined() + expect(controller.signal).toBeDefined() + expect(controller.signal.aborted).toBe(false) + }) + + it('should abort and update signal', () => { + const controller = new AbortController() + + expect(controller.signal.aborted).toBe(false) + controller.abort() + expect(controller.signal.aborted).toBe(true) + }) + + it('should abort with reason', () => { + const controller = new AbortController() + controller.abort('User cancelled') + + expect(controller.signal.aborted).toBe(true) + expect(controller.signal.reason).toBe('User cancelled') + }) + + it('should reject fetch when aborted', async () => { + const controller = new AbortController() + + // Mock fetch to respect abort signal + global.fetch = vi.fn().mockImplementation((url, options) => { + return new Promise((resolve, reject) => { + if (options?.signal?.aborted) { + const error = new DOMException('The operation was aborted.', 'AbortError') + reject(error) + return + } + + options?.signal?.addEventListener('abort', () => { + const error = new DOMException('The operation was aborted.', 'AbortError') + reject(error) + }) + + // Simulate slow request + setTimeout(() => { + resolve(new Response('OK')) + }, 1000) + }) + }) + + const fetchPromise = fetch('/api/slow', { signal: controller.signal }) + + // Abort immediately + controller.abort() + + await expect(fetchPromise).rejects.toThrow() + + try { + await fetchPromise + } catch (error) { + expect(error.name).toBe('AbortError') + } + }) + + it('should handle abort in try/catch', async () => { + const controller = new AbortController() + controller.abort() + + global.fetch = vi.fn().mockRejectedValue( + new DOMException('The operation was aborted.', 'AbortError') + ) + + try { + await fetch('/api/data', { signal: controller.signal }) + expect.fail('Should have thrown') + } catch (error) { + if (error.name === 'AbortError') { + // Expected - request was cancelled + expect(true).toBe(true) + } else { + throw error + } + } + }) + + it('should implement timeout pattern', async () => { + async function fetchWithTimeout(url, timeout = 5000) { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeout) + + try { + const response = await fetch(url, { signal: controller.signal }) + clearTimeout(timeoutId) + return response + } catch (error) { + clearTimeout(timeoutId) + if (error.name === 'AbortError') { + throw new Error(`Request timed out after ${timeout}ms`) + } + throw error + } + } + + // Mock slow request that will be aborted + global.fetch = vi.fn().mockImplementation((url, options) => { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + resolve(new Response('OK')) + }, 10000) // Very slow + + options?.signal?.addEventListener('abort', () => { + clearTimeout(timeout) + reject(new DOMException('The operation was aborted.', 'AbortError')) + }) + }) + }) + + // Should timeout after 100ms + await expect(fetchWithTimeout('/api/slow', 100)) + .rejects.toThrow('Request timed out after 100ms') + }) + }) + + // =========================================================================== + // PARALLEL REQUESTS + // =========================================================================== + describe('Parallel Requests with Promise.all', () => { + it('should fetch multiple resources in parallel', async () => { + global.fetch = vi.fn() + .mockResolvedValueOnce(new Response(JSON.stringify({ id: 1, name: 'User' }))) + .mockResolvedValueOnce(new Response(JSON.stringify([{ id: 1, title: 'Post' }]))) + .mockResolvedValueOnce(new Response(JSON.stringify([{ id: 1, body: 'Comment' }]))) + + const [user, posts, comments] = await Promise.all([ + fetch('/api/user').then(r => r.json()), + fetch('/api/posts').then(r => r.json()), + fetch('/api/comments').then(r => r.json()) + ]) + + expect(user).toEqual({ id: 1, name: 'User' }) + expect(posts).toEqual([{ id: 1, title: 'Post' }]) + expect(comments).toEqual([{ id: 1, body: 'Comment' }]) + expect(fetch).toHaveBeenCalledTimes(3) + }) + + it('should fail fast if any request fails', async () => { + global.fetch = vi.fn() + .mockResolvedValueOnce(new Response(JSON.stringify({ id: 1 }))) + .mockRejectedValueOnce(new Error('Network error')) + .mockResolvedValueOnce(new Response(JSON.stringify({ id: 3 }))) + + await expect(Promise.all([ + fetch('/api/1').then(r => r.json()), + fetch('/api/2').then(r => r.json()), + fetch('/api/3').then(r => r.json()) + ])).rejects.toThrow('Network error') + }) + + it('should use Promise.allSettled for graceful handling', async () => { + global.fetch = vi.fn() + .mockResolvedValueOnce(new Response(JSON.stringify({ id: 1 }))) + .mockRejectedValueOnce(new Error('Failed')) + .mockResolvedValueOnce(new Response(JSON.stringify({ id: 3 }))) + + const results = await Promise.allSettled([ + fetch('/api/1').then(r => r.json()), + fetch('/api/2').then(r => r.json()), + fetch('/api/3').then(r => r.json()) + ]) + + expect(results[0].status).toBe('fulfilled') + expect(results[0].value).toEqual({ id: 1 }) + + expect(results[1].status).toBe('rejected') + expect(results[1].reason.message).toBe('Failed') + + expect(results[2].status).toBe('fulfilled') + expect(results[2].value).toEqual({ id: 3 }) + }) + }) + + // =========================================================================== + // LOADING STATE PATTERN + // =========================================================================== + describe('Loading State Pattern', () => { + it('should track loading, data, and error states', async () => { + async function fetchWithState(url) { + const state = { + data: null, + loading: true, + error: null + } + + try { + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + state.data = await response.json() + } catch (error) { + state.error = error.message + } finally { + state.loading = false + } + + return state + } + + // Test successful fetch + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ name: 'Alice' }), { status: 200 }) + ) + + const successState = await fetchWithState('/api/user') + expect(successState.loading).toBe(false) + expect(successState.data).toEqual({ name: 'Alice' }) + expect(successState.error).toBeNull() + + // Test error fetch + global.fetch = vi.fn().mockResolvedValue( + new Response('Not Found', { status: 404 }) + ) + + const errorState = await fetchWithState('/api/missing') + expect(errorState.loading).toBe(false) + expect(errorState.data).toBeNull() + expect(errorState.error).toBe('HTTP 404') + + // Test network error + global.fetch = vi.fn().mockRejectedValue(new Error('Network error')) + + const networkErrorState = await fetchWithState('/api/unreachable') + expect(networkErrorState.loading).toBe(false) + expect(networkErrorState.data).toBeNull() + expect(networkErrorState.error).toBe('Network error') + }) + }) + + // =========================================================================== + // JSON PARSING + // =========================================================================== + describe('JSON Parsing', () => { + it('should parse valid JSON', async () => { + const response = new Response('{"name": "Alice", "age": 30}') + const data = await response.json() + + expect(data).toEqual({ name: 'Alice', age: 30 }) + }) + + it('should parse JSON arrays', async () => { + const response = new Response('[1, 2, 3, 4, 5]') + const data = await response.json() + + expect(data).toEqual([1, 2, 3, 4, 5]) + }) + + it('should parse nested JSON', async () => { + const nested = { + user: { + name: 'Alice', + address: { + city: 'Wonderland' + } + }, + posts: [ + { id: 1, title: 'First' }, + { id: 2, title: 'Second' } + ] + } + + const response = new Response(JSON.stringify(nested)) + const data = await response.json() + + expect(data.user.address.city).toBe('Wonderland') + expect(data.posts[1].title).toBe('Second') + }) + + it('should throw on invalid JSON', async () => { + const response = new Response('not valid json {') + + await expect(response.json()).rejects.toThrow() + }) + + it('should throw on empty body when expecting JSON', async () => { + const response = new Response('') + + await expect(response.json()).rejects.toThrow() + }) + }) + + // =========================================================================== + // HTTP METHODS + // =========================================================================== + describe('HTTP Methods', () => { + beforeEach(() => { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ success: true }), { status: 200 }) + ) + }) + + it('should default to GET method', async () => { + await fetch('/api/users') + + expect(fetch).toHaveBeenCalledWith('/api/users') + }) + + it('should make POST request', async () => { + await fetch('/api/users', { + method: 'POST', + body: JSON.stringify({ name: 'Alice' }) + }) + + expect(fetch).toHaveBeenCalledWith('/api/users', expect.objectContaining({ + method: 'POST' + })) + }) + + it('should make PUT request', async () => { + await fetch('/api/users/1', { + method: 'PUT', + body: JSON.stringify({ name: 'Alice Updated' }) + }) + + expect(fetch).toHaveBeenCalledWith('/api/users/1', expect.objectContaining({ + method: 'PUT' + })) + }) + + it('should make PATCH request', async () => { + await fetch('/api/users/1', { + method: 'PATCH', + body: JSON.stringify({ name: 'New Name' }) + }) + + expect(fetch).toHaveBeenCalledWith('/api/users/1', expect.objectContaining({ + method: 'PATCH' + })) + }) + + it('should make DELETE request', async () => { + await fetch('/api/users/1', { + method: 'DELETE' + }) + + expect(fetch).toHaveBeenCalledWith('/api/users/1', expect.objectContaining({ + method: 'DELETE' + })) + }) + }) + + // =========================================================================== + // URL AND QUERY PARAMETERS + // =========================================================================== + describe('URL and Query Parameters', () => { + it('should construct URL with search params', () => { + const url = new URL('https://api.example.com/search') + url.searchParams.set('q', 'javascript') + url.searchParams.set('page', '1') + url.searchParams.set('limit', '10') + + expect(url.toString()).toBe('https://api.example.com/search?q=javascript&page=1&limit=10') + }) + + it('should append multiple values for same param', () => { + const url = new URL('https://api.example.com/filter') + url.searchParams.append('tag', 'javascript') + url.searchParams.append('tag', 'nodejs') + + expect(url.toString()).toBe('https://api.example.com/filter?tag=javascript&tag=nodejs') + }) + + it('should get search params', () => { + const url = new URL('https://api.example.com/search?q=javascript&page=2') + + expect(url.searchParams.get('q')).toBe('javascript') + expect(url.searchParams.get('page')).toBe('2') + expect(url.searchParams.get('missing')).toBeNull() + }) + + it('should delete search params', () => { + const url = new URL('https://api.example.com/search?q=javascript&page=2') + url.searchParams.delete('page') + + expect(url.toString()).toBe('https://api.example.com/search?q=javascript') + }) + + it('should check if param exists', () => { + const url = new URL('https://api.example.com/search?q=javascript') + + expect(url.searchParams.has('q')).toBe(true) + expect(url.searchParams.has('page')).toBe(false) + }) + + it('should use URLSearchParams with fetch', async () => { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify([]), { status: 200 }) + ) + + const params = new URLSearchParams({ + q: 'javascript', + page: '1' + }) + + await fetch(`/api/search?${params}`) + + expect(fetch).toHaveBeenCalledWith('/api/search?q=javascript&page=1') + }) + }) + + // =========================================================================== + // REAL WORLD PATTERNS + // =========================================================================== + describe('Real World Patterns', () => { + it('should implement retry logic', async () => { + let attempts = 0 + + async function fetchWithRetry(url, options = {}, retries = 3) { + for (let i = 0; i < retries; i++) { + try { + attempts++ + const response = await fetch(url, options) + if (response.ok) return response + if (response.status >= 500 && i < retries - 1) continue + throw new Error(`HTTP ${response.status}`) + } catch (error) { + if (i === retries - 1) throw error + } + } + } + + // Mock: fail twice, succeed on third + global.fetch = vi.fn() + .mockResolvedValueOnce(new Response('Error', { status: 500 })) + .mockResolvedValueOnce(new Response('Error', { status: 500 })) + .mockResolvedValueOnce(new Response('OK', { status: 200 })) + + const response = await fetchWithRetry('/api/flaky') + + expect(response.status).toBe(200) + expect(attempts).toBe(3) + }) + + it('should implement search with cancel previous', async () => { + let currentController = null + + async function searchWithCancel(query) { + // Cancel previous request + if (currentController) { + currentController.abort() + } + + currentController = new AbortController() + + const response = await fetch(`/api/search?q=${query}`, { + signal: currentController.signal + }) + + return response.json() + } + + // Mock fetch that respects abort + global.fetch = vi.fn().mockImplementation((url, options) => { + return new Promise((resolve, reject) => { + if (options?.signal?.aborted) { + reject(new DOMException('Aborted', 'AbortError')) + return + } + + const handler = () => { + reject(new DOMException('Aborted', 'AbortError')) + } + + options?.signal?.addEventListener('abort', handler) + + // Resolve after short delay + setTimeout(() => { + options?.signal?.removeEventListener('abort', handler) + resolve(new Response(JSON.stringify({ results: [url] }))) + }, 50) + }) + }) + + // Start first search + const search1 = searchWithCancel('java') + + // Start second search (should cancel first) + const search2 = searchWithCancel('javascript') + + // First should be aborted + await expect(search1).rejects.toThrow() + + // Second should succeed + const result = await search2 + expect(result.results[0]).toContain('javascript') + }) + }) +}) From 468eaf21d00c36fb3feb591c7977c0626351b0a8 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:00:42 -0300 Subject: [PATCH 029/128] refactor(http-fetch): improve writing with better context and explanations - Rewrite POST section with MDN link and explanation of what POST is - Rewrite error handling intro to clearly explain the #1 fetch mistake - Add prose descriptions to 'The Mistake' and 'The Fix' subsections - Add async/await concept link for cross-referencing - Rename headers to be more engaging (mix of casual/descriptive) - Enrich section intros with MDN links (Response, Headers, JSON, POST) - Mention GET is the default method in Basic GET section --- docs/concepts/http-fetch.mdx | 42 ++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index 1cffc2bd..e06a0308 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -224,7 +224,7 @@ The **Fetch API** is JavaScript's modern way to make HTTP requests. It replaced ### Basic GET Request -The simplest fetch call retrieves data from a URL: +The simplest fetch call retrieves data from a URL. By default, `fetch()` uses the **GET** method, so you don't need to specify it: ```javascript // Basic fetch - returns a Promise @@ -256,9 +256,9 @@ responsePromise.then(response => { }) ``` -### The Response Object +### Understanding the Response Object -When `fetch()` resolves, you get a `Response` object with useful properties: +When `fetch()` resolves, you get a **[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)** object. This object contains everything about the server's reply — status codes, headers, and methods to read the body: ```javascript const response = await fetch('https://api.example.com/users/1') @@ -288,7 +288,7 @@ response.arrayBuffer() // Parse body as ArrayBuffer ### Reading JSON Data -Most APIs return JSON. Use `.json()` to parse it: +Most modern APIs return data in **[JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)** format. The Response object has a built-in `.json()` method that parses the body and returns a JavaScript object: ```javascript async function getUser(id) { @@ -302,9 +302,11 @@ async function getUser(id) { } ``` -### Making POST Requests +### Sending Data with POST -To send data to a server, use the POST method with a request body: +So far we've only *retrieved* data. But what about *sending* data — like creating a user account or submitting a form? + +That's where **[POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)** comes in. It's the HTTP method that tells the server "I'm sending you data to create something new." To make a POST request, you need to specify the method, set a `Content-Type` header, and include your data in the body: ```javascript async function createUser(userData) { @@ -330,7 +332,7 @@ console.log(user.id) // New user's ID from server ### Setting Headers -Headers provide additional information about the request: +**[HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)** are metadata you send with your request — things like authentication tokens, content types, and caching instructions. You pass them as an object in the `headers` option: ```javascript const response = await fetch('https://api.example.com/data', { @@ -393,9 +395,13 @@ const response = await fetch(`/api/search?${params}`) --- -## Error Handling: The Critical Distinction +## The #1 Fetch Mistake + +Here's a mistake almost every developer makes when learning fetch: + +> "I wrapped my fetch in try/catch, so I'm handling all errors... right?" -This is the **most important section** of this guide. Many developers get this wrong, leading to bugs that are hard to track down. +**Wrong.** The problem? `fetch()` only throws an error when the *network* fails — not when the server returns a 404 or 500. A "Page Not Found" response is still a successful network request from fetch's perspective! ### Two Types of "Errors" @@ -425,7 +431,9 @@ When working with `fetch()`, there are two completely different types of failure **The Trap:** `fetch()` only rejects its Promise for network errors. An HTTP 404 or 500 response is still a "successful" fetch — the network request completed! You must check `response.ok` to detect HTTP errors. </Warning> -### The Wrong Way +### The Mistake: Only Catching Network Errors + +This code looks fine, but it has a subtle bug — HTTP errors like 404 or 500 slip right through the catch block: ```javascript // ❌ WRONG - This misses HTTP errors! @@ -440,7 +448,9 @@ try { } ``` -### The Right Way +### The Fix: Always Check response.ok + +The solution is simple: check `response.ok` before assuming success. This property is `true` for status codes 200-299 and `false` for everything else: ```javascript // ✓ CORRECT - Check response.ok @@ -465,9 +475,9 @@ async function fetchUser(id) { } ``` -### A Reusable Fetch Wrapper +### Building a Reusable Fetch Helper -Here's a pattern you can use in real projects: +Here's a pattern you can use in real projects — a wrapper function that handles the `response.ok` check for you: ```javascript async function fetchJSON(url, options = {}) { @@ -512,9 +522,9 @@ try { --- -## Modern Patterns with async/await +## Using async/await with Fetch -While `fetch()` works with `.then()` chains, modern JavaScript uses `async/await` for cleaner code. +The examples above use `.then()` chains, but modern JavaScript has a cleaner syntax: `async/await`. If you're not familiar with it, check out our [async/await concept](/concepts/async-await) first — it'll make your fetch code much easier to read. ### Basic async/await Pattern @@ -612,7 +622,7 @@ if (result.loading) { --- -## Cancelling Requests with AbortController +## How to Cancel Requests The **[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)** API lets you cancel in-flight fetch requests. This is essential for: From 7d138223b6ef4b7d7bac161f05686e9a50da8db6 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:05:46 -0300 Subject: [PATCH 030/128] chore: move CLAUDE.md to .claude folder and ignore skills - Move project context to .claude/CLAUDE.md - Add .claude/skills/ to .gitignore for local-only skills --- CLAUDE.md => .claude/CLAUDE.md | 3 +++ .gitignore | 3 +++ 2 files changed, 6 insertions(+) rename CLAUDE.md => .claude/CLAUDE.md (98%) diff --git a/CLAUDE.md b/.claude/CLAUDE.md similarity index 98% rename from CLAUDE.md rename to .claude/CLAUDE.md index dbd9b85f..d844d123 100644 --- a/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -17,6 +17,9 @@ The project was recognized by GitHub as one of the **top open source projects of ``` 33-js-concepts/ +├── .claude/ # OpenCode/Claude configuration +│ ├── CLAUDE.md # Project context and guidelines +│ └── skills/ # Custom skills for content creation ├── docs/ # Mintlify documentation site │ ├── docs.json # Mintlify configuration │ ├── index.mdx # Homepage diff --git a/.gitignore b/.gitignore index 39ecea19..a5dd3a59 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ typings/ # webstore IDE created directory .idea + +# Claude/OpenCode skills (local only) +.claude/skills/ From aa846a5e8ad34dabbb25a131354adf79f59a1152 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:07:16 -0300 Subject: [PATCH 031/128] feat(this-call-apply-bind): expand concept with comprehensive guide and 73 tests --- docs/concepts/this-call-apply-bind.mdx | 1391 ++++++++++++++++- .../this-call-apply-bind.test.js | 994 ++++++++++++ 2 files changed, 2353 insertions(+), 32 deletions(-) create mode 100644 tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx index d5f5a589..63315fef 100644 --- a/docs/concepts/this-call-apply-bind.mdx +++ b/docs/concepts/this-call-apply-bind.mdx @@ -1,23 +1,1352 @@ --- title: "this, call, apply and bind" -description: "Understanding context and function binding in JavaScript" +description: "How JavaScript decides what 'this' refers to — and how you take control" --- -## Overview +In our journey through JavaScript, we've already explored [Scope & Closures](/concepts/scope-and-closures) — how variables are accessed and how functions remember their environment. Now it's time to tackle one of JavaScript's most confusing (and powerful) features: the `this` keyword. -The `this` keyword in JavaScript refers to the object that is executing the current function. Its value depends on how the function is called. The `call`, `apply`, and `bind` methods allow you to explicitly set the value of `this`. +You've probably seen `this` pop up everywhere — in object methods, class constructors, event handlers, and callbacks. Sometimes it works exactly as you'd expect. Other times? It points to something completely unexpected and your code breaks in mysterious ways. + +The truth is, `this` isn't magical or random. It follows specific rules. Once you understand these rules, you'll never be confused by `this` again. And with `call`, `apply`, and `bind`, you'll learn how to take complete control over what `this` refers to. + +Let's start with an analogy that makes it all click. + +--- + +## The Pronoun "I": A Real-World Analogy + +Think about the word "I" in everyday conversation. It's a simple word, but its meaning changes completely depending on **who is speaking**: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ THE PRONOUN "I" │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Alice says: "I am a developer" │ +│ ↓ │ +│ "I" = Alice │ +│ │ +│ Bob says: "I am a designer" │ +│ ↓ │ +│ "I" = Bob │ +│ │ +│ The SAME word "I" refers to DIFFERENT people │ +│ depending on WHO is speaking! │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +This is exactly how `this` works in JavaScript! The keyword `this` is like the pronoun "I" — it refers to different objects depending on **who is "speaking"** (which object is calling the function). + +```javascript +const alice = { + name: "Alice", + introduce() { + console.log("I am " + this.name); // "I" = this = alice + } +}; + +const bob = { + name: "Bob", + introduce() { + console.log("I am " + this.name); // "I" = this = bob + } +}; + +alice.introduce(); // "I am Alice" +bob.introduce(); // "I am Bob" +``` + +But here's where JavaScript gets interesting. What if Alice could make Bob say her words? Like a ventriloquist making a puppet speak? + +```javascript +// Alice borrows Bob's voice to introduce herself +bob.introduce.call(alice); // "I am Alice" (Bob's function, Alice's this) +``` + +That's what `call`, `apply`, and `bind` do — they let you control **who "I" refers to**, regardless of which function is speaking. + +<Info> +**What you'll learn in this guide:** +- What `this` actually is and why it's determined at call time +- The 5 binding rules that determine `this` (in priority order) +- How `call()`, `apply()`, and `bind()` work and when to use each +- Arrow functions and why they handle `this` differently +- Common pitfalls and how to avoid them +</Info> + +--- + +## What is `this`? + +The `this` keyword is a special identifier that JavaScript automatically defines in every function. It refers to the **execution context** — essentially, the object that the function is operating on. + +### The Key Insight: Call-Time Binding + +Here's what makes JavaScript different from many other languages: + +> **`this` is determined when the function is CALLED, not when it's defined.** + +This is called **dynamic binding**, and it's both powerful and confusing. The same function can have different `this` values depending on how you call it: + +```javascript +function showThis() { + return this; +} + +const obj = { showThis }; + +// Same function, different this values: +showThis(); // undefined (strict mode) or globalThis +obj.showThis(); // obj (the object before the dot) +showThis.call({}); // {} (explicitly specified) +``` + +### Why Does JavaScript Work This Way? + +This design allows for incredible flexibility: + +1. **Method sharing**: Multiple objects can share the same function +2. **Dynamic behavior**: Functions can work with any object that has the right properties +3. **Borrowing methods**: You can use methods from one object on another + +```javascript +// One function, many objects +function greet() { + return `Hello, I'm ${this.name}!`; +} + +const alice = { name: "Alice", greet }; +const bob = { name: "Bob", greet }; +const charlie = { name: "Charlie", greet }; + +alice.greet(); // "Hello, I'm Alice!" +bob.greet(); // "Hello, I'm Bob!" +charlie.greet(); // "Hello, I'm Charlie!" +``` + +The trade-off? You need to understand the rules that determine `this`. Let's dive in. + +--- + +## The 5 Binding Rules (Priority Order) + +When JavaScript needs to figure out what `this` refers to, it follows these rules **in order of priority**. Higher priority rules override lower ones. + +``` +BINDING RULES (Highest to Lowest Priority) +┌─────────────────────────────────────────┐ +│ 1. new Binding (Highest) │ +├─────────────────────────────────────────┤ +│ 2. Explicit Binding (call/apply/ │ +│ bind) │ +├─────────────────────────────────────────┤ +│ 3. Implicit Binding (method call) │ +├─────────────────────────────────────────┤ +│ 4. Default Binding (plain call) │ +├─────────────────────────────────────────┤ +│ 5. Arrow Functions (lexical) │ +│ (Special case - no own this) │ +└─────────────────────────────────────────┘ +``` + +<Note> +Arrow functions are listed last not because they're lowest priority, but because they work differently — they don't have their own `this` at all. We'll cover them in detail. +</Note> + +--- + +### Rule 1: `new` Binding (Highest Priority) + +When a function is called with the `new` keyword, `this` is set to a **brand new object** that's automatically created. + +```javascript +class Person { + constructor(name) { + // 'this' is the new object being created + this.name = name; + this.greet = function() { + return `Hi, I'm ${this.name}`; + }; + } +} + +const alice = new Person("Alice"); +console.log(alice.name); // "Alice" +console.log(alice.greet()); // "Hi, I'm Alice" +``` + +#### What `new` Does Under the Hood + +When you call `new Person("Alice")`, JavaScript performs these 4 steps: + +<Steps> + <Step title="Create an empty object"> + A brand new empty object is created: `{}` + </Step> + + <Step title="Link the prototype"> + The new object's internal `[[Prototype]]` is set to the constructor's `prototype` property. + + ```javascript + // Conceptually: + newObject.__proto__ = Person.prototype; + ``` + </Step> + + <Step title="Bind this and execute"> + The constructor function is called with `this` bound to the new object. This is where your constructor code runs. + + ```javascript + // Conceptually: + Person.call(newObject, "Alice"); + ``` + </Step> + + <Step title="Return the object"> + If the constructor doesn't explicitly return an object, the new object is returned automatically. + + ```javascript + // Conceptually: + return newObject; + ``` + </Step> +</Steps> + +Here's a simplified implementation of what `new` does: + +```javascript +// What 'new' does behind the scenes +function simulateNew(Constructor, ...args) { + // Step 1: Create empty object + const newObject = {}; + + // Step 2: Link prototype + Object.setPrototypeOf(newObject, Constructor.prototype); + + // Step 3: Bind this and execute + const result = Constructor.apply(newObject, args); + + // Step 4: Return object (unless constructor returns an object) + return result instanceof Object ? result : newObject; +} + +// These are equivalent: +const alice1 = new Person("Alice"); +const alice2 = simulateNew(Person, "Alice"); +``` + +#### ES6 Classes: The Modern Syntax + +With ES6 classes, the syntax is cleaner but the behavior is identical: + +```javascript +class Rectangle { + constructor(width, height) { + this.width = width; // 'this' = new Rectangle instance + this.height = height; + } + + getArea() { + return this.width * this.height; // 'this' = the instance + } +} + +const rect = new Rectangle(10, 5); +console.log(rect.getArea()); // 50 +``` + +<Tip> +For more on constructors, the `new` keyword, and `instanceof`, see the [new, Constructor, instanceof](/concepts/new-constructor) concept page. +</Tip> + +--- + +### Rule 2: Explicit Binding (`call`, `apply`, `bind`) + +You can explicitly specify what `this` should be using `call()`, `apply()`, or `bind()`. This overrides implicit and default binding. + +```javascript +function introduce() { + return `I'm ${this.name}, a ${this.role}`; +} + +const alice = { name: "Alice", role: "developer" }; +const bob = { name: "Bob", role: "designer" }; + +// Explicitly set 'this' to alice +introduce.call(alice); // "I'm Alice, a developer" + +// Explicitly set 'this' to bob +introduce.call(bob); // "I'm Bob, a designer" +``` + +We'll cover `call`, `apply`, and `bind` in detail in the next section. For now, just know that explicit binding has higher priority than implicit binding: + +```javascript +const alice = { + name: "Alice", + greet() { + return `Hi, I'm ${this.name}`; + } +}; + +const bob = { name: "Bob" }; + +// Even though we're calling alice.greet(), we can override 'this' +alice.greet.call(bob); // "Hi, I'm Bob" (explicit wins!) +``` + +--- + +### Rule 3: Implicit Binding (Method Call) + +When a function is called as a **method of an object** (using dot notation), `this` is set to the object **before the dot**. + +```javascript +const user = { + name: "Alice", + greet() { + return `Hello, I'm ${this.name}`; + } +}; + +// The object before the dot becomes 'this' +user.greet(); // "Hello, I'm Alice" (this = user) +``` + +#### The "Left of the Dot" Rule + +A simple way to remember: **look left of the dot** when the function is called. + +```javascript +const company = { + name: "TechCorp", + department: { + name: "Engineering", + getName() { + return this.name; + } + } +}; + +// What's left of the dot at call time? +company.department.getName(); // "Engineering" (this = department) +``` + +<Warning> +**Common trap**: It's the object immediately before the dot that matters, not the outermost object. In the example above, `this` is `department`, not `company`. +</Warning> + +#### The Implicit Binding Gotcha: Lost Context + +This is one of the most common sources of bugs. When you **extract a method** from an object, it loses its implicit binding: + +```javascript +const user = { + name: "Alice", + greet() { + return `Hello, I'm ${this.name}`; + } +}; + +// This works +user.greet(); // "Hello, I'm Alice" + +// But extracting the method loses 'this'! +const greetFn = user.greet; +greetFn(); // "Hello, I'm undefined" (strict mode: this = undefined) +``` + +Why? Because `greetFn()` is a plain function call — there's no dot, so implicit binding doesn't apply. We fall through to default binding. + +``` +IMPLICIT BINDING LOST +┌─────────────────────────────────────────────────────────────┐ +│ │ +│ user.greet() │ +│ ↑ │ +│ └── Object before dot → this = user ✓ │ +│ │ +│ const greetFn = user.greet; │ +│ greetFn() │ +│ ↑ │ +│ └── No dot! → Default binding → this = undefined ✗ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +This happens constantly with: +- Callbacks: `setTimeout(user.greet, 1000)` +- Event handlers: `button.addEventListener('click', user.greet)` +- Array methods: `[1,2,3].forEach(user.process)` + +We'll cover solutions in the "Gotchas" section. + +--- + +### Rule 4: Default Binding (Plain Function Call) + +When a function is called without any of the above conditions, **default binding** applies. + +In **strict mode** (which you should always use): `this` is `undefined`. + +In non-strict mode: `this` is the global object (`window` in browsers, `global` in Node.js). + +```javascript +"use strict"; + +function showThis() { + return this; +} + +showThis(); // undefined (strict mode) +``` + +```javascript +// Without strict mode (not recommended) +function showThis() { + return this; +} + +showThis(); // window (in browser) or global (in Node.js) +``` + +<Warning> +**Always use strict mode!** Non-strict mode's default binding to `globalThis` is dangerous — it can accidentally create or modify global variables, leading to hard-to-find bugs. + +ES6 modules and classes are automatically in strict mode. +</Warning> + +#### When Default Binding Applies + +Default binding kicks in when: + +1. **Plain function call**: `myFunction()` +2. **IIFE**: `(function() { ... })()` +3. **Callback without binding**: `setTimeout(function() { ... }, 100)` + +```javascript +"use strict"; + +// All of these use default binding (this = undefined) +function regularFunction() { + return this; +} + +regularFunction(); // undefined + +(function() { + return this; // undefined +})(); + +setTimeout(function() { + console.log(this); // undefined +}, 100); +``` + +--- + +### Rule 5: Arrow Functions (Lexical `this`) + +Arrow functions are **special**. They don't have their own `this` binding at all. Instead, they **inherit `this`** from their enclosing scope at the time they're defined. + +```javascript +const user = { + name: "Alice", + + // Regular function: 'this' is determined by how it's called + regularGreet: function() { + return `Hi, I'm ${this.name}`; + }, + + // Arrow function: 'this' is inherited from where it's defined + arrowGreet: () => { + return `Hi, I'm ${this.name}`; + } +}; + +user.regularGreet(); // "Hi, I'm Alice" (this = user) +user.arrowGreet(); // "Hi, I'm undefined" (this = enclosing scope, not user!) +``` + +Wait, why is `arrowGreet` showing `undefined`? Because the arrow function was defined in the object literal, and the enclosing scope at that point is the module/global scope — not the `user` object. + +#### Where Arrow Functions Shine + +Arrow functions are perfect for **callbacks** where you want to preserve the outer `this`: + +```javascript +class Counter { + constructor() { + this.count = 0; + } + + // Problem: regular function loses 'this' in callback + startBroken() { + setInterval(function() { + this.count++; // ERROR: 'this' is undefined! + console.log(this.count); + }, 1000); + } + + // Solution: arrow function preserves 'this' + startFixed() { + setInterval(() => { + this.count++; // Works! 'this' is the Counter instance + console.log(this.count); + }, 1000); + } +} +``` + +#### Arrow Functions Cannot Be Rebound + +You cannot change an arrow function's `this` using `call`, `apply`, or `bind`: + +```javascript +const arrowFn = () => this; + +const obj = { name: "Object" }; + +// These all return the same thing - the lexical 'this' +arrowFn(); // lexical this +arrowFn.call(obj); // lexical this (call is ignored!) +arrowFn.apply(obj); // lexical this (apply is ignored!) +arrowFn.bind(obj)(); // lexical this (bind is ignored!) +``` + +#### Arrow Functions as Class Fields + +A common modern pattern is using arrow functions as class methods: + +```javascript +class Button { + constructor(label) { + this.label = label; + } + + // Arrow function as class field - 'this' is always the instance + handleClick = () => { + console.log(`Button "${this.label}" clicked`); + } +} + +const btn = new Button("Submit"); + +// Works even when extracted! +const handler = btn.handleClick; +handler(); // "Button "Submit" clicked" ✓ + +// Works in event listeners! +document.querySelector('button').addEventListener('click', btn.handleClick); +``` + +<Tip> +This pattern is widely used in React class components and other UI frameworks to ensure event handlers always have the correct `this`. +</Tip> + +--- + +## The Decision Flowchart + +When you need to figure out what `this` is, follow this flowchart: + +``` + ┌─────────────────────────┐ + │ Is it an arrow │ + │ function? │ + └───────────┬─────────────┘ + │ │ + YES ◄──┘ └──► NO + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────────┐ + │ this = enclosing│ │ Was it called with │ + │ scope's this │ │ 'new'? │ + │ (DONE) │ └──────────┬──────────┘ + └─────────────────┘ │ │ + YES ◄─┘ └──► NO + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────────┐ + │ this = new │ │ Was call/apply/bind │ + │ object │ │ used? │ + │ (DONE) │ └──────────┬──────────┘ + └─────────────────┘ │ │ + YES ◄──┘ └──► NO + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────────┐ + │ this = specified│ │ Was it called as │ + │ object │ │ obj.method()? │ + │ (DONE) │ └──────────┬──────────┘ + └─────────────────┘ │ │ + YES ◄──┘ └──► NO + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ this = obj │ │ Default binding:│ + │ (left of dot) │ │ this = undefined│ + │ (DONE) │ │ (strict mode) │ + └─────────────────┘ └─────────────────┘ +``` + +--- + +## Deep Dive: `call()`, `apply()`, and `bind()` + +These three methods give you explicit control over `this`. They're built into every function in JavaScript. + +### Quick Comparison + +| Method | Invokes Function? | Arguments | Returns | +|--------|-------------------|-----------|---------| +| `call()` | Yes, immediately | Individual: `call(this, a, b, c)` | Function result | +| `apply()` | Yes, immediately | Array: `apply(this, [a, b, c])` | Function result | +| `bind()` | No | Individual: `bind(this, a, b)` | New function | + +**Memory trick:** +- **C**all = **C**ommas (arguments separated by commas) +- **A**pply = **A**rray (arguments in an array) +- **B**ind = **B**ack later (returns a function for later use) + +--- + +### `call()` — Call with This + +The `call()` method calls a function with a specified `this` value and arguments provided **individually**. + +**Syntax:** +```javascript +func.call(thisArg, arg1, arg2, ...) +``` + +**Basic example:** +```javascript +function greet(greeting, punctuation) { + return `${greeting}, I'm ${this.name}${punctuation}`; +} + +const alice = { name: "Alice" }; +const bob = { name: "Bob" }; + +greet.call(alice, "Hello", "!"); // "Hello, I'm Alice!" +greet.call(bob, "Hi", "..."); // "Hi, I'm Bob..." +``` + +#### Use Case: Method Borrowing + +`call()` is perfect for borrowing methods from one object to use on another: + +```javascript +const arrayLike = { + 0: "a", + 1: "b", + 2: "c", + length: 3 +}; + +// arrayLike doesn't have array methods, but we can borrow them! +const result = Array.prototype.slice.call(arrayLike); +console.log(result); // ["a", "b", "c"] + +const joined = Array.prototype.join.call(arrayLike, "-"); +console.log(joined); // "a-b-c" +``` + +#### Use Case: Calling Parent Methods + +```javascript +class Animal { + constructor(name) { + this.name = name; + } + + speak() { + return `${this.name} makes a sound`; + } +} + +class Dog extends Animal { + speak() { + // Call parent method with 'this' context + const parentSays = Animal.prototype.speak.call(this); + return `${parentSays}. ${this.name} barks!`; + } +} + +const dog = new Dog("Rex"); +dog.speak(); // "Rex makes a sound. Rex barks!" +``` + +--- + +### `apply()` — Apply with Array + +The `apply()` method is almost identical to `call()`, but arguments are passed as an **array** (or array-like object). + +**Syntax:** +```javascript +func.apply(thisArg, [arg1, arg2, ...]) +``` + +**Basic example:** +```javascript +function greet(greeting, punctuation) { + return `${greeting}, I'm ${this.name}${punctuation}`; +} + +const alice = { name: "Alice" }; + +// Same result as call(), but args in an array +greet.apply(alice, ["Hello", "!"]); // "Hello, I'm Alice!" +``` + +#### Classic Use Case: Finding Max/Min + +Before ES6, `apply()` was the way to use `Math.max()` with an array: + +```javascript +const numbers = [5, 2, 9, 1, 7]; + +// Old way with apply +const max = Math.max.apply(null, numbers); // 9 +const min = Math.min.apply(null, numbers); // 1 +``` + +<Tip> +**Modern alternative:** Use the spread operator instead! + +```javascript +const numbers = [5, 2, 9, 1, 7]; +const max = Math.max(...numbers); // 9 +const min = Math.min(...numbers); // 1 +``` + +The spread syntax is cleaner and more readable. Use `apply()` mainly when you need to set `this` AND spread arguments. +</Tip> + +#### When Arguments Are Already an Array + +`apply()` shines when your arguments are already in array form: + +```javascript +function introduce(greeting, role, company) { + return `${greeting}! I'm ${this.name}, ${role} at ${company}.`; +} + +const alice = { name: "Alice" }; +const args = ["Hello", "engineer", "TechCorp"]; + +// When args are already an array, apply is natural +introduce.apply(alice, args); // "Hello! I'm Alice, engineer at TechCorp." + +// With call, you'd need to spread +introduce.call(alice, ...args); // Same result +``` + +--- + +### `bind()` — Bind for Later + +The `bind()` method is different from `call()` and `apply()`. It doesn't call the function immediately. Instead, it returns a **new function** with `this` permanently bound. + +**Syntax:** +```javascript +const boundFunc = func.bind(thisArg, arg1, arg2, ...) +``` + +**Basic example:** +```javascript +function greet() { + return `Hello, I'm ${this.name}`; +} + +const alice = { name: "Alice" }; + +// bind() returns a NEW function +const greetAlice = greet.bind(alice); + +// Call it whenever you want +greetAlice(); // "Hello, I'm Alice" +greetAlice(); // "Hello, I'm Alice" (still works!) +``` + +#### Key Characteristic: Permanent Binding + +Once bound, the `this` value cannot be changed — not even with `call()` or `apply()`: + +```javascript +function showThis() { + return this.name; +} + +const alice = { name: "Alice" }; +const bob = { name: "Bob" }; + +const boundToAlice = showThis.bind(alice); + +boundToAlice(); // "Alice" +boundToAlice.call(bob); // "Alice" (call ignored!) +boundToAlice.apply(bob); // "Alice" (apply ignored!) +boundToAlice.bind(bob)(); // "Alice" (bind ignored!) +``` + +#### Use Case: Event Handlers + +This is one of the most common uses of `bind()`: + +```javascript +class Toggle { + constructor() { + this.isOn = false; + + // Without bind, 'this' would be the button element + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.isOn = !this.isOn; + console.log(`Toggle is ${this.isOn ? 'ON' : 'OFF'}`); + } + + attachTo(button) { + button.addEventListener('click', this.handleClick); + } +} +``` + +#### Use Case: setTimeout and setInterval + +```javascript +class Countdown { + constructor(start) { + this.count = start; + } + + start() { + // Without bind, 'this' would be undefined in the callback + setInterval(this.tick.bind(this), 1000); + } + + tick() { + console.log(this.count--); + } +} + +const countdown = new Countdown(10); +countdown.start(); // 10, 9, 8, 7... +``` + +#### Use Case: Partial Application + +`bind()` can also pre-fill arguments, creating a specialized version of a function: + +```javascript +function multiply(a, b) { + return a * b; +} + +// Create specialized functions +const double = multiply.bind(null, 2); // 'a' is always 2 +const triple = multiply.bind(null, 3); // 'a' is always 3 + +double(5); // 10 (2 * 5) +triple(5); // 15 (3 * 5) +double(7); // 14 (2 * 7) +``` + +This technique is called **partial application** — you're partially applying arguments to create a more specific function. + +```javascript +function greet(greeting, name) { + return `${greeting}, ${name}!`; +} + +// Partial application: pre-fill the greeting +const sayHello = greet.bind(null, "Hello"); +const sayGoodbye = greet.bind(null, "Goodbye"); + +sayHello("Alice"); // "Hello, Alice!" +sayHello("Bob"); // "Hello, Bob!" +sayGoodbye("Alice"); // "Goodbye, Alice!" +``` + +--- + +## Common Patterns & Use Cases + +### Pattern 1: Method Borrowing + +Use array methods on array-like objects: + +```javascript +// Arguments object (old-school, but still seen in legacy code) +function sum() { + // 'arguments' is array-like but not an array + return Array.prototype.reduce.call( + arguments, + (total, n) => total + n, + 0 + ); +} + +sum(1, 2, 3, 4); // 10 + +// NodeList from DOM +const divs = document.querySelectorAll('div'); // NodeList, not Array +const texts = Array.prototype.map.call(divs, div => div.textContent); + +// Modern alternative: Array.from() +const textsModern = Array.from(divs).map(div => div.textContent); +// Or spread +const textsSpread = [...divs].map(div => div.textContent); +``` + +### Pattern 2: Preserving Context in Classes + +The three main approaches to ensure `this` is correct in class methods: + +```javascript +class Player { + constructor(name) { + this.name = name; + this.score = 0; + + // Approach 1: Bind in constructor + this.incrementBound = this.incrementBound.bind(this); + } + + // Regular method - needs binding when used as callback + incrementBound() { + this.score++; + return this.score; + } + + // Approach 2: Arrow function class field + incrementArrow = () => { + this.score++; + return this.score; + } + + // Approach 3: Bind at call site (inline) + regularIncrement() { + this.score++; + return this.score; + } +} + +const player = new Player("Alice"); + +// All these work correctly: +setTimeout(player.incrementBound, 100); // Approach 1 +setTimeout(player.incrementArrow, 100); // Approach 2 +setTimeout(player.regularIncrement.bind(player), 100); // Approach 3 +setTimeout(() => player.regularIncrement(), 100); // Approach 3 alt +``` + +<Tip> +**Which approach is best?** + +- **Arrow class fields** (Approach 2) are the cleanest for most cases +- **Bind in constructor** (Approach 1) is useful when you need the method to also work as a regular method +- **Inline bind/arrow** (Approach 3) is fine for one-off uses but creates new functions each render in React +</Tip> + +### Pattern 3: Partial Application for Reusable Functions + +```javascript +// Generic logging function +function log(level, timestamp, message) { + console.log(`[${level}] ${timestamp}: ${message}`); +} + +// Create specialized loggers +const logError = log.bind(null, "ERROR"); +const logWarning = log.bind(null, "WARNING"); +const logInfo = log.bind(null, "INFO"); + +const now = new Date().toISOString(); + +logError(now, "Database connection failed"); +// [ERROR] 2024-01-15T10:30:00.000Z: Database connection failed + +logInfo(now, "Server started"); +// [INFO] 2024-01-15T10:30:00.000Z: Server started +``` + +--- + +## The Gotchas: Where `this` Goes Wrong + +<AccordionGroup> + <Accordion title="Gotcha 1: Lost Context in Callbacks"> + **The problem:** + ```javascript + class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setInterval(function() { + this.seconds++; // ERROR: this is undefined! + console.log(this.seconds); + }, 1000); + } + } + ``` + + **Why it happens:** The callback function uses default binding, so `this` is `undefined` in strict mode. + + **Solutions:** + ```javascript + // Solution 1: Arrow function + start() { + setInterval(() => { + this.seconds++; // ✓ Arrow inherits 'this' + }, 1000); + } + + // Solution 2: bind() + start() { + setInterval(function() { + this.seconds++; // ✓ Bound to Timer instance + }.bind(this), 1000); + } + + // Solution 3: Store reference (old-school) + start() { + const self = this; + setInterval(function() { + self.seconds++; // ✓ Using closure + }, 1000); + } + ``` + </Accordion> + + <Accordion title="Gotcha 2: Extracting Methods from Objects"> + **The problem:** + ```javascript + const user = { + name: "Alice", + greet() { + return `Hi, I'm ${this.name}`; + } + }; + + const greet = user.greet; + greet(); // "Hi, I'm undefined" + ``` + + **Why it happens:** Assigning the method to a variable loses the implicit binding. + + **Solutions:** + ```javascript + // Solution 1: Keep as method call + user.greet(); // ✓ "Hi, I'm Alice" + + // Solution 2: Bind when extracting + const greet = user.greet.bind(user); + greet(); // ✓ "Hi, I'm Alice" + + // Solution 3: Wrapper function + const greet = () => user.greet(); + greet(); // ✓ "Hi, I'm Alice" + ``` + </Accordion> + + <Accordion title="Gotcha 3: Nested Functions Inside Methods"> + **The problem:** + ```javascript + const calculator = { + value: 0, + + add(numbers) { + numbers.forEach(function(n) { + this.value += n; // ERROR: this is undefined! + }); + return this.value; + } + }; + ``` + + **Why it happens:** The inner function has its own `this` (default binding), it doesn't inherit from `add()`. + + **Solutions:** + ```javascript + // Solution 1: Arrow function (recommended) + add(numbers) { + numbers.forEach((n) => { + this.value += n; // ✓ Arrow inherits 'this' + }); + return this.value; + } + + // Solution 2: Use thisArg parameter + add(numbers) { + numbers.forEach(function(n) { + this.value += n; // ✓ 'this' passed as second arg + }, this); + return this.value; + } + + // Solution 3: bind() + add(numbers) { + numbers.forEach(function(n) { + this.value += n; // ✓ Bound to calculator + }.bind(this)); + return this.value; + } + ``` + </Accordion> + + <Accordion title="Gotcha 4: Arrow Functions as Methods"> + **The problem:** + ```javascript + const user = { + name: "Alice", + greet: () => { + return `Hi, I'm ${this.name}`; // 'this' is NOT user! + } + }; + + user.greet(); // "Hi, I'm undefined" + ``` + + **Why it happens:** Arrow functions don't have their own `this`. The `this` here is from the surrounding scope (module/global), not `user`. + + **Solution:** Use regular functions for object methods: + ```javascript + const user = { + name: "Alice", + greet() { // Shorthand method syntax + return `Hi, I'm ${this.name}`; // ✓ this = user + } + }; + + user.greet(); // "Hi, I'm Alice" + ``` + </Accordion> +</AccordionGroup> + +--- + +## Arrow Functions: The Modern Solution + +Arrow functions were introduced in ES6 partly to solve `this` confusion. They work fundamentally differently. + +### How Arrow Functions Handle `this` + +1. **No own `this`**: Arrow functions don't create their own `this` binding +2. **Lexical inheritance**: They use `this` from the enclosing scope +3. **Permanent**: Cannot be changed by `call`, `apply`, or `bind` + +```javascript +const obj = { + name: "Object", + + regularMethod: function() { + console.log("Regular:", this.name); // "Object" + + // Nested regular function - loses 'this' + function inner() { + console.log("Inner regular:", this); // undefined + } + inner(); + + // Nested arrow function - keeps 'this' + const innerArrow = () => { + console.log("Inner arrow:", this.name); // "Object" + }; + innerArrow(); + } +}; +``` + +### When to Use Arrow Functions vs Regular Functions + +| Use Case | Arrow Function | Regular Function | +|----------|---------------|------------------| +| Object methods | ❌ No | ✅ Yes | +| Class methods (in prototype) | ❌ No | ✅ Yes | +| Callbacks needing outer `this` | ✅ Yes | ❌ No (needs bind) | +| Event handlers in classes | ✅ Yes (as class fields) | ⚠️ Needs binding | +| Functions needing own `this` | ❌ No | ✅ Yes | +| Constructor functions | ❌ No (can't use `new`) | ✅ Yes | +| Methods using `arguments` | ❌ No (no `arguments`) | ✅ Yes | + +### Arrow Functions as Class Fields + +This is the most common pattern in modern JavaScript: + +```javascript +class SearchBox { + constructor(element) { + this.element = element; + this.query = ""; + + // Attach event listener - arrow function ensures correct 'this' + this.element.addEventListener('input', this.handleInput); + } + + // Arrow function as class field + handleInput = (event) => { + this.query = event.target.value; // 'this' is always SearchBox instance + this.performSearch(); + } + + performSearch = () => { + console.log(`Searching for: ${this.query}`); + } +} +``` + +### Limitations of Arrow Functions + +```javascript +// 1. Cannot be used with 'new' +const ArrowClass = () => {}; +new ArrowClass(); // TypeError: ArrowClass is not a constructor + +// 2. No 'arguments' object +const arrow = () => { + console.log(arguments); // ReferenceError: arguments is not defined +}; + +// Use rest parameters instead +const arrowWithRest = (...args) => { + console.log(args); // Works! +}; + +// 3. No 'super' in standalone arrows (works in class methods) + +// 4. Cannot be used as generators +const arrowGen = *() => {}; // SyntaxError +``` + +--- + +## Test Your Knowledge + +Try to figure out what `this` refers to in each example before revealing the answer. + +<AccordionGroup> + <Accordion title="Question 1: What does this log?"> + ```javascript + const user = { + name: "Alice", + greet() { + return `Hi, I'm ${this.name}`; + } + }; + + const greet = user.greet; + console.log(greet()); + ``` + + **Answer:** `"Hi, I'm undefined"` + + When `greet` is assigned to a variable and called without an object, implicit binding is lost. Default binding applies, and in strict mode `this` is `undefined`. + </Accordion> + + <Accordion title="Question 2: What does this log?"> + ```javascript + class Counter { + count = 0; + + increment = () => { + this.count++; + } + } + + const counter = new Counter(); + const inc = counter.increment; + inc(); + inc(); + console.log(counter.count); + ``` + + **Answer:** `2` + + Arrow function class fields have lexical `this` bound to the instance. Even when extracted, `this` still refers to `counter`. + </Accordion> + + <Accordion title="Question 3: What does this log?"> + ```javascript + function greet() { + return `Hello, ${this.name}!`; + } + + const alice = { name: "Alice" }; + const bob = { name: "Bob" }; + + const greetAlice = greet.bind(alice); + console.log(greetAlice.call(bob)); + ``` + + **Answer:** `"Hello, Alice!"` + + Once a function is bound with `bind()`, its `this` cannot be changed — not even with `call()`. The binding is permanent. + </Accordion> + + <Accordion title="Question 4: What does this log?"> + ```javascript + const obj = { + name: "Outer", + inner: { + name: "Inner", + getName() { + return this.name; + } + } + }; + + console.log(obj.inner.getName()); + ``` + + **Answer:** `"Inner"` + + With implicit binding, `this` is the object immediately to the left of the dot at call time. That's `obj.inner`, not `obj`. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Scope & Closures" icon="eye" href="/concepts/scope-and-closures"> + How variables are accessed — related to lexical this in arrow functions + </Card> + <Card title="new, Constructor, instanceof" icon="hammer" href="/concepts/new-constructor"> + How the new keyword creates objects and binds this + </Card> + <Card title="Factories and Classes" icon="industry" href="/concepts/factories-classes"> + Object creation patterns that rely on this binding + </Card> + <Card title="Prototype" icon="link" href="/concepts/prototype"> + Understanding the prototype chain and method inheritance + </Card> +</CardGroup> + +--- ## Reference -<CardGroup cols={3}> +<CardGroup cols={2}> + <Card title="this — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this"> + Official MDN documentation on the this keyword + </Card> <Card title="call() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call"> - MDN documentation + MDN documentation for Function.prototype.call() </Card> <Card title="apply() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply"> - MDN documentation + MDN documentation for Function.prototype.apply() </Card> - <Card title="bind() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind"> - MDN documentation + <Card title="bind() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind"> + MDN documentation for Function.prototype.bind() </Card> </CardGroup> @@ -25,40 +1354,38 @@ The `this` keyword in JavaScript refers to the object that is executing the curr <CardGroup cols={2}> <Card title="Grokking call(), apply() and bind() methods in JavaScript" icon="newspaper" href="https://levelup.gitconnected.com/grokking-call-apply-and-bind-methods-in-javascript-392351a4be8b"> - By Aniket Kudale + Aniket Kudale's comprehensive guide to understanding these three methods with clear examples. </Card> <Card title="Javascript: call(), apply() and bind()" icon="newspaper" href="https://medium.com/@omergoldberg/javascript-call-apply-and-bind-e5c27301f7bb"> - By Omer Goldberg + Omer Goldberg's practical walkthrough of call, apply, and bind with real-world scenarios. + </Card> + <Card title="Let me explain to you what is this (JavaScript)" icon="newspaper" href="https://dev.to/ycmjason/let-me-explain-to-you-what-is-this-javascript-44ja"> + Jason Yu's friendly explanation of the this keyword with clear visual examples. + </Card> + <Card title="Understanding this binding in JavaScript" icon="newspaper" href="https://yasemincidem.medium.com/understanding-this-binding-in-javascript-86687397c76d"> + Yasemin Cidem's deep dive into all the ways this gets bound in JavaScript. + </Card> + <Card title="The Top 7 Tricky this Interview Questions" icon="newspaper" href="https://dmitripavlutin.com/javascript-this-interview-questions/"> + Dmitri Pavlutin's collection of challenging this-related questions to test your understanding. + </Card> + <Card title="How to understand the keyword this and context in JavaScript" icon="newspaper" href="https://www.freecodecamp.org/news/how-to-understand-the-keyword-this-and-context-in-javascript-cd624c6b74b8/"> + Lukas Gisder-Dube's freeCodeCamp article explaining this and execution context. </Card> </CardGroup> -- [JavaScript's Apply, Call, and Bind Methods are Essential for JavaScript Professionals — Richard Bovell](http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/) -- [The difference between call / apply / bind — Ivan Sifrim](https://medium.com/@ivansifrim/the-differences-between-call-apply-bind-276724bb825b) -- [What the hack is call, apply, bind in JavaScript — Ritik](https://dev.to/ritik_dev_js/what-the-hack-is-call-apply-bind-in-javascript-11ce) -- [Mastering 'this' in JavaScript: Callbacks and bind(), apply(), call() — Michelle Gienow](https://thenewstack.io/mastering-javascript-callbacks-bind-apply-call/) -- [JavaScript's apply, call, and bind explained by hosting a cookout — Kevin Kononenko](https://dev.to/kbk0125/javascripts-apply-call-and-bind-explained-by-hosting-a-cookout-32jo) -- [How AND When to use bind, call, and apply in Javascript — Eigen X](https://www.eigenx.com/blog/https/mediumcom/eigen-x/how-and-when-to-use-bind-call-and-apply-in-javascript-77b6f42898fb) -- [Let me explain to you what is `this`. (Javascript) — Jason Yu](https://dev.to/ycmjason/let-me-explain-to-you-what-is-this-javascript-44ja) -- [Understanding the "this" Keyword in JavaScript — Pavan](https://medium.com/quick-code/understanding-the-this-keyword-in-javascript-cb76d4c7c5e8) -- [How to understand the keyword this and context in JavaScript — Lukas Gisder-Dubé](https://medium.freecodecamp.org/how-to-understand-the-keyword-this-and-context-in-javascript-cd624c6b74b8) -- [What are call(), apply() and bind() in JavaScript — Amitav Mishra](https://jscurious.com/what-are-call-apply-and-bind-in-javascript/) -- [Understanding 'this' binding in JavaScript — Yasemin Cidem](https://yasemincidem.medium.com/understanding-this-binding-in-javascript-86687397c76d) -- [Top 7 tricky questions of 'this' keyword](https://dmitripavlutin.com/javascript-this-interview-questions/) - ## Videos <CardGroup cols={2}> <Card title="JavaScript call, apply and bind" icon="video" href="https://www.youtube.com/watch?v=c0mLRpw-9rI"> - By techsith + techsith's clear tutorial on the three function methods with practical examples. </Card> <Card title="JS Function Methods call(), apply(), and bind()" icon="video" href="https://www.youtube.com/watch?v=uBdH0iB1VDM"> - By Steve Griffith + Steve Griffith's in-depth explanation of how these methods work under the hood. + </Card> + <Card title="bind and this - Object Creation in JavaScript" icon="video" href="https://www.youtube.com/watch?v=GhbhD1HR5vk"> + Fun Fun Function's entertaining take on bind and this in object-oriented JavaScript. + </Card> + <Card title="Javascript Interview Questions (Call, Bind and Apply)" icon="video" href="https://www.youtube.com/watch?v=VkmUOktYDAU"> + Roadside Coder's interview-focused video covering common questions about these methods. </Card> </CardGroup> - -- [JavaScript Practical Applications of Call, Apply and Bind functions— techsith](https://www.youtube.com/watch?v=AYVYxezrMWA) -- [JavaScript (call, bind, apply) — curious aatma](https://www.youtube.com/watch?v=Uy0NOXLBraE) -- [Understanding Functions and 'this' In The World of ES2017 — Bryan Hughes](https://www.youtube.com/watch?v=AOSYY1_np_4) -- [bind and this - Object Creation in JavaScript - FunFunFunction](https://www.youtube.com/watch?v=GhbhD1HR5vk) -- [call, apply and bind method in JavaScript](https://www.youtube.com/watch?v=75W8UPQ5l7k&t=261s) -- [Javascript Interview Questions (Call, Bind and Apply) - Roadside Coder](https://youtu.be/VkmUOktYDAU?si=SdvLZ8FBmephPxjS) diff --git a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js new file mode 100644 index 00000000..c12f73e7 --- /dev/null +++ b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js @@ -0,0 +1,994 @@ +import { describe, it, expect } from 'vitest' + +describe('this, call, apply and bind', () => { + + describe('The 5 Binding Rules', () => { + + describe('Rule 1: new Binding', () => { + it('should bind this to new object with constructor function', () => { + function Person(name) { + this.name = name + } + + const alice = new Person('Alice') + expect(alice.name).toBe('Alice') + }) + + it('should bind this to new object with ES6 class', () => { + class Person { + constructor(name) { + this.name = name + } + } + + const bob = new Person('Bob') + expect(bob.name).toBe('Bob') + }) + + it('should create separate instances with their own this', () => { + class Counter { + constructor() { + this.count = 0 + } + increment() { + this.count++ + } + } + + const counter1 = new Counter() + const counter2 = new Counter() + + counter1.increment() + counter1.increment() + counter2.increment() + + expect(counter1.count).toBe(2) + expect(counter2.count).toBe(1) + }) + + it('should allow this to reference instance methods', () => { + class Calculator { + constructor(value) { + this.value = value + } + add(n) { + this.value += n + return this + } + multiply(n) { + this.value *= n + return this + } + } + + const calc = new Calculator(5) + calc.add(3).multiply(2) + + expect(calc.value).toBe(16) + }) + + it('should return the new object unless function returns an object', () => { + function ReturnsNothing(name) { + this.name = name + } + + function ReturnsObject(name) { + this.name = name + return { customName: 'Custom' } + } + + function ReturnsPrimitive(name) { + this.name = name + return 42 // Primitive return is ignored + } + + const obj1 = new ReturnsNothing('Alice') + const obj2 = new ReturnsObject('Bob') + const obj3 = new ReturnsPrimitive('Charlie') + + expect(obj1.name).toBe('Alice') + expect(obj2.customName).toBe('Custom') + expect(obj2.name).toBeUndefined() + expect(obj3.name).toBe('Charlie') // Primitive ignored + }) + + it('should set up prototype chain correctly', () => { + class Animal { + speak() { + return 'Some sound' + } + } + + class Dog extends Animal { + speak() { + return 'Woof!' + } + } + + const dog = new Dog() + expect(dog.speak()).toBe('Woof!') + expect(dog instanceof Dog).toBe(true) + expect(dog instanceof Animal).toBe(true) + }) + + it('should have new binding override explicit binding', () => { + function Person(name) { + this.name = name + } + + const boundPerson = Person.bind({ name: 'Bound' }) + const alice = new boundPerson('Alice') + + // new overrides bind + expect(alice.name).toBe('Alice') + }) + }) + + describe('Rule 2: Explicit Binding (call/apply/bind)', () => { + it('should set this with call()', () => { + function greet() { + return `Hello, ${this.name}` + } + + const alice = { name: 'Alice' } + expect(greet.call(alice)).toBe('Hello, Alice') + }) + + it('should set this with apply()', () => { + function greet() { + return `Hello, ${this.name}` + } + + const bob = { name: 'Bob' } + expect(greet.apply(bob)).toBe('Hello, Bob') + }) + + it('should set this with bind()', () => { + function greet() { + return `Hello, ${this.name}` + } + + const charlie = { name: 'Charlie' } + const boundGreet = greet.bind(charlie) + expect(boundGreet()).toBe('Hello, Charlie') + }) + + it('should have explicit binding override implicit binding', () => { + const alice = { + name: 'Alice', + greet() { + return `Hi, I'm ${this.name}` + } + } + + const bob = { name: 'Bob' } + + // Even though called on alice, we force this to be bob + expect(alice.greet.call(bob)).toBe("Hi, I'm Bob") + }) + + it('should handle null/undefined thisArg in strict mode', () => { + function getThis() { + return this + } + + // In strict mode with null/undefined, this remains null/undefined + expect(getThis.call(null)).toBe(null) + expect(getThis.call(undefined)).toBe(undefined) + }) + }) + + describe('Rule 3: Implicit Binding (Method Call)', () => { + it('should bind this to the object before the dot', () => { + const user = { + name: 'Alice', + getName() { + return this.name + } + } + + expect(user.getName()).toBe('Alice') + }) + + it('should use the immediate object for nested objects', () => { + const company = { + name: 'TechCorp', + department: { + name: 'Engineering', + getName() { + return this.name + } + } + } + + // this is department, not company + expect(company.department.getName()).toBe('Engineering') + }) + + it('should allow method chaining with this', () => { + const calculator = { + value: 0, + add(n) { + this.value += n + return this + }, + subtract(n) { + this.value -= n + return this + }, + getResult() { + return this.value + } + } + + const result = calculator.add(10).subtract(3).add(5).getResult() + expect(result).toBe(12) + }) + + it('should lose implicit binding when method is extracted', () => { + const user = { + name: 'Alice', + getName() { + return this?.name + } + } + + const getName = user.getName + // Lost binding - this is undefined in strict mode + expect(getName()).toBeUndefined() + }) + + it('should lose implicit binding in callbacks', () => { + const user = { + name: 'Alice', + getName() { + return this?.name + } + } + + function executeCallback(callback) { + return callback() + } + + // Passing method as callback loses binding + expect(executeCallback(user.getName)).toBeUndefined() + }) + + it('should work with computed property access', () => { + const obj = { + name: 'Object', + method() { + return this.name + } + } + + const methodName = 'method' + expect(obj[methodName]()).toBe('Object') + }) + }) + + describe('Rule 4: Default Binding', () => { + it('should have undefined this in strict mode for plain function calls', () => { + function getThis() { + return this + } + + // Vitest runs in strict mode + expect(getThis()).toBeUndefined() + }) + + it('should have undefined this in IIFE in strict mode', () => { + const result = (function() { + return this + })() + + expect(result).toBeUndefined() + }) + + it('should have undefined this in nested function calls', () => { + const obj = { + name: 'Object', + method() { + function inner() { + return this + } + return inner() + } + } + + // Inner function uses default binding + expect(obj.method()).toBeUndefined() + }) + }) + + describe('Rule 5: Arrow Functions (Lexical this)', () => { + it('should inherit this from enclosing scope', () => { + const obj = { + name: 'Object', + method() { + const arrow = () => this.name + return arrow() + } + } + + expect(obj.method()).toBe('Object') + }) + + it('should not change this with call/apply/bind on arrow functions', () => { + const obj = { + name: 'Original', + getArrow() { + return () => this.name + } + } + + const arrow = obj.getArrow() + const other = { name: 'Other' } + + // Arrow function ignores explicit binding + expect(arrow()).toBe('Original') + expect(arrow.call(other)).toBe('Original') + expect(arrow.apply(other)).toBe('Original') + expect(arrow.bind(other)()).toBe('Original') + }) + + it('should preserve this in callbacks with arrow functions', () => { + class Counter { + constructor() { + this.count = 0 + } + + incrementWithArrow() { + [1, 2, 3].forEach(() => { + this.count++ + }) + } + } + + const counter = new Counter() + counter.incrementWithArrow() + + expect(counter.count).toBe(3) + }) + + it('should work with arrow function class fields', () => { + class Button { + constructor(label) { + this.label = label + } + + // Arrow function as class field + handleClick = () => { + return `Clicked: ${this.label}` + } + } + + const btn = new Button('Submit') + const handler = btn.handleClick // Extract method + + // Still works because arrow binds lexically + expect(handler()).toBe('Clicked: Submit') + }) + + it('should not have arrow functions work as object methods', () => { + const user = { + name: 'Alice', + // Arrow function as method - BAD! + greet: () => { + return this?.name + } + } + + // this is not user, it's the enclosing scope (undefined in strict mode) + expect(user.greet()).toBeUndefined() + }) + + it('should capture this at definition time, not call time', () => { + function createArrow() { + return () => this + } + + const obj1 = { name: 'obj1' } + const obj2 = { name: 'obj2' } + + // Arrow is created with obj1 as this + const arrow = createArrow.call(obj1) + + // Calling with obj2 doesn't change anything + expect(arrow.call(obj2)).toBe(obj1) + }) + }) + }) + + describe('call() Method', () => { + it('should invoke function immediately with specified this', () => { + function greet() { + return `Hello, ${this.name}` + } + + expect(greet.call({ name: 'World' })).toBe('Hello, World') + }) + + it('should pass arguments individually', () => { + function introduce(greeting, punctuation) { + return `${greeting}, I'm ${this.name}${punctuation}` + } + + const alice = { name: 'Alice' } + expect(introduce.call(alice, 'Hi', '!')).toBe("Hi, I'm Alice!") + }) + + it('should allow method borrowing', () => { + const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 } + + const result = Array.prototype.slice.call(arrayLike) + expect(result).toEqual(['a', 'b', 'c']) + }) + + it('should allow borrowing join method', () => { + const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 } + + const result = Array.prototype.join.call(arrayLike, '-') + expect(result).toBe('a-b-c') + }) + + it('should work with prototype methods', () => { + const obj = { + 0: 1, + 1: 2, + 2: 3, + length: 3 + } + + const sum = Array.prototype.reduce.call(obj, (acc, val) => acc + val, 0) + expect(sum).toBe(6) + }) + + it('should allow calling parent class methods', () => { + class Animal { + speak() { + return `${this.name} makes a sound` + } + } + + class Dog extends Animal { + constructor(name) { + super() + this.name = name + } + speak() { + const parentSays = Animal.prototype.speak.call(this) + return `${parentSays}. Woof!` + } + } + + const dog = new Dog('Rex') + expect(dog.speak()).toBe('Rex makes a sound. Woof!') + }) + + it('should work with no arguments after thisArg', () => { + function getThisName() { + return this.name + } + + expect(getThisName.call({ name: 'Test' })).toBe('Test') + }) + + it('should keep primitives as-is in strict mode when passed as thisArg', () => { + function getThis() { + return this + } + + // In strict mode, primitives are NOT converted to wrapper objects + const result = getThis.call(42) + expect(result).toBe(42) + expect(typeof result).toBe('number') + }) + }) + + describe('apply() Method', () => { + it('should invoke function immediately with specified this', () => { + function greet() { + return `Hello, ${this.name}` + } + + expect(greet.apply({ name: 'World' })).toBe('Hello, World') + }) + + it('should pass arguments as an array', () => { + function introduce(greeting, punctuation) { + return `${greeting}, I'm ${this.name}${punctuation}` + } + + const bob = { name: 'Bob' } + expect(introduce.apply(bob, ['Hey', '?'])).toBe("Hey, I'm Bob?") + }) + + it('should work with Math.max', () => { + const numbers = [5, 2, 9, 1, 7] + + const max = Math.max.apply(null, numbers) + expect(max).toBe(9) + }) + + it('should work with Math.min', () => { + const numbers = [5, 2, 9, 1, 7] + + const min = Math.min.apply(null, numbers) + expect(min).toBe(1) + }) + + it('should work with array-like arguments', () => { + function sum() { + return Array.prototype.reduce.call(arguments, (acc, n) => acc + n, 0) + } + + const numbers = [1, 2, 3, 4, 5] + expect(sum.apply(null, numbers)).toBe(15) + }) + + it('should work with empty array', () => { + function returnArgs() { + return Array.prototype.slice.call(arguments) + } + + expect(returnArgs.apply(null, [])).toEqual([]) + }) + + it('should work with null/undefined args array', () => { + function noArgs() { + return 'called' + } + + expect(noArgs.apply(null, null)).toBe('called') + expect(noArgs.apply(null, undefined)).toBe('called') + }) + + it('should allow combining arrays with concat-like behavior', () => { + const arr1 = [1, 2, 3] + const arr2 = [4, 5, 6] + + Array.prototype.push.apply(arr1, arr2) + expect(arr1).toEqual([1, 2, 3, 4, 5, 6]) + }) + }) + + describe('bind() Method', () => { + it('should return a new function', () => { + function greet() { + return `Hello, ${this.name}` + } + + const alice = { name: 'Alice' } + const boundGreet = greet.bind(alice) + + expect(typeof boundGreet).toBe('function') + expect(boundGreet).not.toBe(greet) + }) + + it('should not invoke the function immediately', () => { + let called = false + function setFlag() { + called = true + } + + const bound = setFlag.bind({}) + expect(called).toBe(false) + + bound() + expect(called).toBe(true) + }) + + it('should permanently bind this', () => { + function getName() { + return this.name + } + + const alice = { name: 'Alice' } + const bob = { name: 'Bob' } + + const boundToAlice = getName.bind(alice) + + expect(boundToAlice()).toBe('Alice') + expect(boundToAlice.call(bob)).toBe('Alice') // call ignored + expect(boundToAlice.apply(bob)).toBe('Alice') // apply ignored + }) + + it('should not allow rebinding with another bind', () => { + function getName() { + return this.name + } + + const alice = { name: 'Alice' } + const bob = { name: 'Bob' } + + const boundToAlice = getName.bind(alice) + const triedRebind = boundToAlice.bind(bob) + + expect(triedRebind()).toBe('Alice') // Still Alice! + }) + + it('should support partial application', () => { + function multiply(a, b) { + return a * b + } + + const double = multiply.bind(null, 2) + const triple = multiply.bind(null, 3) + + expect(double(5)).toBe(10) + expect(triple(5)).toBe(15) + }) + + it('should support partial application with multiple arguments', () => { + function greet(greeting, punctuation, name) { + return `${greeting}, ${name}${punctuation}` + } + + const sayHello = greet.bind(null, 'Hello', '!') + + expect(sayHello('Alice')).toBe('Hello, Alice!') + expect(sayHello('Bob')).toBe('Hello, Bob!') + }) + + it('should work with event handler pattern', () => { + class Button { + constructor(label) { + this.label = label + this.handleClick = this.handleClick.bind(this) + } + + handleClick() { + return `${this.label} clicked` + } + } + + const btn = new Button('Submit') + const handler = btn.handleClick + + expect(handler()).toBe('Submit clicked') + }) + + it('should work with setTimeout pattern', () => { + class Delayed { + constructor(message) { + this.message = message + } + + getMessage() { + return this.message + } + } + + const delayed = new Delayed('Hello') + const boundGetMessage = delayed.getMessage.bind(delayed) + + // Simulating what setTimeout would do + const callback = boundGetMessage + expect(callback()).toBe('Hello') + }) + + it('should preserve the length property minus bound args', () => { + function fn(a, b, c) { + return a + b + c + } + + const bound0 = fn.bind(null) + const bound1 = fn.bind(null, 1) + const bound2 = fn.bind(null, 1, 2) + + expect(fn.length).toBe(3) + expect(bound0.length).toBe(3) + expect(bound1.length).toBe(2) + expect(bound2.length).toBe(1) + }) + + it('should work with new even when bound', () => { + function Person(name) { + this.name = name + } + + const BoundPerson = Person.bind({ name: 'Ignored' }) + const alice = new BoundPerson('Alice') + + // new overrides the bound this + expect(alice.name).toBe('Alice') + }) + }) + + describe('Common Patterns', () => { + describe('Method Borrowing', () => { + it('should borrow array methods for array-like objects', () => { + const arrayLike = { + 0: 'first', + 1: 'second', + 2: 'third', + length: 3 + } + + const mapped = Array.prototype.map.call(arrayLike, item => item.toUpperCase()) + expect(mapped).toEqual(['FIRST', 'SECOND', 'THIRD']) + }) + + it('should borrow methods between similar objects', () => { + const logger = { + prefix: '[LOG]', + log(message) { + return `${this.prefix} ${message}` + } + } + + const errorLogger = { + prefix: '[ERROR]' + } + + expect(logger.log.call(errorLogger, 'Something failed')).toBe('[ERROR] Something failed') + }) + + it('should use hasOwnProperty safely', () => { + const obj = Object.create(null) // No prototype + obj.name = 'test' + + // obj.hasOwnProperty would fail, but we can borrow it + const hasOwn = Object.prototype.hasOwnProperty.call(obj, 'name') + expect(hasOwn).toBe(true) + }) + }) + + describe('Partial Application', () => { + it('should create specialized functions', () => { + function log(level, timestamp, message) { + return `[${level}] ${timestamp}: ${message}` + } + + const logError = log.bind(null, 'ERROR') + const logInfo = log.bind(null, 'INFO') + + expect(logError('2024-01-15', 'Failed')).toBe('[ERROR] 2024-01-15: Failed') + expect(logInfo('2024-01-15', 'Started')).toBe('[INFO] 2024-01-15: Started') + }) + + it('should allow creating multiplier functions', () => { + function multiply(a, b) { + return a * b + } + + const double = multiply.bind(null, 2) + const triple = multiply.bind(null, 3) + const quadruple = multiply.bind(null, 4) + + expect(double(10)).toBe(20) + expect(triple(10)).toBe(30) + expect(quadruple(10)).toBe(40) + }) + + it('should work with more complex functions', () => { + function createUrl(protocol, domain, path) { + return `${protocol}://${domain}${path}` + } + + const httpUrl = createUrl.bind(null, 'https') + const apiUrl = httpUrl.bind(null, 'api.example.com') + + expect(apiUrl('/users')).toBe('https://api.example.com/users') + expect(apiUrl('/posts')).toBe('https://api.example.com/posts') + }) + }) + + describe('Preserving Context in Classes', () => { + it('should preserve context with bind in constructor', () => { + class Timer { + constructor() { + this.seconds = 0 + this.tick = this.tick.bind(this) + } + + tick() { + this.seconds++ + return this.seconds + } + } + + const timer = new Timer() + const tick = timer.tick + + expect(tick()).toBe(1) + expect(tick()).toBe(2) + }) + + it('should preserve context with arrow class fields', () => { + class Timer { + seconds = 0 + + tick = () => { + this.seconds++ + return this.seconds + } + } + + const timer = new Timer() + const tick = timer.tick + + expect(tick()).toBe(1) + expect(tick()).toBe(2) + }) + }) + }) + + describe('Gotchas and Edge Cases', () => { + describe('Lost Context Scenarios', () => { + it('should demonstrate lost context in forEach without arrow', () => { + const calculator = { + value: 10, + addAll(numbers) { + const self = this // Old-school workaround + numbers.forEach(function(n) { + self.value += n + }) + return this.value + } + } + + expect(calculator.addAll([1, 2, 3])).toBe(16) + }) + + it('should fix lost context with arrow function', () => { + const calculator = { + value: 10, + addAll(numbers) { + numbers.forEach((n) => { + this.value += n + }) + return this.value + } + } + + expect(calculator.addAll([1, 2, 3])).toBe(16) + }) + + it('should fix lost context with thisArg parameter', () => { + const calculator = { + value: 10, + addAll(numbers) { + numbers.forEach(function(n) { + this.value += n + }, this) // Pass this as second argument + return this.value + } + } + + expect(calculator.addAll([1, 2, 3])).toBe(16) + }) + }) + + describe('this in Different Contexts', () => { + it('should have correct this in nested methods', () => { + const outer = { + name: 'Outer', + inner: { + name: 'Inner', + getOuterName() { + // Can't access outer.name via this + return this.name + } + } + } + + expect(outer.inner.getOuterName()).toBe('Inner') + }) + + it('should demonstrate closure workaround for nested this', () => { + const outer = { + name: 'Outer', + createInner() { + const outerThis = this + return { + name: 'Inner', + getOuterName() { + return outerThis.name + } + } + } + } + + const inner = outer.createInner() + expect(inner.getOuterName()).toBe('Outer') + }) + }) + + describe('Binding Priority', () => { + it('should have new override bind', () => { + function Foo(value) { + this.value = value + } + + const BoundFoo = Foo.bind({ value: 'bound' }) + const instance = new BoundFoo('new') + + expect(instance.value).toBe('new') + }) + + it('should have explicit override implicit', () => { + const obj1 = { + name: 'obj1', + getName() { + return this.name + } + } + + const obj2 = { name: 'obj2' } + + expect(obj1.getName()).toBe('obj1') + expect(obj1.getName.call(obj2)).toBe('obj2') + }) + + it('should have implicit override default', () => { + function getName() { + return this?.name + } + + const obj = { name: 'obj', getName } + + expect(getName()).toBeUndefined() // Default binding + expect(obj.getName()).toBe('obj') // Implicit binding + }) + }) + }) + + describe('Quiz Questions from Documentation', () => { + it('Question 1: extracted method loses context', () => { + const user = { + name: 'Alice', + greet() { + return `Hi, I'm ${this?.name}` + } + } + + const greet = user.greet + expect(greet()).toBe("Hi, I'm undefined") + }) + + it('Question 2: arrow function class fields preserve context', () => { + class Counter { + count = 0 + + increment = () => { + this.count++ + } + } + + const counter = new Counter() + const inc = counter.increment + inc() + inc() + + expect(counter.count).toBe(2) + }) + + it('Question 3: bind cannot be overridden by call', () => { + function greet() { + return `Hello, ${this.name}!` + } + + const alice = { name: 'Alice' } + const bob = { name: 'Bob' } + + const greetAlice = greet.bind(alice) + expect(greetAlice.call(bob)).toBe('Hello, Alice!') + }) + + it('Question 4: nested object uses immediate parent as this', () => { + const obj = { + name: 'Outer', + inner: { + name: 'Inner', + getName() { + return this.name + } + } + } + + expect(obj.inner.getName()).toBe('Inner') + }) + }) +}) From fdfd1021b4a773282d52a342be4aaf1485742091 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:11:07 -0300 Subject: [PATCH 032/128] test(object-oriented): add canSpeak composition and documentation example tests - factories-classes: add canSpeak behavior composition test - this-call-apply-bind: add documentation examples section with 8 tests - this-call-apply-bind: add spread operator comparison test for apply() --- .../factories-classes.test.js | 42 ++++ .../this-call-apply-bind.test.js | 179 ++++++++++++++++++ 2 files changed, 221 insertions(+) diff --git a/tests/object-oriented/factories-classes/factories-classes.test.js b/tests/object-oriented/factories-classes/factories-classes.test.js index 1921cbd2..a4dc6dd4 100644 --- a/tests/object-oriented/factories-classes/factories-classes.test.js +++ b/tests/object-oriented/factories-classes/factories-classes.test.js @@ -833,6 +833,48 @@ describe('Factories and Classes', () => { expect(penguin.fly).toBe(undefined) // Penguins can't fly }) + it('should support canSpeak behavior composition', () => { + const canSpeak = (state) => ({ + speak(message) { + return `${state.name} says: "${message}"` + } + }) + + const canWalk = (state) => ({ + walk() { + state.position += state.speed + return `${state.name} walks to position ${state.position}` + } + }) + + function createDuck(name) { + const state = { name, position: 0, speed: 2 } + return { + name: state.name, + ...canWalk(state), + ...canSpeak(state), + getPosition: () => state.position + } + } + + function createFish(name) { + const state = { name, position: 0, speed: 4 } + return { + name: state.name, + // Fish can't speak! + getPosition: () => state.position + } + } + + const duck = createDuck('Donald') + const fish = createFish('Nemo') + + expect(duck.speak('Quack!')).toBe('Donald says: "Quack!"') + expect(duck.walk()).toBe('Donald walks to position 2') + + expect(fish.speak).toBe(undefined) // Fish can't speak + }) + it('should allow flexible behavior combinations', () => { const withHealth = (state) => ({ takeDamage(amount) { diff --git a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js index c12f73e7..b7c36a10 100644 --- a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js +++ b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js @@ -2,6 +2,123 @@ import { describe, it, expect } from 'vitest' describe('this, call, apply and bind', () => { + describe('Documentation Examples', () => { + describe('Introduction: The Pronoun I Analogy', () => { + it('should demonstrate this referring to different objects', () => { + const alice = { + name: "Alice", + introduce() { + return "I am " + this.name + } + } + + const bob = { + name: "Bob", + introduce() { + return "I am " + this.name + } + } + + expect(alice.introduce()).toBe("I am Alice") + expect(bob.introduce()).toBe("I am Bob") + }) + + it('should allow borrowing methods with call (ventriloquist analogy)', () => { + const alice = { name: "Alice" } + const bob = { + name: "Bob", + introduce() { + return "I am " + this.name + } + } + + // Alice borrows Bob's voice + expect(bob.introduce.call(alice)).toBe("I am Alice") + }) + }) + + describe('Dynamic Binding: Call-Time Determination', () => { + it('should have different this values depending on how function is called', () => { + function showThis() { + return this + } + + const obj = { showThis } + + // Plain call - default binding (undefined in strict mode) + expect(showThis()).toBeUndefined() + + // Method call - implicit binding + expect(obj.showThis()).toBe(obj) + + // Explicit binding + const customObj = { name: 'custom' } + expect(showThis.call(customObj)).toBe(customObj) + }) + + it('should allow one function to work with many objects', () => { + function greet() { + return `Hello, I'm ${this.name}!` + } + + const alice = { name: "Alice", greet } + const bob = { name: "Bob", greet } + const charlie = { name: "Charlie", greet } + + expect(alice.greet()).toBe("Hello, I'm Alice!") + expect(bob.greet()).toBe("Hello, I'm Bob!") + expect(charlie.greet()).toBe("Hello, I'm Charlie!") + }) + }) + + describe('Rectangle Class Example (ES6 Classes)', () => { + it('should bind this to instance in class methods', () => { + class Rectangle { + constructor(width, height) { + this.width = width + this.height = height + } + + getArea() { + return this.width * this.height + } + } + + const rect = new Rectangle(10, 5) + expect(rect.getArea()).toBe(50) + }) + }) + + describe('Explicit Binding: introduce() Example', () => { + it('should set this explicitly with call', () => { + function introduce() { + return `I'm ${this.name}, a ${this.role}` + } + + const alice = { name: "Alice", role: "developer" } + const bob = { name: "Bob", role: "designer" } + + expect(introduce.call(alice)).toBe("I'm Alice, a developer") + expect(introduce.call(bob)).toBe("I'm Bob, a designer") + }) + }) + + describe('Partial Application: greet with sayHello/sayGoodbye', () => { + it('should create specialized greeting functions', () => { + function greet(greeting, name) { + return `${greeting}, ${name}!` + } + + const sayHello = greet.bind(null, "Hello") + const sayGoodbye = greet.bind(null, "Goodbye") + + expect(sayHello("Alice")).toBe("Hello, Alice!") + expect(sayHello("Bob")).toBe("Hello, Bob!") + expect(sayGoodbye("Alice")).toBe("Goodbye, Alice!") + }) + }) + }) + describe('The 5 Binding Rules', () => { describe('Rule 1: new Binding', () => { @@ -398,6 +515,51 @@ describe('this, call, apply and bind', () => { expect(arrow.call(obj2)).toBe(obj1) }) }) + + describe('Arrow Function Limitations', () => { + it('should throw when using arrow function with new', () => { + const ArrowClass = () => {} + + expect(() => { + new ArrowClass() + }).toThrow(TypeError) + }) + + it('should not have arguments object in arrow functions', () => { + // Arrow functions don't have their own arguments + // They would reference arguments from enclosing scope + const arrowWithRest = (...args) => { + return args + } + + expect(arrowWithRest(1, 2, 3)).toEqual([1, 2, 3]) + }) + + it('should demonstrate regular vs arrow in nested context', () => { + const obj = { + name: "Object", + + regularMethod: function() { + // Nested regular function - loses 'this' + function inner() { + return this + } + return inner() + }, + + arrowMethod: function() { + // Nested arrow function - keeps 'this' + const innerArrow = () => { + return this.name + } + return innerArrow() + } + } + + expect(obj.regularMethod()).toBeUndefined() + expect(obj.arrowMethod()).toBe("Object") + }) + }) }) describe('call() Method', () => { @@ -551,6 +713,23 @@ describe('this, call, apply and bind', () => { Array.prototype.push.apply(arr1, arr2) expect(arr1).toEqual([1, 2, 3, 4, 5, 6]) }) + + it('should be replaceable by spread operator for Math operations', () => { + const numbers = [5, 2, 9, 1, 7] + + // Old way with apply + const maxApply = Math.max.apply(null, numbers) + const minApply = Math.min.apply(null, numbers) + + // Modern way with spread + const maxSpread = Math.max(...numbers) + const minSpread = Math.min(...numbers) + + expect(maxApply).toBe(maxSpread) + expect(minApply).toBe(minSpread) + expect(maxSpread).toBe(9) + expect(minSpread).toBe(1) + }) }) describe('bind() Method', () => { From d318f331c48016d54a242d07700c26f5b90104ad Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:24:03 -0300 Subject: [PATCH 033/128] docs(this-call-apply-bind): add opening hook, MDN links, key takeaways, and quiz questions - Add engaging opening questions with immediate code example - Add inline MDN links for this, call, apply, bind, globalThis, arrow functions - Add Key Takeaways section with 10 essential points - Add 2 more quiz questions (now 6 total) covering forEach gotcha and partial application --- docs/concepts/this-call-apply-bind.mdx | 85 +++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx index 63315fef..f0a6f526 100644 --- a/docs/concepts/this-call-apply-bind.mdx +++ b/docs/concepts/this-call-apply-bind.mdx @@ -3,6 +3,23 @@ title: "this, call, apply and bind" description: "How JavaScript decides what 'this' refers to — and how you take control" --- +Why does `this` sometimes point to the wrong object? Why does your method work perfectly when called directly, but break when passed as a callback? And how do **[`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)**, **[`apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply)**, and **[`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)** let you take control? + +```javascript +const user = { + name: "Alice", + greet() { + return `Hi, I'm ${this.name}`; + } +}; + +user.greet(); // "Hi, I'm Alice" - works! +const greet = user.greet; +greet(); // "Hi, I'm undefined" - broken! +``` + +The **[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)** keyword is one of JavaScript's most confusing features — but it follows specific rules. Once you understand them, you'll never be confused again. + In our journey through JavaScript, we've already explored [Scope & Closures](/concepts/scope-and-closures) — how variables are accessed and how functions remember their environment. Now it's time to tackle one of JavaScript's most confusing (and powerful) features: the `this` keyword. You've probably seen `this` pop up everywhere — in object methods, class constructors, event handlers, and callbacks. Sometimes it works exactly as you'd expect. Other times? It points to something completely unexpected and your code breaks in mysterious ways. @@ -97,7 +114,7 @@ function showThis() { const obj = { showThis }; // Same function, different this values: -showThis(); // undefined (strict mode) or globalThis +showThis(); // undefined (strict mode) or [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) obj.showThis(); // obj (the object before the dot) showThis.call({}); // {} (explicitly specified) ``` @@ -449,7 +466,7 @@ setTimeout(function() { ### Rule 5: Arrow Functions (Lexical `this`) -Arrow functions are **special**. They don't have their own `this` binding at all. Instead, they **inherit `this`** from their enclosing scope at the time they're defined. +**[Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)** are **special**. They don't have their own `this` binding at all. Instead, they **inherit `this`** from their enclosing scope at the time they're defined. ```javascript const user = { @@ -1228,6 +1245,34 @@ const arrowGen = *() => {}; // SyntaxError --- +## Key Takeaways + +<Info> +**Remember these essential points about `this`, `call`, `apply`, and `bind`:** + +1. **`this` is determined at call time** — Not when the function is defined, but when it's called. This is called dynamic binding. + +2. **5 binding rules in priority order** — new binding > explicit binding > implicit binding > default binding (arrow functions are special). + +3. **"Left of the dot" rule** — In method calls like `obj.method()`, `this` is the object immediately left of the dot. + +4. **Extracting methods loses `this`** — `const fn = obj.method; fn()` loses implicit binding. This is the #1 source of `this` bugs. + +5. **`call()` and `apply()` invoke immediately** — They set `this` and call the function right away. `call` takes comma-separated args, `apply` takes an array. + +6. **`bind()` returns a new function** — It permanently binds `this` for later use. The binding cannot be overridden, even with `call` or `apply`. + +7. **Arrow functions have no own `this`** — They inherit `this` from their enclosing scope (lexical binding). Perfect for callbacks. + +8. **Arrow functions can't be rebound** — `call`, `apply`, and `bind` have no effect on arrow functions' `this`. + +9. **Use arrow class fields for event handlers** — `handleClick = () => {}` ensures `this` is always the instance, even when extracted. + +10. **Strict mode changes default binding** — In strict mode, plain function calls have `this` as `undefined`, not the global object. +</Info> + +--- + ## Test Your Knowledge Try to figure out what `this` refers to in each example before revealing the answer. @@ -1310,6 +1355,42 @@ Try to figure out what `this` refers to in each example before revealing the ans With implicit binding, `this` is the object immediately to the left of the dot at call time. That's `obj.inner`, not `obj`. </Accordion> + + <Accordion title="Question 5: What does this log?"> + ```javascript + const calculator = { + value: 10, + add(numbers) { + numbers.forEach(function(n) { + this.value += n; + }); + return this.value; + } + }; + + console.log(calculator.add([1, 2, 3])); + ``` + + **Answer:** `10` (and likely a TypeError in strict mode) + + The callback function inside `forEach` has its own `this` (default binding), which is `undefined` in strict mode. The fix is to use an arrow function: `numbers.forEach((n) => { this.value += n; })`. + </Accordion> + + <Accordion title="Question 6: What does this log?"> + ```javascript + function multiply(a, b) { + return a * b; + } + + const double = multiply.bind(null, 2); + console.log(double(5)); + console.log(double.length); + ``` + + **Answer:** `10` and `1` + + `bind` creates a partially applied function. `double(5)` returns `2 * 5 = 10`. The `length` property of a bound function reflects remaining parameters: `multiply` has 2 params, we pre-filled 1, so `double.length` is 1. + </Accordion> </AccordionGroup> --- From 3a00e76eb89674ae9038ade699f604a72c831f99 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:43:59 -0300 Subject: [PATCH 034/128] docs(factories-classes): improve structure with opening hook, MDN links, and common mistakes section - Add engaging opening questions and immediate code example - Reposition 'What you'll learn' box after opening - Add prerequisites warning linking to prototype and this concepts - Add inline MDN links for JavaScript-specific terms (class, new, closures, prototype, instanceof, static, getters/setters, private fields, extends, super) - Add comprehensive 'Common Mistakes' section covering: - Forgetting new with constructors - Forgetting super() in derived classes - Confusing _underscore convention with true privacy - Using this incorrectly in factory functions - Add horizontal rules between all major sections --- docs/concepts/factories-classes.mdx | 285 +++++++++++++++++++++------- 1 file changed, 215 insertions(+), 70 deletions(-) diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx index b1242be7..29d3a698 100644 --- a/docs/concepts/factories-classes.mdx +++ b/docs/concepts/factories-classes.mdx @@ -3,72 +3,41 @@ title: "Factories and Classes" description: "How to create objects in JavaScript — from factory functions to ES6 classes" --- -In JavaScript, objects are everywhere. User profiles, shopping carts, game characters, API responses — almost everything is an object. But when you need to create **many similar objects**, writing them one by one becomes a problem. +How do you create hundreds of similar objects without copy-pasting? How do game developers spawn thousands of enemies? How does JavaScript let you build blueprints for objects? -**Factories** and **Classes** solve this problem. They are patterns for creating objects efficiently: - -- A **Factory Function** is a regular function that returns a new object each time you call it. It's simple, flexible, and doesn't require special syntax. - -- A **Class** is a blueprint for creating objects. It uses the `class` keyword (introduced in ES6) and the `new` operator to create instances. If you've used Java, C#, or Python, this will feel familiar. - -Both achieve the same goal — creating objects — but they work differently and have different strengths. This guide will teach you both approaches and help you decide which to use. - ---- - -## The Video Game Character Problem - -To understand why we need factories and classes, let's look at a common problem. +```javascript +// Factory function — returns a new object each time +function createPlayer(name) { + return { + name, + health: 100, + attack() { + return `${this.name} attacks!` + } + } +} -Imagine you're building a video game. You need to create enemies — lots of them. Goblins, dragons, zombies, and bosses. Each enemy has a name, health points, attack power, and abilities. +// Class — a blueprint for creating objects +class Enemy { + constructor(name) { + this.name = name + this.health = 100 + } + + attack() { + return `${this.name} attacks!` + } +} -You start creating enemies one by one: +// Both create objects the same way +const player = createPlayer("Alice") // Factory +const enemy = new Enemy("Goblin") // Class -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ THE COPY-PASTE NIGHTMARE │ -│ │ -│ const goblin1 = { name: "Goblin", health: 100, attack: 10 }; │ -│ const goblin2 = { name: "Goblin", health: 100, attack: 10 }; │ -│ const goblin3 = { name: "Goblin", health: 100, attack: 10 }; │ -│ ... 997 more goblins ... │ -│ │ -│ This is like hand-painting 1000 identical action figures! │ -│ │ -│ Problems: │ -│ • Tedious and error-prone │ -│ • What if you need to change goblin health to 150? │ -│ • What if you need to add a new property to all goblins? │ -│ • You'd have to edit 1000 objects! │ -└─────────────────────────────────────────────────────────────────────┘ +console.log(player.attack()) // "Alice attacks!" +console.log(enemy.attack()) // "Goblin attacks!" ``` -The solution? Create a **blueprint** that can produce as many characters as you need: - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ THE BLUEPRINT SOLUTION │ -│ │ -│ ┌──────────────────┐ │ -│ │ ENEMY BLUEPRINT │ Creates: │ -│ │ ────────────── │ ┌─────┐ ┌─────┐ ┌─────┐ │ -│ │ • name │ ───► │ 👹 │ │ 👹 │ │ 👹 │ ... │ -│ │ • health: 100 │ └─────┘ └─────┘ └─────┘ │ -│ │ • attack: 10 │ goblin1 goblin2 goblin3 │ -│ │ • takeDamage() │ │ -│ │ • attack() │ Each one is independent, │ -│ └──────────────────┘ but made from the same blueprint │ -│ │ -│ Change the blueprint once → All new enemies get the change │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -JavaScript gives us several ways to create these blueprints: - -1. **Factory Functions** — Functions that return objects (flexible, simple) -2. **Constructor Functions** — Functions used with `new` (traditional) -3. **ES6 Classes** — Modern syntax for constructors (clean, familiar) - -Let's learn each approach and understand when to use them. +**Factories** and **[Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)** are two patterns for creating objects efficiently. A factory function is a regular function that returns a new object. A class is a blueprint that uses the [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class) keyword and the [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) operator. Both achieve the same goal — but they work differently and have different strengths. <Info> **What you'll learn in this guide:** @@ -82,6 +51,10 @@ Let's learn each approach and understand when to use them. - When to use factories vs classes </Info> +<Warning> +**Prerequisites:** This guide assumes you understand [Prototype Inheritance](/concepts/prototype) and [this, call, apply, bind](/concepts/this-call-apply-bind). If those concepts are new to you, read those guides first! +</Warning> + --- ## Part 1: The Problem — Creating Multiple Objects @@ -288,7 +261,7 @@ const villager = createCharacter({ name: "Villager" }); ### Factory with Private Variables (Closures) -One of the most powerful features of factory functions is creating **truly private** variables using closures: +One of the most powerful features of factory functions is creating **truly private** variables using [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures): ```javascript function createBankAccount(ownerName, initialBalance = 0) { @@ -494,7 +467,7 @@ Here's a function that does what `new` does: ```javascript function myNew(Constructor, ...args) { // Step 1 & 2: Create object with correct prototype - const obj = Object.create(Constructor.prototype); + const obj = Object.create(Constructor.prototype); // Object.create creates a new object with the specified prototype // Step 3: Run constructor with 'this' = obj const result = Constructor.apply(obj, args); @@ -508,7 +481,7 @@ const player1 = new Player("Alice"); const player2 = myNew(Player, "Bob"); ``` -### Adding Methods to the Prototype +### Adding Methods to the [Prototype](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) There's a problem with our constructor: **each instance gets its own copy of methods**: @@ -532,7 +505,7 @@ console.log(p1.attack === p2.attack); // false // 1000 players = 1000 copies of attack function = wasted memory! ``` -The solution is to put methods on the **prototype**: +The solution is to put methods on the **[prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)**: ```javascript function Player(name) { @@ -588,7 +561,7 @@ console.log(p1.attack === p2.attack); // true └─────────────────────────────────────────────────────────────────────┘ ``` -### The `instanceof` Operator +### The [`instanceof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) Operator `instanceof` checks if an object was created by a constructor: @@ -778,7 +751,7 @@ console.log(hero.health); // 0 (setter prevented negative) console.log(Character.MAX_LEVEL); // 99 (static property) ``` -### Static Methods and Properties +### [Static](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) Methods and Properties **Static** members belong to the class itself, not to instances: @@ -817,7 +790,7 @@ console.log(utils.square); // undefined - Utility functions (`Array.isArray(value)`) - Singleton patterns (`Config.getInstance()`) -### Getters and Setters +### [Getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) and [Setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) Getters and setters let you define computed properties and add validation: @@ -869,7 +842,7 @@ console.log(temp.celsius); // 37.78 (converted) // temp.kelvin = 0; // Error: no setter (read-only) ``` -### Private Fields (#) — True Privacy +### [Private Fields (#)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields) — True Privacy ES2022 introduced **private fields** with the `#` prefix. Unlike the `_underscore` convention, these are **truly private**: @@ -986,9 +959,177 @@ console.log(w3.deposit === w4.deposit); // false (each has own copy) --- +## Common Mistakes with Factories and Classes + +When working with factories and classes, there are several common pitfalls that trip up developers. Let's look at the most frequent mistakes and how to avoid them. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE 3 MOST COMMON MISTAKES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. FORGETTING `new` WITH CONSTRUCTORS │ +│ Pollutes global scope or crashes in strict mode │ +│ │ +│ 2. FORGETTING `super()` IN DERIVED CLASSES │ +│ Must call super() before using `this` │ +│ │ +│ 3. CONFUSING `_private` WITH TRULY PRIVATE │ +│ Underscore is just a convention, not enforcement │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Mistake 1: Forgetting `new` with Constructor Functions + +```javascript +// ❌ WRONG - Forgot 'new', 'this' becomes global object +function Player(name) { + this.name = name; + this.health = 100; +} + +const alice = Player("Alice"); // Missing 'new'! + +console.log(alice); // undefined +console.log(globalThis.name); // "Alice" - leaked to global! +console.log(globalThis.health); // 100 - also leaked! + +// ✓ CORRECT - Always use 'new' with constructors +const bob = new Player("Bob"); +console.log(bob.name); // "Bob" +console.log(bob.health); // 100 +``` + +<Tip> +**Pro tip:** Use ES6 classes instead of constructor functions — they throw an error if you forget `new`: + +```javascript +class Player { + constructor(name) { this.name = name; } +} + +const alice = Player("Alice"); // TypeError: Class constructor Player cannot be invoked without 'new' +``` +</Tip> + +### Mistake 2: Forgetting `super()` in Derived Classes + +```javascript +// ❌ WRONG - Using 'this' before calling super() +class Animal { + constructor(name) { + this.name = name; + } +} + +class Dog extends Animal { + constructor(name, breed) { + this.breed = breed; // ReferenceError: Must call super before accessing 'this' + super(name); + } +} + +// ✓ CORRECT - Call super() first, then use 'this' +class Cat extends Animal { + constructor(name, color) { + super(name); // Initialize parent first + this.color = color; // Now 'this' is available + } +} + +const kitty = new Cat("Whiskers", "orange"); +console.log(kitty.name); // "Whiskers" +console.log(kitty.color); // "orange" +``` + +### Mistake 3: Thinking `_underscore` Means Private + +```javascript +// ❌ WRONG - Underscore is just a naming convention +class BankAccount { + constructor(balance) { + this._balance = balance; // Not actually private! + } + + getBalance() { + return this._balance; + } +} + +const account = new BankAccount(1000); +console.log(account._balance); // 1000 - fully accessible! +account._balance = 999999; // Can be modified! +console.log(account.getBalance()); // 999999 - no protection! + +// ✓ CORRECT - Use private fields (#) for true privacy +class SecureBankAccount { + #balance; // Truly private + + constructor(balance) { + this.#balance = balance; + } + + getBalance() { + return this.#balance; + } +} + +const secure = new SecureBankAccount(1000); +// console.log(secure.#balance); // SyntaxError! +console.log(secure.getBalance()); // 1000 - only accessible via methods +``` + +### Mistake 4: Using `this` Incorrectly in Factory Functions + +```javascript +// ❌ WRONG - 'this' in factory return object can cause issues +function createCounter() { + return { + count: 0, + increment() { + this.count++; // 'this' depends on how the method is called + } + }; +} + +const counter = createCounter(); +counter.increment(); // Works +console.log(counter.count); // 1 + +const increment = counter.increment; +increment(); // 'this' is undefined or global! +console.log(counter.count); // Still 1 - didn't work! + +// ✓ CORRECT - Use closure to avoid 'this' issues +function createSafeCounter() { + let count = 0; // Closure variable + + return { + increment() { + count++; // No 'this' needed + }, + getCount() { + return count; + } + }; +} + +const safeCounter = createSafeCounter(); +const safeIncrement = safeCounter.increment; +safeIncrement(); // Works even when extracted! +console.log(safeCounter.getCount()); // 1 +``` + +<Warning> +**The `this` Trap:** When you extract a method from an object and call it standalone, `this` is no longer bound to the original object. Factory functions that use closures instead of `this` avoid this problem entirely. +</Warning> + +--- + ## Part 5: Inheritance — Extending Behavior -### Class Inheritance with `extends` +### Class Inheritance with [`extends`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends) Use `extends` to create a class that inherits from another: @@ -1073,7 +1214,7 @@ console.log(gandalf instanceof Mage); // true console.log(gandalf instanceof Warrior); // false ``` -### The `super` Keyword +### The [`super`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) Keyword `super` does two things: @@ -1591,6 +1732,8 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); </Card> </CardGroup> +--- + ## Articles <CardGroup cols={2}> @@ -1614,6 +1757,8 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); </Card> </CardGroup> +--- + ## Videos <CardGroup cols={2}> From abac5b869590cdbebc66b8c9177cc14015bdcf41 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:44:00 -0300 Subject: [PATCH 035/128] docs(dom): improve concept page with MDN links, opening hook, and common mistakes section - Add engaging opening hook with practical questions - Add MDN inline links throughout for all Web APIs and methods - Add dedicated 'Common Mistakes' section with visual diagram for innerHTML XSS - Expand Reference section with 6 MDN CardGroup links - Add internal concept links to Event Loop, JavaScript Engines, Closures - Fix code style consistency (remove semicolons) - Add output comments to code examples --- docs/concepts/dom.mdx | 628 ++++++++++++++++++++++++++---------------- 1 file changed, 383 insertions(+), 245 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 03360480..1c7e29f3 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -3,15 +3,19 @@ title: "DOM and Layout Trees" description: "Understanding the Document Object Model and how browsers render pages" --- -The **Document Object Model (DOM)** is a programming interface for web documents. It represents your HTML as a **tree of objects** that JavaScript can read and manipulate. +How does JavaScript change what you see on a webpage? How do you click a button and see new content appear, or type in a form and watch suggestions pop up? How does a "dark mode" toggle instantly transform an entire page? + +The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface for web documents. It represents your HTML as a **tree of objects** that JavaScript can read and manipulate. ```javascript // The DOM lets you do things like this: -document.querySelector('h1').textContent = 'Hello, DOM!'; -document.body.style.backgroundColor = 'lightblue'; -document.getElementById('btn').addEventListener('click', handleClick); +document.querySelector('h1').textContent = 'Hello, DOM!' +document.body.style.backgroundColor = 'lightblue' +document.getElementById('btn').addEventListener('click', handleClick) ``` +With the DOM, you can use methods like **[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** to find elements, **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** to grab specific nodes, and **[`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)** to respond to user interactions. + <Info> **What you'll learn in this guide:** - What the DOM actually is (and what it's NOT) @@ -149,7 +153,7 @@ DOM Render Tree ### The `document` Object: Your Entry Point -The `document` object is your gateway to the DOM. It's automatically available in any browser JavaScript: +The **[`document`](https://developer.mozilla.org/en-US/docs/Web/API/Document)** object is your gateway to the DOM. It's automatically available in any browser JavaScript: ```javascript // document is the root of everything @@ -167,7 +171,7 @@ document.title = 'New Title'; // Changes browser tab title ## DOM Tree Structure & Node Types -Everything in the DOM is a **node**. But not all nodes are created equal! +Everything in the DOM is a **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node)**. But not all nodes are created equal! ### The Node Type Hierarchy @@ -192,25 +196,27 @@ Everything in the DOM is a **node**. But not all nodes are created equal! | Node Type | `nodeType` | `nodeName` | Example | |-----------|------------|------------|---------| -| Element | `1` | Tag name (uppercase) | `<div>`, `<p>`, `<span>` | -| Text | `3` | `#text` | Text inside elements | -| Comment | `8` | `#comment` | `<!-- comment -->` | -| Document | `9` | `#document` | The `document` object | -| DocumentFragment | `11` | `#document-fragment` | Virtual container | +| [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) | `1` | Tag name (uppercase) | `<div>`, `<p>`, `<span>` | +| [Text](https://developer.mozilla.org/en-US/docs/Web/API/Text) | `3` | `#text` | Text inside elements | +| [Comment](https://developer.mozilla.org/en-US/docs/Web/API/Comment) | `8` | `#comment` | `<!-- comment -->` | +| [Document](https://developer.mozilla.org/en-US/docs/Web/API/Document) | `9` | `#document` | The `document` object | +| [DocumentFragment](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) | `11` | `#document-fragment` | Virtual container | ```javascript -const div = document.createElement('div'); -console.log(div.nodeType); // 1 (Element) -console.log(div.nodeName); // "DIV" +const div = document.createElement('div') +console.log(div.nodeType) // 1 (Element) +console.log(div.nodeName) // "DIV" -const text = document.createTextNode('Hello'); -console.log(text.nodeType); // 3 (Text) -console.log(text.nodeName); // "#text" +const text = document.createTextNode('Hello') +console.log(text.nodeType) // 3 (Text) +console.log(text.nodeName) // "#text" -console.log(document.nodeType); // 9 (Document) -console.log(document.nodeName); // "#document" +console.log(document.nodeType) // 9 (Document) +console.log(document.nodeName) // "#document" ``` +The **[`createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement)** and **[`createTextNode()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)** methods create new nodes that you can add to the DOM. + ### Node Type Constants Instead of remembering numbers, use the constants: @@ -263,21 +269,23 @@ div#container ## Selecting Elements -Before you can manipulate an element, you need to find it. JavaScript provides several methods: +Before you can manipulate an element, you need to find it. JavaScript provides several methods through the **[`document`](https://developer.mozilla.org/en-US/docs/Web/API/Document)** object: ### The getElementById() Classic -The fastest way to select a single element by its unique ID: +The **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** method is the fastest way to select a single element by its unique ID: ```javascript // HTML: <div id="hero">Welcome!</div> -const hero = document.getElementById('hero'); -console.log(hero); // <div id="hero">Welcome!</div> +const hero = document.getElementById('hero') +console.log(hero) // <div id="hero">Welcome!</div> +console.log(hero.id) // "hero" +console.log(hero.textContent) // "Welcome!" // Returns null if not found (not an error!) -const ghost = document.getElementById('nonexistent'); -console.log(ghost); // null +const ghost = document.getElementById('nonexistent') +console.log(ghost) // null ``` <Tip> @@ -286,7 +294,7 @@ IDs must be unique in a document. If you have duplicate IDs, `getElementById` re ### getElementsByClassName() and getElementsByTagName() -Select multiple elements by class or tag name: +**[`getElementsByClassName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName)** and **[`getElementsByTagName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByTagName)** select multiple elements by class or tag name: ```javascript // HTML: @@ -294,79 +302,80 @@ Select multiple elements by class or tag name: // <p class="intro">Second</p> // <p>Third</p> -const intros = document.getElementsByClassName('intro'); -console.log(intros.length); // 2 -console.log(intros[0]); // <p class="intro">First</p> +const intros = document.getElementsByClassName('intro') +console.log(intros.length) // 2 +console.log(intros[0]) // <p class="intro">First</p> +console.log(intros[0].textContent) // "First" -const allParagraphs = document.getElementsByTagName('p'); -console.log(allParagraphs.length); // 3 +const allParagraphs = document.getElementsByTagName('p') +console.log(allParagraphs.length) // 3 ``` ### The Modern Way: querySelector() and querySelectorAll() -Use CSS selectors to find elements—much more powerful! +**[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** and **[`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)** use CSS selectors to find elements—much more powerful! ```javascript // querySelector returns the FIRST match (or null) -const firstButton = document.querySelector('button'); -const submitBtn = document.querySelector('#submit'); -const firstCard = document.querySelector('.card'); -const navLink = document.querySelector('nav a.active'); -const dataItem = document.querySelector('[data-id="123"]'); +const firstButton = document.querySelector('button') // First <button> element +const submitBtn = document.querySelector('#submit') // Element with id="submit" +const firstCard = document.querySelector('.card') // First element with class="card" +const navLink = document.querySelector('nav a.active') // <a class="active"> inside <nav> +const dataItem = document.querySelector('[data-id="123"]') // Element with data-id="123" // querySelectorAll returns ALL matches (NodeList) -const allButtons = document.querySelectorAll('button'); -const allCards = document.querySelectorAll('.card'); -const evenRows = document.querySelectorAll('tr:nth-child(even)'); +const allButtons = document.querySelectorAll('button') // All <button> elements +const allCards = document.querySelectorAll('.card') // All elements with class="card" +const evenRows = document.querySelectorAll('tr:nth-child(even)') // Every even table row ``` ### Selector Examples ```javascript // By ID -document.querySelector('#main'); +document.querySelector('#main') // By class -document.querySelector('.active'); -document.querySelectorAll('.btn.primary'); +document.querySelector('.active') +document.querySelectorAll('.btn.primary') // By tag -document.querySelector('header'); -document.querySelectorAll('li'); +document.querySelector('header') +document.querySelectorAll('li') // By attribute -document.querySelector('[type="submit"]'); -document.querySelector('[data-modal="login"]'); +document.querySelector('[type="submit"]') +document.querySelector('[data-modal="login"]') // Descendant selectors -document.querySelector('nav ul li a'); -document.querySelector('.sidebar .widget:first-child'); +document.querySelector('nav ul li a') +document.querySelector('.sidebar .widget:first-child') // Pseudo-selectors (limited support) -document.querySelectorAll('input:not([type="hidden"])'); -document.querySelector('p:first-of-type'); +document.querySelectorAll('input:not([type="hidden"])') +document.querySelector('p:first-of-type') ``` ### Live vs Static Collections -This is a crucial difference that trips up many developers: +This is a crucial difference that trips up many developers. **[`getElementsByClassName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName)** returns a live **[HTMLCollection](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)**, while **[`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)** returns a static **[NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)**: ```javascript -const liveList = document.getElementsByClassName('item'); // LIVE -const staticList = document.querySelectorAll('.item'); // STATIC +const liveList = document.getElementsByClassName('item') // LIVE HTMLCollection +const staticList = document.querySelectorAll('.item') // STATIC NodeList // Start with 3 items -console.log(liveList.length); // 3 -console.log(staticList.length); // 3 +console.log(liveList.length) // 3 +console.log(staticList.length) // 3 // Add a new item to the DOM -const newItem = document.createElement('div'); -newItem.className = 'item'; -document.body.appendChild(newItem); +const newItem = document.createElement('div') +newItem.className = 'item' +document.body.appendChild(newItem) // Check lengths again -console.log(liveList.length); // 4 (automatically updated!) -console.log(staticList.length); // 3 (still the old snapshot) +console.log(liveList.length) // 4 (automatically updated!) +console.log(staticList.length) // 3 (still the old snapshot) ``` | Method | Returns | Live? | @@ -382,13 +391,13 @@ console.log(staticList.length); // 3 (still the old snapshot) You can call selection methods on any element, not just `document`: ```javascript -const nav = document.querySelector('nav'); +const nav = document.querySelector('nav') // Find links ONLY inside nav -const navLinks = nav.querySelectorAll('a'); +const navLinks = nav.querySelectorAll('a') // Find the active link inside nav -const activeLink = nav.querySelector('.active'); +const activeLink = nav.querySelector('.active') ``` This is faster than searching the entire document and helps avoid selecting unintended elements. @@ -409,10 +418,10 @@ This is faster than searching the entire document and helps avoid selecting unin ```javascript // Premature optimization - don't do this - const el1 = document.getElementById('myId'); + const el1 = document.getElementById('myId') // This is fine and more readable - const el2 = document.querySelector('#myId'); + const el2 = document.querySelector('#myId') ``` </Accordion> </AccordionGroup> @@ -426,19 +435,19 @@ Once you have an element, you can navigate to related elements without querying ### Traversing Downwards (To Children) ```javascript -const ul = document.querySelector('ul'); +const ul = document.querySelector('ul') // Get ALL child nodes (including text nodes!) -const allChildNodes = ul.childNodes; // NodeList +const allChildNodes = ul.childNodes // NodeList // Get only ELEMENT children (usually what you want) -const elementChildren = ul.children; // HTMLCollection +const elementChildren = ul.children // HTMLCollection // Get specific children -const firstChild = ul.firstChild; // First node (might be text!) -const firstElement = ul.firstElementChild; // First ELEMENT child -const lastChild = ul.lastChild; // Last node -const lastElement = ul.lastElementChild; // Last ELEMENT child +const firstChild = ul.firstChild // First node (might be text!) +const firstElement = ul.firstElementChild // First ELEMENT child +const lastChild = ul.lastChild // Last node +const lastElement = ul.lastElementChild // Last ELEMENT child ``` <Warning> @@ -457,48 +466,48 @@ What is `ul.firstChild`? It's NOT the first `<li>`! It's a **text node** contain ### Traversing Upwards (To Parents) ```javascript -const li = document.querySelector('li'); +const li = document.querySelector('li') // Direct parent -const parent = li.parentNode; // Usually same as parentElement -const parentEl = li.parentElement; // Guaranteed to be an Element (or null) +const parent = li.parentNode // Usually same as parentElement +const parentEl = li.parentElement // Guaranteed to be an Element (or null) // Find ancestor matching selector (very useful!) -const form = li.closest('form'); // Finds nearest ancestor <form> -const card = li.closest('.card'); // Finds nearest ancestor with class "card" +const form = li.closest('form') // Finds nearest ancestor <form> +const card = li.closest('.card') // Finds nearest ancestor with class "card" // closest() includes the element itself -const self = li.closest('li'); // Returns li itself if it matches! +const self = li.closest('li') // Returns li itself if it matches! ``` -The `closest()` method is incredibly useful for event delegation: +The **[`closest()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)** method is incredibly useful for event delegation (see [Event Loop](/concepts/event-loop) for how events are processed): ```javascript // Handle clicks on any button inside a card document.addEventListener('click', (e) => { - const card = e.target.closest('.card'); + const card = e.target.closest('.card') if (card) { - console.log('Clicked inside card:', card); + console.log('Clicked inside card:', card) } -}); +}) ``` ### Traversing Sideways (To Siblings) ```javascript -const secondLi = document.querySelectorAll('li')[1]; +const secondLi = document.querySelectorAll('li')[1] // Previous/next nodes (might be text!) -const prevNode = secondLi.previousSibling; -const nextNode = secondLi.nextSibling; +const prevNode = secondLi.previousSibling +const nextNode = secondLi.nextSibling // Previous/next ELEMENTS (usually what you want) -const prevElement = secondLi.previousElementSibling; -const nextElement = secondLi.nextElementSibling; +const prevElement = secondLi.previousElementSibling +const nextElement = secondLi.nextElementSibling // Returns null at the boundaries -const firstLi = document.querySelector('li'); -console.log(firstLi.previousElementSibling); // null (no previous sibling) +const firstLi = document.querySelector('li') +console.log(firstLi.previousElementSibling) // null (no previous sibling) ``` ### Node vs Element Properties Cheat Sheet @@ -521,19 +530,19 @@ console.log(firstLi.previousElementSibling); // null (no previous sibling) ```javascript // Get all ancestors of an element function getAncestors(element) { - const ancestors = []; - let current = element.parentElement; + const ancestors = [] + let current = element.parentElement while (current && current !== document.body) { - ancestors.push(current); - current = current.parentElement; + ancestors.push(current) + current = current.parentElement } - return ancestors; + return ancestors } -const deepElement = document.querySelector('.deeply-nested'); -console.log(getAncestors(deepElement)); +const deepElement = document.querySelector('.deeply-nested') +console.log(getAncestors(deepElement)) // [<div.parent>, <section>, <main>, ...] ``` @@ -545,36 +554,38 @@ The real power of the DOM is the ability to create, modify, and remove elements ### Creating Elements +Use **[`createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement)** to create new elements and **[`createTextNode()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)** to create text nodes: + ```javascript // Create a new element -const div = document.createElement('div'); -const span = document.createElement('span'); -const img = document.createElement('img'); +const div = document.createElement('div') +const span = document.createElement('span') +const img = document.createElement('img') // Create a text node -const text = document.createTextNode('Hello, world!'); +const text = document.createTextNode('Hello, world!') // Create a comment node -const comment = document.createComment('This is a comment'); +const comment = document.createComment('This is a comment') // Elements are created "detached" - not yet in the DOM! -console.log(div.parentNode); // null +console.log(div.parentNode) // null ``` ### Adding Elements to the DOM -There are many ways to add elements. Here's a comprehensive overview: +There are many ways to add elements. Here's a comprehensive overview using methods like **[`appendChild()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)**, **[`insertBefore()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore)**, **[`append()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/append)**, and **[`prepend()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend)**: <Tabs> <Tab title="appendChild()"> Adds a node as the **last child** of a parent: ```javascript - const ul = document.querySelector('ul'); - const li = document.createElement('li'); - li.textContent = 'New item'; + const ul = document.querySelector('ul') + const li = document.createElement('li') + li.textContent = 'New item' - ul.appendChild(li); + ul.appendChild(li) // <ul> // <li>Existing</li> // <li>New item</li> ← Added at the end @@ -585,12 +596,12 @@ There are many ways to add elements. Here's a comprehensive overview: Inserts a node **before** a reference node: ```javascript - const ul = document.querySelector('ul'); - const existingLi = ul.querySelector('li'); - const newLi = document.createElement('li'); - newLi.textContent = 'First!'; + const ul = document.querySelector('ul') + const existingLi = ul.querySelector('li') + const newLi = document.createElement('li') + newLi.textContent = 'First!' - ul.insertBefore(newLi, existingLi); + ul.insertBefore(newLi, existingLi) // <ul> // <li>First!</li> ← Inserted before // <li>Existing</li> @@ -601,42 +612,42 @@ There are many ways to add elements. Here's a comprehensive overview: Modern methods that accept multiple nodes AND strings: ```javascript - const div = document.querySelector('div'); + const div = document.querySelector('div') // append() - adds to the END - div.append('Text', document.createElement('span'), 'More text'); + div.append('Text', document.createElement('span'), 'More text') // prepend() - adds to the START - div.prepend(document.createElement('strong')); + div.prepend(document.createElement('strong')) ``` </Tab> <Tab title="before() / after()"> Insert as siblings (not children): ```javascript - const h1 = document.querySelector('h1'); + const h1 = document.querySelector('h1') // Insert BEFORE h1 (as previous sibling) - h1.before(document.createElement('nav')); + h1.before(document.createElement('nav')) // Insert AFTER h1 (as next sibling) - h1.after(document.createElement('p')); + h1.after(document.createElement('p')) ``` </Tab> </Tabs> ### insertAdjacentHTML() - The Swiss Army Knife -For inserting HTML strings, `insertAdjacentHTML()` is powerful and fast: +For inserting HTML strings, **[`insertAdjacentHTML()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)** is powerful and fast: ```javascript -const div = document.querySelector('div'); +const div = document.querySelector('div') // Four positions to insert: -div.insertAdjacentHTML('beforebegin', '<p>Before div</p>'); -div.insertAdjacentHTML('afterbegin', '<p>First child of div</p>'); -div.insertAdjacentHTML('beforeend', '<p>Last child of div</p>'); -div.insertAdjacentHTML('afterend', '<p>After div</p>'); +div.insertAdjacentHTML('beforebegin', '<p>Before div</p>') +div.insertAdjacentHTML('afterbegin', '<p>First child of div</p>') +div.insertAdjacentHTML('beforeend', '<p>Last child of div</p>') +div.insertAdjacentHTML('afterend', '<p>After div</p>') ``` Visual representation: @@ -658,74 +669,76 @@ Visual representation: Modern and simple—element removes itself: ```javascript - const element = document.querySelector('.to-remove'); - element.remove(); // Gone! + const element = document.querySelector('.to-remove') + element.remove() // Gone! ``` </Tab> <Tab title="removeChild()"> Classic method—remove via parent: ```javascript - const parent = document.querySelector('ul'); - const child = parent.querySelector('li'); - parent.removeChild(child); + const parent = document.querySelector('ul') + const child = parent.querySelector('li') + parent.removeChild(child) // Or remove from any element - element.parentNode.removeChild(element); + element.parentNode.removeChild(element) ``` </Tab> </Tabs> ### Cloning Elements +Use **[`cloneNode()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode)** to duplicate elements: + ```javascript -const original = document.querySelector('.card'); +const original = document.querySelector('.card') // Shallow clone (element only, no children) -const shallow = original.cloneNode(false); +const shallow = original.cloneNode(false) // Deep clone (element AND all descendants) -const deep = original.cloneNode(true); +const deep = original.cloneNode(true) // Clones are detached - must add to DOM -document.body.appendChild(deep); +document.body.appendChild(deep) ``` <Warning> **ID Collision!** If you clone an element with an ID, you'll have duplicate IDs in your document (invalid HTML). Remove or change the ID after cloning: ```javascript -const clone = original.cloneNode(true); -clone.id = ''; // Remove ID +const clone = original.cloneNode(true) +clone.id = '' // Remove ID // or -clone.id = 'new-unique-id'; +clone.id = 'new-unique-id' ``` </Warning> ### DocumentFragment - Batch Operations -When adding many elements, using a `DocumentFragment` is more efficient: +When adding many elements, using a **[`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment)** is more efficient: ```javascript // Bad: Multiple reflows -const ul = document.querySelector('ul'); +const ul = document.querySelector('ul') for (let i = 0; i < 1000; i++) { - const li = document.createElement('li'); - li.textContent = `Item ${i}`; - ul.appendChild(li); // Triggers reflow each time! + const li = document.createElement('li') + li.textContent = `Item ${i}` + ul.appendChild(li) // Triggers reflow each time! } // Good: Single reflow -const ul = document.querySelector('ul'); -const fragment = document.createDocumentFragment(); +const ul = document.querySelector('ul') +const fragment = document.createDocumentFragment() for (let i = 0; i < 1000; i++) { - const li = document.createElement('li'); - li.textContent = `Item ${i}`; - fragment.appendChild(li); // No reflow (fragment is detached) + const li = document.createElement('li') + li.textContent = `Item ${i}` + fragment.appendChild(li) // No reflow (fragment is detached) } -ul.appendChild(fragment); // Single reflow! +ul.appendChild(fragment) // Single reflow! ``` A `DocumentFragment` is a lightweight container that: @@ -737,21 +750,21 @@ A `DocumentFragment` is a lightweight container that: ## Modifying Content -Three properties let you read and write element content: +Three properties let you read and write element content: **[`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)**, **[`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)**, and **[`innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText)**. ### innerHTML - Parse and Insert HTML ```javascript -const div = document.querySelector('div'); +const div = document.querySelector('div') // Read HTML content -console.log(div.innerHTML); // "<p>Hello</p><span>World</span>" +console.log(div.innerHTML) // "<p>Hello</p><span>World</span>" // Write HTML content (parses the string!) -div.innerHTML = '<h1>New Title</h1><p>New paragraph</p>'; +div.innerHTML = '<h1>New Title</h1><p>New paragraph</p>' // Clear all content -div.innerHTML = ''; +div.innerHTML = '' ``` <Warning> @@ -761,10 +774,10 @@ Never use `innerHTML` with user-provided content: ```javascript // DANGEROUS! User could inject: <img src=x onerror="stealCookies()"> -div.innerHTML = userInput; // NO! +div.innerHTML = userInput // NO! // Safe alternatives: -div.textContent = userInput; // Escapes HTML +div.textContent = userInput // Escapes HTML // or sanitize the input first ``` </Warning> @@ -772,14 +785,14 @@ div.textContent = userInput; // Escapes HTML ### textContent - Plain Text Only ```javascript -const div = document.querySelector('div'); +const div = document.querySelector('div') // Read text (ignores HTML tags) // <div><p>Hello</p><span>World</span></div> -console.log(div.textContent); // "HelloWorld" +console.log(div.textContent) // "HelloWorld" // Write text (HTML is escaped, not parsed) -div.textContent = '<script>alert("XSS")</script>'; +div.textContent = '<script>alert("XSS")</script>' // Displays literally: <script>alert("XSS")</script> // Safe from XSS! ``` @@ -787,13 +800,13 @@ div.textContent = '<script>alert("XSS")</script>'; ### innerText - Rendered Text ```javascript -const div = document.querySelector('div'); +const div = document.querySelector('div') // innerText respects CSS visibility // <div>Hello <span style="display:none">Hidden</span> World</div> -console.log(div.textContent); // "Hello Hidden World" -console.log(div.innerText); // "Hello World" (Hidden is excluded!) +console.log(div.textContent) // "Hello Hidden World" +console.log(div.innerText) // "Hello World" (Hidden is excluded!) ``` ### When to Use Each @@ -809,36 +822,36 @@ console.log(div.innerText); // "Hello World" (Hidden is excluded!) // because innerText must calculate styles // Setting text content (both work, textContent is faster) -element.textContent = 'Hello'; // Preferred -element.innerText = 'Hello'; // Works but slower +element.textContent = 'Hello' // Preferred +element.innerText = 'Hello' // Works but slower ``` --- ## Working with Attributes -HTML elements have attributes. JavaScript lets you read, write, and remove them. +HTML elements have attributes. JavaScript lets you read, write, and remove them using **[`getAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute)**, **[`setAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)**, **[`hasAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute)**, and **[`removeAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute)**. ### Standard Attribute Methods ```javascript -const link = document.querySelector('a'); +const link = document.querySelector('a') // Get attribute value -const href = link.getAttribute('href'); -const target = link.getAttribute('target'); +const href = link.getAttribute('href') +const target = link.getAttribute('target') // Set attribute value -link.setAttribute('href', 'https://example.com'); -link.setAttribute('target', '_blank'); +link.setAttribute('href', 'https://example.com') +link.setAttribute('target', '_blank') // Check if attribute exists if (link.hasAttribute('target')) { - console.log('Link opens in new tab'); + console.log('Link opens in new tab') } // Remove attribute -link.removeAttribute('target'); +link.removeAttribute('target') ``` ### Properties vs Attributes: The Difference @@ -850,20 +863,20 @@ This confuses many developers! **Attributes** are in the HTML. **Properties** ar ``` ```javascript -const input = document.querySelector('input'); +const input = document.querySelector('input') // ATTRIBUTE: The original HTML value -console.log(input.getAttribute('value')); // "initial" +console.log(input.getAttribute('value')) // "initial" // PROPERTY: The current state -console.log(input.value); // "initial" +console.log(input.value) // "initial" // User types "new text"... -console.log(input.getAttribute('value')); // Still "initial"! -console.log(input.value); // "new text" +console.log(input.getAttribute('value')) // Still "initial"! +console.log(input.value) // "new text" // Reset to attribute value -input.value = input.getAttribute('value'); +input.value = input.getAttribute('value') ``` Key differences: @@ -877,21 +890,21 @@ Key differences: ```javascript // Attribute is always a string -checkbox.getAttribute('checked'); // "" or null +checkbox.getAttribute('checked') // "" or null // Property is a boolean -checkbox.checked; // true or false +checkbox.checked // true or false // Attribute (string) -input.getAttribute('maxlength'); // "10" +input.getAttribute('maxlength') // "10" // Property (number) -input.maxLength; // 10 +input.maxLength // 10 ``` ### Data Attributes and the dataset API -Custom data attributes start with `data-` and are accessible via the `dataset` property: +Custom data attributes start with `data-` and are accessible via the **[`dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset)** property: ```html <div id="user" @@ -903,23 +916,23 @@ Custom data attributes start with `data-` and are accessible via the `dataset` p ``` ```javascript -const user = document.querySelector('#user'); +const user = document.querySelector('#user') // Read data attributes (camelCase!) -console.log(user.dataset.userId); // "123" -console.log(user.dataset.role); // "admin" -console.log(user.dataset.isActive); // "true" (string, not boolean!) +console.log(user.dataset.userId) // "123" +console.log(user.dataset.role) // "admin" +console.log(user.dataset.isActive) // "true" (string, not boolean!) // Write data attributes -user.dataset.lastLogin = '2024-01-15'; +user.dataset.lastLogin = '2024-01-15' // Creates: data-last-login="2024-01-15" // Delete data attributes -delete user.dataset.role; +delete user.dataset.role // Check if exists if ('userId' in user.dataset) { - console.log('Has user ID'); + console.log('Has user ID') } ``` @@ -946,27 +959,27 @@ element.title // element.getAttribute('title') ## Styling Elements -JavaScript can modify element styles in several ways. +JavaScript can modify element styles in several ways using the **[`style`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style)** property and **[`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)** API. ### The style Property (Inline Styles) ```javascript -const box = document.querySelector('.box'); +const box = document.querySelector('.box') // Set individual styles (camelCase!) -box.style.backgroundColor = 'blue'; -box.style.fontSize = '20px'; -box.style.marginTop = '10px'; +box.style.backgroundColor = 'blue' +box.style.fontSize = '20px' +box.style.marginTop = '10px' // Read styles (only reads INLINE styles!) -console.log(box.style.backgroundColor); // "blue" -console.log(box.style.color); // "" (not inline, from stylesheet) +console.log(box.style.backgroundColor) // "blue" +console.log(box.style.color) // "" (not inline, from stylesheet) // Set multiple styles at once -box.style.cssText = 'background: red; font-size: 16px; padding: 10px;'; +box.style.cssText = 'background: red; font-size: 16px; padding: 10px;' // Remove an inline style -box.style.backgroundColor = ''; // Removes the style +box.style.backgroundColor = '' // Removes the style ``` <Warning> @@ -975,74 +988,76 @@ box.style.backgroundColor = ''; // Removes the style ### getComputedStyle() - Read Actual Styles +Use **[`getComputedStyle()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle)** to read the final computed styles: + ```javascript -const box = document.querySelector('.box'); +const box = document.querySelector('.box') // Get all computed styles -const styles = getComputedStyle(box); +const styles = getComputedStyle(box) -console.log(styles.backgroundColor); // "rgb(0, 0, 255)" -console.log(styles.fontSize); // "16px" -console.log(styles.display); // "block" +console.log(styles.backgroundColor) // "rgb(0, 0, 255)" +console.log(styles.fontSize) // "16px" +console.log(styles.display) // "block" // Get pseudo-element styles -const beforeStyles = getComputedStyle(box, '::before'); -console.log(beforeStyles.content); // '"Hello"' +const beforeStyles = getComputedStyle(box, '::before') +console.log(beforeStyles.content) // '"Hello"' ``` ### classList - Manipulate CSS Classes -The `classList` API is the modern way to add/remove/toggle classes: +The **[`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)** API is the modern way to add/remove/toggle classes: ```javascript -const button = document.querySelector('button'); +const button = document.querySelector('button') // Add classes -button.classList.add('active'); -button.classList.add('btn', 'btn-primary'); // Multiple at once +button.classList.add('active') +button.classList.add('btn', 'btn-primary') // Multiple at once // Remove classes -button.classList.remove('active'); -button.classList.remove('btn', 'btn-primary'); // Multiple at once +button.classList.remove('active') +button.classList.remove('btn', 'btn-primary') // Multiple at once // Toggle (add if missing, remove if present) -button.classList.toggle('active'); +button.classList.toggle('active') // Toggle with condition -button.classList.toggle('active', isActive); // Add if isActive is true +button.classList.toggle('active', isActive) // Add if isActive is true // Check if class exists if (button.classList.contains('active')) { - console.log('Button is active'); + console.log('Button is active') } // Replace a class -button.classList.replace('btn-primary', 'btn-secondary'); +button.classList.replace('btn-primary', 'btn-secondary') // Iterate over classes -button.classList.forEach(cls => console.log(cls)); +button.classList.forEach(cls => console.log(cls)) // Get number of classes -console.log(button.classList.length); // 2 +console.log(button.classList.length) // 2 ``` ### className vs classList ```javascript // className is a string (old way) -element.className = 'btn btn-primary'; // Replaces ALL classes -element.className += ' active'; // Appending is clunky +element.className = 'btn btn-primary' // Replaces ALL classes +element.className += ' active' // Appending is clunky // classList is a DOMTokenList (modern way) -element.classList.add('active'); // Adds without affecting others -element.classList.remove('btn-primary'); // Removes specifically +element.classList.add('active') // Adds without affecting others +element.classList.remove('btn-primary') // Removes specifically ``` --- ## The Render Tree & Critical Rendering Path -Understanding how browsers render pages helps you write performant code. +Understanding how browsers render pages helps you write performant code. This is where [JavaScript Engines](/concepts/javascript-engines) and the browser's rendering engine work together. ### From HTML to Pixels @@ -1229,52 +1244,156 @@ getComputedStyle(element) ### Use requestAnimationFrame for Visual Changes +Use **[`requestAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)** to batch visual changes with the browser's render cycle: + ```javascript // Bad: DOM changes at unpredictable times window.addEventListener('scroll', () => { - element.style.transform = `translateY(${window.scrollY}px)`; -}); + element.style.transform = `translateY(${window.scrollY}px)` +}) // Good: Batch visual changes with next frame -let ticking = false; +let ticking = false window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { - element.style.transform = `translateY(${window.scrollY}px)`; - ticking = false; - }); - ticking = true; + element.style.transform = `translateY(${window.scrollY}px)` + ticking = false + }) + ticking = true } -}); +}) ``` --- +## The #1 DOM Mistake: Using innerHTML with User Input + +The most dangerous DOM mistake is using **[`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)** with untrusted content. This opens your application to **Cross-Site Scripting (XSS)** attacks. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ innerHTML: THE SECURITY TRAP │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ❌ DANGEROUS ✓ SAFE │ +│ ───────────── ────── │ +│ │ +│ User Input: User Input: │ +│ "<img src=x onerror=alert('XSS')>" "<img src=x onerror=...>" │ +│ │ │ │ +│ ▼ ▼ │ +│ element.innerHTML = userInput element.textContent = input │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ BROWSER PARSES │ │ DISPLAYED AS │ │ +│ │ AS REAL HTML! │ │ PLAIN TEXT │ │ +│ │ │ │ │ │ +│ │ 🚨 Script runs! │ │ "<img src=..." │ │ +│ │ Cookies stolen! │ │ (harmless) │ │ +│ └─────────────────┘ └─────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +```javascript +// ❌ DANGEROUS - Never do this with user input! +const username = getUserInput() // User enters: <img src=x onerror="stealCookies()"> +div.innerHTML = `Welcome, ${username}!` +// The malicious script EXECUTES! + +// ✓ SAFE - textContent escapes HTML +const username = getUserInput() +div.textContent = `Welcome, ${username}!` +// Displays: Welcome, <img src=x onerror="stealCookies()">! +// The HTML is shown as text, not executed + +// ✓ SAFE - Create elements programmatically +const username = getUserInput() +const welcomeText = document.createTextNode(`Welcome, ${username}!`) +div.appendChild(welcomeText) +``` + +<Warning> +**The Trap:** `innerHTML` looks convenient, but it parses strings as real HTML. If that string contains user input, attackers can inject `<script>` tags, malicious event handlers, or other dangerous code. **Always use `textContent` for user-provided content.** +</Warning> + +### Other Common Mistakes + +<AccordionGroup> + <Accordion title="Forgetting that querySelector returns null"> + ```javascript + // ❌ WRONG - Crashes if element doesn't exist + document.querySelector('.maybe-missing').classList.add('active') + // TypeError: Cannot read property 'classList' of null + + // ✓ CORRECT - Check first or use optional chaining + const element = document.querySelector('.maybe-missing') + if (element) { + element.classList.add('active') + } + + // Or use optional chaining (modern) + document.querySelector('.maybe-missing')?.classList.add('active') + ``` + </Accordion> + + <Accordion title="Using childNodes instead of children"> + ```javascript + // ❌ CONFUSING - Includes whitespace text nodes! + const ul = document.querySelector('ul') + console.log(ul.childNodes.length) // 7 (includes text nodes!) + + // ✓ CLEAR - Only element children + console.log(ul.children.length) // 3 (just the <li> elements) + ``` + </Accordion> + + <Accordion title="Layout thrashing in loops"> + ```javascript + // ❌ SLOW - Forces layout on every iteration + boxes.forEach(box => { + const width = box.offsetWidth // READ - forces layout + box.style.width = width + 10 + 'px' // WRITE - invalidates layout + }) + + // ✓ FAST - Batch reads, then batch writes + const widths = boxes.map(box => box.offsetWidth) // All reads + boxes.forEach((box, i) => { + box.style.width = widths[i] + 10 + 'px' // All writes + }) + ``` + </Accordion> +</AccordionGroup> + +--- + ## Common Patterns ### Event Delegation -Instead of adding listeners to many elements, add one to a parent: +Instead of adding listeners to many elements, add one to a parent. This pattern relies on event bubbling (see [Event Loop](/concepts/event-loop) for how the browser processes events): ```javascript // Bad: Many listeners document.querySelectorAll('.btn').forEach(btn => { - btn.addEventListener('click', handleClick); -}); + btn.addEventListener('click', handleClick) +}) // Good: One listener with delegation document.querySelector('.button-container').addEventListener('click', (e) => { - const btn = e.target.closest('.btn'); + const btn = e.target.closest('.btn') if (btn) { - handleClick(e); + handleClick(e) } -}); +}) ``` Benefits: - Works for dynamically added elements - Less memory usage -- Easier cleanup +- Easier cleanup (uses [closures](/concepts/scope-and-closures) to maintain handler references) ### Checking if Element Exists @@ -1297,17 +1416,19 @@ if (el !== null) { ### Waiting for DOM Ready +Listen for the **[`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event)** event to know when the DOM is ready: + ```javascript // Modern: DOMContentLoaded (DOM ready, images may still be loading) document.addEventListener('DOMContentLoaded', () => { - console.log('DOM is ready!'); + console.log('DOM is ready!') // Safe to query elements -}); +}) // Full page load (including images, stylesheets) window.addEventListener('load', () => { - console.log('Everything loaded!'); -}); + console.log('Everything loaded!') +}) // If script is at end of body, DOM is already ready // <script src="app.js"></script> <!-- Just before </body> --> @@ -1530,9 +1651,26 @@ window.addEventListener('load', () => { ## Reference -<Card title="Document Object Model (DOM) — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model"> - The comprehensive MDN reference for all DOM interfaces, methods, and properties. -</Card> +<CardGroup cols={2}> + <Card title="Document Object Model (DOM) — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model"> + The comprehensive MDN reference for all DOM interfaces, methods, and properties. + </Card> + <Card title="Document Interface — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Document"> + The Document interface representing the web page loaded in the browser. + </Card> + <Card title="Element Interface — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Element"> + The base class for all element objects in a Document. + </Card> + <Card title="Node Interface — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Node"> + The abstract base class for DOM nodes including elements, text, and comments. + </Card> + <Card title="NodeList Interface — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/NodeList"> + Collections of nodes returned by querySelectorAll and other methods. + </Card> + <Card title="HTMLCollection Interface — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection"> + Live collections of elements returned by getElementsByClassName and similar. + </Card> +</CardGroup> ## Books From dd9ffc3cd5102fa6fe2f5aac3fe2f243421e06e2 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:44:02 -0300 Subject: [PATCH 036/128] docs(http-fetch): add Tabs comparison, SEO description, and FormData tests - Add Tabs component comparing .then() vs async/await syntax - Enhance meta description for better SEO - Add missing --- separator before HTTP Fundamentals section - Add 10 FormData tests covering append, set, get, delete, File/Blob handling --- docs/concepts/http-fetch.mdx | 103 ++++++++++----- .../http-fetch/http-fetch.test.js | 120 ++++++++++++++++++ 2 files changed, 193 insertions(+), 30 deletions(-) diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index e06a0308..d375e83d 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -1,6 +1,6 @@ --- title: "HTTP & Fetch" -description: "Making network requests in JavaScript" +description: "Learn how to make network requests in JavaScript using the Fetch API, handle HTTP responses, parse JSON data, and avoid common error handling mistakes" --- How does JavaScript get data from a server? How do you load user profiles, submit forms, or fetch the latest posts from an API? The answer is the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** — JavaScript's modern way to make network requests. @@ -85,6 +85,8 @@ This request-response cycle is the core of how the web works. The **Fetch API** --- +--- + ## HTTP Fundamentals Before diving into the Fetch API, let's understand the key concepts of HTTP itself. @@ -224,37 +226,78 @@ The **Fetch API** is JavaScript's modern way to make HTTP requests. It replaced ### Basic GET Request -The simplest fetch call retrieves data from a URL. By default, `fetch()` uses the **GET** method, so you don't need to specify it: - -```javascript -// Basic fetch - returns a Promise -fetch('https://api.example.com/users') - .then(response => response.json()) - .then(data => console.log(data)) - .catch(error => console.error('Error:', error)) -``` +The simplest fetch call retrieves data from a URL. By default, `fetch()` uses the **GET** method, so you don't need to specify it. There are two ways to write this: -Let's break this down step by step: +<Tabs> + <Tab title="Promise .then()"> + ```javascript + // Basic fetch - returns a Promise + fetch('https://api.example.com/users') + .then(response => response.json()) + .then(data => console.log(data)) + .catch(error => console.error('Error:', error)) + ``` + + Let's break this down step by step: + + ```javascript + // Step 1: fetch() returns a Promise that resolves to a Response object + const responsePromise = fetch('https://api.example.com/users') + + // Step 2: When the response arrives, we get a Response object + responsePromise.then(response => { + console.log(response.status) // 200 + console.log(response.ok) // true + console.log(response.headers) // Headers object + + // Step 3: The body is a stream, we need to parse it + // .json() returns ANOTHER Promise + return response.json() + }) + .then(data => { + // Step 4: Now we have the actual data + console.log(data) // { users: [...] } + }) + ``` + </Tab> + <Tab title="async/await"> + ```javascript + // Using async/await - cleaner syntax + async function getUsers() { + try { + const response = await fetch('https://api.example.com/users') + const data = await response.json() + console.log(data) + } catch (error) { + console.error('Error:', error) + } + } + ``` + + Let's break this down step by step: + + ```javascript + async function getUsers() { + // Step 1: await pauses until the Response arrives + const response = await fetch('https://api.example.com/users') + + console.log(response.status) // 200 + console.log(response.ok) // true + console.log(response.headers) // Headers object + + // Step 2: await again to read and parse the body + const data = await response.json() + + // Step 3: Now we have the actual data + console.log(data) // { users: [...] } + } + ``` + </Tab> +</Tabs> -```javascript -// Step 1: fetch() returns a Promise that resolves to a Response object -const responsePromise = fetch('https://api.example.com/users') - -// Step 2: When the response arrives, we get a Response object -responsePromise.then(response => { - console.log(response.status) // 200 - console.log(response.ok) // true - console.log(response.headers) // Headers object - - // Step 3: The body is a stream, we need to parse it - // .json() returns ANOTHER Promise - return response.json() -}) -.then(data => { - // Step 4: Now we have the actual data - console.log(data) // { users: [...] } -}) -``` +<Tip> +**Which should you use?** `async/await` is generally preferred for its cleaner, more readable syntax. Use `.then()` chains when you need to integrate with older codebases or when you specifically want to avoid async functions. +</Tip> ### Understanding the Response Object diff --git a/tests/web-platform/http-fetch/http-fetch.test.js b/tests/web-platform/http-fetch/http-fetch.test.js index 37d5a692..3a11128f 100644 --- a/tests/web-platform/http-fetch/http-fetch.test.js +++ b/tests/web-platform/http-fetch/http-fetch.test.js @@ -1028,4 +1028,124 @@ describe('HTTP & Fetch', () => { expect(result.results[0]).toContain('javascript') }) }) + + // =========================================================================== + // FORMDATA + // =========================================================================== + describe('FormData', () => { + it('should create FormData object', () => { + const formData = new FormData() + formData.append('username', 'alice') + formData.append('email', 'alice@example.com') + + expect(formData.get('username')).toBe('alice') + expect(formData.get('email')).toBe('alice@example.com') + }) + + it('should append multiple values for same key', () => { + const formData = new FormData() + formData.append('tags', 'javascript') + formData.append('tags', 'nodejs') + + const tags = formData.getAll('tags') + expect(tags).toEqual(['javascript', 'nodejs']) + }) + + it('should set value (overwrite)', () => { + const formData = new FormData() + formData.append('name', 'alice') + formData.set('name', 'bob') + + expect(formData.get('name')).toBe('bob') + }) + + it('should check if key exists', () => { + const formData = new FormData() + formData.append('username', 'alice') + + expect(formData.has('username')).toBe(true) + expect(formData.has('password')).toBe(false) + }) + + it('should delete key', () => { + const formData = new FormData() + formData.append('username', 'alice') + formData.append('email', 'alice@example.com') + + formData.delete('email') + + expect(formData.has('email')).toBe(false) + expect(formData.has('username')).toBe(true) + }) + + it('should iterate over entries', () => { + const formData = new FormData() + formData.append('name', 'alice') + formData.append('age', '30') + + const entries = [] + for (const [key, value] of formData) { + entries.push([key, value]) + } + + expect(entries).toContainEqual(['name', 'alice']) + expect(entries).toContainEqual(['age', '30']) + }) + + it('should send FormData with fetch (no Content-Type header needed)', async () => { + global.fetch = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ success: true }), { status: 200 }) + ) + + const formData = new FormData() + formData.append('username', 'alice') + formData.append('avatar', new Blob(['fake image data'], { type: 'image/png' }), 'avatar.png') + + await fetch('/api/profile', { + method: 'POST', + body: formData + // Note: Don't set Content-Type header - browser sets it automatically with boundary + }) + + expect(fetch).toHaveBeenCalledWith('/api/profile', expect.objectContaining({ + method: 'POST', + body: expect.any(FormData) + })) + }) + + it('should parse FormData from response', async () => { + // Create a FormData-like body + const formData = new FormData() + formData.append('field1', 'value1') + formData.append('field2', 'value2') + + // Note: In real browsers, response.formData() parses multipart responses + // For testing, we verify the FormData API works correctly + expect(formData.get('field1')).toBe('value1') + expect(formData.get('field2')).toBe('value2') + }) + + it('should append File objects', () => { + const formData = new FormData() + const file = new File(['hello world'], 'test.txt', { type: 'text/plain' }) + + formData.append('document', file) + + const retrieved = formData.get('document') + expect(retrieved).toBeInstanceOf(File) + expect(retrieved.name).toBe('test.txt') + expect(retrieved.type).toBe('text/plain') + }) + + it('should append Blob objects with filename', () => { + const formData = new FormData() + const blob = new Blob(['image data'], { type: 'image/jpeg' }) + + formData.append('image', blob, 'photo.jpg') + + const retrieved = formData.get('image') + expect(retrieved).toBeInstanceOf(File) // Blob with filename becomes File + expect(retrieved.name).toBe('photo.jpg') + }) + }) }) From 889135c948725a70fdd1ad83b65c194e7e956c30 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 09:47:09 -0300 Subject: [PATCH 037/128] docs: add write-concept skill documentation and update gitignore - Add Custom Skills section to CLAUDE.md with write-concept skill info - Update repository structure to reflect .opencode skill location - Document SEO guidelines, page structure, and quality checklists - Update .gitignore to ignore .opencode/ folder instead of .claude/skills/ --- .claude/CLAUDE.md | 34 +++++++++++++++++++++++++++++++--- .gitignore | 4 ++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index d844d123..a3b6e7d3 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -17,9 +17,11 @@ The project was recognized by GitHub as one of the **top open source projects of ``` 33-js-concepts/ -├── .claude/ # OpenCode/Claude configuration -│ ├── CLAUDE.md # Project context and guidelines -│ └── skills/ # Custom skills for content creation +├── .claude/ # Claude configuration +│ └── CLAUDE.md # Project context and guidelines +├── .opencode/ # OpenCode configuration +│ └── skill/ # Custom skills for content creation +│ └── write-concept/ # Skill for writing concept documentation ├── docs/ # Mintlify documentation site │ ├── docs.json # Mintlify configuration │ ├── index.mdx # Homepage @@ -372,6 +374,32 @@ description: "Brief description" - Community contributions are welcome and encouraged - MIT Licensed +## Custom Skills + +### write-concept Skill + +Use the `/write-concept` skill when writing or improving concept documentation pages. This skill provides comprehensive guidelines for: + +- **Page Structure**: Exact template for concept pages (frontmatter, opening hook, code examples, sections) +- **SEO Optimization**: Critical guidelines for ranking in search results +- **Writing Style**: Voice, tone, and how to make content accessible to beginners +- **Code Examples**: Best practices for clear, educational code +- **Quality Checklists**: Verification steps before publishing + +**When to invoke:** +- Creating a new concept page in `/docs/concepts/` +- Rewriting or significantly improving an existing concept page +- Reviewing an existing concept page for quality + +**SEO is Critical:** Each concept page should rank for searches like: +- "what is [concept] in JavaScript" +- "how does [concept] work in JavaScript" +- "[concept] JavaScript explained" + +The skill includes detailed guidance on title optimization (50-60 chars), meta descriptions (150-160 chars), keyword placement, and featured snippet optimization. + +**Location:** `.opencode/skill/write-concept/SKILL.md` + ## Maintainer **Leonardo Maldonado** - [@leonardomso](https://github.com/leonardomso) diff --git a/.gitignore b/.gitignore index a5dd3a59..0cc13e70 100644 --- a/.gitignore +++ b/.gitignore @@ -63,5 +63,5 @@ typings/ # webstore IDE created directory .idea -# Claude/OpenCode skills (local only) -.claude/skills/ +# OpenCode configuration (local only) +.opencode/ From 2cc74723ef237a85d2c93dbeb38a1e3a01bc6ba0 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:18:00 -0300 Subject: [PATCH 038/128] docs(dom): SEO optimization and add 21 new tests - Update title for better SEO (50-60 chars) - Expand meta description to 168 chars with keywords - Add 'What is the DOM in JavaScript?' section for featured snippets - Rename H2s with 'How to' pattern for search intent - Move Key Takeaways before Test Your Knowledge section - Add MDN links for document properties - Standardize code style (remove semicolons) - Merge Courses into Videos section Tests added: - Security patterns (XSS with innerHTML vs textContent) - Attribute shortcuts (id, className, href, src, title) - className vs classList comparison - Performance patterns (caching, batching, layout thrashing) - Properties vs Attributes (maxLength, checked) - Clone ID collision prevention - Complex selectors (:not(), :first-of-type) --- docs/concepts/dom.mdx | 229 ++++++++++---------- tests/web-platform/dom/dom.test.js | 325 +++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+), 113 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 1c7e29f3..ff60621c 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -1,11 +1,28 @@ --- -title: "DOM and Layout Trees" -description: "Understanding the Document Object Model and how browsers render pages" +title: "DOM in JavaScript: How Browsers Represent Web Pages" +description: "Learn how the DOM works in JavaScript. Understand how browsers represent HTML as a tree, select and manipulate elements, traverse nodes, and optimize rendering performance." --- How does JavaScript change what you see on a webpage? How do you click a button and see new content appear, or type in a form and watch suggestions pop up? How does a "dark mode" toggle instantly transform an entire page? -The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface for web documents. It represents your HTML as a **tree of objects** that JavaScript can read and manipulate. +The answer lies in the **DOM** — the bridge between your HTML and JavaScript. + +<Info> +**What you'll learn in this guide:** +- What the DOM is in JavaScript and how it differs from HTML +- How to select DOM elements (getElementById vs querySelector) +- How to traverse the DOM tree (parent, children, siblings) +- How to manipulate DOM elements (create, modify, remove) +- The difference between properties and attributes +- How the browser turns DOM → pixels (the Critical Rendering Path) +- Performance best practices (avoid layout thrashing!) +</Info> + +--- + +## What is the DOM in JavaScript? + +The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface that represents HTML documents as a tree of objects. When a browser loads a webpage, it parses the HTML and creates the DOM — a live, structured representation that JavaScript can read and modify. Every element, attribute, and piece of text becomes a node in this tree. ```javascript // The DOM lets you do things like this: @@ -16,17 +33,6 @@ document.getElementById('btn').addEventListener('click', handleClick) With the DOM, you can use methods like **[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** to find elements, **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** to grab specific nodes, and **[`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)** to respond to user interactions. -<Info> -**What you'll learn in this guide:** -- What the DOM actually is (and what it's NOT) -- How to select elements like a pro (getElementById vs querySelector) -- How to traverse the tree (parent, children, siblings) -- How to create, modify, and remove elements -- The difference between properties and attributes -- How the browser turns DOM → pixels (the Critical Rendering Path) -- Performance best practices (avoid layout thrashing!) -</Info> - --- ## The Family Tree: Understanding DOM Structure @@ -153,18 +159,18 @@ DOM Render Tree ### The `document` Object: Your Entry Point -The **[`document`](https://developer.mozilla.org/en-US/docs/Web/API/Document)** object is your gateway to the DOM. It's automatically available in any browser JavaScript: +The **[`document`](https://developer.mozilla.org/en-US/docs/Web/API/Document)** object is your gateway to the DOM. It's automatically available in any browser JavaScript. Key properties include **[`document.documentElement`](https://developer.mozilla.org/en-US/docs/Web/API/Document/documentElement)** (the root `<html>` element), **[`document.head`](https://developer.mozilla.org/en-US/docs/Web/API/Document/head)**, **[`document.body`](https://developer.mozilla.org/en-US/docs/Web/API/Document/body)**, and **[`document.title`](https://developer.mozilla.org/en-US/docs/Web/API/Document/title)**: ```javascript // document is the root of everything -console.log(document); // The entire document -console.log(document.documentElement); // <html> element -console.log(document.head); // <head> element -console.log(document.body); // <body> element -console.log(document.title); // Page title (getter/setter!) +console.log(document) // The entire document +console.log(document.documentElement) // <html> element +console.log(document.head) // <head> element +console.log(document.body) // <body> element +console.log(document.title) // Page title (getter/setter!) // You can modify the document -document.title = 'New Title'; // Changes browser tab title +document.title = 'New Title' // Changes browser tab title ``` --- @@ -230,7 +236,7 @@ Node.DOCUMENT_FRAGMENT_NODE // 11 // Check if something is an element if (node.nodeType === Node.ELEMENT_NODE) { - console.log('This is an element!'); + console.log('This is an element!') } ``` @@ -267,7 +273,7 @@ div#container --- -## Selecting Elements +## How to Select DOM Elements Before you can manipulate an element, you need to find it. JavaScript provides several methods through the **[`document`](https://developer.mozilla.org/en-US/docs/Web/API/Document)** object: @@ -428,7 +434,7 @@ This is faster than searching the entire document and helps avoid selecting unin --- -## Traversing the DOM +## How to Traverse the DOM Once you have an element, you can navigate to related elements without querying the entire document. @@ -1169,46 +1175,46 @@ DOM operations can be slow. Here's how to keep your pages fast. ```javascript // Bad: Queries the DOM every iteration for (let i = 0; i < 1000; i++) { - document.querySelector('.result').textContent += i; + document.querySelector('.result').textContent += i } // Good: Query once, reuse -const result = document.querySelector('.result'); +const result = document.querySelector('.result') for (let i = 0; i < 1000; i++) { - result.textContent += i; + result.textContent += i } // Even better: Build string, set once -const result = document.querySelector('.result'); -let text = ''; +const result = document.querySelector('.result') +let text = '' for (let i = 0; i < 1000; i++) { - text += i; + text += i } -result.textContent = text; +result.textContent = text ``` ### Batch DOM Updates ```javascript // Bad: 3 separate reflows -element.style.width = '100px'; -element.style.height = '200px'; -element.style.margin = '10px'; +element.style.width = '100px' +element.style.height = '200px' +element.style.margin = '10px' // Good: Single reflow with cssText -element.style.cssText = 'width: 100px; height: 200px; margin: 10px;'; +element.style.cssText = 'width: 100px; height: 200px; margin: 10px;' // Good: Single reflow with class -element.classList.add('my-styles'); +element.classList.add('my-styles') // Good: DocumentFragment for multiple elements -const fragment = document.createDocumentFragment(); +const fragment = document.createDocumentFragment() items.forEach(item => { - const li = document.createElement('li'); - li.textContent = item; - fragment.appendChild(li); -}); -ul.appendChild(fragment); // Single DOM update + const li = document.createElement('li') + li.textContent = item + fragment.appendChild(li) +}) +ul.appendChild(fragment) // Single DOM update ``` ### Avoid Layout Thrashing @@ -1218,15 +1224,15 @@ ul.appendChild(fragment); // Single DOM update ```javascript // TERRIBLE: Forces layout on EVERY iteration boxes.forEach(box => { - const width = box.offsetWidth; // Read (forces layout) - box.style.width = (width + 10) + 'px'; // Write (invalidates layout) -}); + const width = box.offsetWidth // Read (forces layout) + box.style.width = (width + 10) + 'px' // Write (invalidates layout) +}) // GOOD: Batch reads, then batch writes -const widths = boxes.map(box => box.offsetWidth); // Read all +const widths = boxes.map(box => box.offsetWidth) // Read all boxes.forEach((box, i) => { - box.style.width = (widths[i] + 10) + 'px'; // Write all -}); + box.style.width = (widths[i] + 10) + 'px' // Write all +}) ``` **Properties that trigger layout when read:** @@ -1399,16 +1405,16 @@ Benefits: ```javascript // Using querySelector (returns null if not found) -const element = document.querySelector('.maybe-exists'); +const element = document.querySelector('.maybe-exists') if (element) { - element.textContent = 'Found!'; + element.textContent = 'Found!' } // Optional chaining (modern) -document.querySelector('.maybe-exists')?.classList.add('active'); +document.querySelector('.maybe-exists')?.classList.add('active') // With getElementById -const el = document.getElementById('myId'); +const el = document.getElementById('myId') if (el !== null) { // Element exists } @@ -1443,6 +1449,34 @@ window.addEventListener('load', () => { --- +## Key Takeaways + +<Info> +**Remember these essential points:** + +1. **The DOM is a tree** — Elements are nodes with parent, child, and sibling relationships + +2. **DOM ≠ HTML source** — The browser fixes errors and JavaScript modifies it + +3. **Use querySelector** — More flexible than getElementById, accepts any CSS selector + +4. **Element vs Node properties** — Use `children`, `firstElementChild`, etc. to skip text nodes + +5. **closest() is your friend** — Perfect for event delegation and finding ancestor elements + +6. **innerHTML is dangerous** — Never use with user input; use textContent instead + +7. **Attributes vs Properties** — Attributes are HTML source, properties are live DOM state + +8. **classList over className** — Use add/remove/toggle for cleaner class manipulation + +9. **Batch DOM operations** — Use DocumentFragment or build strings to minimize reflows + +10. **Avoid layout thrashing** — Don't alternate reading and writing layout properties +</Info> + +--- + ## Test Your Knowledge <AccordionGroup> @@ -1458,8 +1492,8 @@ window.addEventListener('load', () => { // <li>Two</li> // </ul> - ul.childNodes.length; // 5 (text, li, text, li, text) - ul.children.length; // 2 (li, li) + ul.childNodes.length // 5 (text, li, text, li, text) + ul.children.length // 2 (li, li) ``` **Rule:** Use `children` unless you specifically need text/comment nodes. @@ -1470,10 +1504,10 @@ window.addEventListener('load', () => { ```javascript // User input: <img src=x onerror="stealCookies()"> - div.innerHTML = userInput; // Executes malicious code! + div.innerHTML = userInput // Executes malicious code! // Safe: textContent escapes HTML - div.textContent = userInput; // Displays as plain text + div.textContent = userInput // Displays as plain text ``` Always sanitize HTML or use `textContent` for user-provided content. @@ -1489,8 +1523,8 @@ window.addEventListener('load', () => { // <input value="initial"> // User types "hello" - input.getAttribute('value'); // "initial" - input.value; // "hello" + input.getAttribute('value') // "initial" + input.value // "hello" ``` Attributes are the HTML source. Properties are the live DOM state. @@ -1504,20 +1538,20 @@ window.addEventListener('load', () => { // <button class="btn">Click</button> // </div> - btn.closest('.card'); // Returns the parent div - btn.closest('button'); // Returns btn itself (it matches!) - btn.closest('.modal'); // null (no matching ancestor) + btn.closest('.card') // Returns the parent div + btn.closest('button') // Returns btn itself (it matches!) + btn.closest('.modal') // null (no matching ancestor) ``` **Super useful for event delegation:** ```javascript document.addEventListener('click', (e) => { - const card = e.target.closest('.card'); + const card = e.target.closest('.card') if (card) { // Handle click inside any card } - }); + }) ``` </Accordion> @@ -1527,16 +1561,16 @@ window.addEventListener('load', () => { ```javascript // BAD: Read-write-read-write pattern boxes.forEach(box => { - const width = box.offsetWidth; // READ → forces layout - box.style.width = width + 10 + 'px'; // WRITE → invalidates layout - }); + const width = box.offsetWidth // READ → forces layout + box.style.width = width + 10 + 'px' // WRITE → invalidates layout + }) // Each iteration forces a new layout calculation! // GOOD: Batch reads, then batch writes - const widths = boxes.map(b => b.offsetWidth); // All reads + const widths = boxes.map(b => b.offsetWidth) // All reads boxes.forEach((box, i) => { - box.style.width = widths[i] + 10 + 'px'; // All writes - }); + box.style.width = widths[i] + 10 + 'px' // All writes + }) // Only one layout calculation! ``` </Accordion> @@ -1568,14 +1602,14 @@ window.addEventListener('load', () => { | Speed | Slightly faster | Slightly slower | ```javascript - const live = document.getElementsByClassName('item'); - const staticList = document.querySelectorAll('.item'); + const live = document.getElementsByClassName('item') + const staticList = document.querySelectorAll('.item') // Add new element with class="item" - document.body.appendChild(newItem); + document.body.appendChild(newItem) - live.length; // Increased (live collection) - staticList.length; // Same (static snapshot) + live.length // Increased (live collection) + staticList.length // Same (static snapshot) ``` </Accordion> @@ -1583,15 +1617,15 @@ window.addEventListener('load', () => { **Answer:** Use a **DocumentFragment** to batch insertions: ```javascript - const fragment = document.createDocumentFragment(); + const fragment = document.createDocumentFragment() for (let i = 0; i < 1000; i++) { - const li = document.createElement('li'); - li.textContent = `Item ${i}`; - fragment.appendChild(li); // No reflow (fragment is detached) + const li = document.createElement('li') + li.textContent = `Item ${i}` + fragment.appendChild(li) // No reflow (fragment is detached) } - ul.appendChild(fragment); // Single reflow! + ul.appendChild(fragment) // Single reflow! ``` A DocumentFragment is a virtual container. When appended, only its children are inserted—the fragment disappears. @@ -1602,34 +1636,6 @@ window.addEventListener('load', () => { --- -## Key Takeaways - -<Info> -**Remember these essential points:** - -1. **The DOM is a tree** — Elements are nodes with parent, child, and sibling relationships - -2. **DOM ≠ HTML source** — The browser fixes errors and JavaScript modifies it - -3. **Use querySelector** — More flexible than getElementById, accepts any CSS selector - -4. **Element vs Node properties** — Use `children`, `firstElementChild`, etc. to skip text nodes - -5. **closest() is your friend** — Perfect for event delegation and finding ancestor elements - -6. **innerHTML is dangerous** — Never use with user input; use textContent instead - -7. **Attributes vs Properties** — Attributes are HTML source, properties are live DOM state - -8. **classList over className** — Use add/remove/toggle for cleaner class manipulation - -9. **Batch DOM operations** — Use DocumentFragment or build strings to minimize reflows - -10. **Avoid layout thrashing** — Don't alternate reading and writing layout properties -</Info> - ---- - ## Related Concepts <CardGroup cols={2}> @@ -1710,15 +1716,12 @@ window.addEventListener('load', () => { </Card> </CardGroup> -## Courses - -<Card title="JavaScript DOM Manipulation – Full Course for Beginners" icon="graduation-cap" href="https://www.youtube.com/watch?v=5fb2aPlgoys"> - A comprehensive freeCodeCamp course covering DOM selection, traversal, manipulation, and events from the ground up. -</Card> - ## Videos <CardGroup cols={2}> + <Card title="JavaScript DOM Manipulation – Full Course for Beginners" icon="graduation-cap" href="https://www.youtube.com/watch?v=5fb2aPlgoys"> + A comprehensive freeCodeCamp course covering DOM selection, traversal, manipulation, and events from the ground up. + </Card> <Card title="JavaScript DOM Tutorial" icon="video" href="https://www.youtube.com/watch?v=FIORjGvT0kk"> The Net Ninja's popular playlist covering DOM fundamentals step by step. </Card> diff --git a/tests/web-platform/dom/dom.test.js b/tests/web-platform/dom/dom.test.js index 17c1d280..2c61cf8b 100644 --- a/tests/web-platform/dom/dom.test.js +++ b/tests/web-platform/dom/dom.test.js @@ -897,5 +897,330 @@ describe('DOM and Layout Trees', () => { expect(clickedCard.classList.contains('card')).toBe(true) }) }) + + describe('Security Patterns - XSS Prevention', () => { + it('should demonstrate innerHTML vulnerability with script-like content', () => { + document.body.innerHTML = '<div id="output"></div>' + const output = document.getElementById('output') + + // innerHTML can render HTML - potential XSS vector + const maliciousInput = '<img src="x" onerror="alert(1)">' + output.innerHTML = maliciousInput + + // The img tag is actually created + const img = output.querySelector('img') + expect(img).not.toBeNull() + expect(img.getAttribute('onerror')).toBe('alert(1)') + }) + + it('should use textContent to safely render user input', () => { + document.body.innerHTML = '<div id="output"></div>' + const output = document.getElementById('output') + + // textContent escapes HTML - safe from XSS + const maliciousInput = '<img src="x" onerror="alert(1)">' + output.textContent = maliciousInput + + // No img tag created - text is escaped + const img = output.querySelector('img') + expect(img).toBeNull() + expect(output.textContent).toBe('<img src="x" onerror="alert(1)">') + }) + + it('should show difference between innerHTML and textContent with HTML entities', () => { + document.body.innerHTML = '<div id="html-output"></div><div id="text-output"></div>' + + const htmlOutput = document.getElementById('html-output') + const textOutput = document.getElementById('text-output') + + const userInput = '<script>steal(cookies)</script>' + + htmlOutput.innerHTML = userInput + textOutput.textContent = userInput + + // innerHTML parses the HTML (script won't execute in modern browsers but DOM is modified) + expect(htmlOutput.children.length).toBeGreaterThanOrEqual(0) + + // textContent treats it as plain text + expect(textOutput.textContent).toBe('<script>steal(cookies)</script>') + expect(textOutput.children.length).toBe(0) + }) + }) + + describe('Attribute Shortcuts', () => { + it('should access id directly on element', () => { + document.body.innerHTML = '<div id="myElement"></div>' + const element = document.getElementById('myElement') + + expect(element.id).toBe('myElement') + + element.id = 'newId' + expect(element.id).toBe('newId') + expect(document.getElementById('newId')).toBe(element) + }) + + it('should access className directly on element', () => { + document.body.innerHTML = '<div class="box large"></div>' + const element = document.querySelector('.box') + + expect(element.className).toBe('box large') + + element.className = 'container small' + expect(element.className).toBe('container small') + }) + + it('should access href directly on anchor elements', () => { + document.body.innerHTML = '<a href="https://example.com">Link</a>' + const link = document.querySelector('a') + + expect(link.href).toBe('https://example.com/') + + link.href = 'https://test.com' + expect(link.href).toBe('https://test.com/') + }) + + it('should access src directly on image elements', () => { + document.body.innerHTML = '<img src="photo.jpg" alt="Photo">' + const img = document.querySelector('img') + + expect(img.src).toContain('photo.jpg') + + img.src = 'newphoto.png' + expect(img.src).toContain('newphoto.png') + }) + + it('should access title directly on elements', () => { + document.body.innerHTML = '<button title="Click me">Button</button>' + const button = document.querySelector('button') + + expect(button.title).toBe('Click me') + + button.title = 'New tooltip' + expect(button.title).toBe('New tooltip') + }) + }) + + describe('className vs classList Comparison', () => { + it('should replace all classes when using className', () => { + document.body.innerHTML = '<div class="one two three"></div>' + const element = document.querySelector('div') + + // className replaces everything + element.className = 'four' + + expect(element.className).toBe('four') + expect(element.classList.contains('one')).toBe(false) + expect(element.classList.contains('four')).toBe(true) + }) + + it('should add single class without affecting others using classList', () => { + document.body.innerHTML = '<div class="one two three"></div>' + const element = document.querySelector('div') + + // classList.add preserves existing classes + element.classList.add('four') + + expect(element.classList.contains('one')).toBe(true) + expect(element.classList.contains('two')).toBe(true) + expect(element.classList.contains('three')).toBe(true) + expect(element.classList.contains('four')).toBe(true) + }) + + it('should toggle class on and off with classList', () => { + document.body.innerHTML = '<div class="active"></div>' + const element = document.querySelector('div') + + expect(element.classList.contains('active')).toBe(true) + + element.classList.toggle('active') + expect(element.classList.contains('active')).toBe(false) + + element.classList.toggle('active') + expect(element.classList.contains('active')).toBe(true) + }) + }) + + describe('Performance Patterns', () => { + it('should cache DOM references instead of repeated queries', () => { + document.body.innerHTML = '<div id="target">Content</div>' + + // Bad: querying multiple times (we just demonstrate the pattern) + const query1 = document.getElementById('target') + const query2 = document.getElementById('target') + const query3 = document.getElementById('target') + + // Good: cache the reference + const cached = document.getElementById('target') + const ref1 = cached + const ref2 = cached + const ref3 = cached + + // All references point to same element + expect(ref1).toBe(ref2) + expect(ref2).toBe(ref3) + expect(cached).toBe(query1) + }) + + it('should batch DOM updates using documentFragment', () => { + document.body.innerHTML = '<ul id="list"></ul>' + const list = document.getElementById('list') + + // Use fragment to batch insertions + const fragment = document.createDocumentFragment() + + for (let i = 0; i < 5; i++) { + const li = document.createElement('li') + li.textContent = `Item ${i}` + fragment.appendChild(li) + } + + // Single DOM update + list.appendChild(fragment) + + expect(list.children.length).toBe(5) + expect(list.children[0].textContent).toBe('Item 0') + expect(list.children[4].textContent).toBe('Item 4') + }) + + it('should avoid layout thrashing by batching reads and writes', () => { + document.body.innerHTML = ` + <div class="box" style="width: 100px; height: 100px;"></div> + <div class="box" style="width: 100px; height: 100px;"></div> + ` + const boxes = document.querySelectorAll('.box') + + // Good pattern: read all first, then write all + const heights = [] + + // Batch reads - get heights from style (JSDOM doesn't compute offsetHeight) + boxes.forEach(box => { + heights.push(parseInt(box.style.height, 10)) + }) + + // Batch writes + boxes.forEach((box, i) => { + box.style.height = `${heights[i] + 10}px` + }) + + expect(boxes[0].style.height).toBe('110px') + expect(boxes[1].style.height).toBe('110px') + }) + + it('should use textContent for better performance than innerHTML for text', () => { + document.body.innerHTML = '<div id="target"></div>' + const target = document.getElementById('target') + + // textContent is faster for plain text (no HTML parsing) + target.textContent = 'Plain text content' + + expect(target.textContent).toBe('Plain text content') + expect(target.innerHTML).toBe('Plain text content') + expect(target.children.length).toBe(0) + }) + }) + + describe('Properties vs Attributes Extended', () => { + it('should handle maxLength property on input', () => { + document.body.innerHTML = '<input type="text" maxlength="10">' + const input = document.querySelector('input') + + // Property returns number + expect(input.maxLength).toBe(10) + expect(typeof input.maxLength).toBe('number') + + // Attribute returns string + expect(input.getAttribute('maxlength')).toBe('10') + expect(typeof input.getAttribute('maxlength')).toBe('string') + }) + + it('should handle checked property on different input types', () => { + document.body.innerHTML = ` + <input type="checkbox" id="cb" checked> + <input type="radio" id="rb" name="group" checked> + ` + + const checkbox = document.getElementById('cb') + const radio = document.getElementById('rb') + + // Both have boolean checked property + expect(checkbox.checked).toBe(true) + expect(radio.checked).toBe(true) + + // Toggle checkbox + checkbox.checked = false + expect(checkbox.checked).toBe(false) + + // Attribute still shows original + expect(checkbox.hasAttribute('checked')).toBe(true) + }) + }) + + describe('Clone ID Collision Prevention', () => { + it('should demonstrate ID collision issue with cloneNode', () => { + document.body.innerHTML = '<div id="original">Content</div>' + const original = document.getElementById('original') + + // Clone keeps the same ID - causes collision! + const clone = original.cloneNode(true) + document.body.appendChild(clone) + + // Now we have two elements with same ID + const allWithId = document.querySelectorAll('#original') + expect(allWithId.length).toBe(2) + + // getElementById returns only first one + expect(document.getElementById('original')).toBe(original) + }) + + it('should fix ID collision by changing cloned element ID', () => { + document.body.innerHTML = '<div id="original">Content</div>' + const original = document.getElementById('original') + + const clone = original.cloneNode(true) + + // Fix: change ID before appending + clone.id = 'clone-1' + document.body.appendChild(clone) + + // No collision - both accessible + expect(document.getElementById('original')).toBe(original) + expect(document.getElementById('clone-1')).toBe(clone) + expect(document.getElementById('clone-1').textContent).toBe('Content') + }) + }) + + describe('Complex Selectors', () => { + it('should select elements using :not() pseudo-selector', () => { + document.body.innerHTML = ` + <button class="btn">Normal</button> + <button class="btn disabled">Disabled</button> + <button class="btn">Another</button> + ` + + // Select buttons that are NOT disabled + const activeButtons = document.querySelectorAll('.btn:not(.disabled)') + + expect(activeButtons.length).toBe(2) + expect(activeButtons[0].textContent).toBe('Normal') + expect(activeButtons[1].textContent).toBe('Another') + }) + + it('should select elements using :first-of-type pseudo-selector', () => { + document.body.innerHTML = ` + <div class="container"> + <span>First span</span> + <p>First paragraph</p> + <span>Second span</span> + <p>Second paragraph</p> + </div> + ` + + const firstSpan = document.querySelector('.container span:first-of-type') + const firstP = document.querySelector('.container p:first-of-type') + + expect(firstSpan.textContent).toBe('First span') + expect(firstP.textContent).toBe('First paragraph') + }) + }) }) }) From e616ea8d8cbd616f2581714e3e22b35bc3bec66c Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:24:39 -0300 Subject: [PATCH 039/128] docs(event-loop): restructure opening for SEO and add 5 new tests - Update title to include 'JavaScript' keyword for SEO - Expand meta description to 156 characters with key learning outcomes - Add opening hook with engaging questions and immediate code example - Add prerequisite Warning box linking to call-stack and promises - Move 'What you'll learn' Info box after opening - Rename analogy section to 'Understanding the Event Loop' - Add 12 inline MDN links throughout document - Add internal links for Call Stack, Promises, async/await references - Expand Reference section from 1 to 4 MDN cards - Add tests for 4ms minimum delay, debounce pattern, setInterval async - Update all test line number comments to match new structure --- docs/concepts/event-loop.mdx | 90 ++++-- .../event-loop/event-loop.test.js | 256 +++++++++++++++--- 2 files changed, 287 insertions(+), 59 deletions(-) diff --git a/docs/concepts/event-loop.mdx b/docs/concepts/event-loop.mdx index 19ca9703..050c4c89 100644 --- a/docs/concepts/event-loop.mdx +++ b/docs/concepts/event-loop.mdx @@ -1,9 +1,42 @@ --- -title: "Event Loop, Timers and Scheduling" -description: "Understanding JavaScript's concurrency model, message queues, and how to schedule code execution" +title: "The JavaScript Event Loop: How Async Code Actually Runs" +description: "Learn how the JavaScript event loop handles async code. Understand the call stack, task queue, microtasks, and why Promises always run before setTimeout()." --- -## The Restaurant Kitchen: A Real-World Analogy +How does JavaScript handle multiple things at once when it can only do one thing at a time? Why does this code print in a surprising order? + +```javascript +console.log('Start'); +setTimeout(() => console.log('Timeout'), 0); +Promise.resolve().then(() => console.log('Promise')); +console.log('End'); + +// Output: +// Start +// End +// Promise +// Timeout +``` + +Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model)** — JavaScript's mechanism for handling asynchronous operations while remaining single-threaded. + +<Warning> +**Prerequisites:** This guide assumes familiarity with [the call stack](/concepts/call-stack) and [Promises](/concepts/promises). If those concepts are new to you, read them first! +</Warning> + +<Info> +**What you'll learn in this guide:** +- Why JavaScript needs an event loop (and what "single-threaded" really means) +- How setTimeout REALLY works (spoiler: the delay is NOT guaranteed!) +- The difference between tasks and microtasks (and why it matters) +- Why `Promise.then()` runs before `setTimeout(..., 0)` +- How to use setTimeout, setInterval, and requestAnimationFrame effectively +- Common interview questions explained step-by-step +</Info> + +--- + +## Understanding the Event Loop: A Restaurant Analogy Imagine a busy restaurant kitchen with a **single chef** who can only cook one dish at a time. Despite this limitation, the restaurant serves hundreds of customers because the kitchen has a clever system: @@ -57,16 +90,6 @@ The chef (JavaScript) can only work on one dish (task) at a time. But kitchen ti **TL;DR:** JavaScript is single-threaded but achieves concurrency by delegating work to browser APIs, which run in the background. When they're done, callbacks go into queues. The Event Loop moves callbacks from queues to the call stack when it's empty. </Note> -<Info> -**What you'll learn in this guide:** -- Why JavaScript needs an event loop (and what "single-threaded" really means) -- How setTimeout REALLY works (spoiler: the delay is NOT guaranteed!) -- The difference between tasks and microtasks (and why it matters) -- Why `Promise.then()` runs before `setTimeout(..., 0)` -- How to use setTimeout, setInterval, and requestAnimationFrame effectively -- Common interview questions explained step-by-step -</Info> - --- ## The Problem: JavaScript is Single-Threaded @@ -82,7 +105,7 @@ console.log('Third'); // 3. Then this ### Why Is This a Problem? -Imagine if every operation blocked the entire program: +Imagine if every operation blocked the entire program. Consider the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API): ```javascript // If fetch() was synchronous (blocking)... @@ -99,7 +122,7 @@ A 30-second API call would freeze your entire webpage for 30 seconds. Users woul ### The Solution: Asynchronous JavaScript -JavaScript solves this by **delegating** long-running tasks to the browser (or Node.js), which handles them in the background: +JavaScript solves this by **delegating** long-running tasks to the browser (or Node.js), which handles them in the background. Functions like [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) don't block: ```javascript console.log('Start'); @@ -182,7 +205,7 @@ To understand the Event Loop, you need to see the full picture: <AccordionGroup> <Accordion title="Call Stack"> - The **Call Stack** is where JavaScript keeps track of what function is currently running. It's a LIFO (Last In, First Out) structure — like a stack of plates. + The **[Call Stack](/concepts/call-stack)** is where JavaScript keeps track of what function is currently running. It's a LIFO (Last In, First Out) structure — like a stack of plates. ```javascript function multiply(a, b) { @@ -227,10 +250,10 @@ To understand the Event Loop, you need to see the full picture: These are **NOT** part of JavaScript itself! They're provided by the environment: **Browser APIs:** - - `setTimeout`, `setInterval` - - `fetch`, `XMLHttpRequest` + - [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) + - [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), `XMLHttpRequest` - DOM events (click, scroll, etc.) - - `requestAnimationFrame` + - [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) - Geolocation, WebSockets, IndexedDB **Node.js APIs:** @@ -256,9 +279,9 @@ To understand the Event Loop, you need to see the full picture: <Accordion title="Microtask Queue"> The **Microtask Queue** holds high-priority callbacks from: - `Promise.then()`, `.catch()`, `.finally()` - - `queueMicrotask()` - - `MutationObserver` - - Code after `await` in async functions + - [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) + - [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) + - Code after `await` in [async functions](/concepts/async-await) **Microtasks ALWAYS run before the next task!** The entire microtask queue is drained before moving to the task queue. </Accordion> @@ -456,7 +479,7 @@ Even though the second promise is created AFTER setTimeout was registered, it st | I/O callbacks | Network responses, file reads | | UI Events | click, scroll, keydown, mousemove | | `setImmediate(fn)` | Node.js only, runs after I/O | -| `MessageChannel` | `postMessage` callbacks | +| [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) | `postMessage` callbacks | <Note> **What about requestAnimationFrame?** rAF is NOT a task — it runs during the rendering phase, after microtasks but before the browser paints. It's covered in detail in the [Timers section](#requestanimationframe-smooth-animations). @@ -1146,7 +1169,7 @@ button.addEventListener('click', () => { <Tabs> <Tab title="Web Workers"> - Move heavy computation to a separate thread: + Move heavy computation to a separate thread using [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API): ```javascript // main.js @@ -1194,7 +1217,7 @@ button.addEventListener('click', () => { </Tab> <Tab title="requestIdleCallback"> - Run code during browser idle time: + Run code during browser idle time with [`requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback): ```javascript function doNonCriticalWork(deadline) { @@ -1589,9 +1612,20 @@ Watch how: ## Reference -<Card title="JavaScript Execution Model — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model"> - Official MDN documentation on the JavaScript runtime, event loop, and execution contexts. -</Card> +<CardGroup cols={2}> + <Card title="JavaScript Execution Model — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model"> + Official MDN documentation on the JavaScript runtime, event loop, and execution contexts. + </Card> + <Card title="setTimeout — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/setTimeout"> + Complete reference for setTimeout including syntax, parameters, and the minimum delay behavior. + </Card> + <Card title="setInterval — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/setInterval"> + Documentation for repeated timed callbacks with usage patterns and gotchas. + </Card> + <Card title="requestAnimationFrame — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame"> + Browser-optimized animation timing API that syncs with display refresh rate. + </Card> +</CardGroup> ## Articles diff --git a/tests/functions-execution/event-loop/event-loop.test.js b/tests/functions-execution/event-loop/event-loop.test.js index 9a661f4c..0785716a 100644 --- a/tests/functions-execution/event-loop/event-loop.test.js +++ b/tests/functions-execution/event-loop/event-loop.test.js @@ -8,7 +8,7 @@ describe('Event Loop, Timers and Scheduling', () => { describe('Synchronous Execution', () => { it('should execute code one line at a time in order', () => { - // From lines 76-80: JavaScript executes these ONE AT A TIME, in order + // From lines 99-104: JavaScript executes these ONE AT A TIME, in order const order = [] order.push('First') // 1. This runs @@ -19,7 +19,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should execute nested function calls correctly (multiply, square, printSquare)', () => { - // From lines 187-201: Call Stack example + // From lines 210-224: Call Stack example function multiply(a, b) { return a * b } @@ -39,7 +39,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should store objects and arrays in the heap', () => { - // From lines 220-222: Heap example + // From lines 243-245: Heap example const user = { name: 'Alice' } // Object stored in heap const numbers = [1, 2, 3] // Array stored in heap @@ -74,7 +74,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should pass arguments to the callback', async () => { - // From lines 540-543: Pass arguments to the callback + // From lines 562-566: Pass arguments to the callback let result = '' setTimeout((name, greeting) => { @@ -87,7 +87,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should cancel timeout with clearTimeout', async () => { - // From lines 548-554: Canceling a timeout + // From lines 569-577: Canceling a timeout const callback = vi.fn() const timerId = setTimeout(callback, 5000) @@ -101,7 +101,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should demonstrate the zero delay myth - setTimeout(fn, 0) does NOT run immediately', async () => { - // From lines 561-566: Zero delay myth + // From lines 580-589: Zero delay myth const order = [] order.push('A') @@ -118,7 +118,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should run synchronous code first, then setTimeout callback', async () => { - // From lines 290-300: Basic setTimeout + // From lines 313-323: Basic setTimeout example const order = [] order.push('Start') @@ -161,6 +161,200 @@ describe('Event Loop, Timers and Scheduling', () => { expect(order).toEqual(['first', 'second', 'third']) }) + + it('should demonstrate the 4ms minimum delay after nested timeouts', async () => { + // From lines 601-615: After 5 nested timeouts, browsers enforce a minimum 4ms delay + // Note: Vitest fake timers don't enforce the 4ms minimum, so we test the pattern + const times = [] + let start = Date.now() + + function run() { + times.push(Date.now() - start) + if (times.length < 10) { + setTimeout(run, 0) + } + } + + setTimeout(run, 0) + + // Run all nested timeouts + await vi.runAllTimersAsync() + + // Should have 10 timestamps recorded + expect(times.length).toBe(10) + + // In fake timers, all execute at 0ms intervals + // In real browsers, after 5 nested calls, minimum becomes 4ms + // Pattern: [1, 1, 1, 1, 4, 9, 14, 19, 24, 29] approximately + }) + }) + + // ============================================================ + // DEBOUNCE PATTERN + // ============================================================ + + describe('Debounce Pattern', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should cancel previous timeout when implementing debounce', async () => { + // From lines 1341-1349: Cancel previous timeout (debounce) + const searchResults = [] + let timeoutId + + function handleInput(value) { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => { + searchResults.push(`search: ${value}`) + }, 300) + } + + // Simulate rapid typing + handleInput('a') + await vi.advanceTimersByTimeAsync(100) + + handleInput('ab') + await vi.advanceTimersByTimeAsync(100) + + handleInput('abc') + await vi.advanceTimersByTimeAsync(100) + + // At this point, 300ms hasn't passed since last input + expect(searchResults).toEqual([]) + + // Wait for debounce delay + await vi.advanceTimersByTimeAsync(300) + + // Only the last input should trigger a search + expect(searchResults).toEqual(['search: abc']) + }) + + it('should execute immediately if enough time passes between inputs', async () => { + const searchResults = [] + let timeoutId + + function handleInput(value) { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => { + searchResults.push(`search: ${value}`) + }, 300) + } + + handleInput('first') + await vi.advanceTimersByTimeAsync(300) + expect(searchResults).toEqual(['search: first']) + + handleInput('second') + await vi.advanceTimersByTimeAsync(300) + expect(searchResults).toEqual(['search: first', 'search: second']) + }) + }) + + // ============================================================ + // SETINTERVAL WITH ASYNC PROBLEM + // ============================================================ + + describe('setInterval with Async Problem', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should demonstrate overlapping requests with setInterval and async', async () => { + // From lines 1521-1526: If fetch takes longer than interval, multiple requests overlap + const requestsStarted = [] + const requestsCompleted = [] + let requestCount = 0 + + // Simulate a slow fetch that takes 1500ms + async function slowFetch() { + const id = ++requestCount + requestsStarted.push(`request ${id} started`) + await new Promise(resolve => setTimeout(resolve, 1500)) + requestsCompleted.push(`request ${id} completed`) + } + + // Start interval that fires every 1000ms + const intervalId = setInterval(async () => { + await slowFetch() + }, 1000) + + // After 1000ms: first request starts + await vi.advanceTimersByTimeAsync(1000) + await Promise.resolve() + expect(requestsStarted).toEqual(['request 1 started']) + expect(requestsCompleted).toEqual([]) + + // After 2000ms: second request starts (first still pending!) + await vi.advanceTimersByTimeAsync(1000) + await Promise.resolve() + expect(requestsStarted).toEqual(['request 1 started', 'request 2 started']) + expect(requestsCompleted).toEqual([]) // First request still not done + + // After 2500ms: first request completes + await vi.advanceTimersByTimeAsync(500) + await Promise.resolve() + expect(requestsCompleted).toEqual(['request 1 completed']) + + // Clean up + clearInterval(intervalId) + }) + + it('should demonstrate the fix using nested setTimeout for polling', async () => { + // From lines 1532-1539: Schedule next AFTER completion + const requestsStarted = [] + const requestsCompleted = [] + let requestCount = 0 + let isPolling = true + + // Simulate a slow fetch that takes 1500ms + async function slowFetch() { + const id = ++requestCount + requestsStarted.push(`request ${id} started`) + await new Promise(resolve => setTimeout(resolve, 1500)) + requestsCompleted.push(`request ${id} completed`) + } + + // Fixed polling pattern + async function poll() { + await slowFetch() + if (isPolling && requestCount < 3) { + setTimeout(poll, 1000) // Schedule next AFTER completion + } + } + + poll() + + // Request 1 starts immediately + await Promise.resolve() + expect(requestsStarted).toEqual(['request 1 started']) + + // After 1500ms: request 1 completes, then waits 1000ms before next + await vi.advanceTimersByTimeAsync(1500) + await Promise.resolve() + expect(requestsCompleted).toEqual(['request 1 completed']) + expect(requestsStarted.length).toBe(1) // No overlapping request! + + // After 2500ms (1500 + 1000): request 2 starts + await vi.advanceTimersByTimeAsync(1000) + await Promise.resolve() + expect(requestsStarted).toEqual(['request 1 started', 'request 2 started']) + + // After 4000ms (1500 + 1000 + 1500): request 2 completes + await vi.advanceTimersByTimeAsync(1500) + await Promise.resolve() + expect(requestsCompleted).toEqual(['request 1 completed', 'request 2 completed']) + + isPolling = false + }) }) // ============================================================ @@ -193,7 +387,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should run Promises BEFORE setTimeout (microtasks before macrotasks)', async () => { - // From lines 368-378: Promises vs setTimeout + // From lines 391-401: Promises vs setTimeout const order = [] order.push('1') @@ -218,7 +412,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should drain entire microtask queue before any macrotask', async () => { - // From lines 428-442: Nested Microtasks + // From lines 449-467: Nested Microtasks const order = [] order.push('Start') @@ -328,7 +522,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should run callback repeatedly at specified interval', async () => { - // From lines 626-637: Basic setInterval usage + // From lines 649-662: Basic setInterval usage let count = 0 const results = [] @@ -400,7 +594,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should implement preciseInterval with nested setTimeout', async () => { - // From lines 672-680: Nested setTimeout guarantees delay BETWEEN executions + // From lines 695-706: Nested setTimeout guarantees delay BETWEEN executions const results = [] let callCount = 0 @@ -453,7 +647,7 @@ describe('Event Loop, Timers and Scheduling', () => { describe('async/await', () => { it('should run code before await synchronously', async () => { - // From lines 932-957: async/await ordering + // From lines 955-964: async/await ordering const order = [] async function foo() { @@ -558,7 +752,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 1: Basic Output Order - should output 1, 4, 3, 2', async () => { - // From lines 879-895 + // From lines 900-918 const order = [] order.push('1') @@ -578,7 +772,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 2: Nested Promises and Timeouts - should output sync, promise 1, promise 2, timeout 1, timeout 2', async () => { - // From lines 900-927 + // From lines 921-951 const order = [] setTimeout(() => order.push('timeout 1'), 0) @@ -608,7 +802,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 3: async/await Ordering - should output script start, foo start, script end, foo end', async () => { - // From lines 932-957 + // From lines 953-981 const order = [] async function foo() { @@ -627,7 +821,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 4a: setTimeout in a loop with var - should output 3, 3, 3', async () => { - // From lines 962-974 + // From lines 985-997 const order = [] for (var i = 0; i < 3; i++) { @@ -641,7 +835,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 4b: setTimeout in a loop with let - should output 0, 1, 2', async () => { - // From lines 976-981 + // From lines 999-1004 const order = [] for (let i = 0; i < 3; i++) { @@ -655,7 +849,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 4c: setTimeout in a loop with closure fix - should output 0, 1, 2', async () => { - // From lines 984-991 + // From lines 1007-1015 const order = [] for (var i = 0; i < 3; i++) { @@ -671,7 +865,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Question 6: Microtask scheduling - microtask should run', async () => { - // From lines 1030-1053 (simplified - not infinite) + // From lines 1051-1077 (simplified - not infinite) const order = [] let count = 0 @@ -696,7 +890,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Misconception 1: setTimeout(fn, 0) does NOT run immediately - should output sync, promise, timeout', async () => { - // From lines 1067-1072 + // From lines 1084-1096 const order = [] setTimeout(() => order.push('timeout'), 0) @@ -711,7 +905,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('Test Your Knowledge Q3: Complex ordering - should output E, B, C, A, D', async () => { - // From lines 1465-1481 + // From lines 1487-1504 const order = [] setTimeout(() => order.push('A'), 0) @@ -749,7 +943,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('this binding: regular function loses this context', async () => { - // From lines 1331-1340 + // From lines 1354-1363 const obj = { name: 'Alice', greet() { @@ -770,7 +964,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('this binding: arrow function preserves this context', async () => { - // From lines 1342-1350 + // From lines 1365-1373 const obj = { name: 'Alice', greet() { @@ -790,7 +984,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('this binding: bind() preserves this context', async () => { - // From lines 1352-1360 + // From lines 1375-1383 const obj = { name: 'Alice', greet() { @@ -810,7 +1004,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('closure in loop: var creates shared reference', async () => { - // From lines 1365-1370 + // From lines 1388-1393 const results = [] for (var i = 0; i < 3; i++) { @@ -824,7 +1018,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('closure in loop: let creates new binding per iteration', async () => { - // From lines 1372-1376 + // From lines 1395-1399 const results = [] for (let i = 0; i < 3; i++) { @@ -838,7 +1032,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('closure in loop: setTimeout third argument passes value', async () => { - // From lines 1378-1382 + // From lines 1401-1405 const results = [] for (var i = 0; i < 3; i++) { @@ -852,7 +1046,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should implement chunking with setTimeout', async () => { - // From lines 1173-1192 + // From lines 1196-1215 const processed = [] const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -886,7 +1080,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should implement async polling with nested setTimeout', async () => { - // From lines 1509-1516 + // From lines 1532-1540 const results = [] let pollCount = 0 @@ -932,7 +1126,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should yield with setTimeout(resolve, 0)', async () => { - // From lines 1523-1525 + // From lines 1547-1548 const order = [] order.push('before yield') @@ -952,7 +1146,7 @@ describe('Event Loop, Timers and Scheduling', () => { }) it('should yield with queueMicrotask', async () => { - // From lines 1527-1528 + // From lines 1550-1551 const order = [] order.push('before yield') From aed1ffc3cc9c68ade8f27c9b0ae16ebf9759a28a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:28:52 -0300 Subject: [PATCH 040/128] docs(http-fetch): SEO optimization with improved title, headings, and 8 new tests - Update title to 'JavaScript Fetch API: Making HTTP Requests the Modern Way' (58 chars) - Improve meta description with more keywords (158 chars) - Change H2 headings to question-format for better SEO - Add Steps component for 'How to Make a Fetch Request' featured snippet - Rewrite 'What is HTTP?' definition for featured snippet optimization - Fix double --- separator bug - Add PATCH code example to HTTP Methods section - Add Note component for browser-only search input pattern - Add 8 new tests for Response body methods (blob, arrayBuffer) and metadata --- docs/concepts/http-fetch.mdx | 45 +++++++++--- .../http-fetch/http-fetch.test.js | 73 +++++++++++++++++++ 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index d375e83d..031f0953 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -1,6 +1,6 @@ --- -title: "HTTP & Fetch" -description: "Learn how to make network requests in JavaScript using the Fetch API, handle HTTP responses, parse JSON data, and avoid common error handling mistakes" +title: "JavaScript Fetch API: Making HTTP Requests the Modern Way" +description: "Master the JavaScript Fetch API for making HTTP requests. Learn GET, POST, response handling, JSON parsing, error patterns, and AbortController for request cancellation." --- How does JavaScript get data from a server? How do you load user profiles, submit forms, or fetch the latest posts from an API? The answer is the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** — JavaScript's modern way to make network requests. @@ -27,7 +27,7 @@ But to really understand Fetch, you need to understand what's happening undernea ## What is HTTP? -**[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)** (Hypertext Transfer Protocol) is the protocol that powers the web. It's not a JavaScript thing — it's a set of rules that *all* web communication follows, regardless of programming language. When your browser loads a webpage, it uses HTTP. When your phone app talks to a server, it uses HTTP. When JavaScript fetches data, it uses HTTP. +**[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)** (Hypertext Transfer Protocol) is the foundation of data communication on the web. It defines how messages are formatted and transmitted between clients (like web browsers) and servers. Every time you load a webpage, submit a form, or fetch data with JavaScript, HTTP is the protocol making that exchange possible. <Warning> **HTTP is not JavaScript.** HTTP is a language-agnostic protocol — Python, Ruby, Go, Java, and every other language uses it too. We cover HTTP basics in this guide because understanding the protocol is essential to using the Fetch API effectively. If you want to dive deeper into HTTP itself, check out the MDN resources below. @@ -85,9 +85,7 @@ This request-response cycle is the core of how the web works. The **Fetch API** --- ---- - -## HTTP Fundamentals +## How Does HTTP Work? Before diving into the Fetch API, let's understand the key concepts of HTTP itself. @@ -141,6 +139,12 @@ fetch('/api/users/123', { body: JSON.stringify({ name: 'Alice Updated' }) }) +// PATCH - Partially update a user +fetch('/api/users/123', { + method: 'PATCH', + body: JSON.stringify({ name: 'New Name' }) +}) + // DELETE - Remove a user fetch('/api/users/123', { method: 'DELETE' @@ -220,13 +224,28 @@ Status codes are three-digit numbers that tell you how the request went: --- -## The Fetch API +## How to Use the Fetch API -The **Fetch API** is JavaScript's modern way to make HTTP requests. It replaced the older `XMLHttpRequest` (XHR) with a cleaner, Promise-based interface. +The **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** is JavaScript's modern way to make HTTP requests. It replaced the older `XMLHttpRequest` (XHR) with a cleaner, Promise-based interface. -### Basic GET Request +### How to Make a Fetch Request -The simplest fetch call retrieves data from a URL. By default, `fetch()` uses the **GET** method, so you don't need to specify it. There are two ways to write this: +<Steps> + <Step title="Call fetch() with a URL"> + The `fetch()` function takes a URL and returns a Promise that resolves to a Response object. By default, it makes a GET request. + </Step> + <Step title="Check if the response was successful"> + Always verify `response.ok` before processing — fetch doesn't throw errors for HTTP status codes like 404 or 500. + </Step> + <Step title="Parse the response body"> + Use `response.json()` for JSON data or `response.text()` for plain text. These methods return another Promise. + </Step> + <Step title="Handle errors properly"> + Wrap everything in try/catch to handle both network failures and HTTP error responses. + </Step> +</Steps> + +Here's what this looks like in code. By default, `fetch()` uses the **GET** method, so you don't need to specify it. There are two ways to write this: <Tabs> <Tab title="Promise .then()"> @@ -565,7 +584,7 @@ try { --- -## Using async/await with Fetch +## How to Use async/await with Fetch The examples above use `.then()` chains, but modern JavaScript has a cleaner syntax: `async/await`. If you're not familiar with it, check out our [async/await concept](/concepts/async-await) first — it'll make your fetch code much easier to read. @@ -811,6 +830,10 @@ searchInput.addEventListener('input', async (e) => { }) ``` +<Note> +This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node.js or server-side contexts, you would trigger the search function differently, but the AbortController pattern remains the same. +</Note> + --- ## Key Takeaways diff --git a/tests/web-platform/http-fetch/http-fetch.test.js b/tests/web-platform/http-fetch/http-fetch.test.js index 3a11128f..b95cdaed 100644 --- a/tests/web-platform/http-fetch/http-fetch.test.js +++ b/tests/web-platform/http-fetch/http-fetch.test.js @@ -103,6 +103,79 @@ describe('HTTP & Fetch', () => { }) }) + // =========================================================================== + // RESPONSE BODY METHODS (blob, arrayBuffer) + // =========================================================================== + describe('Response Body Methods', () => { + it('should parse body as Blob', async () => { + const textContent = 'Hello, World!' + const response = new Response(textContent) + const blob = await response.blob() + + expect(blob).toBeInstanceOf(Blob) + expect(blob.size).toBe(textContent.length) + }) + + it('should parse body as ArrayBuffer', async () => { + const textContent = 'Hello' + const response = new Response(textContent) + const buffer = await response.arrayBuffer() + + expect(buffer).toBeInstanceOf(ArrayBuffer) + expect(buffer.byteLength).toBe(textContent.length) + }) + + it('should parse binary data as Blob', async () => { + const binaryData = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) // "Hello" + const response = new Response(binaryData) + const blob = await response.blob() + + expect(blob.size).toBe(5) + }) + + it('should parse binary data as ArrayBuffer', async () => { + const binaryData = new Uint8Array([1, 2, 3, 4, 5]) + const response = new Response(binaryData) + const buffer = await response.arrayBuffer() + + const view = new Uint8Array(buffer) + expect(view[0]).toBe(1) + expect(view[4]).toBe(5) + }) + }) + + // =========================================================================== + // RESPONSE METADATA PROPERTIES + // =========================================================================== + describe('Response Metadata', () => { + it('should have url property', () => { + // Note: In real fetch, url reflects the final URL after redirects + // For Response constructor, we can't set URL directly + const response = new Response('OK', { status: 200 }) + expect(response.url).toBe('') // Empty for constructed responses + }) + + it('should have type property', () => { + const response = new Response('OK', { status: 200 }) + // Constructed responses have type "default" + expect(response.type).toBe('default') + }) + + it('should have redirected property', () => { + const response = new Response('OK', { status: 200 }) + // Constructed responses are not redirected + expect(response.redirected).toBe(false) + }) + + it('should have bodyUsed property', async () => { + const response = new Response('Hello') + + expect(response.bodyUsed).toBe(false) + await response.text() + expect(response.bodyUsed).toBe(true) + }) + }) + // =========================================================================== // STATUS CODE RANGES // =========================================================================== From 8d31270e0d2c79c4844cf001b5db10c72839509e Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:31:35 -0300 Subject: [PATCH 041/128] docs(iife-modules): SEO optimization, restructure opening, add Common Mistakes section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update title and description for better SEO (50-60 chars, 150-160 chars) - Restructure opening: questions → code example → What you'll learn - Add prerequisite Warning box linking to scope-and-closures - Add Common Mistakes section covering named/default export confusion and circular dependencies - Add inline MDN links for key terms: IIFE, arrow functions, hoisting, var, dynamic imports - Add code example to Test Your Knowledge Q4 - Fix duplicate icons in Related Concepts section --- docs/concepts/iife-modules.mdx | 211 +++++++++++++++++++++++++++++---- 1 file changed, 188 insertions(+), 23 deletions(-) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 4eca8324..59e8fad2 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -1,6 +1,38 @@ --- -title: "IIFE, Modules and Namespaces" -description: "How to organize your JavaScript code and keep it clean" +title: "IIFE, Modules & Namespaces in JavaScript Explained" +description: "Master JavaScript code organization with IIFEs, namespaces, and ES6 modules. Learn private scope, exports, dynamic imports, and avoid common mistakes." +--- + +How do you prevent your JavaScript variables from conflicting with code from other files or libraries? How do modern applications organize thousands of lines of code across multiple files? + +```javascript +// Modern JavaScript: Each file is its own module +// utils.js +export function formatDate(date) { + return date.toLocaleDateString() +} + +// main.js +import { formatDate } from './utils.js' +console.log(formatDate(new Date())) // "12/30/2025" +``` + +This is **[ES6 modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)** — JavaScript's built-in way to organize code into separate files, each with its own private scope. But before modules existed, developers invented clever patterns like **IIFEs** and **namespaces** to solve the same problems. + +<Info> +**What you'll learn in this guide:** +- What IIFEs are and why they were invented +- How to create private variables and avoid global pollution +- What namespaces are and how to use them +- Modern ES6 modules: import, export, and organizing large projects +- The evolution from IIFEs to modules and why it matters +- Common mistakes with modules and how to avoid them +</Info> + +<Warning> +**Prerequisite:** This guide assumes you understand [scope and closures](/concepts/scope-and-closures). IIFEs and the module pattern rely on closures to create private variables. If closures feel unfamiliar, read that guide first! +</Warning> + --- ## The Messy Desk Problem: A Real-World Analogy @@ -46,22 +78,13 @@ This is the story of how JavaScript developers learned to organize their code: Let's learn each approach and understand when to use them. -<Info> -**What you'll learn in this guide:** -- What IIFEs are and why they were invented -- How to create private variables and avoid global pollution -- What namespaces are and how to use them -- Modern ES6 modules: import, export, and organizing large projects -- The evolution from IIFEs to modules and why it matters -</Info> - --- ## Part 1: IIFE — The Self-Running Function ### What is an IIFE? -**IIFE** stands for **Immediately Invoked Function Expression**. It's a function that runs as soon as it's defined. +**[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** stands for **Immediately Invoked Function Expression**. It's a function that runs as soon as it's defined. ```javascript // A normal function — you define it, then call it later @@ -79,7 +102,7 @@ greet(); // You have to call it The name tells you exactly what it does: - **Immediately** — runs right now - **Invoked** — called/executed -- **Function Expression** — a function written as an expression (not a declaration) +- **[Function Expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function)** — a function written as an expression (not a declaration) ### Expression vs Statement: Why It Matters for IIFEs @@ -121,7 +144,7 @@ const greet = function() { return "Hello!"; }; -// Arrow functions are always expressions +// Arrow functions (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) are always expressions const greet = () => "Hello!"; ``` @@ -147,7 +170,7 @@ function() { | Feature | Declaration | Expression | |---------|-------------|------------| | Syntax | `function name() {}` | `const name = function() {}` | -| Hoisting | Yes (can call before definition) | No (must define first) | +| [Hoisting](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) | Yes (can call before definition) | No (must define first) | | Name | Required | Optional | | Use in IIFE | No | Yes (must use parentheses) | </Info> @@ -209,11 +232,11 @@ There are several ways to write an IIFE. They all do the same thing: ### Why Were IIFEs Invented? -Before ES6 modules, JavaScript had a big problem: **everything was global**. +Before ES6 modules, JavaScript had a big problem: **everything was global**. Variables declared with [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) outside of functions became global, leading to conflicts: ```javascript // file1.js -var userName = "Alice"; +var userName = "Alice"; // var creates global variables var count = 0; // file2.js (loaded after file1.js) @@ -250,7 +273,7 @@ One of the most powerful uses of IIFEs is creating **private variables** that ca ```javascript const counter = (function() { // Private variable — can't be accessed directly - let count = 0; + let count = 0; // let is block-scoped, perfect for private state // Private function — also hidden function log(message) { @@ -283,7 +306,7 @@ console.log(counter.count); // undefined (it's private!) counter.log("test"); // TypeError: counter.log is not a function ``` -This pattern is called the **Module Pattern** — it was the standard way to create "modules" before ES6. +This pattern is called the **Module Pattern** — it uses [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to keep variables private. It was the standard way to create "modules" before ES6. ### IIFE with Parameters @@ -330,7 +353,7 @@ With ES6 modules, IIFEs are less common. But they're still useful for: ```javascript // Top-level await isn't always available // IIFE lets you use async/await anywhere - (async () => { + (async () => { // async functions return Promises const response = await fetch('/api/data'); const data = await response.json(); console.log(data); @@ -525,6 +548,8 @@ MyApp.Logger.log("Counter incremented"); **Modules** are JavaScript's built-in way to organize code into separate files, each with its own scope. Unlike IIFEs and namespaces (which are patterns), modules are a **language feature**. +The [`export`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) statement makes functions, objects, or values available to other modules. The [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) statement brings them in. + ```javascript // math.js — A module file export function add(a, b) { @@ -885,7 +910,7 @@ init(); ## Dynamic Imports -Sometimes you don't want to load a module until it's needed. **Dynamic imports** load modules on demand: +Sometimes you don't want to load a module until it's needed. **[Dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)** load modules on demand: ```javascript // Static import — always loaded @@ -1074,6 +1099,130 @@ import { capitalize } from './stringUtils.js'; --- +## Common Mistakes to Avoid + +### Mistake 1: Confusing Named and Default Exports + +One of the most common sources of confusion is mixing up how to import named vs default exports: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ NAMED vs DEFAULT EXPORT CONFUSION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ EXPORTING IMPORTING │ +│ ───────── ───────── │ +│ │ +│ Named Export: Must use { braces }: │ +│ export function greet() {} import { greet } from './mod.js' │ +│ export const PI = 3.14 import { PI } from './mod.js' │ +│ │ +│ Default Export: NO braces: │ +│ export default function() {} import greet from './mod.js' │ +│ export default class User {} import User from './mod.js' │ +│ │ +│ ⚠️ Common Error: │ +│ import greet from './mod.js' ← Looking for default, but file has │ +│ named export! Results in undefined │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +```javascript +// utils.js — has a NAMED export +export function formatDate(date) { + return date.toLocaleDateString() +} + +// ❌ WRONG — Importing without braces looks for a default export +import formatDate from './utils.js' +console.log(formatDate) // undefined! No default export exists + +// ✓ CORRECT — Use braces for named exports +import { formatDate } from './utils.js' +console.log(formatDate) // [Function: formatDate] +``` + +<Warning> +**The Trap:** If you see `undefined` when importing, check whether you're using braces correctly. Named exports require `{ }`, default exports don't. This is the #1 cause of "why is my import undefined?" bugs. +</Warning> + +### Mistake 2: Circular Dependencies + +Circular dependencies occur when two modules import from each other. This creates a "chicken and egg" problem that causes subtle, hard-to-debug issues: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CIRCULAR DEPENDENCY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ user.js userUtils.js │ +│ ┌──────────┐ ┌──────────────┐ │ +│ │ │ ──── imports from ────► │ │ │ +│ │ User │ │ formatUser() │ │ +│ │ class │ ◄─── imports from ───── │ createUser() │ │ +│ │ │ │ │ │ +│ └──────────┘ └──────────────┘ │ +│ │ +│ 🔄 PROBLEM: When user.js loads, it needs userUtils.js │ +│ But userUtils.js needs User from user.js │ +│ Which isn't fully loaded yet! → undefined │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +```javascript +// ❌ PROBLEM: Circular dependency + +// user.js +import { formatUserName } from './userUtils.js' + +export class User { + constructor(name) { + this.name = name + } +} + +// userUtils.js +import { User } from './user.js' // Circular! user.js imports userUtils.js + +export function formatUserName(user) { + return user.name.toUpperCase() +} + +export function createDefaultUser() { + return new User('Guest') // 💥 User might be undefined here! +} +``` + +```javascript +// ✓ SOLUTION: Break the cycle with restructuring + +// user.js — no imports from userUtils +export class User { + constructor(name) { + this.name = name + } +} + +// userUtils.js — imports from user.js (one direction only) +import { User } from './user.js' + +export function formatUserName(user) { + return user.name.toUpperCase() +} + +export function createDefaultUser() { + return new User('Guest') // Works! User is fully loaded +} +``` + +<Tip> +**Rule of Thumb:** Draw your import arrows — they should flow in one direction like a tree, not in circles. If module A imports from B, module B should NOT import from A. If you need shared code, create a third module that both can import from. +</Tip> + +--- + ## Key Takeaways <Info> @@ -1175,6 +1324,22 @@ Try to answer each question before revealing the solution: - Loaded at runtime, returns a Promise - Syntax: `const module = await import('./module.js')` + ```javascript + // Static import — always at the top, always loaded + import { heavyFunction } from './heavy-module.js' + + // Dynamic import — loaded only when needed + async function loadOnDemand() { + const module = await import('./heavy-module.js') + module.heavyFunction() + } + + // Or with .then() syntax + import('./heavy-module.js').then(module => { + module.heavyFunction() + }) + ``` + Use dynamic imports for code splitting and loading modules on demand. </Accordion> @@ -1222,13 +1387,13 @@ Try to answer each question before revealing the solution: <Card title="Scope and Closures" icon="lock" href="/concepts/scope-and-closures"> Understanding how JavaScript manages variable access and function memory </Card> - <Card title="Higher-Order Functions" icon="layer-group" href="/concepts/higher-order-functions"> + <Card title="Higher-Order Functions" icon="function" href="/concepts/higher-order-functions"> Functions that work with other functions — common in modular code </Card> <Card title="Design Patterns" icon="compass" href="/concepts/design-patterns"> Common patterns for organizing code, including the module pattern </Card> - <Card title="Call Stack" icon="layer-group" href="/concepts/call-stack"> + <Card title="Call Stack" icon="bars-staggered" href="/concepts/call-stack"> How JavaScript tracks function execution and manages memory </Card> </CardGroup> From a79d834bc828a212c704af741eea1b2829831efb Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:35:53 -0300 Subject: [PATCH 042/128] docs(event-loop): improve title SEO and add featured snippet optimization - Update title to 'Event Loop: How Async Code Actually Runs in JavaScript' - Add sidebarTitle for cleaner navigation without 'in JavaScript' suffix - Rename first H2 to 'What is the Event Loop?' to match search queries - Add 40-word definition paragraph for Google featured snippet - Move restaurant analogy under H3 subheading --- docs/concepts/event-loop.mdx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/concepts/event-loop.mdx b/docs/concepts/event-loop.mdx index 050c4c89..38ba5284 100644 --- a/docs/concepts/event-loop.mdx +++ b/docs/concepts/event-loop.mdx @@ -1,5 +1,6 @@ --- -title: "The JavaScript Event Loop: How Async Code Actually Runs" +title: "Event Loop: How Async Code Actually Runs in JavaScript" +sidebarTitle: "Event Loop: How Async Code Actually Runs" description: "Learn how the JavaScript event loop handles async code. Understand the call stack, task queue, microtasks, and why Promises always run before setTimeout()." --- @@ -36,7 +37,11 @@ Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event lo --- -## Understanding the Event Loop: A Restaurant Analogy +## What is the Event Loop? + +The **event loop** is JavaScript's mechanism for executing code, handling events, and managing asynchronous operations. It continuously monitors the call stack and callback queues, pushing queued tasks to the stack when it's empty—enabling non-blocking behavior despite JavaScript being single-threaded. + +### The Restaurant Analogy Imagine a busy restaurant kitchen with a **single chef** who can only cook one dish at a time. Despite this limitation, the restaurant serves hundreds of customers because the kitchen has a clever system: From 0a522dc41c7d3b440461518f63cd63d3facf74ae Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:37:58 -0300 Subject: [PATCH 043/128] docs(iife-modules): add sidebarTitle and fix arrow function MDN link - Add sidebarTitle field for cleaner sidebar navigation - Move arrow function MDN link from code comment to prose for proper rendering --- docs/concepts/iife-modules.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 59e8fad2..0ee4cf8f 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -1,5 +1,6 @@ --- title: "IIFE, Modules & Namespaces in JavaScript Explained" +sidebarTitle: "IIFE, Modules & Namespaces" description: "Master JavaScript code organization with IIFEs, namespaces, and ES6 modules. Learn private scope, exports, dynamic imports, and avoid common mistakes." --- @@ -143,8 +144,11 @@ function greet() { const greet = function() { return "Hello!"; }; +``` + +[Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) are always expressions: -// Arrow functions (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) are always expressions +```javascript const greet = () => "Hello!"; ``` From a5992e90b8293fd2b4e64d6376cbe45fc099226c Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:44:20 -0300 Subject: [PATCH 044/128] docs(iife-modules): improve SEO title, add 'What is an IIFE?' section - Update title to 'IIFE, Modules & Namespaces: Structuring Code in JavaScript' (59 chars) - Update sidebarTitle to match without 'in JavaScript' - Change description to start with 'Learn' instead of 'Master' - Add new 'What is an IIFE?' H2 section with 40-60 word definition for featured snippets - Rename duplicate H3 to 'Breaking Down the Name' to avoid duplication --- docs/concepts/iife-modules.mdx | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 0ee4cf8f..234fe307 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -1,7 +1,7 @@ --- -title: "IIFE, Modules & Namespaces in JavaScript Explained" -sidebarTitle: "IIFE, Modules & Namespaces" -description: "Master JavaScript code organization with IIFEs, namespaces, and ES6 modules. Learn private scope, exports, dynamic imports, and avoid common mistakes." +title: "IIFE, Modules & Namespaces: Structuring Code in JavaScript" +sidebarTitle: "IIFE, Modules & Namespaces: Structuring Code" +description: "Learn how to organize JavaScript code with IIFEs, namespaces, and ES6 modules. Understand private scope, exports, dynamic imports, and common module mistakes." --- How do you prevent your JavaScript variables from conflicting with code from other files or libraries? How do modern applications organize thousands of lines of code across multiple files? @@ -36,6 +36,20 @@ This is **[ES6 modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/ --- +## What is an IIFE? + +An **[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it's defined. It creates a private scope to protect variables from polluting the global namespace — a pattern that was essential before ES6 modules existed. + +```javascript +// An IIFE — runs immediately, no calling needed +(function() { + const private = "I'm hidden from the outside world"; + console.log(private); +})(); // Runs right away! +``` + +--- + ## The Messy Desk Problem: A Real-World Analogy Imagine you're working at a desk covered with papers, pens, sticky notes, and coffee cups. Everything is mixed together. When you need to find something specific, you have to dig through the mess. And if someone else uses your desk? Chaos. @@ -83,9 +97,13 @@ Let's learn each approach and understand when to use them. ## Part 1: IIFE — The Self-Running Function -### What is an IIFE? +### Breaking Down the Name -**[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** stands for **Immediately Invoked Function Expression**. It's a function that runs as soon as it's defined. +The acronym IIFE tells you exactly what it does: + +- **Immediately** — runs right now +- **Invoked** — called/executed +- **[Function Expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function)** — a function written as an expression (not a declaration) ```javascript // A normal function — you define it, then call it later @@ -100,11 +118,6 @@ greet(); // You have to call it })(); // Runs right away! ``` -The name tells you exactly what it does: -- **Immediately** — runs right now -- **Invoked** — called/executed -- **[Function Expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function)** — a function written as an expression (not a declaration) - ### Expression vs Statement: Why It Matters for IIFEs To understand IIFEs, you need to understand the difference between **expressions** and **statements** in JavaScript. From 6f09502d515c10d9062b0482a0f186337e5fc851 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 10:46:42 -0300 Subject: [PATCH 045/128] docs(iife-modules): expand 'What is an IIFE?' section with historical context - Add code comment showing private scope behavior - Add explanation of how parentheses work - Add Note callout explaining IIFEs are less common in modern codebases but still worth knowing --- docs/concepts/iife-modules.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 234fe307..3f2de0b9 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -46,8 +46,17 @@ An **[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** (Immediate const private = "I'm hidden from the outside world"; console.log(private); })(); // Runs right away! + +// The variable "private" doesn't exist out here +// console.log(private); // ReferenceError: private is not defined ``` +The parentheses around the function turn it from a declaration into an expression, and the `()` at the end immediately invokes it. This was the go-to pattern for creating private scope before JavaScript had built-in modules. + +<Note> +**Historical context:** IIFEs were everywhere in JavaScript codebases from 2010-2015. Today, most projects use ES6 modules (`import`/`export`), so you won't write many IIFEs in modern code. However, understanding them is valuable — you'll encounter IIFEs in older codebases, libraries, and they're still useful for specific cases like async initialization or quick scripts. +</Note> + --- ## The Messy Desk Problem: A Real-World Analogy From 75e23d10e9ac1871c5314c92ff681ee2de97beb7 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 11:03:11 -0300 Subject: [PATCH 046/128] docs(http-fetch): improve structure with 'What is Fetch API?' section and SEO updates - Add sidebarTitle for cleaner navigation - Change title format to end with 'in JavaScript' for SEO - Change description from 'Master' to 'Learn' to match other pages - Change HTTP clarification from Warning to Note component - Add 'What is the Fetch API?' section after HTTP explanation - Add 'Before Fetch: XMLHttpRequest Days' subsection with jQuery context - Add XHR vs Fetch code comparison - Move Prerequisite Warning before 'What you'll learn' box - Fix HTTP methods count: four -> five (includes PATCH) - Add MDN links for jQuery, Promises, Request, Response objects --- docs/concepts/http-fetch.mdx | 71 ++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index 031f0953..275630ff 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -1,6 +1,7 @@ --- -title: "JavaScript Fetch API: Making HTTP Requests the Modern Way" -description: "Master the JavaScript Fetch API for making HTTP requests. Learn GET, POST, response handling, JSON parsing, error patterns, and AbortController for request cancellation." +title: "Fetch API: Making HTTP Requests the Modern Way in JavaScript" +sidebarTitle: "Fetch API: Making HTTP Requests the Modern Way" +description: "Learn how to make HTTP requests with the JavaScript Fetch API. Understand GET, POST, response handling, JSON parsing, error patterns, and AbortController for cancellation." --- How does JavaScript get data from a server? How do you load user profiles, submit forms, or fetch the latest posts from an API? The answer is the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** — JavaScript's modern way to make network requests. @@ -14,10 +15,14 @@ console.log(user.name) // "Alice" But to really understand Fetch, you need to understand what's happening underneath: **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)**. +<Warning> +**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so those concepts are essential. If you're not comfortable with Promises yet, read that guide first! +</Warning> + <Info> **What you'll learn in this guide:** - How HTTP requests and responses work -- The four main HTTP methods (GET, POST, PUT, DELETE) +- The five main HTTP methods (GET, POST, PUT, PATCH, DELETE) - How to use the Fetch API to make requests - Reading and parsing JSON responses - The critical difference between network errors and HTTP errors @@ -29,9 +34,9 @@ But to really understand Fetch, you need to understand what's happening undernea **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)** (Hypertext Transfer Protocol) is the foundation of data communication on the web. It defines how messages are formatted and transmitted between clients (like web browsers) and servers. Every time you load a webpage, submit a form, or fetch data with JavaScript, HTTP is the protocol making that exchange possible. -<Warning> +<Note> **HTTP is not JavaScript.** HTTP is a language-agnostic protocol — Python, Ruby, Go, Java, and every other language uses it too. We cover HTTP basics in this guide because understanding the protocol is essential to using the Fetch API effectively. If you want to dive deeper into HTTP itself, check out the MDN resources below. -</Warning> +</Note> <CardGroup cols={2}> <Card title="HTTP — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/HTTP"> @@ -77,11 +82,7 @@ Sometimes things go wrong: - **They're out of pasta** (404 Not Found) — The order was received, but they can't fulfill it - **Something's wrong in the kitchen** (500 Server Error) — They tried but something broke -This request-response cycle is the core of how the web works. The **Fetch API** is JavaScript's modern way to participate in this cycle programmatically. - -<Warning> -**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so those concepts are essential. If you're not comfortable with Promises yet, read that guide first! -</Warning> +This request-response cycle is the core of how the web works. The **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** is JavaScript's modern way to participate in this cycle programmatically. --- @@ -224,9 +225,57 @@ Status codes are three-digit numbers that tell you how the request went: --- +## What is the Fetch API? + +The **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** is JavaScript's modern interface for making HTTP requests. It provides a cleaner, Promise-based alternative to the older **[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)**, letting you send requests to servers and handle responses with simple, readable code. Every modern browser supports Fetch natively. + +```javascript +// Fetch in its simplest form +const response = await fetch('https://api.example.com/data') +const data = await response.json() +console.log(data) +``` + +### Before Fetch: The XMLHttpRequest Days + +Before Fetch existed, developers used XMLHttpRequest (XHR) — a verbose, callback-based API that powered "AJAX" requests. Libraries like **[jQuery](https://jquery.com/)** became popular partly because they simplified this painful process. jQuery was revolutionary for JavaScript — for many years it was the go-to library that made DOM manipulation, animations, and AJAX requests dramatically easier. It changed how developers wrote JavaScript and shaped the modern web. + +```javascript +// The old way: XMLHttpRequest (verbose and callback-based) +const xhr = new XMLHttpRequest() +xhr.open('GET', 'https://api.example.com/data') +xhr.onload = function() { + if (xhr.status === 200) { + const data = JSON.parse(xhr.responseText) + console.log(data) + } +} +xhr.onerror = function() { + console.error('Request failed') +} +xhr.send() + +// The modern way: Fetch (clean and Promise-based) +const response = await fetch('https://api.example.com/data') +const data = await response.json() +console.log(data) +``` + +Unlike XMLHttpRequest, Fetch: +- Returns **[Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** instead of using callbacks +- Uses **[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)** and **[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)** objects for cleaner APIs +- Integrates naturally with **async/await** syntax +- Supports streaming responses out of the box + +<Tip> +**You no longer need jQuery for AJAX.** The Fetch API is built into every modern browser, making libraries unnecessary for basic HTTP requests. +</Tip> + +--- + ## How to Use the Fetch API -The **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** is JavaScript's modern way to make HTTP requests. It replaced the older `XMLHttpRequest` (XHR) with a cleaner, Promise-based interface. +Now that you understand what Fetch is and how it compares to older approaches, let's dive into the details of using it effectively. ### How to Make a Fetch Request From 15354e313bc94634c7be8983913bf2e930c9488f Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 11:10:07 -0300 Subject: [PATCH 047/128] docs(dom): add interview questions, misconceptions, and improve structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DOM Concept Page Improvements: - Add sidebarTitle for cleaner navigation - Add prerequisite Warning for HTML/CSS knowledge - Add summary sentence to 'What is the DOM' definition - Rename 7 H2s to match search intent ('How to...') - Add Steps component for Critical Rendering Path - Convert layout-triggering properties to table format - Merge Books section into Articles New Sections: - Common Misconceptions (4 items): DOM vs HTML, querySelector perf, display:none vs remove, live collections gotcha - Classic Interview Questions (6 questions): querySelector vs getElementById, event delegation, layout thrashing, innerHTML vs textContent, efficient DOM insertion, attributes vs properties Tests: - Add 12 new tests covering misconception and interview examples - Total DOM tests: 106 → 118 --- docs/concepts/dom.mdx | 319 +++++++++++++++++++++++++++-- tests/web-platform/dom/dom.test.js | 158 ++++++++++++++ 2 files changed, 455 insertions(+), 22 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index ff60621c..04c9d805 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -1,5 +1,6 @@ --- title: "DOM in JavaScript: How Browsers Represent Web Pages" +sidebarTitle: "DOM: How Browsers Represent Web Pages" description: "Learn how the DOM works in JavaScript. Understand how browsers represent HTML as a tree, select and manipulate elements, traverse nodes, and optimize rendering performance." --- @@ -18,11 +19,15 @@ The answer lies in the **DOM** — the bridge between your HTML and JavaScript. - Performance best practices (avoid layout thrashing!) </Info> +<Warning> +**Prerequisite:** This guide assumes basic familiarity with [HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML) and [CSS](https://developer.mozilla.org/en-US/docs/Learn/CSS/First_steps). If you're new to web development, start there first! +</Warning> + --- ## What is the DOM in JavaScript? -The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface that represents HTML documents as a tree of objects. When a browser loads a webpage, it parses the HTML and creates the DOM — a live, structured representation that JavaScript can read and modify. Every element, attribute, and piece of text becomes a node in this tree. +The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface that represents HTML documents as a tree of objects. When a browser loads a webpage, it parses the HTML and creates the DOM — a live, structured representation that JavaScript can read and modify. Every element, attribute, and piece of text becomes a node in this tree. **In short: the DOM is how JavaScript "sees" and changes a webpage.** ```javascript // The DOM lets you do things like this: @@ -35,7 +40,7 @@ With the DOM, you can use methods like **[`querySelector()`](https://developer.m --- -## The Family Tree: Understanding DOM Structure +## How the DOM Tree Structure Works Think of the DOM like a family tree. At the top sits `document` (the family historian who knows everyone). Below it is `<html>` (the matriarch), which has two children: `<head>` and `<body>`. Each of these has their own children, grandchildren, and so on. @@ -175,7 +180,7 @@ document.title = 'New Title' // Changes browser tab title --- -## DOM Tree Structure & Node Types +## DOM Node Types Explained Everything in the DOM is a **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node)**. But not all nodes are created equal! @@ -834,7 +839,7 @@ element.innerText = 'Hello' // Works but slower --- -## Working with Attributes +## How to Work with DOM Attributes HTML elements have attributes. JavaScript lets you read, write, and remove them using **[`getAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute)**, **[`setAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)**, **[`hasAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute)**, and **[`removeAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute)**. @@ -963,7 +968,7 @@ element.title // element.getAttribute('title') --- -## Styling Elements +## How to Style DOM Elements with JavaScript JavaScript can modify element styles in several ways using the **[`style`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style)** property and **[`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)** API. @@ -1061,7 +1066,7 @@ element.classList.remove('btn-primary') // Removes specifically --- -## The Render Tree & Critical Rendering Path +## How Browsers Render the DOM to Pixels Understanding how browsers render pages helps you write performant code. This is where [JavaScript Engines](/concepts/javascript-engines) and the browser's rendering engine work together. @@ -1069,6 +1074,27 @@ Understanding how browsers render pages helps you write performant code. This is When you load a webpage, the browser goes through these steps: +<Steps> + <Step title="1. Parse HTML → Build DOM"> + Browser reads HTML bytes and constructs the Document Object Model tree. + </Step> + <Step title="2. Parse CSS → Build CSSOM"> + CSS is parsed into the CSS Object Model with styling rules. + </Step> + <Step title="3. Combine → Render Tree"> + DOM + CSSOM merge into the Render Tree (only visible elements). + </Step> + <Step title="4. Layout (Reflow)"> + Calculate exact position and size of every element. + </Step> + <Step title="5. Paint"> + Fill in pixels: colors, borders, shadows, text. + </Step> + <Step title="6. Composite"> + Combine layers into the final image using the GPU. + </Step> +</Steps> + ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ THE CRITICAL RENDERING PATH │ @@ -1166,7 +1192,7 @@ width: 200px; /* Avoid for animations! */ --- -## Performance Best Practices +## How to Optimize DOM Performance DOM operations can be slow. Here's how to keep your pages fast. @@ -1237,15 +1263,21 @@ boxes.forEach((box, i) => { **Properties that trigger layout when read:** +| Property | What It Returns | +|----------|-----------------| +| `offsetWidth` / `offsetHeight` | Element's layout width/height including borders | +| `offsetTop` / `offsetLeft` | Position relative to offset parent | +| `clientWidth` / `clientHeight` | Inner dimensions (padding but no border) | +| `scrollWidth` / `scrollHeight` | Full scrollable dimensions | +| `scrollTop` / `scrollLeft` | Current scroll position | +| `getBoundingClientRect()` | Position and size relative to viewport | +| `getComputedStyle()` | All computed CSS values | + ```javascript -// Reading these forces the browser to calculate layout -element.offsetWidth element.offsetHeight -element.offsetTop element.offsetLeft -element.clientWidth element.clientHeight -element.scrollWidth element.scrollHeight -element.scrollTop element.scrollLeft -element.getBoundingClientRect() -getComputedStyle(element) +// Any of these reads forces a layout calculation +const width = element.offsetWidth // Layout triggered! +const rect = element.getBoundingClientRect() // Layout triggered! +const styles = getComputedStyle(element) // Layout triggered! ``` ### Use requestAnimationFrame for Visual Changes @@ -1375,7 +1407,7 @@ div.appendChild(welcomeText) --- -## Common Patterns +## Common DOM Patterns ### Event Delegation @@ -1449,6 +1481,252 @@ window.addEventListener('load', () => { --- +## Common Misconceptions + +<AccordionGroup> + <Accordion title="Misconception 1: 'The DOM is the same as my HTML source code'"> + **Wrong!** The DOM is NOT your HTML file. The browser: + + 1. **Fixes errors** — Missing `<head>`, `<body>`, unclosed tags are auto-corrected + 2. **Normalizes structure** — Text outside elements gets wrapped properly + 3. **Reflects JavaScript changes** — DOM updates don't change your HTML file + + ```html + <!-- Your HTML file --> + <html>Hello World + + <!-- What the DOM looks like --> + <html> + <head></head> + <body>Hello World</body> + </html> + ``` + + View Source shows your file. DevTools Elements shows the DOM. + </Accordion> + + <Accordion title="Misconception 2: 'querySelector is slow, use getElementById'"> + **Mostly wrong!** Yes, `getElementById` is technically faster (O(1) hashtable lookup), but: + + - The difference is **microseconds** — imperceptible to users + - `querySelector` is more **flexible** and **readable** + - You'd need to call it **thousands of times in a loop** to notice + + ```javascript + // Both are fine for normal use + document.getElementById('myId') + document.querySelector('#myId') + + // Only optimize if you're selecting in a tight loop + // with performance issues (rare!) + ``` + + **Rule:** Write readable code first. Optimize only when you have a measured problem. + </Accordion> + + <Accordion title="Misconception 3: 'display: none removes the element from the DOM'"> + **Wrong!** `display: none` hides the element visually, but it's still in the DOM: + + ```javascript + element.style.display = 'none' + + // Element is STILL in the DOM! + console.log(document.getElementById('hidden')) // Element exists + console.log(element.parentNode) // Still has parent + + // To actually remove from DOM: + element.remove() + // or + element.parentNode.removeChild(element) + ``` + + - `display: none` → Hidden but in DOM, not in Render Tree + - `visibility: hidden` → Hidden but takes up space, in Render Tree + - `remove()` → Actually removed from DOM + </Accordion> + + <Accordion title="Misconception 4: 'Live collections automatically update my code'"> + **Misleading!** Live collections (`getElementsByClassName`, `getElementsByTagName`) update automatically, but this can cause bugs: + + ```javascript + const items = document.getElementsByClassName('item') + + // DANGER: Removing items changes the collection while looping! + for (let i = 0; i < items.length; i++) { + items[i].remove() // Collection shrinks, indices shift! + } + // Some items are skipped! + + // SAFE: Use static NodeList or convert to array + const items = document.querySelectorAll('.item') // Static + items.forEach(item => item.remove()) // Works correctly + ``` + + **Tip:** Prefer `querySelectorAll` (static) unless you specifically need live updates. + </Accordion> +</AccordionGroup> + +--- + +## Classic Interview Questions + +### Question 1: What's the difference between `document.querySelector` and `document.getElementById`? + +<Accordion title="Answer"> +| Feature | `getElementById` | `querySelector` | +|---------|-----------------|-----------------| +| Selector type | ID only | Any CSS selector | +| Returns | Element or `null` | Element or `null` | +| Speed | Faster (hashtable) | Slightly slower (parses CSS) | +| Flexibility | Low | High | + +```javascript +// getElementById — only IDs +document.getElementById('myId') + +// querySelector — any CSS selector +document.querySelector('#myId') // Same as above +document.querySelector('.card:first-child') // Not possible with getElementById +document.querySelector('[data-id="123"]') // Attribute selector +``` + +**Best answer:** "getElementById is marginally faster but querySelector is more flexible. In practice, the performance difference is negligible for most applications. I prefer querySelector for consistency and flexibility." +</Accordion> + +### Question 2: Explain event delegation and why it's useful + +<Accordion title="Answer"> +**Event delegation** is attaching a single event listener to a parent element instead of multiple listeners to child elements. It works because events "bubble up" the DOM tree. + +```javascript +// ❌ Without delegation — 100 listeners for 100 items +document.querySelectorAll('.item').forEach(item => { + item.addEventListener('click', handleClick) +}) + +// ✓ With delegation — 1 listener handles all items +document.querySelector('.container').addEventListener('click', (e) => { + const item = e.target.closest('.item') + if (item) handleClick(e) +}) +``` + +**Benefits:** +1. **Memory efficient** — One listener vs. many +2. **Works for dynamic elements** — New items automatically handled +3. **Easier cleanup** — Remove one listener to clean up + +**Best answer:** Include a code example and mention `closest()` for finding the target element. +</Accordion> + +### Question 3: What causes layout thrashing and how do you avoid it? + +<Accordion title="Answer"> +**Layout thrashing** occurs when you repeatedly alternate between reading and writing DOM layout properties, forcing the browser to recalculate layout multiple times. + +```javascript +// ❌ Thrashing — forces layout on EVERY iteration +boxes.forEach(box => { + const width = box.offsetWidth // READ → triggers layout + box.style.width = width + 10 + 'px' // WRITE → invalidates layout +}) + +// ✓ Batched — one layout calculation +const widths = boxes.map(box => box.offsetWidth) // All reads +boxes.forEach((box, i) => { + box.style.width = widths[i] + 10 + 'px' // All writes +}) +``` + +**Properties that trigger layout:** `offsetWidth/Height`, `clientWidth/Height`, `getBoundingClientRect()`, `getComputedStyle()` + +**Best answer:** Explain the read-write-read-write pattern and show the batched solution. +</Accordion> + +### Question 4: What's the difference between `innerHTML`, `textContent`, and `innerText`? + +<Accordion title="Answer"> +| Property | Parses HTML? | Includes hidden text? | Performance | Security | +|----------|-------------|----------------------|-------------|----------| +| `innerHTML` | Yes | Yes | Slower | XSS risk | +| `textContent` | No | Yes | Fast | Safe | +| `innerText` | No | No (respects CSS) | Slowest | Safe | + +```javascript +// <div id="el"><span style="display:none">Hidden</span> Visible</div> + +el.innerHTML // "<span style="display:none">Hidden</span> Visible" +el.textContent // "Hidden Visible" +el.innerText // " Visible" (hidden text excluded) +``` + +**Security warning:** Never use `innerHTML` with user input — it can execute malicious scripts (XSS attacks). Use `textContent` instead. + +**Best answer:** Mention the XSS security risk with innerHTML — this shows you understand real-world implications. +</Accordion> + +### Question 5: How do you efficiently add 1000 elements to the DOM? + +<Accordion title="Answer"> +Use a **DocumentFragment** to batch insertions: + +```javascript +// ❌ Slow — 1000 DOM updates, 1000 potential reflows +for (let i = 0; i < 1000; i++) { + const li = document.createElement('li') + li.textContent = `Item ${i}` + ul.appendChild(li) // Triggers update each time +} + +// ✓ Fast — 1 DOM update +const fragment = document.createDocumentFragment() +for (let i = 0; i < 1000; i++) { + const li = document.createElement('li') + li.textContent = `Item ${i}` + fragment.appendChild(li) // No DOM update (fragment is detached) +} +ul.appendChild(fragment) // Single update +``` + +**Alternative:** Build an HTML string and use `innerHTML` once (but only with trusted content, never user input). + +**Best answer:** Show the fragment approach and explain WHY it's faster (detached container, single reflow). +</Accordion> + +### Question 6: What's the difference between attributes and properties? + +<Accordion title="Answer"> +**Attributes** are defined in HTML. **Properties** are the live state on DOM objects. + +```html +<input type="text" value="initial"> +``` + +```javascript +const input = document.querySelector('input') + +// Attribute — original HTML value +input.getAttribute('value') // "initial" (never changes) + +// Property — current live value +input.value // "initial" initially, then whatever user types + +// User types "hello"... +input.getAttribute('value') // Still "initial" +input.value // "hello" +``` + +| Aspect | Attribute | Property | +|--------|-----------|----------| +| Source | HTML markup | DOM object | +| Type | Always string | Can be any type | +| Updates | Manual only | Automatically with interaction | + +**Best answer:** Use the `<input value="">` example — it's the clearest demonstration of the difference. +</Accordion> + +--- + ## Key Takeaways <Info> @@ -1678,15 +1956,12 @@ window.addEventListener('load', () => { </Card> </CardGroup> -## Books - -<Card title="Eloquent JavaScript, 3rd Edition: Ch. 14 - The Document Object Model" icon="book" href="https://eloquentjavascript.net/14_dom.html"> - Marijn Haverbeke's excellent free book chapter covering DOM fundamentals with clear explanations and exercises. -</Card> - ## Articles <CardGroup cols={2}> + <Card title="Eloquent JavaScript: The Document Object Model" icon="book" href="https://eloquentjavascript.net/14_dom.html"> + Marijn Haverbeke's excellent free book chapter covering DOM fundamentals with clear explanations and exercises. + </Card> <Card title="How To Understand and Modify the DOM in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/introduction-to-the-dom"> Tania Rascia's beginner-friendly guide covering DOM basics, selection, traversal, and manipulation. </Card> diff --git a/tests/web-platform/dom/dom.test.js b/tests/web-platform/dom/dom.test.js index 2c61cf8b..5c41802d 100644 --- a/tests/web-platform/dom/dom.test.js +++ b/tests/web-platform/dom/dom.test.js @@ -1222,5 +1222,163 @@ describe('DOM and Layout Trees', () => { expect(firstP.textContent).toBe('First paragraph') }) }) + + describe('Common Misconceptions', () => { + describe('Misconception 1: DOM vs HTML source', () => { + it('should show browser always has head and body in DOM', () => { + // Browser fixes missing structure + expect(document.head).toBeDefined() + expect(document.body).toBeDefined() + expect(document.documentElement).toBeDefined() + }) + + it('should show DOM reflects JavaScript changes', () => { + document.body.innerHTML = '<div id="test">Original</div>' + const div = document.getElementById('test') + div.textContent = 'Modified' + + // DOM reflects change + expect(div.textContent).toBe('Modified') + // Original HTML source would still say "Original" + }) + }) + + describe('Misconception 2: querySelector performance', () => { + it('should show both methods return the same element', () => { + document.body.innerHTML = '<div id="myId">Test</div>' + + const byId = document.getElementById('myId') + const byQuery = document.querySelector('#myId') + + expect(byId).toBe(byQuery) + }) + }) + + describe('Misconception 3: display none vs remove', () => { + it('should show display:none keeps element in DOM', () => { + document.body.innerHTML = '<div id="hidden">Content</div>' + const el = document.getElementById('hidden') + el.style.display = 'none' + + // Still in DOM! + expect(document.getElementById('hidden')).toBe(el) + expect(el.parentNode).toBe(document.body) + }) + + it('should show visibility:hidden keeps element in DOM', () => { + document.body.innerHTML = '<div id="invisible">Content</div>' + const el = document.getElementById('invisible') + el.style.visibility = 'hidden' + + expect(document.getElementById('invisible')).toBe(el) + }) + + it('should show remove() actually removes from DOM', () => { + document.body.innerHTML = '<div id="toRemove">Content</div>' + const el = document.getElementById('toRemove') + el.remove() + + expect(document.getElementById('toRemove')).toBeNull() + }) + }) + + describe('Misconception 4: Live collections gotcha', () => { + it('should show live collection changes when DOM changes', () => { + document.body.innerHTML = ` + <div class="item">1</div> + <div class="item">2</div> + <div class="item">3</div> + ` + + const liveCollection = document.getElementsByClassName('item') + expect(liveCollection.length).toBe(3) + + // Remove first item - collection shrinks + liveCollection[0].remove() + expect(liveCollection.length).toBe(2) + }) + + it('should show static NodeList is safe for iteration', () => { + document.body.innerHTML = ` + <div class="item">1</div> + <div class="item">2</div> + <div class="item">3</div> + ` + + const staticList = document.querySelectorAll('.item') + expect(staticList.length).toBe(3) + + // Remove all items safely + staticList.forEach(item => item.remove()) + + // Original NodeList still has references (but elements are removed from DOM) + expect(staticList.length).toBe(3) // Static snapshot + expect(document.querySelectorAll('.item').length).toBe(0) // DOM is empty + }) + }) + }) + + describe('Interview Questions', () => { + describe('Q1: querySelector vs getElementById', () => { + it('should demonstrate querySelector flexibility with complex selectors', () => { + document.body.innerHTML = ` + <div class="card"> + <span data-id="123">Content</span> + </div> + ` + + // querySelector can do what getElementById cannot + const byAttribute = document.querySelector('[data-id="123"]') + const firstCard = document.querySelector('.card:first-child') + + expect(byAttribute.textContent).toBe('Content') + expect(firstCard.className).toBe('card') + }) + + it('should show both return null for non-existent elements', () => { + document.body.innerHTML = '' + + expect(document.getElementById('nonexistent')).toBeNull() + expect(document.querySelector('#nonexistent')).toBeNull() + }) + }) + + describe('Q2: Event delegation', () => { + it('should demonstrate event delegation with closest()', () => { + document.body.innerHTML = ` + <div class="container"> + <div class="item" data-id="1">Item 1</div> + <div class="item" data-id="2">Item 2</div> + </div> + ` + + let clickedId = null + + // Simulated event delegation logic + const item = document.querySelector('[data-id="2"]') + const closestItem = item.closest('.item') + + if (closestItem) { + clickedId = closestItem.dataset.id + } + + expect(clickedId).toBe('2') + }) + + it('should show delegation works for dynamically added elements', () => { + document.body.innerHTML = '<ul class="list"></ul>' + const list = document.querySelector('.list') + + // Add item after "attaching" listener (simulated) + const newItem = document.createElement('li') + newItem.className = 'item' + newItem.textContent = 'New Item' + list.appendChild(newItem) + + // closest() finds it + expect(newItem.closest('.list')).toBe(list) + }) + }) + }) }) }) From 261579f62463e60768008bd74e33734a453d197e Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 11:57:47 -0300 Subject: [PATCH 048/128] docs(factories-classes): add interview questions, misconceptions, and improve SEO structure - Fix title format: move 'in JavaScript' to end for SEO - Add sidebarTitle for cleaner navigation - Remove 'Part X:' prefix from H2 headings for cleaner SEO - Add 'Classic Interview Questions' section with 4 Q&As: - Factory vs class differences - What new keyword does under the hood - How to achieve true privacy - When to use composition over inheritance - Add 'Common Misconceptions' section with 4 items: - JS classes vs Java/C# classes - Factory functions power - Private fields vs underscore convention - Classes as 'modern way' myth - Add MDN links for Object.setPrototypeOf() and Function.apply() - Expand article and video descriptions - Add tests for opening example, common mistakes (forgetting new, underscore vs # privacy, this in factory functions) --- docs/concepts/factories-classes.mdx | 324 ++++++++++++++++-- .../factories-classes.test.js | 223 ++++++++++++ 2 files changed, 519 insertions(+), 28 deletions(-) diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx index 29d3a698..7c4eb5a6 100644 --- a/docs/concepts/factories-classes.mdx +++ b/docs/concepts/factories-classes.mdx @@ -1,6 +1,7 @@ --- -title: "Factories and Classes" -description: "How to create objects in JavaScript — from factory functions to ES6 classes" +title: "Factories and Classes: Creating Objects Efficiently in JavaScript" +sidebarTitle: "Factories and Classes: Creating Objects Efficiently" +description: "Learn JavaScript factory functions and ES6 classes. Understand constructors, prototypes, private fields, inheritance, and when to use each pattern." --- How do you create hundreds of similar objects without copy-pasting? How do game developers spawn thousands of enemies? How does JavaScript let you build blueprints for objects? @@ -57,7 +58,7 @@ console.log(enemy.attack()) // "Goblin attacks!" --- -## Part 1: The Problem — Creating Multiple Objects +## Why Do We Need Object Blueprints? ### The Manual Approach (Don't Do This) @@ -118,15 +119,65 @@ We need a way to: - Ensure all objects have the same properties and methods - Make changes in **one place** that affect all objects -Let's explore three solutions: **Factory Functions**, **Constructor Functions**, and **ES6 Classes**. +### The Assembly Line Analogy + +Think about how real-world manufacturing works: + +- **Hand-crafting** each item individually is slow, inconsistent, and doesn't scale +- **Assembly lines** (factories) take specifications and produce products efficiently +- **Blueprints/molds** define the template once, then stamp out identical copies + +JavaScript gives us the same options: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THREE WAYS TO CREATE OBJECTS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MANUAL CREATION Like hand-carving each chess piece │ +│ ─────────────── Tedious, error-prone, inconsistent │ +│ const obj = { ... } │ +│ │ +│ ───────────────────────────────────────────────────────────────────── │ +│ │ +│ FACTORY FUNCTION Like an assembly line │ +│ ──────────────── Put in specs → Get product │ +│ Flexible, no special keywords │ +│ createPlayer("Alice") │ +│ │ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ Player │ ← New object returned │ +│ │ {name...} │ │ +│ └─────────────┘ │ +│ │ +│ ───────────────────────────────────────────────────────────────────── │ +│ │ +│ CLASS / CONSTRUCTOR Like a blueprint or mold │ +│ ─────────────────── Define template → Stamp out copies │ +│ Uses `new`, supports `instanceof` │ +│ new Player("Alice") │ +│ │ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ Player │ ← Instance created from blueprint │ +│ │ {name...} │ │ +│ └─────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Both factories and classes solve the same problem — they just do it differently. Let's explore each approach. --- -## Part 2: Factory Functions — The Flexible Approach +## What is a Factory Function in JavaScript? + +A **factory function** is a regular JavaScript function that creates and returns a new object each time it's called. Unlike constructors or classes, factory functions don't require the `new` keyword. They can use `this` in returned methods (like simple objects do), or use [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to avoid `this` entirely — giving you flexibility that classes don't offer. -### What is a Factory Function? +### Basic Factory Function -A **factory function** is a function that creates and returns objects. It's like a factory assembly line — you put in the specifications, and it produces the product. +Think of it like an assembly line — you put in the specifications, and it produces the product: ```javascript // A simple factory function @@ -411,11 +462,11 @@ console.log(staff.attack()); // "Cast a spell for 35 damage! (Costs 10 mana)" --- -## Part 3: Constructor Functions — The Traditional Way +## How Do Constructor Functions Work? -### What is a Constructor Function? +A **constructor function** is a regular JavaScript function designed to be called with the [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) keyword. When invoked with `new`, it creates a new object, binds `this` to that object, and returns it automatically. Constructor names conventionally start with a capital letter to distinguish them from regular functions. This was the standard way to create objects before ES6 classes. -A **constructor function** is a regular function designed to be used with the `new` keyword. It's the traditional way to create objects in JavaScript (before ES6 classes). +### Basic Constructor Function ```javascript // Convention: Constructor names start with a capital letter @@ -462,12 +513,27 @@ When you call `new Player("Alice")`, JavaScript performs **4 steps**: └─────────────────────────────────────────────────────────────────────┘ ``` -Here's a function that does what `new` does: +<Steps> + <Step title="Create a new empty object"> + JavaScript creates a fresh object: `const obj = {}` + </Step> + <Step title="Link the prototype"> + Uses [`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) to set `obj.[[Prototype]]` to `Constructor.prototype`, establishing the prototype chain + </Step> + <Step title="Execute the constructor"> + Runs [`Constructor.apply(obj, args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) — the `this` keyword inside the constructor is bound to the new object + </Step> + <Step title="Return the object"> + Returns `obj` automatically (unless the constructor explicitly returns a different object) + </Step> +</Steps> + +Here's a function that simulates what `new` does: ```javascript function myNew(Constructor, ...args) { // Step 1 & 2: Create object with correct prototype - const obj = Object.create(Constructor.prototype); // Object.create creates a new object with the specified prototype + const obj = Object.create(Constructor.prototype); // Object.create() creates a new object with the specified prototype // Step 3: Run constructor with 'this' = obj const result = Constructor.apply(obj, args); @@ -613,11 +679,11 @@ Always use `new` with constructor functions! Without it, `this` refers to the gl --- -## Part 4: ES6 Classes — The Modern Syntax +## What Are ES6 Classes in JavaScript? -### What Are Classes? +An **[ES6 class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)** is JavaScript's modern syntax for creating constructor functions and prototypes. Introduced in ECMAScript 2015, classes provide a cleaner, more familiar syntax for object-oriented programming while working exactly the same as constructor functions under the hood — they're often called "syntactic sugar." Classes use the `class` keyword and require the `new` operator to create instances. -ES6 **classes** are a cleaner syntax for creating constructor functions. They do the same thing as constructors + prototypes, but with nicer syntax. +### Basic Class Syntax ```javascript class Player { @@ -836,7 +902,7 @@ console.log(temp.fahrenheit); // 77 console.log(temp.kelvin); // 298.15 temp.fahrenheit = 100; // Set via fahrenheit -console.log(temp.celsius); // 37.78 (converted) +console.log(temp.celsius); // ~37.78 (converted) // temp.celsius = -300; // Error: Temperature below absolute zero! // temp.kelvin = 0; // Error: no setter (read-only) @@ -1127,7 +1193,209 @@ console.log(safeCounter.getCount()); // 1 --- -## Part 5: Inheritance — Extending Behavior +## Classic Interview Questions + +<AccordionGroup> + <Accordion title="What's the difference between a factory function and a class?"> + **Answer:** + + | Aspect | Factory Function | ES6 Class | + |--------|-----------------|-----------| + | Syntax | Regular function returning object | `class` keyword | + | `new` keyword | Not required | Required | + | `instanceof` | Doesn't work | Works | + | Privacy | Closures (truly private) | Private fields `#` (truly private) | + | Memory | Each instance has own methods | Methods shared via prototype | + | `this` binding | Can avoid `this` entirely | Must use `this` | + + ```javascript + // Factory - just a function + function createUser(name) { + return { name, greet() { return `Hi, ${name}!` } } + } + + // Class - a blueprint + class User { + constructor(name) { this.name = name } + greet() { return `Hi, ${this.name}!` } + } + + const u1 = createUser("Alice") // No 'new' + const u2 = new User("Bob") // Requires 'new' + ``` + + **Best answer:** Explain both syntax differences AND when to use each. + </Accordion> + + <Accordion title="What does the new keyword do under the hood?"> + **Answer:** + + `new` performs 4 steps: + + 1. **Creates** a new empty object `{}` + 2. **Links** its prototype to `Constructor.prototype` + 3. **Executes** the constructor with `this` bound to the new object + 4. **Returns** the object (unless constructor returns a different object) + + ```javascript + // This is essentially what 'new' does: + function myNew(Constructor, ...args) { + const obj = Object.create(Constructor.prototype) + const result = Constructor.apply(obj, args) + return (typeof result === 'object' && result !== null) ? result : obj + } + ``` + + **Best answer:** Mention all 4 steps and show the simulation code. + </Accordion> + + <Accordion title="How do you achieve true privacy in JavaScript?"> + **Answer:** + + Two ways to achieve **true** privacy: + + **1. Private Fields (`#`) in Classes:** + ```javascript + class BankAccount { + #balance = 0 + deposit(amt) { this.#balance += amt } + getBalance() { return this.#balance } + } + // account.#balance → SyntaxError! + ``` + + **2. Closures in Factory Functions:** + ```javascript + function createBankAccount() { + let balance = 0 + return { + deposit(amt) { balance += amt }, + getBalance() { return balance } + } + } + // account.balance → undefined + ``` + + **Not truly private:** The `_underscore` convention is just a naming hint — those properties are fully accessible. + + **Best answer:** Distinguish between the `_underscore` convention (not private) and the two truly private approaches. + </Accordion> + + <Accordion title="When would you use composition over inheritance?"> + **Answer:** + + Use **composition** when: + - You need to mix behaviors from multiple sources (a flying fish, a swimming bird) + - The "is-a" relationship doesn't make sense + - You want loose coupling between components + - You need flexibility to change behaviors at runtime + + Use **inheritance** when: + - There's a clear "is-a" hierarchy (Dog is an Animal) + - You need `instanceof` checks + - You want to share implementation, not just interface + + ```javascript + // Inheritance problem: What about a penguin that can't fly? + class Bird { fly() {} } + class Penguin extends Bird { fly() { throw Error("Can't fly!") } } // Awkward! + + // Composition solution: Mix behaviors + const canSwim = (state) => ({ swim() { /*...*/ } }) + const canWalk = (state) => ({ walk() { /*...*/ } }) + + function createPenguin(name) { + const state = { name } + return { ...canSwim(state), ...canWalk(state) } // No fly! + } + ``` + + **Best answer:** Give the "Gorilla-Banana" problem example and show composition code. + </Accordion> +</AccordionGroup> + +--- + +## Common Misconceptions + +<AccordionGroup> + <Accordion title="Misconception: 'Classes in JavaScript work like classes in Java or C#'"> + **Reality:** JavaScript classes are **syntactic sugar** over prototypes. Under the hood, they still use prototype-based inheritance, not classical inheritance. + + ```javascript + class Player { + constructor(name) { this.name = name } + attack() { return `${this.name} attacks!` } + } + + // Classes ARE functions! + console.log(typeof Player) // "function" + + // Methods are on the prototype, not the instance + console.log(Player.prototype.attack) // [Function: attack] + ``` + + This is why JavaScript has quirks like `this` binding issues that don't exist in true class-based languages. + </Accordion> + + <Accordion title="Misconception: 'Factory functions are less powerful than classes'"> + **Reality:** Factory functions can do everything classes can, plus more: + + - **True privacy** via closures (before `#` existed) + - **No `this` binding issues** when using closures + - **Return different types** based on input + - **No `new` keyword** to forget + + ```javascript + // Factory can return different types! + function createShape(type) { + if (type === 'circle') return { radius: 10, area() { /*...*/ } } + if (type === 'square') return { side: 10, area() { /*...*/ } } + } + + // Classes always return instances of that class + ``` + + The trade-off is memory efficiency (classes share methods via prototype). + </Accordion> + + <Accordion title="Misconception: 'Private fields (#) and _underscore are the same thing'"> + **Reality:** They're completely different: + + | Aspect | `_underscore` | `#privateField` | + |--------|---------------|-----------------| + | Accessibility | Fully public | Truly private | + | Convention only? | Yes | No, enforced | + | Error on access | No error | SyntaxError | + + ```javascript + class Account { + _balance = 100 // Accessible! Just a convention + #pin = 1234 // Truly private + } + + const acc = new Account() + console.log(acc._balance) // 100 — works! + // console.log(acc.#pin) // SyntaxError! + ``` + </Accordion> + + <Accordion title="Misconception: 'You should always use classes because they're the modern way'"> + **Reality:** Classes were added in ES6 (2015), but that doesn't mean they're always better. The JavaScript community has actually moved **toward** functions in many cases: + + - **React:** Moved from class components to function components with hooks + - **Functional programming:** Favors factory functions and composition + - **Simplicity:** Factory functions have fewer footguns (`this`, `new`) + + **Use classes when:** You need `instanceof`, clear hierarchies, or OOP familiarity. + + **Use factories when:** You need composition, true privacy, or functional style. + </Accordion> +</AccordionGroup> + +--- + +## How Does Inheritance Work in JavaScript? ### Class Inheritance with [`extends`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends) @@ -1415,7 +1683,7 @@ nemo.swim(); // Works --- -## Part 6: Factory vs Class — When to Use Each +## Factory vs Class — Which Should You Use? ### Side-by-Side Comparison @@ -1738,22 +2006,22 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); <CardGroup cols={2}> <Card title="How To Use Classes in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-classes-in-javascript"> - Comprehensive tutorial by Tania Rascia + Tania Rascia's comprehensive tutorial covering class syntax, constructors, inheritance, and static methods with clear examples. </Card> <Card title="JavaScript Classes — Under The Hood" icon="newspaper" href="https://medium.com/tech-tajawal/javascript-classes-under-the-hood-6b26d2667677"> - Deep dive into how classes work internally + Deep dive into how classes are just syntactic sugar over prototypes and constructor functions. </Card> <Card title="Factory Functions in JavaScript" icon="newspaper" href="https://atendesigngroup.com/blog/factory-functions-javascript"> - Guide to factory function patterns + Practical guide to factory function patterns including closures for privacy and composition. </Card> <Card title="Class vs Factory function" icon="newspaper" href="https://medium.freecodecamp.org/class-vs-factory-function-exploring-the-way-forward-73258b6a8d15"> - Comparison of both approaches by Cristi Salcescu + Cristi Salcescu's comparison of both approaches with pros, cons, and when to use each. </Card> <Card title="Composition vs Inheritance" icon="newspaper" href="https://ui.dev/javascript-inheritance-vs-composition/"> - When to use each approach + UI.dev's guide explaining why "favor composition over inheritance" is important in JavaScript. </Card> <Card title="Understanding super in JavaScript" icon="newspaper" href="https://jordankasper.com/understanding-super-in-javascript"> - Deep dive into the super keyword + Jordan Kasper's deep dive into the super keyword, constructor calls, and method overriding. </Card> </CardGroup> @@ -1763,15 +2031,15 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); <CardGroup cols={2}> <Card title="JavaScript Factory Functions" icon="video" href="https://www.youtube.com/watch?v=jpegXpQpb3o"> - Clear explanation by Programming with Mosh + Programming with Mosh explains factory functions, when to use them, and how they compare to constructors. </Card> <Card title="Factory Functions in JavaScript" icon="video" href="https://www.youtube.com/watch?v=ImwrezYhw4w"> - Fun Fun Function's take on factories + Fun Fun Function's entertaining take on factories with practical examples and real-world use cases. </Card> <Card title="Composition over Inheritance" icon="video" href="https://www.youtube.com/watch?v=wfMtDGfHWpA"> - Why composition is often better by Fun Fun Function + Fun Fun Function explains why composition is often better than inheritance with the "Gorilla-Banana" problem. </Card> <Card title="JavaScript Classes Tutorial" icon="video" href="https://www.youtube.com/watch?v=2ZphE5HcQPQ"> - Comprehensive class tutorial + Comprehensive class tutorial covering syntax, inheritance, static methods, and private fields. </Card> </CardGroup> diff --git a/tests/object-oriented/factories-classes/factories-classes.test.js b/tests/object-oriented/factories-classes/factories-classes.test.js index a4dc6dd4..337e0bad 100644 --- a/tests/object-oriented/factories-classes/factories-classes.test.js +++ b/tests/object-oriented/factories-classes/factories-classes.test.js @@ -1,6 +1,84 @@ import { describe, it, expect } from 'vitest' describe('Factories and Classes', () => { + // =========================================== + // Opening Example: Factory vs Class + // =========================================== + + describe('Opening Example: Factory vs Class', () => { + it('should create objects with factory function', () => { + function createPlayer(name) { + return { + name, + health: 100, + attack() { + return `${this.name} attacks!` + } + } + } + + const player = createPlayer('Alice') + + expect(player.name).toBe('Alice') + expect(player.health).toBe(100) + expect(player.attack()).toBe('Alice attacks!') + }) + + it('should create objects with class', () => { + class Enemy { + constructor(name) { + this.name = name + this.health = 100 + } + + attack() { + return `${this.name} attacks!` + } + } + + const enemy = new Enemy('Goblin') + + expect(enemy.name).toBe('Goblin') + expect(enemy.health).toBe(100) + expect(enemy.attack()).toBe('Goblin attacks!') + }) + + it('should show both patterns produce similar results', () => { + function createPlayer(name) { + return { + name, + health: 100, + attack() { + return `${this.name} attacks!` + } + } + } + + class Enemy { + constructor(name) { + this.name = name + this.health = 100 + } + + attack() { + return `${this.name} attacks!` + } + } + + const player = createPlayer('Alice') + const enemy = new Enemy('Goblin') + + // Both have same structure + expect(player.name).toBe('Alice') + expect(enemy.name).toBe('Goblin') + expect(player.health).toBe(enemy.health) + + // Both attack methods work + expect(player.attack()).toBe('Alice attacks!') + expect(enemy.attack()).toBe('Goblin attacks!') + }) + }) + // =========================================== // Part 1: The Problem — Manual Object Creation // =========================================== @@ -1032,6 +1110,151 @@ describe('Factories and Classes', () => { }) }) + // =========================================== + // Common Mistakes + // =========================================== + + describe('Common Mistakes', () => { + describe('Mistake 1: Forgetting new with Constructor Functions', () => { + it('should throw or behave unexpectedly when new is forgotten (strict mode)', () => { + function Player(name) { + this.name = name + this.health = 100 + } + + // In strict mode (which modern JS uses), forgetting 'new' throws an error + // because 'this' is undefined, not the global object + expect(() => Player('Alice')).toThrow() + }) + + it('should work correctly with new keyword', () => { + function Player(name) { + this.name = name + this.health = 100 + } + + const bob = new Player('Bob') + + expect(bob.name).toBe('Bob') + expect(bob.health).toBe(100) + expect(bob instanceof Player).toBe(true) + }) + + it('should throw error when calling class without new', () => { + class Player { + constructor(name) { + this.name = name + } + } + + // Classes protect against this mistake + expect(() => Player('Alice')).toThrow() + }) + }) + + describe('Mistake 3: Underscore Convention vs True Privacy', () => { + it('should show underscore properties ARE accessible (not truly private)', () => { + class BankAccount { + constructor(balance) { + this._balance = balance // Convention only, NOT private! + } + + getBalance() { + return this._balance + } + } + + const account = new BankAccount(1000) + + // Underscore properties are fully accessible! + expect(account._balance).toBe(1000) + + // Can be modified directly + account._balance = 999999 + expect(account.getBalance()).toBe(999999) + }) + + it('should show private fields (#) are truly private', () => { + class SecureBankAccount { + #balance // Truly private + + constructor(balance) { + this.#balance = balance + } + + getBalance() { + return this.#balance + } + } + + const secure = new SecureBankAccount(1000) + + // Private field is not accessible + expect(secure.balance).toBe(undefined) + + // Can only access via methods + expect(secure.getBalance()).toBe(1000) + }) + }) + + describe('Mistake 4: Using this Incorrectly in Factory Functions', () => { + it('should show this can break when method is extracted', () => { + function createCounter() { + return { + count: 0, + increment() { + this.count++ // 'this' depends on how method is called + } + } + } + + const counter = createCounter() + counter.increment() // Works - this is counter + expect(counter.count).toBe(1) + + // Extract the method + const increment = counter.increment + + // Call without context - 'this' is undefined in strict mode + // This won't modify counter.count + try { + increment() + } catch (e) { + // In strict mode, this throws because this is undefined + } + + // counter.count is still 1 because the extracted call didn't work + expect(counter.count).toBe(1) + }) + + it('should show closures avoid this problem', () => { + function createSafeCounter() { + let count = 0 // Closure variable - no 'this' needed + + return { + increment() { + count++ // Uses closure, not this + }, + getCount() { + return count + } + } + } + + const counter = createSafeCounter() + counter.increment() + expect(counter.getCount()).toBe(1) + + // Extract the method + const increment = counter.increment + + // Works even when extracted! + increment() + expect(counter.getCount()).toBe(2) + }) + }) + }) + // =========================================== // Additional Edge Cases // =========================================== From f2e4e6caf1236fc333d4871ba5bde032cc5d1c7b Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 12:04:25 -0300 Subject: [PATCH 049/128] docs(this-call-apply-bind): improve SEO, add tests, and refine content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update title to 'The this Keyword: Understanding Context in JavaScript' - Add sidebarTitle for better navigation - Change description to start with 'Learn' instead of 'Master' - Add inline MDN links for new, prototype, strict mode, arguments, globalThis - Fix console.log to return in Pronoun Analogy examples - Fix invalid generator syntax example with proper explanation - Add browser-only notes for DOM examples - Remove 2 external articles per content review - Add 8 new tests covering quiz questions and documentation examples (84 → 92) --- docs/concepts/this-call-apply-bind.mdx | 52 +++--- .../this-call-apply-bind.test.js | 155 ++++++++++++++++++ 2 files changed, 179 insertions(+), 28 deletions(-) diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx index f0a6f526..97669d9e 100644 --- a/docs/concepts/this-call-apply-bind.mdx +++ b/docs/concepts/this-call-apply-bind.mdx @@ -1,6 +1,7 @@ --- -title: "this, call, apply and bind" -description: "How JavaScript decides what 'this' refers to — and how you take control" +title: "The this Keyword: Understanding Context in JavaScript" +sidebarTitle: "The this Keyword, call, apply, and bind" +description: "Learn how JavaScript's 'this' keyword works and how to control context binding. Understand the 5 binding rules, call/apply/bind methods, arrow functions, and common pitfalls." --- Why does `this` sometimes point to the wrong object? Why does your method work perfectly when called directly, but break when passed as a callback? And how do **[`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)**, **[`apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply)**, and **[`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)** let you take control? @@ -20,11 +21,7 @@ greet(); // "Hi, I'm undefined" - broken! The **[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)** keyword is one of JavaScript's most confusing features — but it follows specific rules. Once you understand them, you'll never be confused again. -In our journey through JavaScript, we've already explored [Scope & Closures](/concepts/scope-and-closures) — how variables are accessed and how functions remember their environment. Now it's time to tackle one of JavaScript's most confusing (and powerful) features: the `this` keyword. - -You've probably seen `this` pop up everywhere — in object methods, class constructors, event handlers, and callbacks. Sometimes it works exactly as you'd expect. Other times? It points to something completely unexpected and your code breaks in mysterious ways. - -The truth is, `this` isn't magical or random. It follows specific rules. Once you understand these rules, you'll never be confused by `this` again. And with `call`, `apply`, and `bind`, you'll learn how to take complete control over what `this` refers to. +Building on our exploration of [Scope & Closures](/concepts/scope-and-closures), understanding `this` is the next step to learning how JavaScript handles context. You've probably seen `this` pop up everywhere — in object methods, class constructors, event handlers, and callbacks. Sometimes it works exactly as you'd expect. Other times? It points to something completely unexpected and your code breaks in mysterious ways. Let's start with an analogy that makes it all click. @@ -59,14 +56,14 @@ This is exactly how `this` works in JavaScript! The keyword `this` is like the p const alice = { name: "Alice", introduce() { - console.log("I am " + this.name); // "I" = this = alice + return "I am " + this.name; // "I" = this = alice } }; const bob = { name: "Bob", introduce() { - console.log("I am " + this.name); // "I" = this = bob + return "I am " + this.name; // "I" = this = bob } }; @@ -94,9 +91,9 @@ That's what `call`, `apply`, and `bind` do — they let you control **who "I" re --- -## What is `this`? +## What is `this` in JavaScript? -The `this` keyword is a special identifier that JavaScript automatically defines in every function. It refers to the **execution context** — essentially, the object that the function is operating on. +The **`this`** keyword is a special identifier that JavaScript automatically creates in every function execution context. It refers to the object that is currently executing the code — typically the object that "owns" the method being called. Unlike most languages where `this` is fixed at definition time, JavaScript determines `this` dynamically at **call time**, based on how a function is invoked. ### The Key Insight: Call-Time Binding @@ -114,11 +111,13 @@ function showThis() { const obj = { showThis }; // Same function, different this values: -showThis(); // undefined (strict mode) or [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) +showThis(); // undefined (strict mode) or globalThis obj.showThis(); // obj (the object before the dot) showThis.call({}); // {} (explicitly specified) ``` +In non-strict mode, plain function calls return **[`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)** (the global object — `window` in browsers, `global` in Node.js). + ### Why Does JavaScript Work This Way? This design allows for incredible flexibility: @@ -175,7 +174,7 @@ Arrow functions are listed last not because they're lowest priority, but because ### Rule 1: `new` Binding (Highest Priority) -When a function is called with the `new` keyword, `this` is set to a **brand new object** that's automatically created. +When a function is called with the **[`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new)** keyword, `this` is set to a **brand new object** that's automatically created. ```javascript class Person { @@ -203,7 +202,7 @@ When you call `new Person("Alice")`, JavaScript performs these 4 steps: </Step> <Step title="Link the prototype"> - The new object's internal `[[Prototype]]` is set to the constructor's `prototype` property. + The new object's internal `[[Prototype]]` is set to the constructor's **[`prototype`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype)** property. ```javascript // Conceptually: @@ -238,7 +237,7 @@ function simulateNew(Constructor, ...args) { // Step 1: Create empty object const newObject = {}; - // Step 2: Link prototype + // Step 2: Link prototype (see Object.setPrototypeOf on MDN) Object.setPrototypeOf(newObject, Constructor.prototype); // Step 3: Bind this and execute @@ -406,7 +405,7 @@ We'll cover solutions in the "Gotchas" section. When a function is called without any of the above conditions, **default binding** applies. -In **strict mode** (which you should always use): `this` is `undefined`. +In **[strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)** (which you should always use): `this` is `undefined`. In non-strict mode: `this` is the global object (`window` in browsers, `global` in Node.js). @@ -611,7 +610,7 @@ When you need to figure out what `this` is, follow this flowchart: --- -## Deep Dive: `call()`, `apply()`, and `bind()` +## How Do `call()`, `apply()`, and `bind()` Work? These three methods give you explicit control over `this`. They're built into every function in JavaScript. @@ -901,7 +900,7 @@ Use array methods on array-like objects: ```javascript // Arguments object (old-school, but still seen in legacy code) function sum() { - // 'arguments' is array-like but not an array + // 'arguments' is array-like but not an array (see MDN: Arguments object) return Array.prototype.reduce.call( arguments, (total, n) => total + n, @@ -911,7 +910,7 @@ function sum() { sum(1, 2, 3, 4); // 10 -// NodeList from DOM +// NodeList from DOM (browser-only example) const divs = document.querySelectorAll('div'); // NodeList, not Array const texts = Array.prototype.map.call(divs, div => div.textContent); @@ -1192,7 +1191,7 @@ const obj = { | Event handlers in classes | ✅ Yes (as class fields) | ⚠️ Needs binding | | Functions needing own `this` | ❌ No | ✅ Yes | | Constructor functions | ❌ No (can't use `new`) | ✅ Yes | -| Methods using `arguments` | ❌ No (no `arguments`) | ✅ Yes | +| Methods using [`arguments`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments) | ❌ No (no `arguments`) | ✅ Yes | ### Arrow Functions as Class Fields @@ -1227,7 +1226,7 @@ class SearchBox { const ArrowClass = () => {}; new ArrowClass(); // TypeError: ArrowClass is not a constructor -// 2. No 'arguments' object +// 2. No 'arguments' object (arrow functions don't have their own arguments binding) const arrow = () => { console.log(arguments); // ReferenceError: arguments is not defined }; @@ -1240,7 +1239,9 @@ const arrowWithRest = (...args) => { // 3. No 'super' in standalone arrows (works in class methods) // 4. Cannot be used as generators -const arrowGen = *() => {}; // SyntaxError +// There's no arrow generator syntax - you must use function* +function* generatorFn() { yield 1; } // Works +// () =>* { yield 1; } // No such syntax exists ``` --- @@ -1440,12 +1441,7 @@ Try to figure out what `this` refers to in each example before revealing the ans <Card title="Javascript: call(), apply() and bind()" icon="newspaper" href="https://medium.com/@omergoldberg/javascript-call-apply-and-bind-e5c27301f7bb"> Omer Goldberg's practical walkthrough of call, apply, and bind with real-world scenarios. </Card> - <Card title="Let me explain to you what is this (JavaScript)" icon="newspaper" href="https://dev.to/ycmjason/let-me-explain-to-you-what-is-this-javascript-44ja"> - Jason Yu's friendly explanation of the this keyword with clear visual examples. - </Card> - <Card title="Understanding this binding in JavaScript" icon="newspaper" href="https://yasemincidem.medium.com/understanding-this-binding-in-javascript-86687397c76d"> - Yasemin Cidem's deep dive into all the ways this gets bound in JavaScript. - </Card> + <Card title="The Top 7 Tricky this Interview Questions" icon="newspaper" href="https://dmitripavlutin.com/javascript-this-interview-questions/"> Dmitri Pavlutin's collection of challenging this-related questions to test your understanding. </Card> diff --git a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js index b7c36a10..36c26582 100644 --- a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js +++ b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js @@ -1169,5 +1169,160 @@ describe('this, call, apply and bind', () => { expect(obj.inner.getName()).toBe('Inner') }) + + it('Question 5: forEach callback loses this context', () => { + const calculator = { + value: 10, + add(numbers) { + // This demonstrates the BROKEN behavior + let localValue = this.value + numbers.forEach(function(n) { + // this.value would be undefined here in strict mode + // so we can't actually add to it + localValue += 0 // simulating the broken behavior + }) + return this.value // returns original value unchanged + } + } + + // The value stays 10 because the callback can't access this.value + expect(calculator.add([1, 2, 3])).toBe(10) + }) + + it('Question 5 fixed: forEach with arrow function preserves this', () => { + const calculator = { + value: 10, + add(numbers) { + numbers.forEach((n) => { + this.value += n // Arrow function preserves this + }) + return this.value + } + } + + expect(calculator.add([1, 2, 3])).toBe(16) + }) + + it('Question 6: bind partial application and length property', () => { + function multiply(a, b) { + return a * b + } + + const double = multiply.bind(null, 2) + + expect(double(5)).toBe(10) + expect(double.length).toBe(1) // multiply has 2 params, we pre-filled 1 + }) + }) + + describe('Additional Documentation Examples', () => { + describe('simulateNew function', () => { + it('should simulate new keyword behavior', () => { + function simulateNew(Constructor, ...args) { + // Step 1: Create empty object + const newObject = {} + + // Step 2: Link prototype + Object.setPrototypeOf(newObject, Constructor.prototype) + + // Step 3: Bind this and execute + const result = Constructor.apply(newObject, args) + + // Step 4: Return object (unless constructor returns an object) + return result instanceof Object ? result : newObject + } + + function Person(name) { + this.name = name + } + Person.prototype.greet = function() { + return `Hi, I'm ${this.name}` + } + + const alice1 = new Person("Alice") + const alice2 = simulateNew(Person, "Alice") + + expect(alice1.name).toBe("Alice") + expect(alice2.name).toBe("Alice") + expect(alice1.greet()).toBe("Hi, I'm Alice") + expect(alice2.greet()).toBe("Hi, I'm Alice") + expect(alice2 instanceof Person).toBe(true) + }) + + it('should return custom object if constructor returns one', () => { + function simulateNew(Constructor, ...args) { + const newObject = {} + Object.setPrototypeOf(newObject, Constructor.prototype) + const result = Constructor.apply(newObject, args) + return result instanceof Object ? result : newObject + } + + function ReturnsObject() { + this.name = "ignored" + return { custom: "object" } + } + + const obj = simulateNew(ReturnsObject) + expect(obj.custom).toBe("object") + expect(obj.name).toBeUndefined() + }) + }) + + describe('apply with args array', () => { + it('should work with introduce function and args array', () => { + function introduce(greeting, role, company) { + return `${greeting}! I'm ${this.name}, ${role} at ${company}.` + } + + const alice = { name: "Alice" } + const args = ["Hello", "engineer", "TechCorp"] + + expect(introduce.apply(alice, args)).toBe("Hello! I'm Alice, engineer at TechCorp.") + expect(introduce.call(alice, ...args)).toBe("Hello! I'm Alice, engineer at TechCorp.") + }) + }) + + describe('Countdown class pattern', () => { + it('should preserve this with bind in setInterval pattern', () => { + class Countdown { + constructor(start) { + this.count = start + } + + tick() { + this.count-- + return this.count + } + } + + const countdown = new Countdown(10) + + // Simulate what setInterval would do - extract the method + const boundTick = countdown.tick.bind(countdown) + + expect(boundTick()).toBe(9) + expect(boundTick()).toBe(8) + expect(boundTick()).toBe(7) + expect(countdown.count).toBe(7) + }) + + it('should lose this without bind', () => { + class Countdown { + constructor(start) { + this.count = start + } + + tick() { + return this?.count + } + } + + const countdown = new Countdown(10) + const unboundTick = countdown.tick + + // Without bind, this is undefined + expect(unboundTick()).toBeUndefined() + }) + }) }) }) From bf24aaad5907f40815449c01620e6d8d3a5b6ccd Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 12:09:08 -0300 Subject: [PATCH 050/128] docs(call-stack): improve SEO, add misconceptions section, and align with project structure - Update title and description for better SEO (50-60 char title, 150-160 char description) - Add sidebarTitle for navigation consistency - Add prerequisite warning linking to MDN functions guide - Add inline MDN links throughout content (call stack, single-threaded, execution context, RangeError, stack trace, setTimeout, fetch, Event Loop) - Add code example in 'What is the Call Stack?' section - Rename 'Stack Overflow' section to 'The #1 Call Stack Mistake' with ASCII diagram - Add 'Common Misconceptions' section with 4 verified misconceptions - Rename 'Test Your Knowledge' to 'Classic Interview Questions' - Add 3 more interview questions (now 6 total) - Add 3 more key takeaways (now 10 total) - Expand Reference section with 4 MDN links in CardGroup --- docs/concepts/call-stack.mdx | 283 ++++++++++++++++++++++++++++++++--- 1 file changed, 259 insertions(+), 24 deletions(-) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 43df6400..bbeda620 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -1,6 +1,41 @@ --- -title: "Call Stack" -description: "Understanding how JavaScript tracks function execution" +title: "JavaScript Call Stack: How Function Execution Works" +sidebarTitle: "Call Stack: How Function Execution Works" +description: "Learn how the JavaScript call stack tracks function execution. Understand stack frames, LIFO ordering, execution contexts, stack overflow errors, and debugging with stack traces." +--- + +How does JavaScript keep track of which function is running? When a function calls another function, how does JavaScript know where to return when that function finishes? + +The answer is the **[call stack](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)** — JavaScript's mechanism for tracking function execution. + +```javascript +function greet(name) { + const message = createMessage(name) + console.log(message) +} + +function createMessage(name) { + return "Hello, " + name + "!" +} + +greet("Alice") // "Hello, Alice!" +``` + +When `greet` calls `createMessage`, JavaScript remembers where it was in `greet` so it can return there after `createMessage` finishes. The call stack is what makes this possible. + +<Info> +**What you'll learn in this guide:** +- What the call stack is and why JavaScript needs it +- How functions are added and removed from the stack +- What happens step-by-step when your code runs +- Why you sometimes see "Maximum call stack size exceeded" errors +- How to debug call stack issues like a pro +</Info> + +<Warning> +**Prerequisite:** This guide assumes basic familiarity with [JavaScript functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions). If you're new to functions, start there first! +</Warning> + --- ## The Stack of Plates: A Real-World Analogy @@ -21,20 +56,21 @@ This is exactly how JavaScript keeps track of your functions! When you call a fu This simple concept — **adding to the top and removing from the top** — is the foundation of how JavaScript executes your code. -<Info> -**What you'll learn in this guide:** -- What the call stack is and why JavaScript needs it -- How functions are added and removed from the stack -- What happens step-by-step when your code runs -- Why you sometimes see "Maximum call stack size exceeded" errors -- How to debug call stack issues like a pro -</Info> - --- ## What is the Call Stack? -The **call stack** is a mechanism that JavaScript uses to keep track of where it is in your code. Think of it as JavaScript's "to-do list" for function calls — but one where it can only work on the item at the top. +The **[call stack](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)** is a mechanism that JavaScript uses to keep track of where it is in your code. Think of it as JavaScript's "to-do list" for function calls — but one where it can only work on the item at the top. + +```javascript +function first() { second(); } +function second() { third(); } +function third() { console.log('Hello!'); } + +first(); +// Stack grows: [first] → [second, first] → [third, second, first] +// Stack shrinks: [second, first] → [first] → [] +``` ### The LIFO Principle @@ -57,14 +93,14 @@ LIFO = Last In, First Out ### Why Does JavaScript Need a Call Stack? -JavaScript is **single-threaded**, meaning it can only do **one thing at a time**. The call stack helps JavaScript: +JavaScript is **[single-threaded](https://developer.mozilla.org/en-US/docs/Glossary/Thread)**, meaning it can only do **one thing at a time**. The call stack helps JavaScript: 1. **Remember where it is** — Which function is currently running? 2. **Know where to go back** — When a function finishes, where should execution continue? 3. **Keep track of local variables** — Each function has its own variables that shouldn't interfere with others <Info> -**ECMAScript Specification**: According to the official JavaScript specification, the call stack is implemented through "execution contexts." Each function call creates a new execution context that gets pushed onto the stack. +**ECMAScript Specification**: According to the official JavaScript specification, the call stack is implemented through "[execution contexts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#description)." Each function call creates a new execution context that gets pushed onto the stack. </Info> --- @@ -183,7 +219,7 @@ console.log("Done!"); ## Execution Context: What's Actually on the Stack? -When we say a function is "on the stack," what does that actually mean? Each entry on the call stack is called an **execution context** (or **stack frame**). It contains everything JavaScript needs to execute that function. +When we say a function is "on the stack," what does that actually mean? Each entry on the call stack is called an **[execution context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#description)** (or **stack frame**). It contains everything JavaScript needs to execute that function. <AccordionGroup> <Accordion title="Function Arguments"> @@ -343,12 +379,40 @@ printSquare(4); --- -## Stack Overflow: When Things Go Wrong +## The #1 Call Stack Mistake: Stack Overflow -The call stack has a **limited size**. If you keep adding functions without removing them, eventually you'll run out of space. This is called a **stack overflow**. +The call stack has a **limited size**. If you keep adding functions without removing them, eventually you'll run out of space. This is called a **stack overflow**, and JavaScript throws a **[RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError)** when it happens. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ STACK OVERFLOW │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ WRONG: No Base Case RIGHT: With Base Case │ +│ ──────────────────── ───────────────────── │ +│ │ +│ function count() { function count(n) { │ +│ count() // Forever! if (n <= 0) return // Stop! │ +│ } count(n - 1) │ +│ } │ +│ │ +│ Stack grows forever... Stack grows, then shrinks │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ count() │ │ count(0)│ ← Returns │ +│ ├─────────┤ ├─────────┤ │ +│ │ count() │ │ count(1)│ │ +│ ├─────────┤ ├─────────┤ │ +│ │ count() │ │ count(2)│ │ +│ ├─────────┤ └─────────┘ │ +│ │ .... │ │ +│ └─────────┘ │ +│ 💥 CRASH! ✓ Success! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` <Warning> -**Stack overflow** happens when the call stack exceeds its maximum size. The most common cause is **infinite recursion** — a function that calls itself forever. +**The Trap:** Recursive functions without a proper stopping condition will crash your program. The most common cause is **infinite recursion** — a function that calls itself forever without a base case. </Warning> ### The Classic Mistake: Missing Base Case @@ -484,7 +548,7 @@ When something goes wrong, the call stack is your best friend for figuring out w ### Reading a Stack Trace -When an error occurs, JavaScript gives you a **stack trace** — a snapshot of the call stack at the moment of the error. +When an error occurs, JavaScript gives you a **[stack trace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack)** — a snapshot of the call stack at the moment of the error. ```javascript function a() { b(); } @@ -555,7 +619,7 @@ You might be wondering: "If JavaScript can only do one thing at a time, how does Great question! The call stack is only **part** of the picture. <Note> -When you use asynchronous functions like `setTimeout`, `fetch`, or event listeners, JavaScript doesn't put them on the call stack immediately. Instead, they go through a different system involving the **Event Loop** and **Callback Queue**. +When you use asynchronous functions like [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), or event listeners, JavaScript doesn't put them on the call stack immediately. Instead, they go through a different system involving the **[Event Loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop)** and **Callback Queue**. This is covered in detail in the [Event Loop](/concepts/event-loop) section. </Note> @@ -608,11 +672,112 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in 6. **Always have a base case** — Recursive functions need a stopping condition 7. **Stack traces are your friend** — They show you exactly how your program got to an error + +8. **Async callbacks wait** — `setTimeout`, `fetch`, and event callbacks don't run until the call stack is empty + +9. **Each frame is isolated** — Local variables in one function call don't affect variables in another call of the same function + +10. **Debugging tools show the stack** — Browser DevTools let you pause execution and inspect the current call stack </Info> --- -## Test Your Knowledge +## Common Misconceptions + +<AccordionGroup> + <Accordion title="The call stack and memory heap are the same thing"> + **Wrong!** The call stack and heap are completely different structures: + + | Component | Purpose | Structure | + |-----------|---------|-----------| + | **Call Stack** | Tracks function execution | Ordered (LIFO), small, fast | + | **Heap** | Stores data (objects, arrays) | Unstructured, large | + + ```javascript + function example() { + // Primitives live in the stack frame + const x = 10; + const name = "Alice"; + + // Objects live in the HEAP (reference stored in stack) + const user = { name: "Alice" }; + const numbers = [1, 2, 3]; + } + ``` + + When the function returns, the stack frame is popped (primitives gone), but heap objects persist until garbage collected. + </Accordion> + + <Accordion title="Async callbacks execute immediately when the timer finishes"> + **Wrong!** When a timer finishes, the callback does NOT run immediately. It goes to the **Task Queue** and must wait for: + + 1. The call stack to be completely empty + 2. All microtasks to be processed first + 3. Its turn in the task queue + + ```javascript + console.log('Start'); + + setTimeout(() => { + console.log('Timer'); // Does NOT run at 0ms! + }, 0); + + console.log('End'); + + // Output: Start, End, Timer + // Even with 0ms delay, 'Timer' prints LAST + ``` + + The callback must wait until the current script finishes and the stack is empty. + </Accordion> + + <Accordion title="JavaScript can run multiple functions at once"> + **Wrong!** JavaScript is **single-threaded** — it has ONE call stack and can only execute ONE thing at a time. + + ```javascript + function a() { + console.log('A start'); + b(); // JS pauses 'a' and runs 'b' completely + console.log('A end'); + } + + function b() { + console.log('B'); + } + + a(); + // Output: A start, B, A end (sequential, not parallel) + ``` + + **The source of confusion:** People mistake JavaScript's *asynchronous behavior* for *parallel execution*. Web APIs (timers, fetch, etc.) run in separate browser threads, but JavaScript code itself runs one operation at a time. The Event Loop coordinates callbacks, creating the *illusion* of concurrency. + </Accordion> + + <Accordion title="Promises are completely asynchronous"> + **Wrong!** The Promise *constructor* runs **synchronously** — only the `.then()` callbacks are asynchronous: + + ```javascript + console.log('1'); + + new Promise((resolve) => { + console.log('2'); // Runs SYNCHRONOUSLY! + resolve(); + }).then(() => { + console.log('3'); // Async (microtask) + }); + + console.log('4'); + + // Output: 1, 2, 4, 3 + // Note: '2' prints before '4'! + ``` + + The executor function passed to `new Promise()` runs immediately on the call stack. Only the `.then()`, `.catch()`, and `.finally()` callbacks are queued as microtasks. + </Accordion> +</AccordionGroup> + +--- + +## Classic Interview Questions <AccordionGroup> <Accordion title="Question 1: What does LIFO stand for and why is it important?"> @@ -665,6 +830,65 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in greet(3); ``` </Accordion> + + <Accordion title="Question 4: What information is stored in an execution context?"> + **Answer:** An execution context (stack frame) contains: + + 1. **Function arguments** — The values passed to the function + 2. **Local variables** — Variables declared with `var`, `let`, or `const` + 3. **The `this` value** — The context binding for the function + 4. **Return address** — Where to continue executing after the function returns + 5. **Scope chain** — Access to variables from outer (parent) functions + + This is why each function call can have its own independent set of variables without interfering with other calls. + </Accordion> + + <Accordion title="Question 5: What's the output of this code and why?"> + ```javascript + console.log('First') + + setTimeout(() => { + console.log('Second') + }, 0) + + console.log('Third') + ``` + + **Answer:** The output is: + ``` + First + Third + Second + ``` + + Even though `setTimeout` has a 0ms delay, "Second" prints last because: + + 1. `setTimeout` doesn't put the callback directly on the call stack + 2. Instead, the callback waits in the **task queue** + 3. The event loop only moves it to the call stack when the stack is empty + 4. "Third" runs first because it's already on the call stack + + This demonstrates that the call stack must be empty before async callbacks execute. + </Accordion> + + <Accordion title="Question 6: How do you read a stack trace?"> + Given this error: + ``` + Error: Something went wrong! + at c (script.js:4:9) + at b (script.js:2:14) + at a (script.js:1:14) + at script.js:7:1 + ``` + + **Answer:** Read stack traces from **top to bottom** (most recent to oldest): + + 1. **Top line** (`at c`) — Where the error actually occurred (function `c`, line 4, column 9) + 2. **Following lines** — The chain of function calls that led here + 3. **Bottom line** — Where the chain started (the initial call) + + The trace tells you: the program started at line 7, called `a()`, which called `b()`, which called `c()`, where the error was thrown. This helps you trace back through your code to find the root cause. + </Accordion> </AccordionGroup> --- @@ -690,9 +914,20 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in ## Reference -<Card title="Call Stack — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Call_stack"> - Official MDN documentation on the Call Stack -</Card> +<CardGroup cols={2}> + <Card title="Call Stack — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Call_stack"> + Official MDN documentation on the Call Stack + </Card> + <Card title="JavaScript Event Loop — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop"> + How the event loop interacts with the call stack + </Card> + <Card title="RangeError — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError"> + The error thrown when the call stack overflows + </Card> + <Card title="Error.stack — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack"> + How to read and use stack traces for debugging + </Card> +</CardGroup> ## Articles From daeeca41b2300e83a787b88faa54ffffc031c83a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 12:14:02 -0300 Subject: [PATCH 051/128] docs(scope-and-closures): improve SEO, structure, and add MDN links - Add sidebarTitle and optimize title/description for SEO - Add prerequisite warning linking to call-stack concept - Restructure page: move definition before analogy section - Add MDN links for garbage collection, addEventListener, removeEventListener, Fetch API - Consolidate common mistakes into dedicated section - Add internal links to event-loop and iife-modules concepts - Expand Reference section with var, let, const, and Scope MDN links - Add JavaScript Tutor visualization tool --- docs/concepts/scope-and-closures.mdx | 160 +++++++++++++++++---------- 1 file changed, 101 insertions(+), 59 deletions(-) diff --git a/docs/concepts/scope-and-closures.mdx b/docs/concepts/scope-and-closures.mdx index 96279429..0ff14a78 100644 --- a/docs/concepts/scope-and-closures.mdx +++ b/docs/concepts/scope-and-closures.mdx @@ -1,9 +1,51 @@ --- -title: "Scope & Closures" -description: "Understanding variable visibility and how functions remember their environment" +title: "Scope and Closures: How Variables Really Work in JavaScript" +sidebarTitle: "Scope and Closures: How Variables Really Work" +description: "Master JavaScript scope and closures. Learn the three types of scope, var vs let vs const, lexical scoping, the scope chain, and closure patterns for data privacy." --- -## Sneaking Through an Office Building +Why can some variables be accessed from anywhere in your code, while others seem to disappear? How do functions "remember" variables from their parent functions, even after those functions have finished running? + +```javascript +function createCounter() { + let count = 0 // This variable is "enclosed" + + return function() { + count++ + return count + } +} + +const counter = createCounter() +console.log(counter()) // 1 +console.log(counter()) // 2 — it remembers! +``` + +The answers lie in understanding **scope** and **closures** — two fundamental concepts that govern how variables work in JavaScript. Scope determines *where* variables are visible, while closures allow functions to *remember* their original environment. + +<Info> +**What you'll learn in this guide:** +- The 3 types of scope: global, function, and block +- How [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var), [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), and [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) behave differently +- What lexical scope means and how the scope chain works +- What closures are and why every JavaScript developer must understand them +- Practical patterns: data privacy, factories, and memoization +- The classic closure gotchas and how to avoid them +</Info> + +<Warning> +**Prerequisite:** This guide builds on your understanding of the [call stack](/concepts/call-stack). Knowing how JavaScript tracks function execution will help you understand how scope and closures work under the hood. +</Warning> + +--- + +## What is Scope in JavaScript? + +**[Scope](https://developer.mozilla.org/en-US/docs/Glossary/Scope)** is the current context of execution in which values and expressions are "visible" or can be referenced. It's the set of rules that determines where and how variables can be accessed in your code. If a variable is not in the current scope, it cannot be used. Scopes can be nested, and inner scopes have access to outer scopes, but not vice versa. + +--- + +## The Office Building Analogy Imagine it's after hours and you're wandering through your office building (legally — you work there, promise). You notice something interesting about what you can and can't see: @@ -40,26 +82,6 @@ This is exactly how **scope** works in JavaScript! Code in inner scopes can "loo And here's where it gets really interesting: imagine someone who worked in that private office quits and leaves the building. But they took a mental snapshot of everything in there — the passwords on sticky notes, the secret project plans, the snack drawer location. Even though they've left, they still *remember* everything. That's essentially what a **closure** is: a function that "remembers" the scope where it was created, even after that scope is gone. -<Note> -**TL;DR:** Scope determines where variables are accessible in your code. Closures are functions that remember and can access variables from their birth scope, even after that scope has finished executing. -</Note> - -<Info> -**What you'll learn in this guide:** -- The 3 types of scope: global, function, and block -- How `var`, `let`, and `const` behave differently -- What lexical scope means and how the scope chain works -- What closures are and why every JavaScript developer must understand them -- Practical patterns: data privacy, factories, and memoization -- The classic closure gotchas and how to avoid them -</Info> - ---- - -## Scope: Who Can See What? - -**Scope** is the set of rules that determines where and how variables can be accessed in your code. Think of it as the "visibility" or "reach" of a variable. - ### Why Does Scope Exist? Scope exists for three critical reasons: @@ -82,7 +104,7 @@ Scope exists for three critical reasons: </Accordion> <Accordion title="2. Memory Management"> - When a scope ends, variables declared in that scope can be garbage collected (cleaned up from memory). This keeps your program efficient. + When a scope ends, variables declared in that scope can be [garbage collected](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management#garbage_collection) (cleaned up from memory). This keeps your program efficient. ```javascript function processData() { @@ -135,7 +157,7 @@ if (true) { #### The Global Object -In browsers, global variables become properties of the `window` object. In Node.js, they attach to `global`. The modern, universal way to access the global object is `globalThis`. +In browsers, global variables become properties of the [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) object. In Node.js, they attach to `global`. The modern, universal way to access the global object is [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis). ```javascript var oldSchool = "I'm on window"; // window.oldSchool (var only) @@ -168,7 +190,7 @@ const MyApp = { ### 2. Function Scope -Variables declared with `var` inside a function are **function-scoped** — they're only accessible within that function. +Variables declared with [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) inside a function are **function-scoped** — they're only accessible within that function. ```javascript function calculateTotal() { @@ -222,7 +244,7 @@ Your code: How JS sees it: ### 3. Block Scope -Variables declared with `let` and `const` are **block-scoped**. A block is any code within curly braces `{}`: if statements, for loops, while loops, or just standalone blocks. +Variables declared with [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) and [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) are **block-scoped**. A block is any code within curly braces `{}`: if statements, for loops, while loops, or just standalone blocks. ```javascript if (true) { @@ -238,7 +260,7 @@ console.log(functionVar); // ✓ "I escape the block!" #### The Temporal Dead Zone (TDZ) -Unlike `var`, variables declared with `let` and `const` are not initialized until their declaration is evaluated. Accessing them before declaration causes a `ReferenceError`. This period is called the **Temporal Dead Zone**. +Unlike `var`, variables declared with `let` and `const` are not initialized until their declaration is evaluated. Accessing them before declaration causes a [`ReferenceError`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError). This period is called the **Temporal Dead Zone**. ```javascript function demo() { @@ -303,11 +325,11 @@ Here's a comprehensive comparison of the three variable declaration keywords: console.log(name); // "Bob" // let and const prevent redeclaration - let age = 25; - // let age = 30; // SyntaxError: 'age' has already been declared + let age = 25 + // let age = 30 // SyntaxError: 'age' has already been declared - const PI = 3.14; - // const PI = 3.14159; // SyntaxError + const PI = 3.14 + // const PI = 3.14159 // SyntaxError ``` </Tab> <Tab title="Reassignment"> @@ -320,14 +342,14 @@ Here's a comprehensive comparison of the three variable declaration keywords: score = 200; // ✓ Fine // const prevents reassignment - const API_KEY = "abc123"; - // API_KEY = "xyz789"; // TypeError: Assignment to constant variable + const API_KEY = "abc123" + // API_KEY = "xyz789" // TypeError: Assignment to constant variable // BUT: const objects/arrays CAN be mutated! - const user = { name: "Alice" }; - user.name = "Bob"; // ✓ This works! - user.age = 25; // ✓ This works too! - // user = {}; // ✗ This fails (reassignment) + const user = { name: "Alice" } + user.name = "Bob" // ✓ This works! + user.age = 25 // ✓ This works too! + // user = {} // ✗ This fails (reassignment) ``` </Tab> <Tab title="Hoisting Behavior"> @@ -357,8 +379,8 @@ This is one of the most common JavaScript gotchas, and it perfectly illustrates // The Problem: var is function-scoped for (var i = 0; i < 3; i++) { setTimeout(() => { - console.log(i); - }, 100); + console.log(i) + }, 100) } // Output: 3, 3, 3 (not 0, 1, 2!) @@ -366,12 +388,14 @@ for (var i = 0; i < 3; i++) { // By the time the setTimeout callbacks run, the loop has finished and i === 3. ``` +The [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) callbacks all close over the same `i` variable, which equals `3` by the time they execute. (To understand why the callbacks don't run immediately, see our [Event Loop](/concepts/event-loop) guide.) + ```javascript // The Solution: let is block-scoped for (let i = 0; i < 3; i++) { setTimeout(() => { - console.log(i); - }, 100); + console.log(i) + }, 100) } // Output: 0, 1, 2 (correct!) @@ -497,16 +521,12 @@ Shadowing can be confusing. While sometimes intentional, accidental shadowing is --- -## Closures: Functions with Memories +## What is a Closure in JavaScript? -Now we arrive at one of JavaScript's most powerful features. A **closure** is created when a function "remembers" the variables from its lexical scope, even when that function is executed outside that scope. +A **[closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)** is a function bundled together with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to variables from an outer (enclosing) scope, even after that outer function has finished executing and returned. Every function in JavaScript creates a closure at creation time. Remember our office building analogy? A closure is like someone who worked in the private office, left the building, but still remembers exactly where everything was — and can still use that knowledge! -<Info> -**The simplest definition:** A closure is a function that has access to variables from its outer (enclosing) scope, even after the outer function has returned. -</Info> - ### Every Function Creates a Closure In JavaScript, closures are created automatically every time you create a function. The function maintains a reference to its lexical environment. @@ -616,7 +636,7 @@ console.log(counter.count); // undefined ``` <Tip> -This pattern is the foundation of the **Module Pattern**, widely used before ES6 modules became available. It's still useful for creating private state. +This pattern is the foundation of the **Module Pattern**, widely used before ES6 modules became available. Learn more in our [IIFE, Modules and Namespaces](/concepts/iife-modules) guide. </Tip> ### 2. Function Factories @@ -641,6 +661,8 @@ console.log(tenX(5)); // 50 // Each function "remembers" its own multiplier ``` +This pattern works great with the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) for creating reusable API clients: + ```javascript // Real-world example: API request factories function createApiClient(baseUrl) { @@ -667,7 +689,7 @@ myApi.get('/users/1'); ### 3. Preserving State in Callbacks & Event Handlers -Closures are essential for maintaining state in asynchronous code: +Closures are essential for maintaining state in asynchronous code. When you use [`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) to attach event handlers, those handlers can close over variables from their outer scope: ```javascript function setupClickCounter(buttonId) { @@ -724,9 +746,13 @@ console.log(memoizedFactorial(5)); // Returning cached result → 120 --- -## The Trap Everyone Falls Into +## Common Mistakes and Pitfalls -We mentioned this earlier, but it's so important it deserves its own section. This is the #1 closure interview question — and almost everyone gets it wrong the first time: +Understanding scope and closures means understanding where things go wrong. These are the mistakes that trip up even experienced developers. + +### The #1 Closure Interview Question + +This is the classic closure trap — almost everyone gets it wrong the first time: ### The Problem @@ -816,11 +842,9 @@ What actually happens: </Tab> </Tabs> ---- - -## With Great Power Comes Great Responsibility +### Memory Leaks from Closures -Closures are powerful, but they come with responsibility. Since closures keep references to their outer scope variables, those variables can't be garbage collected. +Closures are powerful, but they come with responsibility. Since closures keep references to their outer scope variables, those variables can't be [garbage collected](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management#garbage_collection). ### Potential Memory Leaks @@ -840,7 +864,7 @@ const leakyFunction = createHeavyClosure(); ### Breaking Closure References -When you're done with a closure, explicitly break the reference: +When you're done with a closure, explicitly break the reference. Use [`removeEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) to clean up event handlers: ```javascript function setupHandler(element) { @@ -1076,9 +1100,21 @@ cleanup(); // Removes listener, allows memory to be freed <CardGroup cols={2}> <Card title="Closures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures"> - Official MDN documentation on closures + Official MDN documentation on closures and lexical scoping </Card> - <Card title="Closure — JavaScript.Info" icon="book" href="https://javascript.info/closure"> + <Card title="Scope — MDN Glossary" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Scope"> + MDN glossary entry explaining scope in JavaScript + </Card> + <Card title="var — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var"> + Reference for the var keyword, function scope, and hoisting + </Card> + <Card title="let — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let"> + Reference for the let keyword and block scope + </Card> + <Card title="const — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const"> + Reference for the const keyword and immutable bindings + </Card> + <Card title="Closures — JavaScript.Info" icon="book" href="https://javascript.info/closure"> In-depth tutorial on closures and lexical environment </Card> </CardGroup> @@ -1112,6 +1148,12 @@ cleanup(); // Removes listener, allows memory to be freed </Card> </CardGroup> +## Tools + +<Card title="JavaScript Tutor — Visualize Code Execution" icon="play" href="https://pythontutor.com/javascript.html"> + Step through JavaScript code and see how closures capture variables in real-time. Visualize the scope chain, execution contexts, and how functions "remember" their environment. Perfect for understanding closures visually. +</Card> + ## Courses <Card title="JavaScript: Understanding the Weird Parts (First 3.5 Hours)" icon="graduation-cap" href="https://www.youtube.com/watch?v=Bv_5Zv5c-Ts"> From 0f1f17bd39bfdac49d97b4e1a784cf40b566b3df Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 12:18:46 -0300 Subject: [PATCH 052/128] docs: standardize page structure across concept pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - event-loop: swap Info/Warning order, move Key Takeaways before Test Your Knowledge - dom: update title format ('in JavaScript' at end), move code example after opening hook - http-fetch: swap Info/Warning order to match skill template All pages now follow consistent structure: hook → code → explanation → Info → Warning --- docs/concepts/dom.mdx | 20 +++++------ docs/concepts/event-loop.mdx | 64 ++++++++++++++++++------------------ docs/concepts/http-fetch.mdx | 8 ++--- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 04c9d805..7fd6781b 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -1,12 +1,19 @@ --- -title: "DOM in JavaScript: How Browsers Represent Web Pages" +title: "DOM: How Browsers Represent Web Pages in JavaScript" sidebarTitle: "DOM: How Browsers Represent Web Pages" description: "Learn how the DOM works in JavaScript. Understand how browsers represent HTML as a tree, select and manipulate elements, traverse nodes, and optimize rendering performance." --- How does JavaScript change what you see on a webpage? How do you click a button and see new content appear, or type in a form and watch suggestions pop up? How does a "dark mode" toggle instantly transform an entire page? -The answer lies in the **DOM** — the bridge between your HTML and JavaScript. +```javascript +// The DOM lets you do things like this: +document.querySelector('h1').textContent = 'Hello, DOM!' +document.body.style.backgroundColor = 'lightblue' +document.getElementById('btn').addEventListener('click', handleClick) +``` + +The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is the bridge between your HTML and JavaScript — it lets you read, modify, and respond to changes in web page content. With the DOM, you can use methods like **[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** to find elements, **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** to grab specific nodes, and **[`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)** to respond to user interactions. <Info> **What you'll learn in this guide:** @@ -29,15 +36,6 @@ The answer lies in the **DOM** — the bridge between your HTML and JavaScript. The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface that represents HTML documents as a tree of objects. When a browser loads a webpage, it parses the HTML and creates the DOM — a live, structured representation that JavaScript can read and modify. Every element, attribute, and piece of text becomes a node in this tree. **In short: the DOM is how JavaScript "sees" and changes a webpage.** -```javascript -// The DOM lets you do things like this: -document.querySelector('h1').textContent = 'Hello, DOM!' -document.body.style.backgroundColor = 'lightblue' -document.getElementById('btn').addEventListener('click', handleClick) -``` - -With the DOM, you can use methods like **[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** to find elements, **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** to grab specific nodes, and **[`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)** to respond to user interactions. - --- ## How the DOM Tree Structure Works diff --git a/docs/concepts/event-loop.mdx b/docs/concepts/event-loop.mdx index 38ba5284..012c6422 100644 --- a/docs/concepts/event-loop.mdx +++ b/docs/concepts/event-loop.mdx @@ -21,10 +21,6 @@ console.log('End'); Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model)** — JavaScript's mechanism for handling asynchronous operations while remaining single-threaded. -<Warning> -**Prerequisites:** This guide assumes familiarity with [the call stack](/concepts/call-stack) and [Promises](/concepts/promises). If those concepts are new to you, read them first! -</Warning> - <Info> **What you'll learn in this guide:** - Why JavaScript needs an event loop (and what "single-threaded" really means) @@ -35,6 +31,10 @@ Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event lo - Common interview questions explained step-by-step </Info> +<Warning> +**Prerequisites:** This guide assumes familiarity with [the call stack](/concepts/call-stack) and [Promises](/concepts/promises). If those concepts are new to you, read them first! +</Warning> + --- ## What is the Event Loop? @@ -1474,6 +1474,34 @@ Watch how: --- +## Key Takeaways + +<Info> +**Remember these essential points:** + +1. **JavaScript is single-threaded** — only one thing runs at a time on the call stack + +2. **The Event Loop enables async** — it coordinates between the call stack and callback queues + +3. **Web APIs run in separate threads** — timers, network requests, and events are handled by the browser + +4. **Microtasks > Tasks** — Promise callbacks ALWAYS run before setTimeout callbacks + +5. **setTimeout delay is a minimum** — actual timing depends on call stack and queue state + +6. **setInterval can drift** — use nested setTimeout for precise timing + +7. **requestAnimationFrame for animations** — syncs with browser refresh rate, pauses in background + +8. **Never block the main thread** — long sync operations freeze the entire UI + +9. **Microtasks can starve tasks** — infinite microtask loops prevent rendering + +10. **The Event Loop isn't JavaScript** — it's part of the runtime environment (browser/Node.js) +</Info> + +--- + ## Test Your Knowledge <AccordionGroup> @@ -1568,34 +1596,6 @@ Watch how: --- -## Key Takeaways - -<Info> -**Remember these essential points:** - -1. **JavaScript is single-threaded** — only one thing runs at a time on the call stack - -2. **The Event Loop enables async** — it coordinates between the call stack and callback queues - -3. **Web APIs run in separate threads** — timers, network requests, and events are handled by the browser - -4. **Microtasks > Tasks** — Promise callbacks ALWAYS run before setTimeout callbacks - -5. **setTimeout delay is a minimum** — actual timing depends on call stack and queue state - -6. **setInterval can drift** — use nested setTimeout for precise timing - -7. **requestAnimationFrame for animations** — syncs with browser refresh rate, pauses in background - -8. **Never block the main thread** — long sync operations freeze the entire UI - -9. **Microtasks can starve tasks** — infinite microtask loops prevent rendering - -10. **The Event Loop isn't JavaScript** — it's part of the runtime environment (browser/Node.js) -</Info> - ---- - ## Related Concepts <CardGroup cols={2}> diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index 275630ff..c8e89979 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -15,10 +15,6 @@ console.log(user.name) // "Alice" But to really understand Fetch, you need to understand what's happening underneath: **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)**. -<Warning> -**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so those concepts are essential. If you're not comfortable with Promises yet, read that guide first! -</Warning> - <Info> **What you'll learn in this guide:** - How HTTP requests and responses work @@ -30,6 +26,10 @@ But to really understand Fetch, you need to understand what's happening undernea - How to cancel requests with AbortController </Info> +<Warning> +**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so those concepts are essential. If you're not comfortable with Promises yet, read that guide first! +</Warning> + ## What is HTTP? **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)** (Hypertext Transfer Protocol) is the foundation of data communication on the web. It defines how messages are formatted and transmitted between clients (like web browsers) and servers. Every time you load a webpage, submit a form, or fetch data with JavaScript, HTTP is the protocol making that exchange possible. From 065d690fd8126acf0a3a64232cd26445f5936718 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 12:24:31 -0300 Subject: [PATCH 053/128] docs(this-call-apply-bind): standardize page structure and update title format - Update title to new format: 'this, call, apply, and bind: How Context Works in JavaScript' - Move Info box to opening section (after code example, before first ## section) - Add Warning box with prerequisites (links to scope-and-closures) - Remove duplicate Info box from middle of content --- docs/concepts/this-call-apply-bind.mdx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx index 97669d9e..a302d746 100644 --- a/docs/concepts/this-call-apply-bind.mdx +++ b/docs/concepts/this-call-apply-bind.mdx @@ -1,6 +1,6 @@ --- -title: "The this Keyword: Understanding Context in JavaScript" -sidebarTitle: "The this Keyword, call, apply, and bind" +title: "this, call, apply, and bind: How Context Works in JavaScript" +sidebarTitle: "this, call, apply, and bind: How Context Works" description: "Learn how JavaScript's 'this' keyword works and how to control context binding. Understand the 5 binding rules, call/apply/bind methods, arrow functions, and common pitfalls." --- @@ -21,9 +21,18 @@ greet(); // "Hi, I'm undefined" - broken! The **[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)** keyword is one of JavaScript's most confusing features — but it follows specific rules. Once you understand them, you'll never be confused again. -Building on our exploration of [Scope & Closures](/concepts/scope-and-closures), understanding `this` is the next step to learning how JavaScript handles context. You've probably seen `this` pop up everywhere — in object methods, class constructors, event handlers, and callbacks. Sometimes it works exactly as you'd expect. Other times? It points to something completely unexpected and your code breaks in mysterious ways. +<Info> +**What you'll learn in this guide:** +- What `this` actually is and why it's determined at call time +- The 5 binding rules that determine `this` (in priority order) +- How `call()`, `apply()`, and `bind()` work and when to use each +- Arrow functions and why they handle `this` differently +- Common pitfalls and how to avoid them +</Info> -Let's start with an analogy that makes it all click. +<Warning> +**Prerequisite:** This guide builds on [Scope & Closures](/concepts/scope-and-closures). Understanding scope will help you see why `this` behaves differently than regular variables. +</Warning> --- @@ -80,15 +89,6 @@ bob.introduce.call(alice); // "I am Alice" (Bob's function, Alice's this) That's what `call`, `apply`, and `bind` do — they let you control **who "I" refers to**, regardless of which function is speaking. -<Info> -**What you'll learn in this guide:** -- What `this` actually is and why it's determined at call time -- The 5 binding rules that determine `this` (in priority order) -- How `call()`, `apply()`, and `bind()` work and when to use each -- Arrow functions and why they handle `this` differently -- Common pitfalls and how to avoid them -</Info> - --- ## What is `this` in JavaScript? From 3ba592320e55e944e8be564ad91eee531bdb8903 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:07:12 -0300 Subject: [PATCH 054/128] docs(equality-operators): improve SEO, structure, and add comprehensive content - Update title to include "in JavaScript" for SEO optimization - Add sidebarTitle for cleaner navigation - Expand description to 156 characters with keywords - Restructure opening with hook questions and immediate code example - Move "What you'll learn" box after opening code - Reorder sections: Overview before Analogy (per skill template) - Add 16 inline MDN links throughout the document - Add "Common Misconceptions" section with 6 accordions - Add "Interactive Visualization Tool" section - Update Key Takeaways header to match skill format - Add horizontal rules between Reference/Articles/Videos/Books - Change TL;DR from Note to Tip component --- docs/concepts/equality-operators.mdx | 257 +++++++++++++++++++++------ 1 file changed, 207 insertions(+), 50 deletions(-) diff --git a/docs/concepts/equality-operators.mdx b/docs/concepts/equality-operators.mdx index 0fad546b..0d8bb626 100644 --- a/docs/concepts/equality-operators.mdx +++ b/docs/concepts/equality-operators.mdx @@ -1,48 +1,31 @@ --- -title: "Equality and Type Checking" -description: "How JavaScript compares values — and why it sometimes gets confused" +title: "Equality Operators: == vs === Type Checking in JavaScript" +sidebarTitle: "Equality Operators: == vs === Type Checking" +description: "Learn JavaScript equality operators == vs ===, typeof quirks, and Object.is(). Understand type coercion, why NaN !== NaN, and why typeof null returns 'object'." --- -## The Teacher Grading Papers: A Real-World Analogy - -Imagine a teacher grading a math test. The question asks: "What is 2 + 2?" +Why does `1 == "1"` return `true` but `1 === "1"` return `false`? Why does `typeof null` return `"object"`? And why is `NaN` the only value in JavaScript that isn't equal to itself? -One student writes: `4` -Another student writes: `"4"` (as text) -A third student writes: `4.0` - -How strict should the teacher be when grading? +```javascript +// Same values, different results +console.log(1 == "1"); // true — loose equality converts types +console.log(1 === "1"); // false — strict equality checks type first -``` - RELAXED GRADING (==) STRICT GRADING (===) - "Is the answer correct?" "Is it exactly right?" - - ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ - │ 4 │ = │ "4" │ │ 4 │ ≠ │ "4" │ - │ (number) │ │ (string) │ │ (number) │ │ (string) │ - └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ - │ │ │ │ - └────────┬────────┘ └────────┬────────┘ - ▼ ▼ - "Close enough!" ✓ "Different types!" ✗ +// The famous quirks +console.log(typeof null); // "object" — a 28-year-old bug! +console.log(NaN === NaN); // false — NaN never equals anything ``` -JavaScript gives you both types of teachers: - -- **Loose equality (`==`)** — The relaxed teacher. Accepts `4` and `"4"` as the same answer because the *meaning* is similar. Converts values to match before comparing. -- **Strict equality (`===`)** — The strict teacher. Only accepts the *exact* answer in the *exact* format. The number `4` and the string `"4"` are different answers. -- **`typeof`** — Asks "What kind of answer is this?" Is it a number? A string? Something else? -- **`Object.is()`** — The most precise teacher. Even stricter than `===` — can spot tiny differences that others miss. - -Understanding these comparison tools is important because comparison bugs are very common in JavaScript. This guide will help you write code that works as expected and debug problems faster. +Understanding JavaScript's **[equality operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality)** is crucial because comparison bugs are among the most common in JavaScript code. This guide will teach you exactly how `==`, **[`===`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality)**, and **[`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)** work — and when to use each one. <Info> **What you'll learn in this guide:** -- The difference between `==` and `===` (and when to use each) -- How JavaScript converts values when comparing them -- The `typeof` operator and its quirks -- When to use `Object.is()` for special cases -- Common mistakes and how to avoid them +- The difference between `==` (loose) and `===` (strict) equality +- How JavaScript converts values during loose equality comparisons +- The **[`typeof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof)** operator and its famous quirks (including the `null` bug) +- When to use `Object.is()` for edge cases like `NaN` and `-0` +- Common comparison mistakes and how to avoid them +- A simple rule: when to use which operator </Info> --- @@ -73,9 +56,46 @@ console.log(Object.is(num, str)); // false (different types) --- +## The Teacher Grading Papers: A Real-World Analogy + +Imagine a teacher grading a math test. The question asks: "What is 2 + 2?" + +One student writes: `4` +Another student writes: `"4"` (as text) +A third student writes: `4.0` + +How strict should the teacher be when grading? + +``` + RELAXED GRADING (==) STRICT GRADING (===) + "Is the answer correct?" "Is it exactly right?" + + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ + │ 4 │ = │ "4" │ │ 4 │ ≠ │ "4" │ + │ (number) │ │ (string) │ │ (number) │ │ (string) │ + └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ │ + └────────┬────────┘ └────────┬────────┘ + ▼ ▼ + "Close enough!" ✓ "Different types!" ✗ +``` + +JavaScript gives you both types of teachers: + +- **Loose equality (`==`)** — The relaxed teacher. Accepts `4` and `"4"` as the same answer because the *meaning* is similar. Converts values to match before comparing. +- **Strict equality (`===`)** — The strict teacher. Only accepts the *exact* answer in the *exact* format. The number `4` and the string `"4"` are different answers. +- **`typeof`** — Asks "What kind of answer is this?" Is it a number? A string? Something else? +- **`Object.is()`** — The most precise teacher. Even stricter than `===` — can spot tiny differences that others miss. + +<Tip> +**TL;DR:** Use `===` for almost everything. Use `== null` to check for both `null` and `undefined`. Use `Object.is()` only for `NaN` or `-0` edge cases. +</Tip> + +--- + ## Loose Equality (`==`): The Relaxed Comparison -The `==` operator tries to be helpful. Before comparing two values, it converts them to the same type. This automatic conversion is called **type coercion**. +The **[`==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality)** tries to be helpful. Before comparing two values, it converts them to the same type. This automatic conversion is called **[type coercion](/concepts/type-coercion)**. For example, if you compare the number `5` with the string `"5"`, JavaScript thinks: "These look similar. Let me convert them and check." So `5 == "5"` returns `true`. @@ -103,7 +123,7 @@ Here's the complete algorithm from the ECMAScript specification. When comparing </Step> <Step title="null and undefined"> - If `x` is `null` and `y` is `undefined` (or vice versa), return `true`. + If `x` is **[`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null)** and `y` is **[`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined)** (or vice versa), return `true`. ```javascript null == undefined // true (special case!) @@ -122,7 +142,7 @@ Here's the complete algorithm from the ECMAScript specification. When comparing </Step> <Step title="BigInt and String"> - If one is a BigInt and the other is a String, convert the String to a BigInt. + If one is a **[BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)** and the other is a String, convert the String to a BigInt. ```javascript 10n == "10" // "10" → 10n, then 10n == 10n → true @@ -140,7 +160,7 @@ Here's the complete algorithm from the ECMAScript specification. When comparing </Step> <Step title="Object to Primitive"> - If one is an Object and the other is a String, Number, BigInt, or Symbol, convert the Object to a primitive using `ToPrimitive`. + If one is an Object and the other is a String, Number, BigInt, or **[Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)**, convert the Object to a primitive using **[`ToPrimitive`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive)**. ```javascript [1] == 1 // [1] → "1" → 1, then 1 == 1 → true @@ -161,9 +181,9 @@ Here's the complete algorithm from the ECMAScript specification. When comparing If none of the above rules apply, return `false`. ```javascript - null == 0 // false (null only equals undefined) - undefined == 0 // false - Symbol() == Symbol() // false (symbols are unique) + null == 0 // false (null only equals undefined) + undefined == 0 // false + Symbol() == Symbol() // false (Symbols are always unique) ``` </Step> </Steps> @@ -227,7 +247,7 @@ Here are some comparison results that surprise most developers. Understanding *w // But string-to-string is direct comparison "" == "0" // false (both strings, different values) - // NaN conversions + // NaN conversions (NaN is "Not a Number") NaN == "NaN" // false (NaN ≠ anything, including itself) 0 == "hello" // false ("hello" → NaN, 0 ≠ NaN) ``` @@ -403,7 +423,7 @@ When you write `x === y`, JavaScript asks: 1. Are `x` and `y` the same type? No → return `false` 2. Same type? → Compare their values -That's it. No conversions, no surprises (well, *almost* — there's one special case with `NaN`). +That's it. No conversions, no surprises (well, *almost* — there's one special case with **[`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN)**). ### The Strict Equality Comparison Algorithm @@ -560,7 +580,7 @@ Even `===` has two edge cases that might surprise you: // How to check for NaN: Number.isNaN(NaN) // true (recommended) - isNaN(NaN) // true (but has quirks) + isNaN(NaN) // true (but has quirks — see below) Object.is(NaN, NaN) // true (ES6) // The isNaN() quirk: @@ -569,7 +589,7 @@ Even `===` has two edge cases that might surprise you: ``` <Warning> - Always use `Number.isNaN()` instead of `isNaN()`. The global `isNaN()` function converts its argument to a Number first, which means `isNaN("hello")` returns `true`. That's rarely what you want. + Always use **[`Number.isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN)** instead of the global `isNaN()`. The global `isNaN()` function converts its argument to a Number first, which means `isNaN("hello")` returns `true`. That's rarely what you want. </Warning> </Tab> @@ -644,7 +664,7 @@ obj1 === obj2 → false (different objects, even with same content) <Tip> To compare objects by their content (deep equality), you need to: -- Use `JSON.stringify()` for simple objects (has limitations) +- Use **[`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)** for simple objects (has limitations) - Write a recursive comparison function - Use a library like Lodash's `_.isEqual()` </Tip> @@ -724,7 +744,7 @@ For most everyday code, you won't need `Object.is()`. Use `===` as your default, ## The `typeof` Operator -The `typeof` operator tells you what type a value is. It returns a string like `"number"`, `"string"`, or `"boolean"`. It's very useful, but it has some famous quirks that surprise many developers. +The **[`typeof` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof)** tells you what type a value is. It returns a string like `"number"`, `"string"`, or `"boolean"`. It's very useful, but it has some famous quirks that surprise many developers. ### How It Works @@ -796,6 +816,8 @@ typeof(operand) // Both forms are valid // Or using Object.prototype.toString Object.prototype.toString.call([]) // "[object Array]" ``` + + Use **[`Array.isArray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)** — it's the most reliable method. </Accordion> <Accordion title="Functions Return 'function'"> @@ -885,9 +907,13 @@ Since `typeof` has limitations, here are more reliable approaches: // Safe integers Number.isSafeInteger(value) // true for safe integers ``` + + These methods from **[`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** are more reliable than `typeof` for numeric checks. </Tab> <Tab title="instanceof"> + The **[`instanceof` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)** checks if an object is an instance of a constructor: + ```javascript // Check if an object is an instance of a constructor [] instanceof Array // true @@ -906,7 +932,7 @@ Since `typeof` has limitations, here are more reliable approaches: </Tab> <Tab title="Object.prototype.toString"> - The most reliable method for getting precise type information: + The **[`Object.prototype.toString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString)** method is the most reliable for getting precise type information: ```javascript const getType = (value) => @@ -1215,10 +1241,115 @@ These common mistakes trip up many JavaScript developers. Learning about them no --- +## Common Misconceptions + +<AccordionGroup> + <Accordion title="Misconception 1: '== is always bad and should never be used'"> + **Not quite!** While `===` should be your default, there's one legitimate use case for `==`: + + ```javascript + // The one acceptable use of == + if (value == null) { + // Catches both null AND undefined + } + + // Equivalent to: + if (value === null || value === undefined) { + // Same result, but more verbose + } + ``` + + This is cleaner than checking for both values separately and is explicitly allowed by most style guides (including ESLint's `eqeqeq` rule with the `"null": "ignore"` option). + </Accordion> + + <Accordion title="Misconception 2: '=== checks if types are the same'"> + **Partially wrong!** `===` doesn't *just* check types — it checks if two values are the **same type AND same value**. + + ```javascript + // Same type, different values → false + 5 === 10 // false (both numbers, different values) + "hello" === "hi" // false (both strings, different values) + + // Different types → immediately false + 5 === "5" // false (no value comparison even attempted) + ``` + + The key point: `===` returns `false` immediately if types differ, then compares values if types match. + </Accordion> + + <Accordion title="Misconception 3: 'typeof is reliable for checking all types'"> + **Wrong!** `typeof` has several well-known quirks: + + ```javascript + typeof null // "object" — famous bug from 1995! + typeof [] // "object" — arrays are objects + typeof NaN // "number" — Not-a-Number is a number type + typeof function(){} // "function" — but functions ARE objects! + ``` + + **Better alternatives:** + - Use `Array.isArray()` for arrays + - Use `value === null` for null + - Use `Number.isNaN()` for NaN + - Use `Object.prototype.toString.call()` for precise type detection + </Accordion> + + <Accordion title="Misconception 4: 'Objects with the same content are equal'"> + **Wrong!** Objects (including arrays and functions) are compared by **reference**, not by content: + + ```javascript + { a: 1 } === { a: 1 } // false — different objects in memory! + [] === [] // false — different arrays! + (() => {}) === (() => {}) // false — different functions! + + const obj = { a: 1 }; + obj === obj // true — same reference + ``` + + To compare object contents, use `JSON.stringify()` for simple cases or a deep equality function like Lodash's `_.isEqual()`. + </Accordion> + + <Accordion title="Misconception 5: 'NaN means the value is not a number'"> + **Misleading!** `NaN` is actually a *numeric value* that represents an undefined or unrepresentable mathematical result: + + ```javascript + typeof NaN // "number" — NaN IS a number type! + + // NaN appears from invalid math operations + 0 / 0 // NaN + Math.sqrt(-1) // NaN + parseInt("xyz") // NaN + Infinity - Infinity // NaN + ``` + + Think of `NaN` as "the result of a calculation that doesn't produce a meaningful number" rather than literally "not a number." + </Accordion> + + <Accordion title="Misconception 6: 'Truthy values are == true'"> + **Wrong!** Truthy/falsy and `==` equality are completely different concepts: + + ```javascript + // These are truthy but NOT == true + "hello" == true // false! ("hello" → NaN, true → 1) + 2 == true // false! (2 !== 1) + [] == true // false! ([] → "" → 0, true → 1) + + // But they ARE truthy + if ("hello") { } // executes + if (2) { } // executes + if ([]) { } // executes + ``` + + **Rule:** Don't use `== true` or `== false`. Either use `===` or just rely on truthiness directly: `if (value)`. + </Accordion> +</AccordionGroup> + +--- + ## Key Takeaways <Info> -**Key Points to Remember:** +**Remember these essential points about Equality Operators:** 1. **Use `===` by default** — It's predictable and doesn't convert types @@ -1243,6 +1374,26 @@ These common mistakes trip up many JavaScript developers. Learning about them no --- +## Interactive Visualization Tool + +The best way to internalize JavaScript's equality rules is to see all the comparisons at once. + +<Card title="JavaScript Equality Table" icon="table" href="https://dorey.github.io/JavaScript-Equality-Table/"> + Interactive comparison table by dorey showing the results of `==` and `===` for all type combinations. Hover over cells to see explanations. An essential reference for understanding JavaScript equality! +</Card> + +**Try these in the table:** +- Compare `[]` with `false`, `0`, `""`, and `![]` to see why `[] == ![]` is `true` +- See why `null == undefined` is `true` but neither equals `0` or `false` +- Observe how `NaN` never equals anything (including itself) +- Notice how objects only equal themselves (same reference) + +<Tip> +**Bookmark this table!** It's invaluable for debugging comparison issues and preparing for technical interviews. +</Tip> + +--- + ## Test Your Knowledge Try to answer each question before revealing the solution: @@ -1395,6 +1546,8 @@ Try to answer each question before revealing the solution: </Card> </CardGroup> +--- + ## Articles <CardGroup cols={2}> @@ -1418,6 +1571,8 @@ Try to answer each question before revealing the solution: </Card> </CardGroup> +--- + ## Videos <CardGroup cols={2}> @@ -1435,6 +1590,8 @@ Try to answer each question before revealing the solution: </Card> </CardGroup> +--- + ## Books <Card title="You Don't Know JS: Types & Grammar — Kyle Simpson" icon="book" href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/types-grammar/README.md"> From b50a302a416a27428683927a3648a795a3c71476 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:08:39 -0300 Subject: [PATCH 055/128] docs(value-reference-types): improve SEO, structure, and add MDN links - Update title to include hook and 'in JavaScript' for SEO (59 chars) - Add sidebarTitle for cleaner navigation - Expand description to 156 chars with action word 'Learn' - Restructure opening: hook questions -> code example -> Info -> Warning - Add prerequisite warning linking to primitive-types concept - Swap section order: 'What Are' before 'Analogy' per skill template - Swap section order: 'Key Takeaways' before 'Test Your Knowledge' - Add inline MDN links for Symbol, Map, Set, Object.freeze, structuredClone, WeakMap - Box main ASCII diagrams with titles (Stack, Heap, Stack+Heap) - Fix Related Concepts: remove external MDN links, keep internal only - Expand Reference section with 4 MDN cards in CardGroup - Merge Books section into Articles - Change Note component to Tip for quick rule --- docs/concepts/value-reference-types.mdx | 285 ++++++++++++++---------- 1 file changed, 162 insertions(+), 123 deletions(-) diff --git a/docs/concepts/value-reference-types.mdx b/docs/concepts/value-reference-types.mdx index 054f700e..b07d705f 100644 --- a/docs/concepts/value-reference-types.mdx +++ b/docs/concepts/value-reference-types.mdx @@ -1,36 +1,20 @@ --- -title: "Value Types and Reference Types" -description: "Understanding how JavaScript stores and passes data in memory" +title: "Value vs Reference Types: How Memory Works in JavaScript" +sidebarTitle: "Value vs Reference Types: How Memory Works" +description: "Learn how value types and reference types work in JavaScript. Understand how primitives and objects are stored, why copying objects shares references, and how to avoid mutation bugs." --- -## The Sticky Note vs The Map: A Real-World Analogy - -Imagine you have two ways to share information with a friend: +Have you ever wondered why changing one variable unexpectedly changes another? Why does this happen? -**Sticky Note (Value Types):** You write "42" on a sticky note and hand it to your friend. They now have their own note with "42" on it. If they change their note to "100", your note still says "42". You each have independent copies. - -**Map to Treasure (Reference Types):** Instead of giving your friend the treasure itself, you give them a map to where the treasure is buried. Now you BOTH have maps pointing to the SAME treasure. If they dig it up and add more gold, you'll see the extra gold too — because you're both looking at the same treasure! +```javascript +const original = { name: "Alice" }; +const copy = original; +copy.name = "Bob"; +console.log(original.name); // "Bob" — Wait, what?! ``` -VALUE TYPE (Sticky Note) REFERENCE TYPE (Map to Treasure) - -┌─────────────┐ ┌─────────────┐ -│ x = 42 │ │ x = ────────────────┐ -└─────────────┘ └─────────────┘ │ - ▼ -┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ -│ y = 42 │ (independent copy) │ y = ────────────►│ { name: "Alice" } │ -└─────────────┘ └─────────────┘ └──────────────────┘ - -Change y? Change the object via y? -x stays the same! x sees the change too! -``` - -This difference between "storing the value itself" vs "storing a map to the value" is one of the most important concepts in JavaScript — and the source of countless bugs for developers who don't understand it. -<Note> -**TL;DR:** Primitives (numbers, strings, booleans, etc.) store the actual value — copying creates an independent copy. Objects and arrays store a *reference* (pointer) to the data — copying creates another pointer to the SAME data. -</Note> +The answer lies in how JavaScript stores data in memory. **Primitives** (like numbers and strings) store actual values, while **objects** store *references* (pointers) to data. This difference is one of the most important concepts in JavaScript — and the source of countless bugs. <Info> **What you'll learn in this guide:** @@ -42,6 +26,10 @@ This difference between "storing the value itself" vs "storing a map to the valu - Common bugs caused by reference sharing </Info> +<Warning> +**Prerequisite:** This guide assumes you understand [Primitive Types](/concepts/primitive-types). If you're not familiar with the 7 primitive types in JavaScript, read that guide first! +</Warning> + --- ## What Are Value Types and Reference Types? @@ -60,7 +48,7 @@ JavaScript has two categories of data types that behave very differently: | `boolean` | `true` | The actual boolean | | `undefined` | `undefined` | The undefined value | | `null` | `null` | The null value | -| `symbol` | `Symbol("id")` | The unique symbol | +| [`symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) | `Symbol("id")` | The unique symbol | **Key characteristics:** - Stored directly in the variable @@ -79,8 +67,8 @@ JavaScript has two categories of data types that behave very differently: | Function | `function() {}` | Reference to function | | Date | `new Date()` | Reference to date | | RegExp | `/pattern/` | Reference to regex | -| Map | `new Map()` | Reference to map | -| Set | `new Set()` | Reference to set | +| [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) | `new Map()` | Reference to map | +| [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) | `new Set()` | Reference to set | **Key characteristics:** - Variable stores a *reference* (pointer) to the actual data @@ -90,6 +78,43 @@ JavaScript has two categories of data types that behave very differently: --- +## The Sticky Note vs The Map: A Real-World Analogy + +Imagine you have two ways to share information with a friend: + +**Sticky Note (Value Types):** You write "42" on a sticky note and hand it to your friend. They now have their own note with "42" on it. If they change their note to "100", your note still says "42". You each have independent copies. + +**Map to Treasure (Reference Types):** Instead of giving your friend the treasure itself, you give them a map to where the treasure is buried. Now you BOTH have maps pointing to the SAME treasure. If they dig it up and add more gold, you'll see the extra gold too — because you're both looking at the same treasure! + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ VALUE TYPES vs REFERENCE TYPES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ VALUE TYPE (Sticky Note) REFERENCE TYPE (Map to Treasure) │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ x = 42 │ │ x = ────────────────┐ │ +│ └─────────────┘ └─────────────┘ │ │ +│ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ +│ │ y = 42 │ (independent copy) │ y = ────────────►│ {...} │ │ +│ └─────────────┘ └─────────────┘ └──────────┘ │ +│ │ +│ Change y? Change the object via y? │ +│ x stays the same! x sees the change too! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +This difference between "storing the value itself" vs "storing a map to the value" is fundamental to understanding JavaScript. + +<Tip> +**Quick Rule:** Primitives store the actual value — copying creates an independent copy. Objects and arrays store a *reference* (pointer) — copying creates another pointer to the SAME data. +</Tip> + +--- + ## How Memory Works: Stack vs Heap To truly understand the difference, you need to know where JavaScript stores data. @@ -102,17 +127,23 @@ The **stack** is a fast, organized region of memory. It stores: - Function call information ``` -THE STACK -┌────────────────────────────┐ -│ name = "Alice" │ ← Actual string value -├────────────────────────────┤ -│ age = 25 │ ← Actual number value -├────────────────────────────┤ -│ isActive = true │ ← Actual boolean value -├────────────────────────────┤ -│ user = 0x7F3A ──────┼──── Points to heap -└────────────────────────────┘ - Fixed size, fast access +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE STACK │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────────────────┐ │ +│ │ name = "Alice" │ ← Actual string value │ +│ ├────────────────────────────┤ │ +│ │ age = 25 │ ← Actual number value │ +│ ├────────────────────────────┤ │ +│ │ isActive = true │ ← Actual boolean value │ +│ ├────────────────────────────┤ │ +│ │ user = 0x7F3A ──────┼──── Points to heap │ +│ └────────────────────────────┘ │ +│ │ +│ Fixed size, fast access │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ ``` ### The Heap: Home of Objects @@ -124,25 +155,27 @@ The **heap** is a larger, less organized region for dynamic data: - Anything that can grow or change size ``` -THE HEAP -┌─────────────────────────────────────────────────┐ -│ │ -│ ┌─────────────────────────────────────┐ │ -│ │ 0x7F3A: │ │ -│ │ { │ │ -│ │ name: "Alice", │ │ -│ │ age: 25, │ │ -│ │ hobbies: ["reading", "gaming"] │ │ -│ │ } │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────┐ │ -│ │ 0x8B2C: │ │ -│ │ [1, 2, 3, 4, 5] │ │ -│ └─────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────┘ - Dynamic size, slower access +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE HEAP │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 0x7F3A: │ │ +│ │ { │ │ +│ │ name: "Alice", │ │ +│ │ age: 25, │ │ +│ │ hobbies: ["reading", "gaming"] │ │ +│ │ } │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 0x8B2C: │ │ +│ │ [1, 2, 3, 4, 5] │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ Dynamic size, slower access │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ ``` ### Putting It All Together @@ -157,18 +190,24 @@ let scores = [95, 87, 92]; // Reference on stack, array on heap ``` ``` -STACK HEAP -┌─────────────────────┐ ┌────────────────────────────┐ -│ name = "Alice" │ │ │ -├─────────────────────┤ │ ┌──────────────────────┐ │ -│ age = 25 │ │ │ { name: "Alice" } │ │ -├─────────────────────┤ │ └──────────────────────┘ │ -│ user = 0x001 ─────┼───────────┼──────────▲ │ -├─────────────────────┤ │ │ -│ scores = 0x002 ─────┼───────────┼───┐ ┌──────────────────┐ │ -└─────────────────────┘ │ └─►│ [95, 87, 92] │ │ - │ └──────────────────┘ │ - └────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────┐ +│ STACK AND HEAP TOGETHER │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ STACK HEAP │ +│ ┌─────────────────────┐ ┌────────────────────────────┐ │ +│ │ name = "Alice" │ │ │ │ +│ ├─────────────────────┤ │ ┌──────────────────────┐ │ │ +│ │ age = 25 │ │ │ { name: "Alice" } │ │ │ +│ ├─────────────────────┤ │ └──────────────────────┘ │ │ +│ │ user = 0x001 ─────┼───────────┼──────────▲ │ │ +│ ├─────────────────────┤ │ │ │ +│ │ scores = 0x002 ─────┼───────────┼───┐ ┌──────────────────┐ │ │ +│ └─────────────────────┘ │ └─►│ [95, 87, 92] │ │ │ +│ │ └──────────────────┘ │ │ +│ └────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ ``` --- @@ -514,7 +553,7 @@ obj = { name: "Eve" }; // TypeError: Assignment to constant variable --- -## True Immutability with Object.freeze() +## True Immutability with [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) We learned that `const` doesn't make objects immutable — it only prevents reassignment. But what if you NEED a truly immutable object? @@ -695,7 +734,7 @@ original shallow A deep copy creates completely independent copies at every level. -**Method 1: `structuredClone()` (Recommended)** +**Method 1: [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) (Recommended)** ```javascript const original = { @@ -931,7 +970,7 @@ const deep = JSON.parse(JSON.stringify(original)); **Solutions:** - - **WeakMap** — Keys are "weakly held" and can be garbage collected + - **[WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)** — Keys are "weakly held" and can be garbage collected - **Manual cleanup** — Delete entries when done ```javascript @@ -995,6 +1034,34 @@ const deep = JSON.parse(JSON.stringify(original)); --- +## Key Takeaways + +<Info> +**Remember these essential points:** + +1. **Value types** (primitives) store values directly; **reference types** store pointers + +2. **Copying a primitive** creates an independent copy — changing one doesn't affect the other + +3. **Copying an object/array** copies the reference — both point to the SAME data + +4. **Comparison:** primitives compare by value, objects compare by reference + +5. **Functions:** JavaScript passes everything by value, but object values ARE references + +6. **Mutation ≠ Reassignment:** `const` only prevents reassignment, not mutation + +7. **Shallow copy** (spread, Object.assign) only copies one level — nested objects are shared + +8. **Deep copy** with `structuredClone()` creates completely independent copies + +9. **Know your array methods:** `push/pop/sort` mutate; `map/filter/slice` don't + +10. **True immutability** requires `Object.freeze()` — but it's shallow, so use deep freeze for nested objects +</Info> + +--- + ## Test Your Knowledge <AccordionGroup> @@ -1134,34 +1201,6 @@ const deep = JSON.parse(JSON.stringify(original)); --- -## Key Takeaways - -<Info> -**Remember these essential points:** - -1. **Value types** (primitives) store values directly; **reference types** store pointers - -2. **Copying a primitive** creates an independent copy — changing one doesn't affect the other - -3. **Copying an object/array** copies the reference — both point to the SAME data - -4. **Comparison:** primitives compare by value, objects compare by reference - -5. **Functions:** JavaScript passes everything by value, but object values ARE references - -6. **Mutation ≠ Reassignment:** `const` only prevents reassignment, not mutation - -7. **Shallow copy** (spread, Object.assign) only copies one level — nested objects are shared - -8. **Deep copy** with `structuredClone()` creates completely independent copies - -9. **Know your array methods:** `push/pop/sort` mutate; `map/filter/slice` don't - -10. **True immutability** requires `Object.freeze()` — but it's shallow, so use deep freeze for nested objects -</Info> - ---- - ## Related Concepts <CardGroup cols={2}> @@ -1174,14 +1213,8 @@ const deep = JSON.parse(JSON.stringify(original)); <Card title="Scope and Closures" icon="layer-group" href="/concepts/scope-and-closures"> How closures capture references to variables </Card> - <Card title="== vs === vs typeof" icon="equals" href="/concepts/equality-operators"> - Understanding equality operators and type checking - </Card> - <Card title="Object.freeze() — MDN" icon="snowflake" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze"> - Official documentation on freezing objects for immutability - </Card> - <Card title="WeakMap — MDN" icon="key" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"> - Maps with weak references that allow garbage collection + <Card title="Equality Operators" icon="equals" href="/concepts/equality-operators"> + Understanding == vs === and type checking </Card> </CardGroup> @@ -1189,9 +1222,20 @@ const deep = JSON.parse(JSON.stringify(original)); ## Reference -<Card title="JavaScript data types and data structures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures"> - Official MDN documentation on JavaScript's type system, including primitives and objects. -</Card> +<CardGroup cols={2}> + <Card title="JavaScript Data Types — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures"> + Official documentation on JavaScript's type system, including primitives and objects. + </Card> + <Card title="Object.freeze() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze"> + Documentation on freezing objects for immutability. + </Card> + <Card title="structuredClone() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/structuredClone"> + The modern way to create deep copies of objects. + </Card> + <Card title="WeakMap — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"> + Maps with weak references that allow garbage collection. + </Card> +</CardGroup> ## Articles @@ -1199,22 +1243,17 @@ const deep = JSON.parse(JSON.stringify(original)); <Card title="Explaining Value vs. Reference in Javascript" icon="newspaper" href="https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0"> Clear explanation by Arnav Aggarwal with visual diagrams showing how primitives and objects behave differently in memory. </Card> - <Card title="JavaScript Primitive vs. Reference Values" icon="newspaper" href="https://www.javascripttutorial.net/javascript-primitive-vs-reference-values/"> - Comprehensive tutorial with excellent stack/heap diagrams and practical examples. One of the best visual explanations available. + Comprehensive tutorial with excellent stack/heap diagrams and practical examples. </Card> - <Card title="Back to roots: JavaScript Value vs Reference" icon="newspaper" href="https://medium.com/dailyjs/back-to-roots-javascript-value-vs-reference-8fb69d587a18"> - Deep dive by Miro Koczka into how JavaScript handles values and references, with clear explanations of common pitfalls. + Deep dive by Miro Koczka into how JavaScript handles values and references. + </Card> + <Card title="You Don't Know JS: Types & Grammar" icon="book" href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/types-grammar/README.md"> + Kyle Simpson's definitive guide to JavaScript's type system. Free to read online. </Card> </CardGroup> -## Books - -<Card title="You Don't Know JS: Types & Grammar — Kyle Simpson" icon="book" href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/types-grammar/README.md"> - The definitive guide to JavaScript's type system. Kyle Simpson explains value vs reference, coercion, and more. Free to read online. -</Card> - ## Videos <CardGroup cols={2}> From e800889f632cf16eff0ad874367945da80b68d1c Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:09:16 -0300 Subject: [PATCH 056/128] docs(type-coercion): improve SEO, structure, and add MDN links - Update title to end with 'in JavaScript' for SEO (58 chars) - Add sidebarTitle for cleaner navigation - Expand description to 160 chars with action word 'Learn' - Restructure opening with hook questions and immediate code example - Move 'What Is Type Coercion?' before analogy per skill template - Expand definition to 40-60 words for featured snippet optimization - Add Prerequisites warning linking to Primitive Types - Add inline MDN links for Number(), String(), Boolean(), parseInt(), parseFloat(), toString(), template literals, Symbol, NaN, BigInt, Symbol.toPrimitive, Number.isNaN(), isNaN() - Add internal links to Primitive Types, Equality Operators, Value/Reference Types in body content - Expand Reference section to 4 MDN cards in CardGroup - Add 4th Related Concept card (JavaScript Engines) - Improve Key Takeaways formatting with em-dash style (10 items) - Add bordered headers to all ASCII diagrams - Move Books content to Articles section --- docs/concepts/type-coercion.mdx | 204 ++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 78 deletions(-) diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index 9c3b1852..8fc2e4d6 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -1,45 +1,19 @@ --- -title: "Type Coercion" -description: "Understanding implicit and explicit type conversion in JavaScript" +title: "Type Coercion: How Values Convert Automatically in JavaScript" +sidebarTitle: "Type Coercion: How Values Convert Automatically" +description: "Learn JavaScript type coercion and implicit conversion. Understand how values convert to strings, numbers, and booleans, the 8 falsy values, and how to avoid common coercion bugs." --- -## The Shapeshifter: A Real-World Analogy +Why does `"5" + 3` give you `"53"` but `"5" - 3` gives you `2`? Why does `[] == ![]` return `true`? How does JavaScript decide what type a value should be? -Imagine JavaScript as an overly helpful translator. When you give it values of different types, it tries to "help" by converting them — sometimes correctly, sometimes... creatively. - -``` -YOU: "Hey JavaScript, add 5 and '3' together" - -JAVASCRIPT (thinking): "Hmm, one's a number, one's a string... - I'll just convert the number to a string! - '5' + '3' = '53'. You're welcome!" - -YOU: "That's... not what I meant." - -JAVASCRIPT: "¯\_(ツ)_/¯" -``` - -This "helpful" behavior is called **type coercion** — JavaScript automatically converting values from one type to another. Sometimes it's useful, sometimes it creates bugs that will haunt your dreams. - -``` - TYPE COERCION: THE SHAPESHIFTER - - ┌─────────┐ ┌─────────┐ - │ "5" │ ──── + 3 ────────► │ "53" │ String won! - │ string │ │ string │ - └─────────┘ └─────────┘ - - ┌─────────┐ ┌─────────┐ - │ "5" │ ──── - 3 ────────► │ 2 │ Number won! - │ string │ │ number │ - └─────────┘ └─────────┘ - - Same values, different operators, different results! +```javascript +// JavaScript's "helpful" type conversion in action +console.log("5" + 3); // "53" (string concatenation!) +console.log("5" - 3); // 2 (numeric subtraction) +console.log([] == ![]); // true (wait, what?!) ``` -<Note> -**TL;DR:** Type coercion is JavaScript automatically converting values between types. It can be **implicit** (automatic) or **explicit** (you control it). Understanding the rules helps you avoid bugs and write cleaner code. -</Note> +This surprising behavior is **[type coercion](https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion)** — JavaScript automatically converting values from one type to another. Understanding these rules helps you avoid bugs and write more predictable code. <Info> **What you'll learn in this guide:** @@ -51,11 +25,59 @@ This "helpful" behavior is called **type coercion** — JavaScript automatically - Best practices for avoiding coercion bugs </Info> +<Warning> +**Prerequisites:** This guide assumes you understand [Primitive Types](/concepts/primitive-types). If terms like string, number, boolean, null, and undefined are new to you, read that guide first! +</Warning> + --- ## What Is Type Coercion? -**Type coercion** is the process of converting a value from one type to another. In JavaScript, this happens constantly — sometimes because you asked for it, sometimes because JavaScript decided to "help." +**Type coercion** is the automatic or implicit conversion of values from one data type to another in JavaScript. When you use operators or functions that expect a certain type, JavaScript will convert (coerce) values to make the operation work — sometimes helpfully, sometimes surprisingly. Understanding these conversion rules is essential for writing predictable, bug-free code. + +### The Shapeshifter Analogy + +Imagine JavaScript as an overly helpful translator. When you give it values of different types, it tries to "help" by converting them — sometimes correctly, sometimes... creatively. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE OVERLY HELPFUL TRANSLATOR │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ YOU: "Hey JavaScript, add 5 and '3' together" │ +│ │ +│ JAVASCRIPT (thinking): "Hmm, one's a number, one's a string... │ +│ I'll just convert the number to a string! │ +│ '5' + '3' = '53'. You're welcome!" │ +│ │ +│ YOU: "That's... not what I meant." │ +│ │ +│ JAVASCRIPT: "¯\_(ツ)_/¯" │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +This "helpful" behavior is called **type coercion** — JavaScript automatically converting values from one type to another. Sometimes it's useful, sometimes it creates bugs that will haunt your dreams. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ TYPE COERCION: THE SHAPESHIFTER │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ "5" │ ──── + 3 ────────► │ "53" │ String won! │ +│ │ string │ │ string │ │ +│ └─────────┘ └─────────┘ │ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ "5" │ ──── - 3 ────────► │ 2 │ Number won! │ +│ │ string │ │ number │ │ +│ └─────────┘ └─────────┘ │ +│ │ +│ Same values, different operators, different results! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` ### Explicit vs Implicit Coercion @@ -75,6 +97,8 @@ There are two ways coercion happens: parseFloat("3.14") // 3.14 ``` + These functions — [`Number()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), [`String()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), [`Boolean()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean), [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt), and [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat) — give you full control. + This is the **safe** way — you know exactly what's happening. </Tab> <Tab title="Implicit Coercion"> @@ -114,7 +138,7 @@ Other languages would throw an error. JavaScript tries to make it work. Whether ## The Three Types of Conversion -Here's the most important rule: **JavaScript can only convert to THREE types:** +Here's the most important rule: **JavaScript can only convert to THREE [primitive types](/concepts/primitive-types):** | Target Type | Explicit Method | Common Implicit Triggers | |-------------|-----------------|--------------------------| @@ -125,20 +149,26 @@ Here's the most important rule: **JavaScript can only convert to THREE types:** That's it. No matter how complex the coercion seems, the end result is always a string, number, or boolean. ``` - ┌──────────────────────┐ - │ ANY VALUE │ - │ (string, number, │ - │ object, array...) │ - └──────────┬───────────┘ - │ - ┌────────────────┼────────────────┐ - ▼ ▼ ▼ - ┌──────────┐ ┌──────────┐ ┌──────────┐ - │ String │ │ Number │ │ Boolean │ - │ "42" │ │ 42 │ │ true │ - └──────────┘ └──────────┘ └──────────┘ - - These are the ONLY three possible destinations! +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE THREE CONVERSION DESTINATIONS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────┐ │ +│ │ ANY VALUE │ │ +│ │ (string, number, │ │ +│ │ object, array...) │ │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ┌────────────────┼────────────────┐ │ +│ ▼ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ String │ │ Number │ │ Boolean │ │ +│ │ "42" │ │ 42 │ │ true │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ These are the ONLY three possible destinations! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ ``` --- @@ -161,6 +191,8 @@ String(true) // "true" "Hello " + 123 // "Hello 123" (+ with a string) ``` +The [`toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) method and [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) are also common ways to convert values to strings. + ### String Conversion Rules | Value | Result | Notes | @@ -174,7 +206,7 @@ String(true) // "true" | `[1, 2, 3]` | `"1,2,3"` | Arrays join with commas | | `[]` | `""` | Empty array becomes empty string | | `{}` | `"[object Object]"` | Objects become this (usually useless) | -| `Symbol("id")` | Throws TypeError! | Symbols can't implicitly convert | +| [`Symbol("id")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) | Throws TypeError! | Symbols can't implicitly convert | ### The + Operator's Split Personality @@ -237,7 +269,7 @@ parseFloat("3.14") // 3.14 |-------|--------|-------| | `"123"` | `123` | Numeric strings work | | `" 123 "` | `123` | Whitespace is trimmed | -| `"123abc"` | `NaN` | Any non-numeric char → NaN | +| `"123abc"` | [`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN) | Any non-numeric char → NaN | | `""` | `0` | Empty string becomes 0 | | `" "` | `0` | Whitespace-only becomes 0 | | `true` | `1` | | @@ -322,7 +354,7 @@ There are exactly **8 values** that convert to `false`. Everything else is `true Boolean(false) // false (obviously) Boolean(0) // false Boolean(-0) // false (yes, -0 exists) -Boolean(0n) // false (BigInt zero) +Boolean(0n) // false ([BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) zero) Boolean("") // false (empty string) Boolean(null) // false Boolean(undefined) // false @@ -389,7 +421,7 @@ const display = user && user.name; ## Object to Primitive Conversion -When JavaScript needs to convert an object (including arrays) to a primitive, it follows a specific algorithm. +When JavaScript needs to convert an [object to a primitive](/concepts/value-reference-types) (including arrays), it follows a specific algorithm. ### The ToPrimitive Algorithm @@ -476,7 +508,7 @@ price + "" // "99.99" (valueOf returned number, then → string) ### ES6 Symbol.toPrimitive -ES6 introduced a cleaner way to control conversion — `Symbol.toPrimitive`: +ES6 introduced a cleaner way to control conversion — [`Symbol.toPrimitive`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive): ```javascript const obj = { @@ -503,7 +535,7 @@ obj + "" // "default value" (hint: "default") ## The == Algorithm Explained -The loose equality operator `==` is where type coercion gets wild. Here's how it actually works: +The loose equality operator `==` is where type coercion gets wild. For a deeper dive into all equality operators, see our [Equality Operators guide](/concepts/equality-operators). Here's how `==` actually works: ### Simplified == Rules @@ -716,7 +748,10 @@ Let's explore the famous "weird parts" that make JavaScript... special. isNaN(NaN) // true isNaN("hello") // true (wrong! it converts first) Number.isNaN("hello") // false (correct) + ``` + + Use [`Number.isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) instead of the global [`isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN) for reliable NaN checking. </Accordion> <Accordion title="7. typeof Quirks"> @@ -891,23 +926,25 @@ function process(count) { <Info> **Remember these essentials about Type Coercion:** -1. **Three conversions only:** JavaScript converts to String, Number, or Boolean — nothing else +1. **Three conversions only** — JavaScript converts to String, Number, or Boolean — nothing else + +2. **Implicit vs Explicit** — Know when JS converts automatically vs when you control it -2. **Implicit vs Explicit:** Know when JS converts automatically vs when you control it +3. **The 8 falsy values** — `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy -3. **The 8 falsy values:** `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy +4. **+ is special** — It prefers string concatenation if ANY operand is a string -4. **+ is special:** It prefers string concatenation if ANY operand is a string +5. **- * / % are consistent** — They ALWAYS convert to numbers -5. **- * / % are consistent:** They ALWAYS convert to numbers +6. **== coerces, === doesn't** — Use `===` by default to avoid surprises -6. **== coerces, === doesn't:** Use `===` by default to avoid surprises +7. **null == undefined** — This is true, but neither equals anything else with `==` -7. **null == undefined:** This is true, but neither equals anything else with `==` +8. **Objects convert via valueOf() and toString()** — Learn these methods to control conversion -8. **Objects convert via:** `valueOf()` and `toString()` methods +9. **When in doubt, be explicit** — Use `Number()`, `String()`, `Boolean()` -9. **When in doubt, be explicit:** Use `Number()`, `String()`, `Boolean()` +10. **NaN is unique** — It's the only value not equal to itself; use `Number.isNaN()` to check </Info> --- @@ -921,8 +958,11 @@ function process(count) { <Card title="Value Types vs Reference Types" icon="clone" href="/concepts/value-reference-types"> How primitives and objects behave differently during coercion </Card> - <Card title="== vs === vs typeof" icon="equals" href="/concepts/equality-operators"> - Deep dive into equality operators and type checking + <Card title="Equality Operators" icon="equals" href="/concepts/equality-operators"> + Deep dive into ==, ===, and how coercion affects comparisons + </Card> + <Card title="JavaScript Engines" icon="gear" href="/concepts/javascript-engines"> + How engines like V8 implement type coercion internally </Card> </CardGroup> @@ -930,9 +970,20 @@ function process(count) { ## Reference -<Card title="Type Coercion — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion"> - Official MDN documentation on type coercion in JavaScript -</Card> +<CardGroup cols={2}> + <Card title="Type Coercion — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion"> + Official MDN glossary entry explaining type coercion fundamentals. + </Card> + <Card title="Equality Comparisons — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness"> + Comprehensive guide to ==, ===, Object.is() and the coercion rules behind each. + </Card> + <Card title="Type Conversion — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Type_Conversion"> + The difference between type coercion (implicit) and type conversion (explicit). + </Card> + <Card title="Truthy and Falsy — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy"> + Complete list of falsy values and how boolean context works. + </Card> +</CardGroup> ## Articles @@ -946,14 +997,11 @@ function process(count) { <Card title="Object to Primitive Conversion" icon="newspaper" href="https://javascript.info/object-toprimitive"> Deep-dive from javascript.info into how objects convert to primitives using Symbol.toPrimitive, toString, and valueOf. Essential for advanced understanding. </Card> + <Card title="You Don't Know JS: Types & Grammar, Ch. 4" icon="newspaper" href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/types-grammar/ch4.md"> + Kyle Simpson's definitive deep-dive into JavaScript coercion. Explains abstract operations, ToString, ToNumber, ToBoolean, and the "why" behind every rule. Free to read online. + </Card> </CardGroup> -## Books - -<Card title="You Don't Know JS: Types & Grammar, Chapter 4 — Kyle Simpson" icon="book" href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/types-grammar/ch4.md"> - The definitive deep-dive into JavaScript coercion. Kyle Simpson explains abstract operations, ToString, ToNumber, ToBoolean, and the "why" behind every coercion rule. Free to read online. -</Card> - ## Videos <CardGroup cols={2}> From 7b78afcc208e78cf7a85fd523f14c6e74059c6ae Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:22:18 -0300 Subject: [PATCH 057/128] docs(call-stack): standardize page structure and update title format - Update title to new format: 'Call Stack: How Function Execution Works in JavaScript' - Move Common Misconceptions section before Key Takeaways - Rename 'Classic Interview Questions' to 'Test Your Knowledge' --- docs/concepts/call-stack.mdx | 60 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index bbeda620..6f7e02ed 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -1,5 +1,5 @@ --- -title: "JavaScript Call Stack: How Function Execution Works" +title: "Call Stack: How Function Execution Works in JavaScript" sidebarTitle: "Call Stack: How Function Execution Works" description: "Learn how the JavaScript call stack tracks function execution. Understand stack frames, LIFO ordering, execution contexts, stack overflow errors, and debugging with stack traces." --- @@ -654,34 +654,6 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in --- -## Key Takeaways - -<Info> -**Remember these essential points about the Call Stack:** - -1. **JavaScript is single-threaded** — It has ONE call stack and can only do one thing at a time - -2. **LIFO principle** — Last In, First Out. The most recent function call finishes first - -3. **Execution contexts** — Each function call creates a "frame" containing arguments, local variables, and return address - -4. **Synchronous execution** — Functions must complete before their callers can continue - -5. **Stack overflow** — Happens when the stack gets too deep, usually from infinite recursion - -6. **Always have a base case** — Recursive functions need a stopping condition - -7. **Stack traces are your friend** — They show you exactly how your program got to an error - -8. **Async callbacks wait** — `setTimeout`, `fetch`, and event callbacks don't run until the call stack is empty - -9. **Each frame is isolated** — Local variables in one function call don't affect variables in another call of the same function - -10. **Debugging tools show the stack** — Browser DevTools let you pause execution and inspect the current call stack -</Info> - ---- - ## Common Misconceptions <AccordionGroup> @@ -777,7 +749,35 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in --- -## Classic Interview Questions +## Key Takeaways + +<Info> +**Remember these essential points about the Call Stack:** + +1. **JavaScript is single-threaded** — It has ONE call stack and can only do one thing at a time + +2. **LIFO principle** — Last In, First Out. The most recent function call finishes first + +3. **Execution contexts** — Each function call creates a "frame" containing arguments, local variables, and return address + +4. **Synchronous execution** — Functions must complete before their callers can continue + +5. **Stack overflow** — Happens when the stack gets too deep, usually from infinite recursion + +6. **Always have a base case** — Recursive functions need a stopping condition + +7. **Stack traces are your friend** — They show you exactly how your program got to an error + +8. **Async callbacks wait** — `setTimeout`, `fetch`, and event callbacks don't run until the call stack is empty + +9. **Each frame is isolated** — Local variables in one function call don't affect variables in another call of the same function + +10. **Debugging tools show the stack** — Browser DevTools let you pause execution and inspect the current call stack +</Info> + +--- + +## Test Your Knowledge <AccordionGroup> <Accordion title="Question 1: What does LIFO stand for and why is it important?"> From c7d7c338b9182d071c4d760360cec621fdc9717e Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:22:31 -0300 Subject: [PATCH 058/128] docs(primitive-types): improve SEO, structure, and content quality - Update title to include hook and 'in JavaScript' for SEO - Add sidebarTitle for cleaner navigation - Rewrite description starting with 'Learn' (157 chars) - Restructure opening with hook questions and immediate code example - Move analogy section after 'What Are Primitive Types?' - Add inline MDN links for all 7 primitive types and key APIs - Add 'Common Mistakes' section with ASCII diagram - Clarify template literals are syntax, not a separate type - Change beginner notice from Warning to Note (semantic) - Expand Reference section with CardGroup (4 MDN cards) - Add 2 more Key Takeaways (now 10 total) - Add Question 6 to Test Your Knowledge (now 6 total) - Reorder Key Takeaways before Test Your Knowledge per guidelines --- docs/concepts/primitive-types.mdx | 241 ++++++++++++++++++++++-------- 1 file changed, 180 insertions(+), 61 deletions(-) diff --git a/docs/concepts/primitive-types.mdx b/docs/concepts/primitive-types.mdx index 73cdd407..3803ca5e 100644 --- a/docs/concepts/primitive-types.mdx +++ b/docs/concepts/primitive-types.mdx @@ -1,29 +1,27 @@ --- -title: "Primitive Types" -description: "Understanding JavaScript's fundamental data types" +title: "Primitive Types: Building Blocks of Data in JavaScript" +sidebarTitle: "Primitive Types: Building Blocks of Data" +description: "Learn JavaScript's 7 primitive types: string, number, bigint, boolean, undefined, null, and symbol. Understand immutability, typeof quirks, and autoboxing." --- -## Atoms vs Molecules: A Real-World Analogy +What's the difference between `"hello"` and `{ text: "hello" }`? Why can you call `"hello".toUpperCase()` if strings aren't objects? And why does `typeof null` return `"object"`? -Think of data in JavaScript like chemistry class (but way more fun, and no lab goggles required). **Primitives** are like atoms — the fundamental, indivisible building blocks that cannot be broken down further. **Objects** are like molecules — complex structures made up of multiple atoms combined together. - -``` - PRIMITIVES (Atoms) OBJECTS (Molecules) - - ┌───┐ ┌─────┐ ┌──────┐ ┌────────────────────────────┐ - │ 5 │ │"hi" │ │ true │ │ { name: "Alice", age: 25 } │ - └───┘ └─────┘ └──────┘ └────────────────────────────┘ - - Simple, indivisible Complex, contains multiple values - Stored directly Stored as reference - Compared by value Compared by reference +```javascript +// JavaScript has exactly 7 primitive types +const str = "hello"; // string +const num = 42; // number +const big = 9007199254740993n; // bigint +const bool = true; // boolean +const undef = undefined; // undefined +const nul = null; // null +const sym = Symbol("id"); // symbol + +console.log(typeof str); // "string" +console.log(typeof num); // "number" +console.log(typeof nul); // "object" — Wait, what?! ``` -Just like atoms are the foundation of all matter, primitives are the foundation of all data in JavaScript. Every complex data structure you create — arrays, objects, functions — is ultimately built on top of these simple primitive values. - -<Note> -**TL;DR:** JavaScript has 7 primitive types: string, number, bigint, boolean, undefined, null, and symbol. They're simple, unchangeable values. Everything else is an object. -</Note> +These seven **[primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)** types are the foundation of all data in JavaScript. Unlike objects, primitives are **immutable** (unchangeable) and **compared by value**. Understanding them deeply is essential because every complex structure you'll ever build — arrays, objects, classes — ultimately relies on these simple building blocks. <Info> **What you'll learn in this guide:** @@ -32,9 +30,14 @@ Just like atoms are the foundation of all matter, primitives are the foundation - Why primitives are "immutable" and what that means - The magic of autoboxing — how `"hello".toUpperCase()` works - The difference between `null` and `undefined` +- Common mistakes to avoid with primitives - Famous JavaScript gotchas every developer should know </Info> +<Note> +**New to JavaScript?** This guide is beginner-friendly! No prior knowledge required — we'll explain everything from the ground up. +</Note> + --- ## What Are Primitive Types? @@ -93,6 +96,33 @@ All primitives share these fundamental traits: --- +## The Atoms vs Molecules Analogy + +Think of data in JavaScript like chemistry class (but way more fun, and no lab goggles required). **Primitives** are like atoms — the fundamental, indivisible building blocks that cannot be broken down further. **Objects** are like molecules — complex structures made up of multiple atoms combined together. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PRIMITIVES VS OBJECTS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ PRIMITIVES (Atoms) OBJECTS (Molecules) │ +│ │ +│ ┌───┐ ┌─────┐ ┌──────┐ ┌────────────────────────────┐ │ +│ │ 5 │ │"hi" │ │ true │ │ { name: "Alice", age: 25 } │ │ +│ └───┘ └─────┘ └──────┘ └────────────────────────────┘ │ +│ │ +│ • Simple, indivisible • Complex, contains values │ +│ • Stored directly • Stored as reference │ +│ • Compared by value • Compared by reference │ +│ • Immutable • Mutable │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Just like atoms are the foundation of all matter, primitives are the foundation of all data in JavaScript. Every complex data structure you create — arrays, objects, functions — is ultimately built on top of these simple primitive values. + +--- + ## The 7 Primitive Types: Deep Dive Let's explore each primitive type in detail. @@ -101,7 +131,7 @@ Let's explore each primitive type in detail. ### String -A **string** represents text data — a sequence of characters. +A **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** represents text data — a sequence of characters. ```javascript // Three ways to create strings @@ -110,9 +140,9 @@ let double = "World"; // Double quotes let backtick = `Hello World`; // Template literal (ES6) ``` -#### Template Literals +#### Template Literals (Still Just Strings!) -Template literals (backticks) offer special features: +[Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) (backticks) are **not a separate type** — they're just a more powerful syntax for creating strings. The result is still a regular string primitive: ```javascript let name = "Alice"; @@ -120,13 +150,15 @@ let age = 25; // String interpolation - embed expressions let greeting = `Hello, ${name}! You are ${age} years old.`; -console.log(greeting); // "Hello, Alice! You are 25 years old." +console.log(greeting); // "Hello, Alice! You are 25 years old." +console.log(typeof greeting); // "string" — it's just a string! // Multi-line strings let multiLine = ` This is line 1 This is line 2 `; +console.log(typeof multiLine); // "string" ``` #### Strings Are Immutable @@ -151,7 +183,7 @@ String methods like `toUpperCase()`, `slice()`, `replace()` always return **new ### Number -JavaScript has only **one number type** for both integers and decimals. All numbers are stored as 64-bit floating-point (a standard way computers store decimals). +JavaScript has only **one [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) type** for both integers and decimals. All numbers are stored as 64-bit floating-point (a standard way computers store decimals). ```javascript let integer = 42; // Integer @@ -168,6 +200,8 @@ console.log(-1 / 0); // -Infinity console.log("hello" * 2); // NaN (Not a Number) ``` +JavaScript has special number values: [`Infinity`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity) for values too large to represent, and [`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN) (Not a Number) for invalid mathematical operations. + #### The Famous Floating-Point Problem ```javascript @@ -178,7 +212,7 @@ console.log(0.1 + 0.2 === 0.3); // false! Welcome to JavaScript! This isn't a JavaScript bug — it's how floating-point math works in all programming languages. The decimal `0.1` cannot be perfectly represented in binary. <Warning> -**Working with money?** Never use floating-point for calculations! Store amounts in cents as integers, then use JavaScript's built-in `Intl.NumberFormat` for display. +**Working with money?** Never use floating-point for calculations! Store amounts in cents as integers, then use JavaScript's built-in [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) for display. ```javascript // Bad: floating-point errors in calculations @@ -214,7 +248,11 @@ JavaScript can only safely represent integers up to a certain size: ```javascript console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1) console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991 +``` +[`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) is the largest integer that can be safely represented. Beyond this range, precision is lost: + +```javascript // Beyond this range, precision is lost console.log(9007199254740992 === 9007199254740993); // true! (wrong!) ``` @@ -225,7 +263,7 @@ For larger integers, use `BigInt`. ### BigInt -**BigInt** (ES2020) represents integers larger than `Number.MAX_SAFE_INTEGER`. +**[BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)** (ES2020) represents integers larger than `Number.MAX_SAFE_INTEGER`. ```javascript // Add 'n' suffix to create a BigInt @@ -256,7 +294,7 @@ console.log(Number(big) + regular); // 15 ### Boolean -**Boolean** has exactly two values: `true` and `false`. +**[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** has exactly two values: `true` and `false`. ```javascript let isLoggedIn = true; @@ -304,7 +342,7 @@ Learn more about how JavaScript converts between types in the [Type Coercion](/c ### undefined -**`undefined`** means "no value has been assigned." JavaScript uses it automatically in several situations: +**[`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined)** means "no value has been assigned." JavaScript uses it automatically in several situations: ```javascript // 1. Declared but not assigned @@ -336,7 +374,7 @@ Don't explicitly assign `undefined` to variables. Use `null` instead to indicate ### null -**`null`** means "intentionally empty" — you're explicitly saying "this has no value." +**[`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null)** means "intentionally empty" — you're explicitly saying "this has no value." ```javascript // Intentionally clearing a variable @@ -368,7 +406,7 @@ console.log(value === null); // true (use strict equality) ### Symbol -**Symbol** (ES6) creates unique identifiers. Even symbols with the same description are different. +**[Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)** (ES6) creates unique identifiers. Even symbols with the same description are different. ```javascript let id1 = Symbol("id"); @@ -404,6 +442,8 @@ JavaScript has built-in symbols for customizing object behavior: // Symbol.toPrimitive - customize type conversion ``` +These are called [well-known symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#well-known_symbols) and allow you to customize how objects behave with built-in operations. + <Note> Symbols are an advanced feature. As a beginner, focus on understanding that they exist and create unique values. You'll encounter them when diving into advanced patterns and library code. </Note> @@ -412,7 +452,7 @@ Symbols are an advanced feature. As a beginner, focus on understanding that they ## The typeof Operator -The `typeof` operator returns a string indicating the type of a value. +The [`typeof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) operator returns a string indicating the type of a value. ```javascript console.log(typeof "hello"); // "string" @@ -463,6 +503,8 @@ Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call(new Date()); // "[object Date]" ``` +[`Array.isArray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) is the reliable way to check for arrays, since `typeof []` returns `"object"`. For more complex type checking, [`Object.prototype.toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) gives precise type information. + --- ## Immutability Explained @@ -688,6 +730,49 @@ if (value != null) { --- +## The #1 Primitive Mistake: Using Wrapper Constructors + +The most common mistake developers make with primitives is using `new String()`, `new Number()`, or `new Boolean()` instead of literal values. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PRIMITIVES VS WRAPPER OBJECTS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ WRONG WAY RIGHT WAY │ +│ ───────── ───────── │ +│ new String("hello") "hello" │ +│ new Number(42) 42 │ +│ new Boolean(true) true │ +│ │ +│ typeof new String("hi") → "object" typeof "hi" → "string" │ +│ new String("hi") === "hi" → false "hi" === "hi" → true │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +```javascript +// ❌ WRONG - Creates an object, not a primitive +const str = new String("hello"); +console.log(typeof str); // "object" (not "string"!) +console.log(str === "hello"); // false (object vs primitive) + +// ✓ CORRECT - Use primitive literals +const str2 = "hello"; +console.log(typeof str2); // "string" +console.log(str2 === "hello"); // true +``` + +<Warning> +**The Trap:** Using `new String()`, `new Number()`, or `new Boolean()` creates wrapper **objects**, not primitives. This breaks equality checks (`===`), `typeof` comparisons, and can cause subtle bugs. Always use literal syntax: `"hello"`, `42`, `true`. +</Warning> + +<Tip> +**Rule of Thumb:** Never use `new` with `String`, `Number`, or `Boolean`. The only exception is when you intentionally need the wrapper object (which is rare). For type conversion, use them as functions without `new`: `String(123)` returns `"123"` (a primitive). +</Tip> + +--- + ## JavaScript Quirks & Gotchas JavaScript has some famous "weird parts" that every developer should know. Most relate to primitives and type coercion. @@ -732,7 +817,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most ``` <Note> - `isNaN()` converts the value first, so `isNaN("hello")` is `true`. `Number.isNaN()` only returns `true` for actual `NaN`. + [`isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN) converts the value first, so `isNaN("hello")` is `true`. [`Number.isNaN()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) only returns `true` for actual `NaN`. </Note> </Accordion> @@ -757,7 +842,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most }).format(0.30); // "$0.30" // 3. Compare with tolerance for equality checks - Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true + Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true (Number.EPSILON is the smallest difference) // 4. Use toFixed() for simple rounding (0.1 + 0.2).toFixed(2); // "0.30" @@ -816,6 +901,34 @@ JavaScript has some famous "weird parts" that every developer should know. Most --- +## Key Takeaways + +<Info> +**Remember these essential points about Primitive Types:** + +1. **7 primitives**: string, number, bigint, boolean, undefined, null, symbol + +2. **Primitives are immutable** — you can't change the value itself, only create new values + +3. **Compared by value** — `"hello" === "hello"` is true because the values match + +4. **typeof works for most types** — except `typeof null` returns `"object"` (historical bug) + +5. **Autoboxing** allows primitives to use methods — JavaScript wraps them temporarily + +6. **undefined vs null** — undefined is "not assigned," null is "intentionally empty" + +7. **Be aware of gotchas** — `NaN !== NaN`, `0.1 + 0.2 !== 0.3`, falsy values + +8. **Don't use `new String()` etc.** — creates objects, not primitives + +9. **Symbols create unique identifiers** — even `Symbol("id") !== Symbol("id")` + +10. **Use `Number.isNaN()` to check for NaN** — don't use equality comparison since `NaN !== NaN` +</Info> + +--- + ## Test Your Knowledge <AccordionGroup> @@ -873,34 +986,29 @@ JavaScript has some famous "weird parts" that every developer should know. Most So `"hello".toUpperCase()` becomes `(new String("hello")).toUpperCase()` behind the scenes. The original primitive `"hello"` is never changed. </Accordion> + + <Accordion title="Question 6: Why can't you use === to check if a value is NaN?"> + **Answer:** Because `NaN` is the only value in JavaScript that is not equal to itself! + + ```javascript + console.log(NaN === NaN); // false! + ``` + + This is per the IEEE 754 floating-point specification. `NaN` represents an undefined or unrepresentable mathematical result, so it can't equal anything — including itself. + + **How to check for NaN:** + ```javascript + // ❌ WRONG - Never works! + if (value === NaN) { } + + // ✓ CORRECT - Use Number.isNaN() + if (Number.isNaN(value)) { } + ``` + </Accordion> </AccordionGroup> --- -## Key Takeaways - -<Info> -**Remember these essential points about Primitive Types:** - -1. **7 primitives**: string, number, bigint, boolean, undefined, null, symbol - -2. **Primitives are immutable** — you can't change the value itself, only create new values - -3. **Compared by value** — `"hello" === "hello"` is true because the values match - -4. **typeof works for most types** — except `typeof null` returns `"object"` (historical bug) - -5. **Autoboxing** allows primitives to use methods — JavaScript wraps them temporarily - -6. **undefined vs null** — undefined is "not assigned," null is "intentionally empty" - -7. **Be aware of gotchas** — `NaN !== NaN`, `0.1 + 0.2 !== 0.3`, falsy values - -8. **Don't use `new String()` etc.** — creates objects, not primitives -</Info> - ---- - ## Related Concepts <CardGroup cols={2}> @@ -922,9 +1030,20 @@ JavaScript has some famous "weird parts" that every developer should know. Most ## Reference -<Card title="JavaScript data types and data structures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Primitive_values"> - Official MDN documentation on primitive values and JavaScript's type system -</Card> +<CardGroup cols={2}> + <Card title="Primitive — MDN Glossary" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Primitive"> + Official MDN glossary definition of primitive values in JavaScript. + </Card> + <Card title="JavaScript data types and data structures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures"> + Comprehensive MDN guide to JavaScript's type system and data structures. + </Card> + <Card title="typeof operator — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof"> + Complete reference for the typeof operator including its quirks and return values. + </Card> + <Card title="Symbol — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol"> + Deep dive into JavaScript Symbols, well-known symbols, and use cases. + </Card> +</CardGroup> ## Articles From f2f1608bd2f1c01a47735d2d3da52ddafc3a7796 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:30:36 -0300 Subject: [PATCH 059/128] docs: standardize structure for type-coercion and equality-operators - type-coercion: move Key Takeaways before Test Your Knowledge - equality-operators: add Warning with prerequisites --- docs/concepts/equality-operators.mdx | 4 ++ docs/concepts/type-coercion.mdx | 56 ++++++++++++++-------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/concepts/equality-operators.mdx b/docs/concepts/equality-operators.mdx index 0d8bb626..fabafffc 100644 --- a/docs/concepts/equality-operators.mdx +++ b/docs/concepts/equality-operators.mdx @@ -28,6 +28,10 @@ Understanding JavaScript's **[equality operators](https://developer.mozilla.org/ - A simple rule: when to use which operator </Info> +<Warning> +**Prerequisites:** This guide assumes you understand [Primitive Types](/concepts/primitive-types) and [Type Coercion](/concepts/type-coercion). Equality operators rely heavily on how JavaScript converts types — if those concepts are new to you, read those guides first! +</Warning> + --- ## The Three Equality Operators: Overview diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index 8fc2e4d6..edcaf7cc 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -851,6 +851,34 @@ function process(count) { --- +## Key Takeaways + +<Info> +**Remember these essentials about Type Coercion:** + +1. **Three conversions only** — JavaScript converts to String, Number, or Boolean — nothing else + +2. **Implicit vs Explicit** — Know when JS converts automatically vs when you control it + +3. **The 8 falsy values** — `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy + +4. **+ is special** — It prefers string concatenation if ANY operand is a string + +5. **- * / % are consistent** — They ALWAYS convert to numbers + +6. **== coerces, === doesn't** — Use `===` by default to avoid surprises + +7. **null == undefined** — This is true, but neither equals anything else with `==` + +8. **Objects convert via valueOf() and toString()** — Learn these methods to control conversion + +9. **When in doubt, be explicit** — Use `Number()`, `String()`, `Boolean()` + +10. **NaN is unique** — It's the only value not equal to itself; use `Number.isNaN()` to check +</Info> + +--- + ## Test Your Knowledge <AccordionGroup> @@ -921,34 +949,6 @@ function process(count) { --- -## Key Takeaways - -<Info> -**Remember these essentials about Type Coercion:** - -1. **Three conversions only** — JavaScript converts to String, Number, or Boolean — nothing else - -2. **Implicit vs Explicit** — Know when JS converts automatically vs when you control it - -3. **The 8 falsy values** — `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy - -4. **+ is special** — It prefers string concatenation if ANY operand is a string - -5. **- * / % are consistent** — They ALWAYS convert to numbers - -6. **== coerces, === doesn't** — Use `===` by default to avoid surprises - -7. **null == undefined** — This is true, but neither equals anything else with `==` - -8. **Objects convert via valueOf() and toString()** — Learn these methods to control conversion - -9. **When in doubt, be explicit** — Use `Number()`, `String()`, `Boolean()` - -10. **NaN is unique** — It's the only value not equal to itself; use `Number.isNaN()` to check -</Info> - ---- - ## Related Concepts <CardGroup cols={2}> From e27aa78e01e752ec38d4a103d63e666aa179dcb0 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 13:47:14 -0300 Subject: [PATCH 060/128] docs: reorganize Functional Programming concepts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder FP concepts: HOFs → Pure Functions → map/reduce/filter → Recursion → Currying - Move Currying & Composition from Advanced Topics to Functional Programming - Remove Event Propagation from Pure Functions (unrelated to FP) - Add comprehensive Event Propagation section to DOM concept - Update all FP concept titles to follow standard format with SEO descriptions --- docs/concepts/currying-composition.mdx | 5 +- docs/concepts/dom.mdx | 141 ++++++++++++++++++++++- docs/concepts/higher-order-functions.mdx | 5 +- docs/concepts/map-reduce-filter.mdx | 5 +- docs/concepts/pure-functions.mdx | 12 +- docs/concepts/recursion.mdx | 5 +- docs/docs.json | 104 ++++++++--------- 7 files changed, 206 insertions(+), 71 deletions(-) diff --git a/docs/concepts/currying-composition.mdx b/docs/concepts/currying-composition.mdx index b6439edb..eeb0acf8 100644 --- a/docs/concepts/currying-composition.mdx +++ b/docs/concepts/currying-composition.mdx @@ -1,6 +1,7 @@ --- -title: "Partial Applications, Currying, Compose and Pipe" -description: "Advanced functional programming techniques in JavaScript" +title: "Currying & Composition: Building Functions from Functions in JavaScript" +sidebarTitle: "Currying & Composition: Building Functions" +description: "Learn currying and function composition in JavaScript. Master partial application, compose, pipe, and how to build complex functions from simple ones for cleaner, reusable code." --- ## Overview diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 7fd6781b..5673b518 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -1405,11 +1405,141 @@ div.appendChild(welcomeText) --- +## Event Propagation: Bubbling and Capturing + +When an event occurs on a DOM element, it doesn't just trigger on that element — it travels through the DOM tree in a process called **event propagation**. Understanding this is crucial for event handling. + +### The Three Phases + +Every DOM event goes through three phases: + +``` +1. CAPTURING PHASE ↓ (from window → target's parent) +2. TARGET PHASE ● (at the target element) +3. BUBBLING PHASE ↑ (from target's parent → window) +``` + +```javascript +// Most events bubble UP by default +document.querySelector('.child').addEventListener('click', (e) => { + console.log('Child clicked') +}) + +document.querySelector('.parent').addEventListener('click', (e) => { + console.log('Parent also receives the click!') // This fires too! +}) +``` + +### Capturing vs Bubbling + +By default, event listeners fire during the **bubbling phase** (bottom-up). You can listen during the **capturing phase** (top-down) with the third parameter: + +```javascript +// Bubbling (default) — fires on the way UP +element.addEventListener('click', handler) +element.addEventListener('click', handler, false) + +// Capturing — fires on the way DOWN +element.addEventListener('click', handler, true) +element.addEventListener('click', handler, { capture: true }) +``` + +```javascript +// Practical example: see the order +document.querySelector('.parent').addEventListener('click', () => { + console.log('1. Parent - capturing') +}, true) + +document.querySelector('.child').addEventListener('click', () => { + console.log('2. Child - target') +}) + +document.querySelector('.parent').addEventListener('click', () => { + console.log('3. Parent - bubbling') +}) + +// Click on child outputs: 1, 2, 3 +``` + +### Stopping Propagation + +You can stop an event from traveling further: + +```javascript +element.addEventListener('click', (e) => { + e.stopPropagation() // Stop bubbling/capturing + // Parent handlers won't fire +}) + +element.addEventListener('click', (e) => { + e.stopImmediatePropagation() // Stop ALL handlers, even on same element +}) +``` + +<Warning> +**Use `stopPropagation()` sparingly!** It breaks event delegation and can make debugging difficult. Usually there's a better solution. +</Warning> + +### Preventing Default Behavior + +Don't confuse propagation with default behavior: + +```javascript +// Prevent the browser's default action (e.g., following a link) +link.addEventListener('click', (e) => { + e.preventDefault() // Don't navigate + // Event still bubbles unless you also call stopPropagation() +}) + +// Common use cases: +// - Prevent form submission: form.addEventListener('submit', e => e.preventDefault()) +// - Prevent link navigation: link.addEventListener('click', e => e.preventDefault()) +// - Prevent context menu: element.addEventListener('contextmenu', e => e.preventDefault()) +``` + +### The `event.target` vs `event.currentTarget` + +This distinction matters for event delegation: + +```javascript +document.querySelector('.parent').addEventListener('click', (e) => { + console.log(e.target) // The element that was actually clicked + console.log(e.currentTarget) // The element with the listener (.parent) + console.log(this) // Same as currentTarget (in regular functions) +}) +``` + +```javascript +// If you click on a <span> inside .parent: +// e.target = <span> (what you clicked) +// e.currentTarget = .parent (what has the listener) +``` + +### Events That Don't Bubble + +Most events bubble, but some don't: + +| Event | Bubbles? | Notes | +|-------|----------|-------| +| `click`, `mousedown`, `keydown` | Yes | Most user events bubble | +| `focus`, `blur` | No | Use `focusin`/`focusout` for bubbling versions | +| `mouseenter`, `mouseleave` | No | Use `mouseover`/`mouseout` for bubbling versions | +| `load`, `unload`, `scroll` | No | Window/document events | + +```javascript +// focus doesn't bubble, but focusin does +form.addEventListener('focusin', (e) => { + console.log('Something in the form was focused:', e.target) +}) +``` + +--- + ## Common DOM Patterns ### Event Delegation -Instead of adding listeners to many elements, add one to a parent. This pattern relies on event bubbling (see [Event Loop](/concepts/event-loop) for how the browser processes events): +Instead of adding listeners to many elements, add one to a parent. This pattern relies on **event bubbling** — when you click a child element, the event bubbles up to the parent where your listener catches it: ```javascript // Bad: Many listeners @@ -1987,6 +2117,12 @@ input.value // "hello" <Card title="JavaScript DOM Tutorial" icon="newspaper" href="https://www.javascripttutorial.net/javascript-dom/"> A comprehensive multi-part tutorial covering selection, traversal, manipulation, and events. </Card> + <Card title="Event Propagation — MDN" icon="newspaper" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events"> + MDN's guide to event handling including bubbling, capturing, and delegation patterns. + </Card> + <Card title="Bubbling and Capturing" icon="newspaper" href="https://javascript.info/bubbling-and-capturing"> + JavaScript.info's detailed explanation of how events travel through the DOM tree. + </Card> </CardGroup> ## Videos @@ -2007,4 +2143,7 @@ input.value // "hello" <Card title="JavaScript DOM Traversal Methods" icon="video" href="https://www.youtube.com/watch?v=v7rSSy8CaYE"> Web Dev Simplified covers parent, child, and sibling traversal methods. </Card> + <Card title="Event Propagation - JavaScript Event Bubbling and Propagation" icon="video" href="https://www.youtube.com/watch?v=JYc7gr9Ehl0"> + Steve Griffith explains event bubbling, capturing, and how to control event flow. + </Card> </CardGroup> diff --git a/docs/concepts/higher-order-functions.mdx b/docs/concepts/higher-order-functions.mdx index acbcd7a6..dc6bd995 100644 --- a/docs/concepts/higher-order-functions.mdx +++ b/docs/concepts/higher-order-functions.mdx @@ -1,6 +1,7 @@ --- -title: "High Order Functions" -description: "Functions that operate on other functions" +title: "Higher-Order Functions: Functions as Data in JavaScript" +sidebarTitle: "Higher-Order Functions: Functions as Data" +description: "Learn higher-order functions in JavaScript — functions that take functions as arguments or return them. Master callbacks, closures, and functional patterns like map, filter, and reduce." --- ## Overview diff --git a/docs/concepts/map-reduce-filter.mdx b/docs/concepts/map-reduce-filter.mdx index 279eb24d..d1e9e6d7 100644 --- a/docs/concepts/map-reduce-filter.mdx +++ b/docs/concepts/map-reduce-filter.mdx @@ -1,6 +1,7 @@ --- -title: "map, reduce, filter" -description: "Array transformation methods in JavaScript" +title: "map, reduce & filter: Transforming Arrays in JavaScript" +sidebarTitle: "map, reduce & filter: Transforming Arrays" +description: "Learn JavaScript's map, reduce, and filter array methods. Master functional array transformations, method chaining, and data processing without mutating original arrays." --- ## Overview diff --git a/docs/concepts/pure-functions.mdx b/docs/concepts/pure-functions.mdx index e2dda5a4..8e57c0f5 100644 --- a/docs/concepts/pure-functions.mdx +++ b/docs/concepts/pure-functions.mdx @@ -1,16 +1,13 @@ --- -title: "Pure Functions, Side Effects, State Mutation and Event Propagation" -description: "Understanding functional programming concepts in JavaScript" +title: "Pure Functions: Side Effects & Immutability in JavaScript" +sidebarTitle: "Pure Functions: Side Effects & Immutability" +description: "Learn pure functions in JavaScript — functions with no side effects that always return the same output for the same input. Master immutability, state mutation, and predictable code." --- ## Overview **Pure functions** are functions that always return the same output for the same input and have no side effects. They don't modify external state or depend on external mutable state. Understanding pure functions, side effects, and state mutation is fundamental to writing predictable and testable JavaScript code. -<Info> -**Event propagation** refers to the order in which events are handled in the DOM, including the capturing phase (from ancestor to target) and bubbling phase (from target to ancestor). -</Info> - ## Articles <CardGroup cols={2}> @@ -33,8 +30,6 @@ description: "Understanding functional programming concepts in JavaScript" - [JavaScript: Pure Functions — William S. Vincent](https://wsvincent.com/javascript-pure-functions/) - [Functional programming paradigms in modern JavaScript: Pure functions — Alexander Kondov](https://hackernoon.com/functional-programming-paradigms-in-modern-javascript-pure-functions-797d9abbee1) - [Understanding Javascript Mutation and Pure Functions — Chidume Nnamdi](https://blog.bitsrc.io/understanding-javascript-mutation-and-pure-functions-7231cc2180d3) -- [Event Propagation — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) -- [Event Propagation — Bubbling and capturing](https://javascript.info/bubbling-and-capturing) ## Videos @@ -50,4 +45,3 @@ description: "Understanding functional programming concepts in JavaScript" - [JavaScript Pure Functions — Seth Alexander](https://www.youtube.com/watch?v=frT3H-eBmPc) - [JavaScript Pure vs Impure Functions Explained — Theodore Anderson](https://www.youtube.com/watch?v=AHbRVJzpB54) - [Pure Functions - Programação Funcional: Parte 1 - Fun Fun Function](https://www.youtube.com/watch?v=BMUiFMZr7vk) -- [Event Propagation - JavaScript Event Bubbling and Propagation - Steve Griffith](https://www.youtube.com/watch?v=JYc7gr9Ehl0) diff --git a/docs/concepts/recursion.mdx b/docs/concepts/recursion.mdx index 4711fe7c..343ef6e7 100644 --- a/docs/concepts/recursion.mdx +++ b/docs/concepts/recursion.mdx @@ -1,6 +1,7 @@ --- -title: "Recursion" -description: "Understanding recursive functions in JavaScript" +title: "Recursion: Functions That Call Themselves in JavaScript" +sidebarTitle: "Recursion: Functions That Call Themselves" +description: "Learn recursion in JavaScript — functions that call themselves to solve problems. Master base cases, recursive cases, stack overflow prevention, and when to use recursion vs iteration." --- ## Overview diff --git a/docs/docs.json b/docs/docs.json index 6ff7944d..3c651f71 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -50,59 +50,57 @@ "concepts/iife-modules" ] }, - { - "group": "Web Platform", - "icon": "browser", - "pages": [ - "concepts/dom", - "concepts/http-fetch" - ] - }, - { - "group": "Object-Oriented JS", - "icon": "sitemap", - "pages": [ - "concepts/factories-classes", - "concepts/this-call-apply-bind", - "concepts/new-constructor", - "concepts/prototype", - "concepts/object-create-assign" - ] - }, - { - "group": "Functional Programming", - "icon": "filter", - "pages": [ - "concepts/map-reduce-filter", - "concepts/pure-functions", - "concepts/higher-order-functions", - "concepts/recursion" - ] - }, - { - "group": "Async JavaScript", - "icon": "clock", - "pages": [ - "concepts/collections-generators", - "concepts/promises", - "concepts/async-await" - ] - }, - { - "group": "Advanced Topics", - "icon": "graduation-cap", - "pages": [ - "concepts/javascript-engines", - "concepts/data-structures", - "concepts/big-o-notation", - "concepts/algorithms", - "concepts/inheritance-polymorphism", - "concepts/design-patterns", - "concepts/currying-composition", - "concepts/clean-code" - ] - } - ] +{ + "group": "Web Platform", + "icon": "browser", + "pages": [ + "concepts/dom", + "concepts/http-fetch" + ] + }, + { + "group": "Object-Oriented JS", + "icon": "sitemap", + "pages": [ + "concepts/factories-classes", + "concepts/this-call-apply-bind", + "concepts/object-creation-prototypes" + ] + }, + { + "group": "Functional Programming", + "icon": "filter", + "pages": [ + "concepts/higher-order-functions", + "concepts/pure-functions", + "concepts/map-reduce-filter", + "concepts/recursion", + "concepts/currying-composition" + ] + }, + { + "group": "Async JavaScript", + "icon": "clock", + "pages": [ + "concepts/collections-generators", + "concepts/promises", + "concepts/async-await" + ] + }, + { + "group": "Advanced Topics", + "icon": "graduation-cap", + "pages": [ + "concepts/javascript-engines", + "concepts/data-structures", + "concepts/big-o-notation", + "concepts/algorithms", + "concepts/inheritance-polymorphism", + "concepts/design-patterns", + "concepts/clean-code" + ] + } + ] }, { "tab": "Community", From ef9bf2932d235a92627d7c7ebfcfb71b99188313 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 14:06:20 -0300 Subject: [PATCH 061/128] docs: reorganize Async JavaScript concepts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new Callbacks concept as foundation of async (with Event Loop prerequisite) - Split collections-generators into two focused concepts: - generators-iterators.mdx (stays in Async JavaScript) - collections.mdx (moved to Advanced Topics) - Reorder navigation: Async JavaScript now comes before Functional Programming - Update all Async concept titles to follow standard format - New Async order: Callbacks → Promises → async/await → Generators & Iterators --- docs/concepts/async-await.mdx | 5 ++- docs/concepts/callbacks.mdx | 54 +++++++++++++++++++++++ docs/concepts/collections-generators.mdx | 52 ---------------------- docs/concepts/collections.mdx | 49 +++++++++++++++++++++ docs/concepts/generators-iterators.mdx | 56 ++++++++++++++++++++++++ docs/concepts/promises.mdx | 5 ++- docs/docs.json | 20 +++++---- 7 files changed, 176 insertions(+), 65 deletions(-) create mode 100644 docs/concepts/callbacks.mdx delete mode 100644 docs/concepts/collections-generators.mdx create mode 100644 docs/concepts/collections.mdx create mode 100644 docs/concepts/generators-iterators.mdx diff --git a/docs/concepts/async-await.mdx b/docs/concepts/async-await.mdx index a895f754..05503374 100644 --- a/docs/concepts/async-await.mdx +++ b/docs/concepts/async-await.mdx @@ -1,6 +1,7 @@ --- -title: "async/await" -description: "Modern asynchronous JavaScript syntax" +title: "async/await: Modern Async Syntax in JavaScript" +sidebarTitle: "async/await: Modern Async Syntax" +description: "Learn async/await in JavaScript — syntactic sugar over Promises that makes async code look synchronous. Master error handling with try/catch, parallel execution, and common pitfalls." --- ## Overview diff --git a/docs/concepts/callbacks.mdx b/docs/concepts/callbacks.mdx new file mode 100644 index 00000000..900f33ab --- /dev/null +++ b/docs/concepts/callbacks.mdx @@ -0,0 +1,54 @@ +--- +title: "Callbacks: The Foundation of Async JavaScript" +sidebarTitle: "Callbacks: The Foundation of Async" +description: "Learn JavaScript callbacks — functions passed to other functions to be called later. Understand async patterns, callback hell, error-first callbacks, and why Promises were invented." +--- + +## Overview + +A **callback** is a function passed as an argument to another function, to be executed later — either synchronously or asynchronously. Callbacks are the foundation of asynchronous JavaScript: event handlers, timers, and Node.js APIs all rely on this pattern. + +<Warning> +**Prerequisites:** Before diving into async JavaScript, make sure you understand [the Event Loop](/concepts/event-loop) — it's the mechanism that makes all async code work! The Event Loop explains *how* JavaScript handles async operations; callbacks are *what* gets executed when those operations complete. +</Warning> + +<Info> +**What you'll learn in this guide:** +- What callbacks are and why JavaScript uses them +- Synchronous vs asynchronous callbacks +- The error-first callback pattern (Node.js convention) +- Callback hell and the "pyramid of doom" +- Why Promises were invented to solve callback problems +</Info> + +## Articles + +<CardGroup cols={2}> + <Card title="JavaScript Callbacks Explained" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-callback-functions-what-are-callbacks-in-js-and-how-to-use-them/"> + By freeCodeCamp + </Card> + <Card title="Understanding Callbacks in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-callbacks-in-javascript"> + By DigitalOcean + </Card> +</CardGroup> + +- [Callback Functions in JavaScript — JavaScript.info](https://javascript.info/callbacks) +- [What is a Callback Function in JavaScript? — MDN](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) +- [JavaScript Callbacks — W3Schools](https://www.w3schools.com/js/js_callback.asp) +- [Understand Callback Functions in JavaScript — Brandon Morelli](https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced) +- [Callback Hell — callbackhell.com](http://callbackhell.com/) + +## Videos + +<CardGroup cols={2}> + <Card title="Callbacks in JavaScript Explained!" icon="video" href="https://www.youtube.com/watch?v=cNjIUSDnb9k"> + By Mosh Hamedani + </Card> + <Card title="JavaScript Callbacks" icon="video" href="https://www.youtube.com/watch?v=QRq2zMHlBz4"> + By Web Dev Simplified + </Card> +</CardGroup> + +- [Callback Functions — Fun Fun Function](https://www.youtube.com/watch?v=Nau-iEEgEoM) +- [JavaScript Callback Functions — techsith](https://www.youtube.com/watch?v=pTbSfCT42_M) +- [Asynchronous JavaScript Tutorial - Callbacks, Promises, Async/Await — Traversy Media](https://www.youtube.com/watch?v=PoRJizFvM7s) diff --git a/docs/concepts/collections-generators.mdx b/docs/concepts/collections-generators.mdx deleted file mode 100644 index 0b6ffb05..00000000 --- a/docs/concepts/collections-generators.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "Collections and Generators" -description: "Understanding iterables and lazy evaluation in JavaScript" ---- - -## Overview - -ES6 introduced new collection types like **Map**, **Set**, **WeakMap**, and **WeakSet**, as well as **generators** - functions that can be paused and resumed. Generators provide an elegant way to work with iterables and implement lazy evaluation patterns. - -## Reference - -<Card title="Generator — MDN web docs" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"> - MDN documentation -</Card> - -## Articles - -<CardGroup cols={2}> - <Card title="ES6 Collections: Using Map, Set, WeakMap, WeakSet" icon="newspaper" href="https://www.sitepoint.com/es6-collections-map-set-weakmap-weakset/"> - By Kyle Pennell - </Card> - <Card title="What are JavaScript Generators and how to use them" icon="newspaper" href="https://codeburst.io/what-are-javascript-generators-and-how-to-use-them-c6f2713fd12e"> - By Vladislav Stepanov - </Card> -</CardGroup> - -- [ES6 WeakMaps, Sets, and WeakSets in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-weakmaps-sets-and-weaksets-in-depth) -- [Map, Set, WeakMap and WeakSet — JavaScript.Info](https://javascript.info/map-set-weakmap-weakset) -- [Maps in ES6 - A Quick Guide — Ben Mildren](https://dev.to/mildrenben/maps-in-es6---a-quick-guide-35pk) -- [ES6 — Set vs Array — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-set-vs-array-what-and-when-efc055655e1a) -- [ES6 — Map vs Object — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-map-vs-object-what-and-when-b80621932373) -- [Array vs Set vs Map vs Object — Real-time use cases in Javascript (ES6/ES7) — Rajesh Babu](https://codeburst.io/array-vs-set-vs-map-vs-object-real-time-use-cases-in-javascript-es6-47ee3295329b) -- [How to create an array of unique values in JavaScript using Sets — Claire Parker-Jones](https://dev.to/claireparker/how-to-create-an-array-of-unique-values-in-javascript-using-sets-5dg6) -- [What You Should Know About ES6 Maps — Just Chris](https://hackernoon.com/what-you-should-know-about-es6-maps-dc66af6b9a1e) -- [ES6 Maps in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-maps-in-depth) -- [Understanding Generators in ES6 JavaScript with Examples — Arfat Salman](https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5) -- [The Basics of ES6 Generators — Kyle Simpson](https://davidwalsh.name/es6-generators) -- [An Introduction to JavaScript Generators — Alice Kallaugher](https://dev.to/kallaugher/an-introduction-to-javascript-generators-1224) - -## Videos - -<CardGroup cols={2}> - <Card title="JavaScript ES6 / ES2015 Set, Map, WeakSet and WeakMap" icon="video" href="https://www.youtube.com/watch?v=ycohYSx5h9w"> - By Traversy Media - </Card> - <Card title="JavaScript ES6 / ES2015 - Generators" icon="video" href="https://www.youtube.com/watch?v=dcP039DYzmE"> - By Traversy Media - </Card> -</CardGroup> - -- [The Differences between ES6 Maps and Sets — Steve Griffith](https://www.youtube.com/watch?v=m4abICrldQI) -- [Javascript Generators - THEY CHANGE EVERYTHING - ES6 Generators Harmony Generators — LearnCode.academy](https://www.youtube.com/watch?v=QO07THdLWQo) diff --git a/docs/concepts/collections.mdx b/docs/concepts/collections.mdx new file mode 100644 index 00000000..0575128d --- /dev/null +++ b/docs/concepts/collections.mdx @@ -0,0 +1,49 @@ +--- +title: "ES6 Collections: Map, Set, WeakMap & WeakSet in JavaScript" +sidebarTitle: "ES6 Collections: Map, Set, WeakMap & WeakSet" +description: "Learn ES6 collections in JavaScript — Map, Set, WeakMap, and WeakSet. Understand when to use each collection type, their differences from objects and arrays, and memory management with weak references." +--- + +## Overview + +ES6 introduced four new collection types: **Map**, **Set**, **WeakMap**, and **WeakSet**. These provide alternatives to plain objects and arrays with specific use cases — Maps for key-value pairs with any key type, Sets for unique values, and Weak variants for memory-efficient object references. + +<Info> +**What you'll learn in this guide:** +- Map vs Object — when to use each +- Set vs Array — unique value collections +- WeakMap and WeakSet — garbage collection friendly references +- Real-world use cases for each collection type +- Performance characteristics and best practices +</Info> + +## Articles + +<CardGroup cols={2}> + <Card title="ES6 Collections: Using Map, Set, WeakMap, WeakSet" icon="newspaper" href="https://www.sitepoint.com/es6-collections-map-set-weakmap-weakset/"> + By Kyle Pennell + </Card> + <Card title="Map, Set, WeakMap and WeakSet" icon="newspaper" href="https://javascript.info/map-set-weakmap-weakset"> + By JavaScript.info + </Card> +</CardGroup> + +- [ES6 WeakMaps, Sets, and WeakSets in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-weakmaps-sets-and-weaksets-in-depth) +- [Maps in ES6 - A Quick Guide — Ben Mildren](https://dev.to/mildrenben/maps-in-es6---a-quick-guide-35pk) +- [ES6 — Set vs Array — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-set-vs-array-what-and-when-efc055655e1a) +- [ES6 — Map vs Object — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-map-vs-object-what-and-when-b80621932373) +- [Array vs Set vs Map vs Object — Real-time use cases in Javascript (ES6/ES7) — Rajesh Babu](https://codeburst.io/array-vs-set-vs-map-vs-object-real-time-use-cases-in-javascript-es6-47ee3295329b) +- [How to create an array of unique values in JavaScript using Sets — Claire Parker-Jones](https://dev.to/claireparker/how-to-create-an-array-of-unique-values-in-javascript-using-sets-5dg6) +- [What You Should Know About ES6 Maps — Just Chris](https://hackernoon.com/what-you-should-know-about-es6-maps-dc66af6b9a1e) +- [ES6 Maps in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-maps-in-depth) + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript ES6 / ES2015 Set, Map, WeakSet and WeakMap" icon="video" href="https://www.youtube.com/watch?v=ycohYSx5h9w"> + By Traversy Media + </Card> + <Card title="The Differences between ES6 Maps and Sets" icon="video" href="https://www.youtube.com/watch?v=m4abICrldQI"> + By Steve Griffith + </Card> +</CardGroup> diff --git a/docs/concepts/generators-iterators.mdx b/docs/concepts/generators-iterators.mdx new file mode 100644 index 00000000..a189ae1f --- /dev/null +++ b/docs/concepts/generators-iterators.mdx @@ -0,0 +1,56 @@ +--- +title: "Generators & Iterators: Pausable Functions in JavaScript" +sidebarTitle: "Generators & Iterators: Pausable Functions" +description: "Learn JavaScript generators and iterators — functions that can pause and resume execution. Master the yield keyword, iteration protocols, lazy evaluation, and async generators." +--- + +## Overview + +**Generators** are special functions that can be paused and resumed, yielding multiple values over time. Combined with **iterators** (objects that define a sequence), they enable powerful patterns like lazy evaluation, infinite sequences, and custom iteration — and form the foundation for async/await under the hood. + +<Info> +**What you'll learn in this guide:** +- Generator functions and the `yield` keyword +- The iteration protocol (`Symbol.iterator`) +- Creating custom iterables +- Lazy evaluation and infinite sequences +- Async generators and `for await...of` +- Real-world use cases (Redux-Saga, data streaming) +</Info> + +## Reference + +<Card title="Generator — MDN web docs" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"> + MDN documentation for Generator objects +</Card> + +## Articles + +<CardGroup cols={2}> + <Card title="What are JavaScript Generators and how to use them" icon="newspaper" href="https://codeburst.io/what-are-javascript-generators-and-how-to-use-them-c6f2713fd12e"> + By Vladislav Stepanov + </Card> + <Card title="Understanding Generators in ES6 JavaScript with Examples" icon="newspaper" href="https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5"> + By Arfat Salman + </Card> +</CardGroup> + +- [The Basics of ES6 Generators — Kyle Simpson](https://davidwalsh.name/es6-generators) +- [An Introduction to JavaScript Generators — Alice Kallaugher](https://dev.to/kallaugher/an-introduction-to-javascript-generators-1224) +- [Iterators and Generators — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) +- [A Simple Guide to ES6 Iterators in JavaScript with Examples — Brandon Morelli](https://codeburst.io/a-simple-guide-to-es6-iterators-in-javascript-with-examples-189d052c3d8e) +- [Generators — JavaScript.info](https://javascript.info/generators) + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript ES6 / ES2015 - Generators" icon="video" href="https://www.youtube.com/watch?v=dcP039DYzmE"> + By Traversy Media + </Card> + <Card title="JavaScript Generators" icon="video" href="https://www.youtube.com/watch?v=QO07THdLWQo"> + By LearnCode.academy + </Card> +</CardGroup> + +- [Generators in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=ategZqxHkz4) +- [JavaScript Iterators and Generators — Fireship](https://www.youtube.com/watch?v=IJ6EgdiI_wU) diff --git a/docs/concepts/promises.mdx b/docs/concepts/promises.mdx index ed17def0..4cc8ca83 100644 --- a/docs/concepts/promises.mdx +++ b/docs/concepts/promises.mdx @@ -1,6 +1,7 @@ --- -title: "Promises" -description: "Handling asynchronous operations in JavaScript" +title: "Promises: Managing Async Operations in JavaScript" +sidebarTitle: "Promises: Managing Async Operations" +description: "Learn JavaScript Promises — objects representing eventual completion or failure of async operations. Master then/catch/finally, Promise.all, Promise.race, and error handling patterns." --- ## Overview diff --git a/docs/docs.json b/docs/docs.json index 3c651f71..83c6e068 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -67,6 +67,16 @@ "concepts/object-creation-prototypes" ] }, + { + "group": "Async JavaScript", + "icon": "clock", + "pages": [ + "concepts/callbacks", + "concepts/promises", + "concepts/async-await", + "concepts/generators-iterators" + ] + }, { "group": "Functional Programming", "icon": "filter", @@ -78,20 +88,12 @@ "concepts/currying-composition" ] }, - { - "group": "Async JavaScript", - "icon": "clock", - "pages": [ - "concepts/collections-generators", - "concepts/promises", - "concepts/async-await" - ] - }, { "group": "Advanced Topics", "icon": "graduation-cap", "pages": [ "concepts/javascript-engines", + "concepts/collections", "concepts/data-structures", "concepts/big-o-notation", "concepts/algorithms", From b4e44303392859dc3883ab4748ecfb7a47f97164 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 14:36:09 -0300 Subject: [PATCH 062/128] docs: reorganize Advanced Topics and reach 33 concepts - Move Inheritance & Polymorphism from Advanced Topics to Object-Oriented JS - Merge Big O Notation + Algorithms into single 'Algorithms & Big O' concept - Remove Collections concept (ES6 Map/Set - not essential for 33) - Add 5 new modern concepts: - Error Handling: try/catch, error types, async errors - Modern JS Syntax: destructuring, spread, ?., ?? - ES Modules: import/export, dynamic imports - Regular Expressions: pattern matching (Advanced Topics) - Web Workers: parallel JavaScript (Web Platform) - Update all Advanced Topics titles to standard format - Final count: exactly 33 concepts --- docs/concepts/algorithms-big-o.mdx | 98 ++++++++++++++++++++++ docs/concepts/algorithms.mdx | 43 ---------- docs/concepts/big-o-notation.mdx | 45 ---------- docs/concepts/clean-code.mdx | 5 +- docs/concepts/collections.mdx | 49 ----------- docs/concepts/data-structures.mdx | 5 +- docs/concepts/design-patterns.mdx | 5 +- docs/concepts/error-handling.mdx | 62 ++++++++++++++ docs/concepts/es-modules.mdx | 76 +++++++++++++++++ docs/concepts/inheritance-polymorphism.mdx | 5 +- docs/concepts/javascript-engines.mdx | 5 +- docs/concepts/modern-js-syntax.mdx | 73 ++++++++++++++++ docs/concepts/regular-expressions.mdx | 65 ++++++++++++++ docs/concepts/web-workers.mdx | 69 +++++++++++++++ docs/docs.json | 15 ++-- 15 files changed, 467 insertions(+), 153 deletions(-) create mode 100644 docs/concepts/algorithms-big-o.mdx delete mode 100644 docs/concepts/algorithms.mdx delete mode 100644 docs/concepts/big-o-notation.mdx delete mode 100644 docs/concepts/collections.mdx create mode 100644 docs/concepts/error-handling.mdx create mode 100644 docs/concepts/es-modules.mdx create mode 100644 docs/concepts/modern-js-syntax.mdx create mode 100644 docs/concepts/regular-expressions.mdx create mode 100644 docs/concepts/web-workers.mdx diff --git a/docs/concepts/algorithms-big-o.mdx b/docs/concepts/algorithms-big-o.mdx new file mode 100644 index 00000000..64221752 --- /dev/null +++ b/docs/concepts/algorithms-big-o.mdx @@ -0,0 +1,98 @@ +--- +title: "Algorithms & Big O: Problem Solving & Performance in JavaScript" +sidebarTitle: "Algorithms & Big O: Problem Solving & Performance" +description: "Learn algorithms and Big O notation in JavaScript — understand time/space complexity, common algorithms, and how to analyze and optimize your code's performance." +--- + +## Overview + +**Algorithms** are step-by-step procedures for solving problems, while **Big O notation** is how we measure their efficiency. Understanding both is essential for writing performant code, passing technical interviews, and making informed decisions about data processing approaches. + +<Info> +**What you'll learn in this guide:** +- Big O notation and complexity analysis +- Time complexity vs space complexity +- Common complexities: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ) +- Sorting algorithms (bubble, merge, quick sort) +- Searching algorithms (linear, binary search) +- Common algorithm patterns (sliding window, two pointers, etc.) +- How to analyze your own code's complexity +</Info> + +<Info> +Common Big O complexities from fastest to slowest: O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n²) quadratic, O(2ⁿ) exponential. +</Info> + +## Reference + +<CardGroup cols={2}> + <Card title="Data Structures and Algorithms using ES6" icon="newspaper" href="https://github.com/Crizstian/data-structure-and-algorithms-with-ES6"> + GitHub Repository + </Card> + <Card title="Algorithms and data structures implemented in JavaScript" icon="newspaper" href="https://github.com/trekhleb/javascript-algorithms"> + By Oleksii Trekhleb + </Card> +</CardGroup> + +## Articles + +### Big O Notation + +<CardGroup cols={2}> + <Card title="Big O Notation in Javascript" icon="newspaper" href="https://medium.com/cesars-tech-insights/big-o-notation-javascript-25c79f50b19b"> + By César Antón Dorantes + </Card> + <Card title="Time Complexity/Big O Notation" icon="newspaper" href="https://medium.com/javascript-scene/time-complexity-big-o-notation-1a4310c3ee4b"> + By Tim Roberts + </Card> +</CardGroup> + +- [Big O in JavaScript — Gabriela Medina](https://medium.com/@gmedina229/big-o-in-javascript-36ff67766051) +- [Big O Search Algorithms in JavaScript — Bradley Braithwaite](https://www.bradoncode.com/blog/2012/04/big-o-algorithm-examples-in-javascript.html) +- [Algorithms in plain English: time complexity and Big-O Notation — Michael Olorunnisola](https://medium.freecodecamp.org/time-is-complex-but-priceless-f0abd015063c) +- [An Introduction to Big O Notation — Joseph Trettevik](https://dev.to/lofiandcode/an-introduction-to-big-o-notation-210o) + +### Algorithms + +- [JS: Interview Algorithm](http://www.thatjsdude.com/interview/js1.html) +- [Algorithms in JavaScript — Thon Ly](https://medium.com/siliconwat/algorithms-in-javascript-b0bed68f4038) +- [JavaScript Objects, Square Brackets and Algorithms — Dmitri Grabov](https://medium.freecodecamp.org/javascript-objects-square-brackets-and-algorithms-e9a2916dc158) +- [Atwood's Law applied to CS101 - Classic algorithms and data structures implemented in JavaScript](https://github.com/felipernb/algorithms.js) +- [Data Structures and Algorithms library in JavaScript](https://github.com/yangshun/lago) +- [Collection of computer science algorithms and data structures written in JavaScript](https://github.com/idosela/algorithms-in-javascript) +- [Algorithms and Data Structures in JavaScript — Oleksii Trekhleb](https://dev.to/trekhleb/algorithms-and-data-structures-in-javascript-49i3) + +## Videos + +### Big O Notation + +<CardGroup cols={2}> + <Card title="JavaScript: Intro to Big O Notation and Function Runtime" icon="video" href="https://www.youtube.com/watch?v=HgA5VOFan5E"> + By Eric Traub + </Card> + <Card title="Learn Big O Notation In 12 Minutes" icon="video" href="https://www.youtube.com/watch?v=itn09C2ZB9Y"> + By Web Dev Simplified + </Card> +</CardGroup> + +- [Essential Big O for JavaScript Developers — Dave Smith](https://www.youtube.com/watch?v=KatlvCFHPRo) +- [Big O Notation - Time Complexity Analysis — WebTunings](https://www.youtube.com/watch?v=ALl86xJiTD8) +- [JavaScript Algorithms: Big-O Notation - Codevolution](https://www.youtube.com/watch?v=3yUuo7TqMW8) +- [JavaScript Algorithms Crash Course: Learn Algorithms & "Big O" from the Ground Up! - Academind](https://www.youtube.com/watch?v=JgWm6sQwS_I) +- [Big O Notation - Data Structures and Algorithms in Javascript - RoadSideCoder](https://www.youtube.com/watch?v=LaexPVi1VRE) + +### Algorithms + +<CardGroup cols={2}> + <Card title="JavaScript Algorithms" icon="video" href="https://www.youtube.com/playlist?list=PLC3y8-rFHvwiRYB4-HHKHblh3_bQNJTMa"> + By Codevolution + </Card> + <Card title="Dynamic Programming - Learn to Solve Algorithmic Problems" icon="video" href="https://www.youtube.com/watch?v=oBt53YbR9Kk&t=1021s"> + By FreeCodeCamp + </Card> +</CardGroup> + +- [Data Structures and Algorithms in Javascript | DSA with JS - RoadsideCoder](https://www.youtube.com/playlist?list=PLKhlp2qtUcSZtJefDThsXcsAbRBCSTgW4) +- [Javascript Algorithms + Data Structures - KodingKevin](https://www.youtube.com/playlist?list=PLn2ipk-jqgZiAHiA70hOxAj8RMUeqYNK3) +- [JavaScript Data Structures: Getting Started - Academind](https://www.youtube.com/watch?v=41GSinwoMYA) +- [Algorithms and Data Structures - The Coding Train (Daniel Shiffman)](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH) diff --git a/docs/concepts/algorithms.mdx b/docs/concepts/algorithms.mdx deleted file mode 100644 index 156c9129..00000000 --- a/docs/concepts/algorithms.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Algorithms" -description: "Common algorithms implemented in JavaScript" ---- - -## Overview - -An **algorithm** is a step-by-step procedure for solving a problem or accomplishing a task. Understanding common algorithms and their implementations in JavaScript is essential for technical interviews and writing efficient code. - -## Articles - -<CardGroup cols={2}> - <Card title="Data Structures and Algorithms using ES6" icon="newspaper" href="https://github.com/Crizstian/data-structure-and-algorithms-with-ES6"> - GitHub Repository - </Card> - <Card title="Algorithms and data structures implemented in JavaScript" icon="newspaper" href="https://github.com/trekhleb/javascript-algorithms"> - By Oleksii Trekhleb - </Card> -</CardGroup> - -- [JS: Interview Algorithm](http://www.thatjsdude.com/interview/js1.html) -- [Algorithms in JavaScript — Thon Ly](https://medium.com/siliconwat/algorithms-in-javascript-b0bed68f4038) -- [JavaScript Objects, Square Brackets and Algorithms — Dmitri Grabov](https://medium.freecodecamp.org/javascript-objects-square-brackets-and-algorithms-e9a2916dc158) -- [Atwood's Law applied to CS101 - Classic algorithms and data structures implemented in JavaScript](https://github.com/felipernb/algorithms.js) -- [Data Structures and Algorithms library in JavaScript](https://github.com/yangshun/lago) -- [Collection of computer science algorithms and data structures written in JavaScript](https://github.com/idosela/algorithms-in-javascript) -- [Algorithms and Data Structures in JavaScript — Oleksii Trekhleb](https://dev.to/trekhleb/algorithms-and-data-structures-in-javascript-49i3) - -## Videos - -<CardGroup cols={2}> - <Card title="JavaScript Algorithms" icon="video" href="https://www.youtube.com/playlist?list=PLC3y8-rFHvwiRYB4-HHKHblh3_bQNJTMa"> - By Codevolution - </Card> - <Card title="Dynamic Programming - Learn to Solve Algorithmic Problems" icon="video" href="https://www.youtube.com/watch?v=oBt53YbR9Kk&t=1021s"> - By FreeCodeCamp - </Card> -</CardGroup> - -- [Data Structures and Algorithms in Javascript | DSA with JS - RoadsideCoder](https://www.youtube.com/playlist?list=PLKhlp2qtUcSZtJefDThsXcsAbRBCSTgW4) -- [Javascript Algorithms + Data Structures - KodingKevin](https://www.youtube.com/playlist?list=PLn2ipk-jqgZiAHiA70hOxAj8RMUeqYNK3) -- [JavaScript Data Structures: Getting Started - Academind](https://www.youtube.com/watch?v=41GSinwoMYA) -- [Algorithms and Data Structures - The Coding Train (Daniel Shiffman)](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH) diff --git a/docs/concepts/big-o-notation.mdx b/docs/concepts/big-o-notation.mdx deleted file mode 100644 index 471bb1a0..00000000 --- a/docs/concepts/big-o-notation.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: "Expensive Operation and Big O Notation" -description: "Understanding algorithm complexity in JavaScript" ---- - -## Overview - -**Big O notation** is a mathematical notation used to describe the upper bound of an algorithm's time or space complexity. It helps developers understand how an algorithm's performance scales as the input size grows, which is crucial for writing efficient code. - -<Info> -Common Big O complexities from fastest to slowest: O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n²) quadratic, O(2ⁿ) exponential. -</Info> - -## Articles - -<CardGroup cols={2}> - <Card title="Big O Notation in Javascript" icon="newspaper" href="https://medium.com/cesars-tech-insights/big-o-notation-javascript-25c79f50b19b"> - By César Antón Dorantes - </Card> - <Card title="Time Complexity/Big O Notation" icon="newspaper" href="https://medium.com/javascript-scene/time-complexity-big-o-notation-1a4310c3ee4b"> - By Tim Roberts - </Card> -</CardGroup> - -- [Big O in JavaScript — Gabriela Medina](https://medium.com/@gmedina229/big-o-in-javascript-36ff67766051) -- [Big O Search Algorithms in JavaScript — Bradley Braithwaite](https://www.bradoncode.com/blog/2012/04/big-o-algorithm-examples-in-javascript.html) -- [Algorithms in plain English: time complexity and Big-O Notation — Michael Olorunnisola](https://medium.freecodecamp.org/time-is-complex-but-priceless-f0abd015063c) -- [An Introduction to Big O Notation — Joseph Trettevik](https://dev.to/lofiandcode/an-introduction-to-big-o-notation-210o) - -## Videos - -<CardGroup cols={2}> - <Card title="JavaScript: Intro to Big O Notation and Function Runtime" icon="video" href="https://www.youtube.com/watch?v=HgA5VOFan5E"> - By Eric Traub - </Card> - <Card title="Learn Big O Notation In 12 Minutes" icon="video" href="https://www.youtube.com/watch?v=itn09C2ZB9Y"> - By Web Dev Simplified - </Card> -</CardGroup> - -- [Essential Big O for JavaScript Developers — Dave Smith](https://www.youtube.com/watch?v=KatlvCFHPRo) -- [Big O Notation - Time Complexity Analysis — WebTunings](https://www.youtube.com/watch?v=ALl86xJiTD8) -- [JavaScript Algorithms: Big-O Notation - Codevolution](https://www.youtube.com/watch?v=3yUuo7TqMW8) -- [JavaScript Algorithms Crash Course: Learn Algorithms & "Big O" from the Ground Up! - Academind](https://www.youtube.com/watch?v=JgWm6sQwS_I) -- [Big O Notation - Data Structures and Algorithms in Javascript - RoadSideCoder](https://www.youtube.com/watch?v=LaexPVi1VRE) diff --git a/docs/concepts/clean-code.mdx b/docs/concepts/clean-code.mdx index 4ed834dc..7d29d4bf 100644 --- a/docs/concepts/clean-code.mdx +++ b/docs/concepts/clean-code.mdx @@ -1,6 +1,7 @@ --- -title: "Clean Code" -description: "Writing maintainable and readable JavaScript" +title: "Clean Code: Writing Readable JavaScript" +sidebarTitle: "Clean Code: Writing Readable JavaScript" +description: "Learn clean code principles for JavaScript — meaningful names, small functions, DRY, SOLID, and best practices. Write code that's easy to read, understand, and maintain." --- ## Overview diff --git a/docs/concepts/collections.mdx b/docs/concepts/collections.mdx deleted file mode 100644 index 0575128d..00000000 --- a/docs/concepts/collections.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "ES6 Collections: Map, Set, WeakMap & WeakSet in JavaScript" -sidebarTitle: "ES6 Collections: Map, Set, WeakMap & WeakSet" -description: "Learn ES6 collections in JavaScript — Map, Set, WeakMap, and WeakSet. Understand when to use each collection type, their differences from objects and arrays, and memory management with weak references." ---- - -## Overview - -ES6 introduced four new collection types: **Map**, **Set**, **WeakMap**, and **WeakSet**. These provide alternatives to plain objects and arrays with specific use cases — Maps for key-value pairs with any key type, Sets for unique values, and Weak variants for memory-efficient object references. - -<Info> -**What you'll learn in this guide:** -- Map vs Object — when to use each -- Set vs Array — unique value collections -- WeakMap and WeakSet — garbage collection friendly references -- Real-world use cases for each collection type -- Performance characteristics and best practices -</Info> - -## Articles - -<CardGroup cols={2}> - <Card title="ES6 Collections: Using Map, Set, WeakMap, WeakSet" icon="newspaper" href="https://www.sitepoint.com/es6-collections-map-set-weakmap-weakset/"> - By Kyle Pennell - </Card> - <Card title="Map, Set, WeakMap and WeakSet" icon="newspaper" href="https://javascript.info/map-set-weakmap-weakset"> - By JavaScript.info - </Card> -</CardGroup> - -- [ES6 WeakMaps, Sets, and WeakSets in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-weakmaps-sets-and-weaksets-in-depth) -- [Maps in ES6 - A Quick Guide — Ben Mildren](https://dev.to/mildrenben/maps-in-es6---a-quick-guide-35pk) -- [ES6 — Set vs Array — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-set-vs-array-what-and-when-efc055655e1a) -- [ES6 — Map vs Object — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-map-vs-object-what-and-when-b80621932373) -- [Array vs Set vs Map vs Object — Real-time use cases in Javascript (ES6/ES7) — Rajesh Babu](https://codeburst.io/array-vs-set-vs-map-vs-object-real-time-use-cases-in-javascript-es6-47ee3295329b) -- [How to create an array of unique values in JavaScript using Sets — Claire Parker-Jones](https://dev.to/claireparker/how-to-create-an-array-of-unique-values-in-javascript-using-sets-5dg6) -- [What You Should Know About ES6 Maps — Just Chris](https://hackernoon.com/what-you-should-know-about-es6-maps-dc66af6b9a1e) -- [ES6 Maps in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-maps-in-depth) - -## Videos - -<CardGroup cols={2}> - <Card title="JavaScript ES6 / ES2015 Set, Map, WeakSet and WeakMap" icon="video" href="https://www.youtube.com/watch?v=ycohYSx5h9w"> - By Traversy Media - </Card> - <Card title="The Differences between ES6 Maps and Sets" icon="video" href="https://www.youtube.com/watch?v=m4abICrldQI"> - By Steve Griffith - </Card> -</CardGroup> diff --git a/docs/concepts/data-structures.mdx b/docs/concepts/data-structures.mdx index 5d58ec34..0aff8db1 100644 --- a/docs/concepts/data-structures.mdx +++ b/docs/concepts/data-structures.mdx @@ -1,6 +1,7 @@ --- -title: "Data Structures" -description: "Understanding data structures in JavaScript" +title: "Data Structures: Organizing Data in JavaScript" +sidebarTitle: "Data Structures: Organizing Data" +description: "Learn data structures in JavaScript — arrays, objects, stacks, queues, linked lists, trees, and graphs. Understand how to organize and access data efficiently." --- ## Overview diff --git a/docs/concepts/design-patterns.mdx b/docs/concepts/design-patterns.mdx index 4868d604..f19bff56 100644 --- a/docs/concepts/design-patterns.mdx +++ b/docs/concepts/design-patterns.mdx @@ -1,6 +1,7 @@ --- -title: "Design Patterns" -description: "Proven solutions to common programming problems" +title: "Design Patterns: Proven Solutions in JavaScript" +sidebarTitle: "Design Patterns: Proven Solutions" +description: "Learn JavaScript design patterns — Singleton, Factory, Observer, Module, and more. Master proven solutions to common programming problems for maintainable code." --- ## Overview diff --git a/docs/concepts/error-handling.mdx b/docs/concepts/error-handling.mdx new file mode 100644 index 00000000..f074bca0 --- /dev/null +++ b/docs/concepts/error-handling.mdx @@ -0,0 +1,62 @@ +--- +title: "Error Handling: Managing Errors & Exceptions in JavaScript" +sidebarTitle: "Error Handling: Managing Errors & Exceptions" +description: "Learn JavaScript error handling — try/catch/finally, Error types, custom errors, and async error patterns. Master throwing, catching, and handling errors in sync and async code." +--- + +## Overview + +**Error handling** is how you detect, respond to, and recover from errors in your code. JavaScript provides the `try...catch...finally` statement for synchronous errors, and special patterns for handling Promise rejections and async/await errors. Proper error handling makes your applications more robust and debuggable. + +<Info> +**What you'll learn in this guide:** +- The `try...catch...finally` statement +- Built-in Error types (TypeError, ReferenceError, SyntaxError, etc.) +- Throwing errors with the `throw` statement +- Creating custom Error classes +- Async error handling (Promise `.catch()`, async/await) +- Global error handlers (`window.onerror`, `unhandledrejection`) +- Error handling best practices +</Info> + +## Reference + +<CardGroup cols={2}> + <Card title="Error — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"> + MDN documentation for the Error object + </Card> + <Card title="try...catch — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"> + MDN documentation for try...catch statement + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="Error handling, try...catch" icon="newspaper" href="https://javascript.info/try-catch"> + By JavaScript.info + </Card> + <Card title="A Guide to Error Handling in JavaScript" icon="newspaper" href="https://www.sitepoint.com/javascript-error-handling/"> + By SitePoint + </Card> +</CardGroup> + +- [JavaScript Error Handling — A Beginner's Guide — freeCodeCamp](https://www.freecodecamp.org/news/javascript-error-handling/) +- [Error Handling in JavaScript — Dmitri Pavlutin](https://dmitripavlutin.com/javascript-error-handling/) +- [Custom errors, extending Error — JavaScript.info](https://javascript.info/custom-errors) +- [A Definitive Guide to Handling Errors in JavaScript — Kinsta](https://kinsta.com/blog/errors-in-javascript/) +- [How to Handle Errors in JavaScript — Rollbar](https://rollbar.com/guides/javascript/how-to-handle-errors-in-javascript/) + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript Error Handling" icon="video" href="https://www.youtube.com/watch?v=blBoIyNhGvY"> + By Web Dev Simplified + </Card> + <Card title="Error Handling in JavaScript" icon="video" href="https://www.youtube.com/watch?v=cFTFtuEQ-10"> + By Fireship + </Card> +</CardGroup> + +- [Try, Catch, Finally in JavaScript — techsith](https://www.youtube.com/watch?v=s6M1nd7DtCE) +- [JavaScript Error Handling — The Coding Train](https://www.youtube.com/watch?v=1Rq_LrpcgIM) diff --git a/docs/concepts/es-modules.mdx b/docs/concepts/es-modules.mdx new file mode 100644 index 00000000..bcc64c73 --- /dev/null +++ b/docs/concepts/es-modules.mdx @@ -0,0 +1,76 @@ +--- +title: "ES Modules: Modern JavaScript Module System" +sidebarTitle: "ES Modules: Modern Module System" +description: "Learn ES Modules in JavaScript — import/export syntax, named and default exports, dynamic imports, and module patterns. Master the modern module system used in all JavaScript projects." +--- + +## Overview + +**ES Modules** (ESM) is JavaScript's official module system, introduced in ES6. It uses `import` and `export` statements to share code between files, enabling better code organization, encapsulation, and tree-shaking for smaller bundle sizes. ES Modules are now the standard for both browser and Node.js development. + +<Info> +**What you'll learn in this guide:** +- `import` and `export` syntax +- Named exports vs default exports +- Re-exporting and aggregating modules +- Dynamic imports with `import()` +- Module scope vs global scope +- CommonJS vs ES Modules differences +- Tree-shaking and dead code elimination +- Top-level await +- Import maps and import assertions +</Info> + +<Warning> +**Prerequisites:** This guide builds on [IIFE, Modules and Namespaces](/concepts/iife-modules) which covers the history and evolution of JavaScript module patterns. +</Warning> + +## Reference + +<CardGroup cols={2}> + <Card title="JavaScript Modules — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"> + MDN guide to JavaScript modules + </Card> + <Card title="import — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"> + MDN documentation for import statement + </Card> + <Card title="export — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export"> + MDN documentation for export statement + </Card> + <Card title="Dynamic import() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import"> + MDN documentation for dynamic imports + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="ES Modules: A Cartoon Deep-Dive" icon="newspaper" href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/"> + By Lin Clark — Mozilla Hacks + </Card> + <Card title="JavaScript Modules" icon="newspaper" href="https://javascript.info/modules-intro"> + By JavaScript.info + </Card> +</CardGroup> + +- [ES Modules in Node.js — Node.js Documentation](https://nodejs.org/api/esm.html) +- [Understanding ES6 Modules — SitePoint](https://www.sitepoint.com/understanding-es6-modules/) +- [ES6 Modules in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-modules-in-depth) +- [Dynamic imports — JavaScript.info](https://javascript.info/modules-dynamic-imports) +- [CommonJS vs ES Modules — LogRocket](https://blog.logrocket.com/commonjs-vs-es-modules-node-js/) +- [Tree Shaking — webpack](https://webpack.js.org/guides/tree-shaking/) + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript ES6 Modules" icon="video" href="https://www.youtube.com/watch?v=cRHQNNcYf6s"> + By Web Dev Simplified + </Card> + <Card title="ES Modules in 100 Seconds" icon="video" href="https://www.youtube.com/watch?v=qgRUr-YUk1Q"> + By Fireship + </Card> +</CardGroup> + +- [JavaScript Modules in 100 Seconds — Fireship](https://www.youtube.com/watch?v=qgRUr-YUk1Q) +- [ES6 Modules — Traversy Media](https://www.youtube.com/watch?v=_3oSWwapPKQ) +- [Import, Export, and Require in JavaScript — The Coding Train](https://www.youtube.com/watch?v=jN4IM5tp1SE) diff --git a/docs/concepts/inheritance-polymorphism.mdx b/docs/concepts/inheritance-polymorphism.mdx index 904501d5..fcde0f9e 100644 --- a/docs/concepts/inheritance-polymorphism.mdx +++ b/docs/concepts/inheritance-polymorphism.mdx @@ -1,6 +1,7 @@ --- -title: "Inheritance, Polymorphism and Code Reuse" -description: "Object-oriented programming principles in JavaScript" +title: "Inheritance & Polymorphism: OOP Principles in JavaScript" +sidebarTitle: "Inheritance & Polymorphism: OOP Principles" +description: "Learn inheritance and polymorphism in JavaScript — extending classes, prototype chains, method overriding, and code reuse patterns. Master object-oriented programming principles." --- ## Overview diff --git a/docs/concepts/javascript-engines.mdx b/docs/concepts/javascript-engines.mdx index cd241667..719fded0 100644 --- a/docs/concepts/javascript-engines.mdx +++ b/docs/concepts/javascript-engines.mdx @@ -1,6 +1,7 @@ --- -title: "JavaScript Engines" -description: "Understanding how JavaScript code is executed" +title: "JavaScript Engines: How Your Code Gets Executed" +sidebarTitle: "JavaScript Engines: How Code Gets Executed" +description: "Learn how JavaScript engines work — V8, SpiderMonkey, parsing, compilation, JIT optimization, and the execution pipeline. Understand what happens when your code runs." --- ## Overview diff --git a/docs/concepts/modern-js-syntax.mdx b/docs/concepts/modern-js-syntax.mdx new file mode 100644 index 00000000..4e23a89b --- /dev/null +++ b/docs/concepts/modern-js-syntax.mdx @@ -0,0 +1,73 @@ +--- +title: "Modern JavaScript Syntax: ES6+ Features You Need to Know" +sidebarTitle: "Modern JavaScript Syntax: ES6+ Features" +description: "Learn modern JavaScript syntax — destructuring, spread/rest operators, optional chaining, nullish coalescing, and template literals. Master the ES6+ features used in every modern codebase." +--- + +## Overview + +**Modern JavaScript syntax** (ES6 and beyond) introduced powerful features that make code more concise, readable, and expressive. Features like destructuring, spread operators, optional chaining (`?.`), and nullish coalescing (`??`) are now used in virtually every JavaScript codebase and framework. + +<Info> +**What you'll learn in this guide:** +- Destructuring (objects and arrays) +- Spread operator (`...`) and rest parameters +- Optional chaining (`?.`) +- Nullish coalescing (`??`) +- Logical assignment operators (`??=`, `||=`, `&&=`) +- Template literals and tagged templates +- Default parameters +- Short-circuit evaluation patterns +- Computed property names +</Info> + +## Reference + +<CardGroup cols={2}> + <Card title="Destructuring Assignment — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment"> + MDN documentation for destructuring + </Card> + <Card title="Optional Chaining — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"> + MDN documentation for optional chaining (?.) + </Card> + <Card title="Nullish Coalescing — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"> + MDN documentation for nullish coalescing (??) + </Card> + <Card title="Spread Syntax — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"> + MDN documentation for spread syntax (...) + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="ES6 Destructuring: The Complete Guide" icon="newspaper" href="https://codeburst.io/es6-destructuring-the-complete-guide-7f842d08b98f"> + By Glad Chinda + </Card> + <Card title="Optional Chaining in JavaScript" icon="newspaper" href="https://javascript.info/optional-chaining"> + By JavaScript.info + </Card> +</CardGroup> + +- [JavaScript Destructuring — Wes Bos](https://wesbos.com/destructuring-objects) +- [ES6 Spread Operator — JavaScript.info](https://javascript.info/rest-parameters-spread) +- [Nullish Coalescing Operator Explained — freeCodeCamp](https://www.freecodecamp.org/news/nullish-coalescing-operator-in-javascript/) +- [ES6 Template Literals — CSS-Tricks](https://css-tricks.com/template-literals/) +- [Tagged Template Literals — Wes Bos](https://wesbos.com/tagged-template-literals) +- [JavaScript's Logical Assignment Operators — V8 Blog](https://v8.dev/features/logical-assignment) +- [Rest Parameters and Spread Syntax — JavaScript.info](https://javascript.info/rest-parameters-spread) + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript ES6 Destructuring" icon="video" href="https://www.youtube.com/watch?v=NIq3qLaHCIs"> + By Web Dev Simplified + </Card> + <Card title="Optional Chaining & Nullish Coalescing" icon="video" href="https://www.youtube.com/watch?v=v2tJ3nzXh8I"> + By Web Dev Simplified + </Card> +</CardGroup> + +- [ES6 Spread Operator and Rest Parameters — Traversy Media](https://www.youtube.com/watch?v=iLx4ma8ZqvQ) +- [JavaScript Template Literals — Programming with Mosh](https://www.youtube.com/watch?v=NgF9-pdTDGs) +- [ES6 Features Every JavaScript Developer Should Know — Fireship](https://www.youtube.com/watch?v=NCwa_xi0Uuc) diff --git a/docs/concepts/regular-expressions.mdx b/docs/concepts/regular-expressions.mdx new file mode 100644 index 00000000..2625a0d3 --- /dev/null +++ b/docs/concepts/regular-expressions.mdx @@ -0,0 +1,65 @@ +--- +title: "Regular Expressions: Pattern Matching in JavaScript" +sidebarTitle: "Regular Expressions: Pattern Matching" +description: "Learn regular expressions in JavaScript — pattern syntax, flags, methods like test/match/replace, and common patterns. Master regex for validation, parsing, and text manipulation." +--- + +## Overview + +**Regular expressions** (regex) are patterns used to match character combinations in strings. JavaScript provides built-in regex support through the `RegExp` object and string methods. Mastering regex enables powerful text validation, searching, and transformation — essential skills for form validation, data parsing, and text processing. + +<Info> +**What you'll learn in this guide:** +- Regex literal syntax (`/pattern/flags`) vs `RegExp` constructor +- Character classes, quantifiers, and anchors +- Capturing groups and backreferences +- Lookahead and lookbehind assertions +- Common methods: `test()`, `match()`, `matchAll()`, `replace()`, `split()` +- Flags: `g`, `i`, `m`, `s`, `u`, `y` +- Common patterns (email, phone, URL validation) +- Performance considerations and regex best practices +</Info> + +## Reference + +<CardGroup cols={2}> + <Card title="Regular Expressions — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"> + MDN guide to regular expressions + </Card> + <Card title="RegExp — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp"> + MDN documentation for the RegExp object + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="Regular Expressions" icon="newspaper" href="https://javascript.info/regular-expressions"> + By JavaScript.info — comprehensive tutorial + </Card> + <Card title="A Practical Guide to Regular Expressions" icon="newspaper" href="https://www.freecodecamp.org/news/practical-regex-guide-with-real-life-examples/"> + By freeCodeCamp + </Card> +</CardGroup> + +- [Learn Regex the Easy Way — Zeeshan Ahmad](https://github.com/ziishaned/learn-regex) +- [Regular Expressions Demystified — Vijay Kumar](https://dev.to/vijay/regular-expressions-demystified-4hcn) +- [Regex Cheat Sheet — Quick Reference](https://quickref.me/regex) +- [Regular Expressions in JavaScript — Eloquent JavaScript](https://eloquentjavascript.net/09_regexp.html) +- [JavaScript Regular Expressions — W3Schools](https://www.w3schools.com/js/js_regexp.asp) +- [Named Capture Groups in JavaScript Regex — V8 Blog](https://v8.dev/features/regexp-named-captures) + +## Videos + +<CardGroup cols={2}> + <Card title="Learn Regular Expressions In 20 Minutes" icon="video" href="https://www.youtube.com/watch?v=rhzKDrUiJVk"> + By Web Dev Simplified + </Card> + <Card title="Regular Expressions (Regex) in JavaScript" icon="video" href="https://www.youtube.com/watch?v=909NfO1St0A"> + By Fireship + </Card> +</CardGroup> + +- [Regular Expressions (RegEx) Tutorial — The Coding Train](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6YEypLuls7iidwHMdCM6o2w) +- [JavaScript Regex — Programming with Mosh](https://www.youtube.com/watch?v=VrT3TRDDE4M) +- [Regex Tutorial — Corey Schafer](https://www.youtube.com/watch?v=sa-TUpSx1JA) diff --git a/docs/concepts/web-workers.mdx b/docs/concepts/web-workers.mdx new file mode 100644 index 00000000..e1ff02a7 --- /dev/null +++ b/docs/concepts/web-workers.mdx @@ -0,0 +1,69 @@ +--- +title: "Web Workers: Parallel JavaScript Execution" +sidebarTitle: "Web Workers: Parallel JavaScript" +description: "Learn Web Workers in JavaScript — run scripts in background threads for parallel execution. Master dedicated workers, shared workers, and off-main-thread processing for better performance." +--- + +## Overview + +**Web Workers** allow JavaScript to run scripts in background threads, separate from the main execution thread. This enables parallel processing without blocking the UI, making them essential for CPU-intensive tasks like data processing, image manipulation, and complex calculations. + +<Info> +**What you'll learn in this guide:** +- Why JavaScript needs Web Workers (single-threaded limitations) +- Creating and communicating with workers (`postMessage`, `onmessage`) +- Dedicated Workers vs Shared Workers vs Service Workers +- Transferable objects for efficient data transfer +- Worker limitations (no DOM access, different global scope) +- Use cases: heavy computations, data parsing, image processing +- Worker pools and performance patterns +- Comlink and other worker libraries +</Info> + +<Warning> +**Prerequisites:** This guide builds on your understanding of [the Event Loop](/concepts/event-loop) and [async JavaScript](/concepts/async-await). Web Workers provide true parallelism, unlike async code which is still single-threaded. +</Warning> + +## Reference + +<CardGroup cols={2}> + <Card title="Web Workers API — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API"> + MDN documentation for Web Workers API + </Card> + <Card title="Using Web Workers — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"> + MDN guide to using Web Workers + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="The Basics of Web Workers" icon="newspaper" href="https://www.html5rocks.com/en/tutorials/workers/basics/"> + By HTML5 Rocks — comprehensive introduction + </Card> + <Card title="Using Web Workers for Safe, Concurrent JavaScript" icon="newspaper" href="https://www.smashingmagazine.com/2021/06/web-workers-2021/"> + By Smashing Magazine + </Card> +</CardGroup> + +- [JavaScript Web Workers: A Beginner's Guide — SitePoint](https://www.sitepoint.com/javascript-web-workers/) +- [Web Workers — JavaScript.info](https://javascript.info/web-workers) +- [Comlink: Web Workers Made Easy — Google Chrome Labs](https://github.com/GoogleChromeLabs/comlink) +- [Parallel Programming in JavaScript Using Web Workers — LogRocket](https://blog.logrocket.com/parallel-programming-in-javascript-using-web-workers/) +- [A Simple Introduction to Web Workers — David Walsh](https://davidwalsh.name/web-workers) +- [Threading the Web with Module Workers — web.dev](https://web.dev/module-workers/) + +## Videos + +<CardGroup cols={2}> + <Card title="Web Workers Explained" icon="video" href="https://www.youtube.com/watch?v=Gcp7triXFjg"> + By Web Dev Simplified + </Card> + <Card title="JavaScript Web Workers" icon="video" href="https://www.youtube.com/watch?v=EiPytIxrZtU"> + By Fireship + </Card> +</CardGroup> + +- [Web Workers Crash Course — Traversy Media](https://www.youtube.com/watch?v=tPwkKF8WAXs) +- [Multi-Threading in JavaScript with Web Workers — Hussein Nasser](https://www.youtube.com/watch?v=kUXRxEwOL3A) +- [Service Workers vs Web Workers — Google Chrome Developers](https://www.youtube.com/watch?v=JYXXGNFJjwc) diff --git a/docs/docs.json b/docs/docs.json index 83c6e068..bb3be7e1 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -55,7 +55,8 @@ "icon": "browser", "pages": [ "concepts/dom", - "concepts/http-fetch" + "concepts/http-fetch", + "concepts/web-workers" ] }, { @@ -64,7 +65,8 @@ "pages": [ "concepts/factories-classes", "concepts/this-call-apply-bind", - "concepts/object-creation-prototypes" + "concepts/object-creation-prototypes", + "concepts/inheritance-polymorphism" ] }, { @@ -93,11 +95,12 @@ "icon": "graduation-cap", "pages": [ "concepts/javascript-engines", - "concepts/collections", + "concepts/error-handling", + "concepts/regular-expressions", + "concepts/modern-js-syntax", + "concepts/es-modules", "concepts/data-structures", - "concepts/big-o-notation", - "concepts/algorithms", - "concepts/inheritance-polymorphism", + "concepts/algorithms-big-o", "concepts/design-patterns", "concepts/clean-code" ] From e6f00510dd82f179c94129a97ef22781ad367849 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 14:43:56 -0300 Subject: [PATCH 063/128] docs: consolidate prototype concepts into object-creation-prototypes - Merge new-constructor, object-create-assign, and prototype into single concept - Update factories-classes to reference the consolidated concept - Provides comprehensive coverage of object creation patterns in one place --- docs/concepts/factories-classes.mdx | 56 +- docs/concepts/new-constructor.mdx | 30 - docs/concepts/object-create-assign.mdx | 51 - docs/concepts/object-creation-prototypes.mdx | 1101 ++++++++++++++++++ docs/concepts/prototype.mdx | 70 -- 5 files changed, 1112 insertions(+), 196 deletions(-) delete mode 100644 docs/concepts/new-constructor.mdx delete mode 100644 docs/concepts/object-create-assign.mdx create mode 100644 docs/concepts/object-creation-prototypes.mdx delete mode 100644 docs/concepts/prototype.mdx diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx index 7c4eb5a6..dfe03d88 100644 --- a/docs/concepts/factories-classes.mdx +++ b/docs/concepts/factories-classes.mdx @@ -53,7 +53,7 @@ console.log(enemy.attack()) // "Goblin attacks!" </Info> <Warning> -**Prerequisites:** This guide assumes you understand [Prototype Inheritance](/concepts/prototype) and [this, call, apply, bind](/concepts/this-call-apply-bind). If those concepts are new to you, read those guides first! +**Prerequisites:** This guide assumes you understand [Object Creation & Prototypes](/concepts/object-creation-prototypes) and [this, call, apply, bind](/concepts/this-call-apply-bind). If those concepts are new to you, read those guides first! </Warning> --- @@ -494,58 +494,24 @@ console.log(alice instanceof Player); // true When you call `new Player("Alice")`, JavaScript performs **4 steps**: -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ WHAT new Player("Alice") DOES: │ -│ │ -│ Step 1: Create a new empty object │ -│ const obj = {}; │ -│ │ -│ Step 2: Link the object to the constructor's prototype │ -│ Object.setPrototypeOf(obj, Player.prototype); │ -│ │ -│ Step 3: Run the constructor with 'this' bound to the new object │ -│ Player.call(obj, "Alice"); │ -│ // Now obj.name = "Alice", obj.health = 100, etc. │ -│ │ -│ Step 4: Return the object (unless constructor returns an object) │ -│ return obj; │ -└─────────────────────────────────────────────────────────────────────┘ -``` - <Steps> <Step title="Create a new empty object"> JavaScript creates a fresh object: `const obj = {}` </Step> <Step title="Link the prototype"> - Uses [`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) to set `obj.[[Prototype]]` to `Constructor.prototype`, establishing the prototype chain + Sets `obj.[[Prototype]]` to `Constructor.prototype`, establishing the prototype chain </Step> <Step title="Execute the constructor"> - Runs [`Constructor.apply(obj, args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) — the `this` keyword inside the constructor is bound to the new object + Runs the constructor with `this` bound to the new object </Step> <Step title="Return the object"> Returns `obj` automatically (unless the constructor explicitly returns a different object) </Step> </Steps> -Here's a function that simulates what `new` does: - -```javascript -function myNew(Constructor, ...args) { - // Step 1 & 2: Create object with correct prototype - const obj = Object.create(Constructor.prototype); // Object.create() creates a new object with the specified prototype - - // Step 3: Run constructor with 'this' = obj - const result = Constructor.apply(obj, args); - - // Step 4: Return result if it's an object, otherwise return obj - return (result !== null && typeof result === 'object') ? result : obj; -} - -// These do the same thing: -const player1 = new Player("Alice"); -const player2 = myNew(Player, "Bob"); -``` +<Tip> +**Want to dive deeper?** For a detailed explanation of how `new` works under the hood, including how to simulate it yourself, see [Object Creation & Prototypes](/concepts/object-creation-prototypes). +</Tip> ### Adding Methods to the [Prototype](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) @@ -1967,15 +1933,15 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); ## Related Concepts <CardGroup cols={2}> - <Card title="Prototype Inheritance" icon="link" href="/concepts/prototype"> - Deep dive into JavaScript's prototype chain and how inheritance really works - </Card> - <Card title="new, Constructor, instanceof" icon="plus" href="/concepts/new-constructor"> - More details on the new keyword and checking object types + <Card title="Object Creation & Prototypes" icon="link" href="/concepts/object-creation-prototypes"> + Deep dive into JavaScript's prototype chain, Object.create(), and how the new keyword works under the hood </Card> <Card title="this, call, apply, bind" icon="hand-pointer" href="/concepts/this-call-apply-bind"> Understanding this binding in different contexts </Card> + <Card title="Inheritance and Polymorphism" icon="sitemap" href="/concepts/inheritance-polymorphism"> + Advanced inheritance patterns and polymorphism in JavaScript + </Card> <Card title="Design Patterns" icon="compass" href="/concepts/design-patterns"> Common patterns including Factory, Singleton, and more </Card> diff --git a/docs/concepts/new-constructor.mdx b/docs/concepts/new-constructor.mdx deleted file mode 100644 index 70b54d1e..00000000 --- a/docs/concepts/new-constructor.mdx +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: "new, Constructor, instanceof and Instances" -description: "Understanding object instantiation in JavaScript" ---- - -## Overview - -The `new` operator creates an instance of a user-defined object type or of one of the built-in object types. When you use `new`, JavaScript creates a new empty object, sets the prototype, binds `this` to the new object, and returns the object. - -<Info> -The `instanceof` operator tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object. This is useful for checking if an object was created by a particular constructor. -</Info> - -## Articles - -<CardGroup cols={2}> - <Card title="JavaScript For Beginners: the 'new' operator" icon="newspaper" href="https://codeburst.io/javascript-for-beginners-the-new-operator-cee35beb669e"> - By Brandon Morelli - </Card> - <Card title="Let's demystify JavaScript's 'new' keyword" icon="newspaper" href="https://medium.freecodecamp.org/demystifying-javascripts-new-keyword-874df126184c"> - By Cynthia Lee - </Card> -</CardGroup> - -- [Constructor, operator "new" — JavaScript.Info](https://javascript.info/constructor-new) -- [Understanding JavaScript Constructors — Faraz Kelhini](https://css-tricks.com/understanding-javascript-constructors/) -- [Use Constructor Functions — Openclassrooms](https://openclassrooms.com/en/courses/3523231-learn-to-code-with-javascript/4379006-use-constructor-functions) -- [Beyond `typeof` and `instanceof`: simplifying dynamic type checks — Dr. Axel Rauschmayer](http://2ality.com/2017/08/type-right.html) -- [Function and Object, instances of each other — Kiro Risk](https://javascriptrefined.io/function-and-object-instances-of-each-other-1e1095d5faac) -- [JavaScript instanceof operator](https://flexiple.com/javascript/instanceof-javascript) diff --git a/docs/concepts/object-create-assign.mdx b/docs/concepts/object-create-assign.mdx deleted file mode 100644 index 4e2dee49..00000000 --- a/docs/concepts/object-create-assign.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "Object.create and Object.assign" -description: "Object manipulation methods in JavaScript" ---- - -## Overview - -`Object.create()` creates a new object with the specified prototype object and properties. `Object.assign()` copies all enumerable own properties from one or more source objects to a target object and returns the modified target object. - -## Reference - -<CardGroup cols={2}> - <Card title="Object.create() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create"> - MDN documentation - </Card> - <Card title="Object.assign() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign"> - MDN documentation - </Card> -</CardGroup> - -## Articles - -<CardGroup cols={2}> - <Card title="Object.create in JavaScript" icon="newspaper" href="https://medium.com/@happymishra66/object-create-in-javascript-fa8674df6ed2"> - By Rupesh Mishra - </Card> - <Card title="Object.create(): the New Way to Create Objects in JavaScript" icon="newspaper" href="https://www.htmlgoodies.com/beyond/javascript/object.create-the-new-way-to-create-objects-in-javascript.html"> - By Rob Gravelle - </Card> -</CardGroup> - -- [Basic Inheritance with Object.create — Joshua Clanton](http://adripofjavascript.com/blog/drips/basic-inheritance-with-object-create.html) -- [Object.create() In JavaScript — GeeksforGeeks](https://www.geeksforgeeks.org/object-create-javascript/) -- [Understanding the difference between Object.create() and the new operator — Jonathan Voxland](https://medium.com/@jonathanvox01/understanding-the-difference-between-object-create-and-the-new-operator-b2a2f4749358) -- [JavaScript Object Creation: Patterns and Best Practices — Jeff Mott](https://www.sitepoint.com/javascript-object-creation-patterns-best-practises/) -- [Dealing With Objects in JavaScript With Object.assign, Object.keys and hasOwnProperty](https://www.digitalocean.com/community/tutorials/js-dealing-with-objects) -- [Copying Objects in JavaScript ― Orinami Olatunji](https://scotch.io/bar-talk/copying-objects-in-javascript) -- [JavaScript: Object.assign() — Thiago S. Adriano](https://codeburst.io/javascript-object-assign-bc9696dcbb6e) -- [How to deep clone a JavaScript Object — Flavio Copes](https://flaviocopes.com/how-to-clone-javascript-object/) -- [Object.create(): When and Why to Use — VZing](https://dev.to/vzing/object-create-when-and-why-to-use-20m9) - -## Videos - -<CardGroup cols={2}> - <Card title="Object.assign() explained" icon="video" href="https://www.youtube.com/watch?v=aw7NfYhR5rc"> - By Aaron Writes Code - </Card> - <Card title="Object.assign() Method" icon="video" href="https://www.youtube.com/watch?v=9Ky4X6inpi4"> - By techsith - </Card> -</CardGroup> diff --git a/docs/concepts/object-creation-prototypes.mdx b/docs/concepts/object-creation-prototypes.mdx new file mode 100644 index 00000000..62c56e6b --- /dev/null +++ b/docs/concepts/object-creation-prototypes.mdx @@ -0,0 +1,1101 @@ +--- +title: "Object Creation & Prototypes: How Objects Inherit in JavaScript" +sidebarTitle: "Object Creation & Prototypes: How Objects Inherit" +description: "Learn JavaScript's prototype chain and object creation. Understand how inheritance works, the new operator's 4 steps, Object.create(), Object.assign(), and prototype methods." +--- + +How does a plain JavaScript object know about methods like `.toString()` or `.hasOwnProperty()` that you never defined? How does JavaScript let objects inherit from other objects without traditional classes? + +```javascript +// You create a simple object +const player = { name: "Alice", health: 100 } + +// But it has methods you never defined! +console.log(player.toString()) // "[object Object]" +console.log(player.hasOwnProperty("name")) // true + +// Where do these come from? +console.log(Object.getPrototypeOf(player)) // { constructor: Object, toString: f, ... } +``` + +The answer is the **[prototype chain](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)** — JavaScript's inheritance mechanism. Every object has a hidden link to another object called its **prototype**. When you access a property, JavaScript looks for it on the object first, then follows this chain of prototypes until it finds the property or reaches the end (`null`). + +<Info> +**What you'll learn in this guide:** +- What the prototype chain is and how property lookup works +- The difference between `[[Prototype]]`, `__proto__`, and `.prototype` +- How to create objects with `Object.create()` +- What the `new` operator does (the 4 steps) +- How to copy properties with `Object.assign()` +- How to inspect and modify prototypes +- Common prototype methods like `hasOwnProperty()` +- Prototype pitfalls and how to avoid them +</Info> + +<Warning> +**Prerequisites:** This guide assumes you understand [Primitive Types](/concepts/primitive-types) and [Value vs Reference Types](/concepts/value-reference-types). If objects and their properties are new to you, read those guides first! +</Warning> + +--- + +## What is the Prototype Chain? + +The **prototype chain** is JavaScript's way of implementing inheritance. Every object has an internal link (called `[[Prototype]]`) to another object — its prototype. When you try to access a property on an object, JavaScript: + +1. First looks for the property on the object itself +2. If not found, looks on the object's prototype +3. If still not found, looks on the prototype's prototype +4. Continues until it finds the property or reaches `null` (the end of the chain) + +```javascript +// Create a simple object +const wizard = { + name: "Gandalf", + castSpell() { + return `${this.name} casts a spell!` + } +} + +// Create another object that inherits from wizard +const apprentice = Object.create(wizard) +apprentice.name = "Harry" + +// apprentice has its own 'name' property +console.log(apprentice.name) // "Harry" + +// But castSpell comes from the prototype (wizard) +console.log(apprentice.castSpell()) // "Harry casts a spell!" + +// The prototype chain: +// apprentice → wizard → Object.prototype → null +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE PROTOTYPE CHAIN │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ apprentice.castSpell() │ +│ │ │ +│ │ 1. Does apprentice have castSpell? NO │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ apprentice │ │ +│ │──────────────│ │ +│ │ name: "Harry"│ │ +│ │ [[Prototype]]│────┐ │ +│ └──────────────┘ │ │ +│ │ 2. Does wizard have castSpell? YES! Use it │ +│ ▼ │ +│ ┌──────────────────┐ │ +│ │ wizard │ │ +│ │──────────────────│ │ +│ │ name: "Gandalf" │ │ +│ │ castSpell: fn │ ◄── Found here! │ +│ │ [[Prototype]] │────┐ │ +│ └──────────────────┘ │ │ +│ │ 3. If not found, keep going... │ +│ ▼ │ +│ ┌────────────────────┐ │ +│ │ Object.prototype │ │ +│ │────────────────────│ │ +│ │ toString: fn │ │ +│ │ hasOwnProperty: fn │ │ +│ │ [[Prototype]] │────┐ │ +│ └────────────────────┘ │ │ +│ │ │ +│ ▼ │ +│ null │ +│ (end of chain) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Tip> +**The Chain Always Ends:** Every prototype chain eventually reaches `Object.prototype`, then `null`. This is why all objects have access to methods like `toString()` and `hasOwnProperty()` — they inherit them from `Object.prototype`. +</Tip> + +--- + +## Understanding `[[Prototype]]`, `__proto__`, and `.prototype` + +These three terms confuse many developers. Let's clarify: + +| Term | What It Is | How to Access | +|------|------------|---------------| +| `[[Prototype]]` | The internal prototype link every object has | Not directly accessible (it's internal) | +| `__proto__` | A getter/setter that exposes `[[Prototype]]` | `obj.__proto__` (deprecated, avoid in production) | +| `.prototype` | A property on **functions** used when creating instances with `new` | `Function.prototype` | + +```javascript +// Every object has [[Prototype]] — an internal link to its prototype +const player = { name: "Alice" } + +// __proto__ exposes [[Prototype]] (deprecated but works) +console.log(player.__proto__ === Object.prototype) // true + +// .prototype exists only on FUNCTIONS +function Player(name) { + this.name = name +} + +// When you use 'new Player()', the new object's [[Prototype]] +// is set to Player.prototype +console.log(Player.prototype) // { constructor: Player } + +const alice = new Player("Alice") +console.log(Object.getPrototypeOf(alice) === Player.prototype) // true +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE THREE PROTOTYPE TERMS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ [[Prototype]] The hidden internal slot every object has │ +│ ────────────── Points to the object's prototype │ +│ You can't access it directly │ +│ │ +│ __proto__ A way to READ/WRITE [[Prototype]] │ +│ ───────── obj.__proto__ = Object.getPrototypeOf(obj) │ +│ DEPRECATED! Use Object.getPrototypeOf() instead │ +│ │ +│ .prototype A property that exists ONLY on functions │ +│ ────────── Used as the [[Prototype]] for objects │ +│ created with new │ +│ │ +│ ───────────────────────────────────────────────────────────────────── │ +│ │ +│ function Player(name) { this.name = name } │ +│ │ +│ Player.prototype ─────────────┐ │ +│ │ │ +│ const p = new Player("A") │ │ +│ │ │ │ +│ │ [[Prototype]] ════════╧═══▶ { constructor: Player } │ +│ │ │ │ +│ ▼ │ [[Prototype]] │ +│ ┌───────────┐ ▼ │ +│ │ p │ Object.prototype │ +│ │───────────│ │ │ +│ │name: "A" │ │ [[Prototype]] │ +│ └───────────┘ ▼ │ +│ null │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Warning> +**Don't use `__proto__` in production code!** It's deprecated and has performance issues. Use `Object.getPrototypeOf()` to read and `Object.setPrototypeOf()` to write (sparingly). +</Warning> + +--- + +## How Property Lookup Works + +When you access a property on an object, JavaScript performs a **prototype chain lookup**: + +<Steps> + <Step title="Check the object itself"> + JavaScript first looks for the property directly on the object. + </Step> + <Step title="Check the prototype"> + If not found, it looks at `Object.getPrototypeOf(obj)` (the object's prototype). + </Step> + <Step title="Continue up the chain"> + If still not found, it checks the prototype's prototype, and so on. + </Step> + <Step title="Reach null or find the property"> + The search stops when the property is found OR when `null` is reached (property is `undefined`). + </Step> +</Steps> + +```javascript +const grandparent = { + familyName: "Smith", + sayHello() { + return `Hello from the ${this.familyName} family!` + } +} + +const parent = Object.create(grandparent) +parent.job = "Engineer" + +const child = Object.create(parent) +child.name = "Alice" + +// Property lookup in action: +console.log(child.name) // "Alice" (found on child) +console.log(child.job) // "Engineer" (found on parent) +console.log(child.familyName) // "Smith" (found on grandparent) +console.log(child.sayHello()) // "Hello from the Smith family!" +console.log(child.age) // undefined (not found anywhere) + +// Visualizing the chain +console.log(Object.getPrototypeOf(child) === parent) // true +console.log(Object.getPrototypeOf(parent) === grandparent) // true +console.log(Object.getPrototypeOf(grandparent) === Object.prototype) // true +console.log(Object.getPrototypeOf(Object.prototype)) // null +``` + +### Property Shadowing + +When you set a property on an object, it creates or updates the property **on that object**, even if a property with the same name exists on the prototype: + +```javascript +const prototype = { + greeting: "Hello", + count: 0 +} + +const obj = Object.create(prototype) + +// Reading — uses prototype's value +console.log(obj.greeting) // "Hello" (from prototype) +console.log(obj.count) // 0 (from prototype) + +// Writing — creates property on obj, "shadows" the prototype's +obj.greeting = "Hi" +obj.count = 5 + +console.log(obj.greeting) // "Hi" (own property) +console.log(prototype.greeting) // "Hello" (unchanged!) + +console.log(obj.count) // 5 (own property) +console.log(prototype.count) // 0 (unchanged!) + +// Check what's "own" vs inherited +console.log(obj.hasOwnProperty("greeting")) // true (it's on obj now) +console.log(obj.hasOwnProperty("count")) // true +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PROPERTY SHADOWING │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ BEFORE obj.greeting = "Hi" AFTER obj.greeting = "Hi" │ +│ ────────────────────────── ───────────────────────── │ +│ │ +│ obj obj │ +│ ┌─────────────┐ ┌──────────────────┐ │ +│ │ (empty) │ │ greeting: "Hi" │ ◄── shadows │ +│ │ [[Proto]]───┼──┐ │ [[Proto]]────────┼──┐ │ +│ └─────────────┘ │ └──────────────────┘ │ │ +│ │ │ │ +│ ▼ ▼ │ +│ prototype prototype │ +│ ┌──────────────────────┐ ┌──────────────────────┐ │ +│ │ greeting: "Hello" │ │ greeting: "Hello" │ hidden │ +│ │ count: 0 │ │ count: 0 │ │ +│ └──────────────────────┘ └──────────────────────┘ │ +│ │ +│ obj.greeting returns "Hello" obj.greeting returns "Hi" │ +│ (found on prototype) (found on obj, stops looking) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Ways to Create Objects in JavaScript + +JavaScript gives you several ways to create objects, each with different use cases: + +### 1. Object Literals + +The simplest way — great for one-off objects: + +```javascript +// Object literal — prototype is automatically Object.prototype +const player = { + name: "Alice", + health: 100, + attack() { + return `${this.name} attacks!` + } +} + +console.log(Object.getPrototypeOf(player) === Object.prototype) // true +``` + +### 2. Object.create() — Create with Specific Prototype + +[`Object.create()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) creates a new object with a specified prototype: + +```javascript +// Create a prototype object +const animalProto = { + speak() { + return `${this.name} makes a sound.` + }, + eat(food) { + return `${this.name} eats ${food}.` + } +} + +// Create objects that inherit from animalProto +const dog = Object.create(animalProto) +dog.name = "Rex" +dog.breed = "German Shepherd" + +const cat = Object.create(animalProto) +cat.name = "Whiskers" +cat.color = "orange" + +console.log(dog.speak()) // "Rex makes a sound." +console.log(cat.eat("fish")) // "Whiskers eats fish." + +// Both share the same prototype +console.log(Object.getPrototypeOf(dog) === animalProto) // true +console.log(Object.getPrototypeOf(cat) === animalProto) // true +``` + +#### Creating Objects with No Prototype + +Pass `null` to create an object with **no prototype** — useful for dictionaries: + +```javascript +// Regular object inherits from Object.prototype +const regular = {} +console.log(regular.toString) // [Function: toString] +console.log("toString" in regular) // true + +// Object with null prototype — truly empty +const dict = Object.create(null) +console.log(dict.toString) // undefined +console.log("toString" in dict) // false + +// Useful for safe dictionaries (no inherited properties to collide with) +dict["hasOwnProperty"] = "I can use any key!" +console.log(dict["hasOwnProperty"]) // "I can use any key!" + +// With regular object, this would shadow the method: +const risky = {} +risky["hasOwnProperty"] = "oops" +// risky.hasOwnProperty("x") would now throw an error! +``` + +#### Object.create() with Property Descriptors + +You can define properties with descriptors: + +```javascript +const person = Object.create(Object.prototype, { + name: { + value: "Alice", + writable: true, + enumerable: true, + configurable: true + }, + age: { + value: 30, + writable: false, // Can't change age + enumerable: true, + configurable: false + }, + secret: { + value: "hidden", + enumerable: false // Won't show in for...in or Object.keys() + } +}) + +console.log(person.name) // "Alice" +console.log(person.age) // 30 +person.age = 25 // Silently fails (or throws in strict mode) +console.log(person.age) // Still 30 + +console.log(Object.keys(person)) // ["name", "age"] (no "secret") +``` + +### 3. The `new` Operator — Create from Constructor + +The [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) operator creates an object from a constructor function. When you call `new Constructor(args)`, JavaScript performs **4 steps**: + +<Steps> + <Step title="Create a new empty object"> + JavaScript creates a fresh object: `const obj = {}` + </Step> + <Step title="Link the prototype"> + Sets `obj`'s `[[Prototype]]` to `Constructor.prototype` + </Step> + <Step title="Execute the constructor"> + Runs the constructor with `this` bound to the new object + </Step> + <Step title="Return the object"> + Returns `obj` (unless the constructor explicitly returns a different object) + </Step> +</Steps> + +```javascript +// A constructor function +function Player(name, health) { + // Step 3: 'this' is bound to the new object + this.name = name + this.health = health +} + +// Methods go on the prototype (shared by all instances) +Player.prototype.attack = function() { + return `${this.name} attacks!` +} + +// Create instance with 'new' +const alice = new Player("Alice", 100) + +console.log(alice.name) // "Alice" +console.log(alice.attack()) // "Alice attacks!" +console.log(alice instanceof Player) // true +console.log(Object.getPrototypeOf(alice) === Player.prototype) // true +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ WHAT new Player("Alice", 100) DOES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Step 1: Create a new empty object │ +│ const obj = {} │ +│ │ +│ Step 2: Link the object's prototype to Constructor.prototype │ +│ Object.setPrototypeOf(obj, Player.prototype) │ +│ │ +│ Step 3: Run the constructor with 'this' bound to the new object │ +│ Player.call(obj, "Alice", 100) │ +│ // Now obj.name = "Alice", obj.health = 100 │ +│ │ +│ Step 4: Return the object (unless constructor returns an object) │ +│ return obj │ +│ │ +│ ───────────────────────────────────────────────────────────────────── │ +│ │ +│ RESULT: │ +│ │ +│ Player.prototype │ +│ ┌─────────────────────┐ │ +│ │ attack: function() │◄───── Shared by all instances │ +│ │ constructor: Player │ │ +│ └─────────────────────┘ │ +│ ▲ │ +│ │ [[Prototype]] │ +│ │ │ +│ ┌────────┴────────┐ │ +│ │ alice │ │ +│ │─────────────────│ │ +│ │ name: "Alice" │ │ +│ │ health: 100 │ │ +│ └─────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +#### Simulating `new` + +Here's a function that does what `new` does: + +```javascript +function myNew(Constructor, ...args) { + // Steps 1 & 2: Create object with correct prototype + const obj = Object.create(Constructor.prototype) + + // Step 3: Run constructor with 'this' = obj + const result = Constructor.apply(obj, args) + + // Step 4: Return result if it's an object, otherwise return obj + return (result !== null && typeof result === 'object') ? result : obj +} + +// These do the same thing: +const player1 = new Player("Alice", 100) +const player2 = myNew(Player, "Bob", 100) + +console.log(player1 instanceof Player) // true +console.log(player2 instanceof Player) // true +``` + +<Warning> +**Don't forget `new`!** Without it, `this` in a constructor refers to the global object (or `undefined` in strict mode), causing bugs. ES6 classes throw an error if you forget `new`, which is safer. +</Warning> + +### 4. Object.assign() — Copy Properties + +[`Object.assign()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) copies enumerable own properties from source objects to a target: + +```javascript +// Basic usage: copy properties to target +const target = { a: 1 } +const source = { b: 2, c: 3 } + +const result = Object.assign(target, source) + +console.log(result) // { a: 1, b: 2, c: 3 } +console.log(target) // { a: 1, b: 2, c: 3 } — target is modified! +console.log(result === target) // true — returns the target +``` + +#### Merging Multiple Objects + +```javascript +const defaults = { theme: "light", fontSize: 14, showSidebar: true } +const userPrefs = { theme: "dark", fontSize: 16 } +const sessionOverrides = { fontSize: 18 } + +// Later sources overwrite earlier ones +const settings = Object.assign({}, defaults, userPrefs, sessionOverrides) + +console.log(settings) +// { theme: "dark", fontSize: 18, showSidebar: true } + +// Original objects are unchanged (because we used {} as target) +console.log(defaults.fontSize) // 14 +``` + +#### Cloning Objects (Shallow) + +```javascript +const original = { name: "Alice", scores: [90, 85, 92] } + +// Shallow clone +const clone = Object.assign({}, original) + +clone.name = "Bob" +console.log(original.name) // "Alice" — primitive copied by value + +clone.scores.push(100) +console.log(original.scores) // [90, 85, 92, 100] — array shared! +``` + +<Warning> +**`Object.assign()` performs a shallow copy!** Nested objects and arrays are copied by reference, not cloned. For deep cloning, use `structuredClone()` or a library like Lodash. + +```javascript +// Deep clone with structuredClone (modern browsers) +const deepClone = structuredClone(original) +deepClone.scores.push(100) +console.log(original.scores) // [90, 85, 92] — unchanged! +``` +</Warning> + +#### Object.assign() Only Copies Own, Enumerable Properties + +```javascript +const proto = { inherited: "from prototype" } +const source = Object.create(proto) +source.own = "my own property" + +Object.defineProperty(source, "hidden", { + value: "non-enumerable", + enumerable: false +}) + +const target = {} +Object.assign(target, source) + +console.log(target.own) // "my own property" — copied +console.log(target.inherited) // undefined — NOT copied (inherited) +console.log(target.hidden) // undefined — NOT copied (non-enumerable) +``` + +--- + +## Inspecting and Modifying Prototypes + +JavaScript provides methods to work with prototypes: + +### Object.getPrototypeOf() — Read the Prototype + +```javascript +const player = { name: "Alice" } + +// Get the prototype +const proto = Object.getPrototypeOf(player) +console.log(proto === Object.prototype) // true + +// Works with any object +function Game() {} +const game = new Game() +console.log(Object.getPrototypeOf(game) === Game.prototype) // true + +// End of the chain +console.log(Object.getPrototypeOf(Object.prototype)) // null +``` + +### Object.setPrototypeOf() — Change the Prototype + +[`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) changes an object's prototype after creation: + +```javascript +const swimmer = { + swim() { return `${this.name} swims!` } +} + +const flyer = { + fly() { return `${this.name} flies!` } +} + +const duck = { name: "Donald" } + +// Start as a swimmer +Object.setPrototypeOf(duck, swimmer) +console.log(duck.swim()) // "Donald swims!" + +// Change to a flyer +Object.setPrototypeOf(duck, flyer) +console.log(duck.fly()) // "Donald flies!" +// console.log(duck.swim()) // TypeError: duck.swim is not a function +``` + +<Warning> +**Avoid `Object.setPrototypeOf()` in performance-critical code!** Changing an object's prototype after creation is slow and can deoptimize your code. Set the prototype correctly at creation time with `Object.create()` instead. +</Warning> + +### instanceof — Check the Prototype Chain + +The [`instanceof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) operator checks if `Constructor.prototype` exists in the object's prototype chain: + +```javascript +function Animal(name) { + this.name = name +} + +function Dog(name, breed) { + Animal.call(this, name) + this.breed = breed +} + +// Set up inheritance +Dog.prototype = Object.create(Animal.prototype) +Dog.prototype.constructor = Dog + +const rex = new Dog("Rex", "German Shepherd") + +console.log(rex instanceof Dog) // true +console.log(rex instanceof Animal) // true +console.log(rex instanceof Object) // true +console.log(rex instanceof Array) // false +``` + +### isPrototypeOf() — Check if Object is in Chain + +```javascript +const animal = { eats: true } +const dog = Object.create(animal) +dog.barks = true + +console.log(animal.isPrototypeOf(dog)) // true +console.log(Object.prototype.isPrototypeOf(dog)) // true +console.log(Array.prototype.isPrototypeOf(dog)) // false +``` + +--- + +## Common Prototype Methods + +These methods help you work with object properties and prototypes: + +### hasOwnProperty() — Check Own Properties + +```javascript +const proto = { inherited: true } +const obj = Object.create(proto) +obj.own = true + +// hasOwnProperty checks ONLY the object, not the chain +console.log(obj.hasOwnProperty("own")) // true +console.log(obj.hasOwnProperty("inherited")) // false + +// 'in' operator checks the whole chain +console.log("own" in obj) // true +console.log("inherited" in obj) // true +``` + +### Object.keys() vs for...in + +```javascript +const proto = { inherited: "value" } +const obj = Object.create(proto) +obj.own1 = "a" +obj.own2 = "b" + +// Object.keys() — only own enumerable properties +console.log(Object.keys(obj)) // ["own1", "own2"] + +// for...in — own AND inherited enumerable properties +for (const key in obj) { + console.log(key) // "own1", "own2", "inherited" +} + +// Filter for...in to only own properties +for (const key in obj) { + if (obj.hasOwnProperty(key)) { + console.log(key) // "own1", "own2" + } +} +``` + +### Object.getOwnPropertyNames() — All Own Properties + +```javascript +const obj = { visible: true } +Object.defineProperty(obj, "hidden", { + value: "secret", + enumerable: false +}) + +// Object.keys() — only enumerable +console.log(Object.keys(obj)) // ["visible"] + +// Object.getOwnPropertyNames() — all own properties +console.log(Object.getOwnPropertyNames(obj)) // ["visible", "hidden"] +``` + +### Summary Table + +| Method | Own? | Enumerable? | Inherited? | +|--------|------|-------------|------------| +| `obj.hasOwnProperty(key)` | Yes | Both | No | +| `key in obj` | Yes | Both | Yes | +| `Object.keys(obj)` | Yes | Yes only | No | +| `Object.getOwnPropertyNames(obj)` | Yes | Both | No | +| `for...in` | Yes | Yes only | Yes | + +--- + +## The Prototype Pitfall: Common Mistakes + +### Mistake 1: Modifying Object.prototype + +```javascript +// ❌ NEVER do this! +Object.prototype.greet = function() { + return "Hello!" +} + +// Now EVERY object has greet() +const player = { name: "Alice" } +const numbers = [1, 2, 3] +const date = new Date() + +console.log(player.greet()) // "Hello!" +console.log(numbers.greet()) // "Hello!" +console.log(date.greet()) // "Hello!" + +// This can break for...in loops +for (const key in player) { + console.log(key) // "name", "greet" — greet shows up! +} + +// And cause conflicts with libraries +``` + +<Warning> +**Never modify `Object.prototype`!** It affects every object in your application and can break third-party code. If you need to add methods to all objects of a type, create your own constructor or class. +</Warning> + +### Mistake 2: Confusing `.prototype` with `[[Prototype]]` + +```javascript +function Player(name) { + this.name = name +} + +const alice = new Player("Alice") + +// ❌ WRONG — instances don't have .prototype +console.log(alice.prototype) // undefined + +// ✓ CORRECT — use Object.getPrototypeOf() +console.log(Object.getPrototypeOf(alice) === Player.prototype) // true + +// .prototype is ONLY on functions +console.log(Player.prototype) // { constructor: Player } +``` + +### Mistake 3: Prototype Pollution + +When using objects as dictionaries, user input could overwrite prototype properties: + +```javascript +// ❌ DANGEROUS with user input +const userData = {} + +// Imagine this comes from user input +const key = "__proto__" +const value = { isAdmin: true } + +userData[key] = value // In some environments, this pollutes the prototype! + +// Safer approach: use null prototype or Map +const safeDict = Object.create(null) +safeDict[key] = value // No prototype to pollute + +// Or use Map for dictionaries +const map = new Map() +map.set(key, value) // Completely safe +``` + +### Mistake 4: Shared Reference on Prototype + +```javascript +// ❌ WRONG — array on prototype is shared by all instances +function Player(name) { + this.name = name +} +Player.prototype.inventory = [] // Shared by ALL players! + +const alice = new Player("Alice") +const bob = new Player("Bob") + +alice.inventory.push("sword") +console.log(bob.inventory) // ["sword"] — Bob has Alice's sword! + +// ✓ CORRECT — initialize arrays in constructor +function Player(name) { + this.name = name + this.inventory = [] // Each player gets their own array +} +``` + +--- + +## Key Takeaways + +<Info> +**Essential Points About Prototypes and Object Creation:** + +1. **Every object has a prototype** — a hidden link (`[[Prototype]]`) to another object, forming a chain that ends at `null` + +2. **Property lookup walks the chain** — JavaScript searches the object first, then its prototype, then the prototype's prototype, and so on + +3. **`[[Prototype]]` vs `.prototype`** — `[[Prototype]]` is the internal link every object has; `.prototype` is a property on functions used with `new` + +4. **Use `Object.getPrototypeOf()`** — not `__proto__`, which is deprecated + +5. **`Object.create(proto)`** — creates an object with a specific prototype; pass `null` for no prototype + +6. **The `new` operator does 4 things** — creates object, links prototype, runs constructor with `this`, returns the object + +7. **`Object.assign()` is shallow** — nested objects are copied by reference, not cloned + +8. **`hasOwnProperty()` vs `in`** — `hasOwnProperty` checks only the object; `in` checks the whole prototype chain + +9. **Never modify `Object.prototype`** — it affects all objects and can break code + +10. **Put methods on the prototype** — for memory efficiency, don't define methods in the constructor +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What is the prototype chain and how does property lookup work?"> + **Answer:** + + The prototype chain is JavaScript's inheritance mechanism. Every object has a `[[Prototype]]` link to another object (its prototype). + + When you access a property: + 1. JavaScript looks for it on the object itself + 2. If not found, looks on the object's prototype + 3. Continues up the chain until found or `null` is reached + + ```javascript + const parent = { greet: "Hello" } + const child = Object.create(parent) + + console.log(child.greet) // "Hello" — found on prototype + console.log(child.missing) // undefined — not found anywhere + ``` + </Accordion> + + <Accordion title="Question 2: What's the difference between [[Prototype]], __proto__, and .prototype?"> + **Answer:** + + - **`[[Prototype]]`**: The internal slot every object has, pointing to its prototype. Not directly accessible. + + - **`__proto__`**: A deprecated getter/setter that exposes `[[Prototype]]`. Use `Object.getPrototypeOf()` instead. + + - **`.prototype`**: A property that exists **only on functions**. When you use `new`, the created object's `[[Prototype]]` is set to this value. + + ```javascript + function Foo() {} + const f = new Foo() + + // f's [[Prototype]] is Foo.prototype + Object.getPrototypeOf(f) === Foo.prototype // true + + // Foo is a function, so it has .prototype + Foo.prototype // { constructor: Foo } + + // f is NOT a function, so it has no .prototype + f.prototype // undefined + ``` + </Accordion> + + <Accordion title="Question 3: What are the 4 steps the new keyword performs?"> + **Answer:** + + When you call `new Constructor(args)`: + + 1. **Create** a new empty object `{}` + 2. **Link** the object's `[[Prototype]]` to `Constructor.prototype` + 3. **Execute** the constructor with `this` bound to the new object + 4. **Return** the object (unless the constructor returns a different object) + + ```javascript + function myNew(Constructor, ...args) { + const obj = Object.create(Constructor.prototype) // Steps 1-2 + const result = Constructor.apply(obj, args) // Step 3 + return (typeof result === 'object' && result !== null) ? result : obj // Step 4 + } + ``` + </Accordion> + + <Accordion title="Question 4: How does Object.create() differ from using new?"> + **Answer:** + + - **`Object.create(proto)`** creates an object with the specified object as its prototype. It doesn't call any constructor. + + - **`new Constructor()`** creates an object with `Constructor.prototype` as its prototype AND runs the constructor function. + + ```javascript + const proto = { greet() { return "Hi!" } } + + // Object.create — just links the prototype + const obj1 = Object.create(proto) + + // new — links prototype AND runs constructor + function MyClass() { + this.initialized = true + } + MyClass.prototype = proto + + const obj2 = new MyClass() + console.log(obj2.initialized) // true (constructor ran) + console.log(obj1.initialized) // undefined (no constructor) + ``` + </Accordion> + + <Accordion title="Question 5: Why should you avoid modifying Object.prototype?"> + **Answer:** + + Modifying `Object.prototype` affects **every object** in your application because all objects inherit from it. This can: + + 1. Break `for...in` loops (new properties show up) + 2. Conflict with third-party libraries + 3. Cause unexpected behavior throughout your codebase + + ```javascript + // ❌ BAD + Object.prototype.bad = "affects everything" + + const obj = {} + for (const key in obj) { + console.log(key) // "bad" — unexpected! + } + ``` + + Instead, create your own constructors/classes or use composition. + </Accordion> + + <Accordion title="Question 6: What's the difference between Object.assign() shallow copy and deep copy?"> + **Answer:** + + **Shallow copy**: Copies the top-level properties. Nested objects/arrays are copied by reference (they point to the same data). + + **Deep copy**: Recursively copies all levels. Nested objects/arrays are fully cloned. + + ```javascript + const original = { + name: "Alice", + scores: [90, 85] // nested array + } + + // Shallow copy with Object.assign + const shallow = Object.assign({}, original) + shallow.scores.push(100) + console.log(original.scores) // [90, 85, 100] — modified! + + // Deep copy with structuredClone + const deep = structuredClone(original) + deep.scores.push(100) + console.log(original.scores) // [90, 85, 100] — still modified from before + // But if we had deep copied first, original would be unchanged + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Factories and Classes" icon="industry" href="/concepts/factories-classes"> + Learn different patterns for creating objects using factories and ES6 classes + </Card> + <Card title="this, call, apply, bind" icon="hand-pointer" href="/concepts/this-call-apply-bind"> + Understand how `this` binding works, which is crucial when working with constructors + </Card> + <Card title="Inheritance and Polymorphism" icon="sitemap" href="/concepts/inheritance-polymorphism"> + Explore advanced inheritance patterns and polymorphism in JavaScript + </Card> + <Card title="Value vs Reference Types" icon="copy" href="/concepts/value-reference-types"> + Understand the difference between primitives and objects — essential for prototypes + </Card> +</CardGroup> + +--- + +## Reference + +<CardGroup cols={2}> + <Card title="Inheritance and the Prototype Chain — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain"> + Comprehensive MDN guide to JavaScript's prototype-based inheritance + </Card> + <Card title="Object.create() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create"> + Official documentation on creating objects with specific prototypes + </Card> + <Card title="Object.assign() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign"> + How to copy properties between objects + </Card> + <Card title="new operator — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new"> + What happens when you use the new keyword + </Card> + <Card title="Object.getPrototypeOf() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf"> + How to read an object's prototype + </Card> + <Card title="instanceof — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof"> + Checking prototype chain membership + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="A Beginner's Guide to JavaScript's Prototype" icon="newspaper" href="https://www.freecodecamp.org/news/a-beginners-guide-to-javascripts-prototype/"> + A beginner-friendly introduction to prototypes with clear examples + </Card> + <Card title="Understanding Prototypes in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript"> + DigitalOcean's comprehensive tutorial on prototypes and inheritance + </Card> + <Card title="JavaScript Object Creation Patterns" icon="newspaper" href="https://www.patterns.dev/vanilla/object-creation-patterns"> + Different patterns for creating objects in JavaScript + </Card> + <Card title="The Prototype Chain Explained" icon="newspaper" href="https://javascript.info/prototype-inheritance"> + JavaScript.info's detailed explanation of prototype inheritance + </Card> +</CardGroup> + +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript Prototypes Explained" icon="video" href="https://www.youtube.com/watch?v=riDVvXZ_Kb4"> + Fun Funktion's visual explanation of how prototypes work + </Card> + <Card title="Object.create and Prototypes" icon="video" href="https://www.youtube.com/watch?v=MACDGu96wrA"> + Kyle Simpson explains Object.create and prototype linkage + </Card> + <Card title="The new Keyword Explained" icon="video" href="https://www.youtube.com/watch?v=Y3zzCY62NYc"> + What happens under the hood when you use new + </Card> +</CardGroup> diff --git a/docs/concepts/prototype.mdx b/docs/concepts/prototype.mdx deleted file mode 100644 index f2938bc9..00000000 --- a/docs/concepts/prototype.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: "Prototype Inheritance and Prototype Chain" -description: "Understanding JavaScript's inheritance model" ---- - -## Overview - -JavaScript uses prototypal inheritance, where objects can inherit directly from other objects. Every object has an internal link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with `null` as its prototype. This chain of prototypes is called the **prototype chain**. - -## Reference - -<Card title="Inheritance and the prototype chain — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain"> - Official MDN documentation -</Card> - -## Articles - -<CardGroup cols={2}> - <Card title="Javascript : Prototype vs Class" icon="newspaper" href="https://medium.com/@parsyval/javascript-prototype-vs-class-a7015d5473b"> - By Valentin PARSY - </Card> - <Card title="JavaScript engine fundamentals: optimizing prototypes" icon="newspaper" href="https://mathiasbynens.be/notes/prototypes"> - By Mathias Bynens - </Card> -</CardGroup> - -- [JavaScript Prototype — NC Patro](https://codeburst.io/javascript-prototype-cb29d82b8809) -- [Prototypes in JavaScript — Rupesh Mishra](https://hackernoon.com/prototypes-in-javascript-5bba2990e04b) -- [Prototype in JavaScript: it's quirky, but here's how it works — Pranav Jindal](https://medium.freecodecamp.org/prototype-in-js-busted-5547ec68872) -- [Understanding JavaScript: Prototype and Inheritance — Alexander Kondov](https://hackernoon.com/understanding-javascript-prototype-and-inheritance-d55a9a23bde2) -- [Understanding Classes (ES5) and Prototypal Inheritance in JavaScript — Hridayesh Sharma](https://dev.to/_hridaysharma/understanding-classes-es5-and-prototypal-inheritance-in-javascript-n8d) -- [prototype, **proto** and Prototypal inheritance in JavaScript — Varun Dey](https://dev.to/varundey/prototype-proto-and-prototypal-inheritance-in-javascript-2inl) -- [Prototypal Inheritance — JavaScript.Info](https://javascript.info/prototype-inheritance) -- [How To Work with Prototypes and Inheritance in JavaScript — Tania Rascia](https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript) -- [Master JavaScript Prototypes & Inheritance — Arnav Aggarwal](https://codeburst.io/master-javascript-prototypes-inheritance-d0a9a5a75c4e) -- [JavaScript's Prototypal Inheritance Explained Using CSS — Nash Vail](https://medium.freecodecamp.org/understanding-prototypal-inheritance-in-javascript-with-css-93b2fcda75e4) -- [Prototypal Inheritance in JavaScript — Jannis Redmann](https://gist.github.com/derhuerst/a585c4916b1c361cc6f0) -- [Demystifying ES6 Classes And Prototypal Inheritance ― Neo Ighodaro](https://scotch.io/tutorials/demystifying-es6-classes-and-prototypal-inheritance) - -## Videos - -<CardGroup cols={2}> - <Card title="Javascript Prototype Inheritance" icon="video" href="https://www.youtube.com/watch?v=sOrtAjyk4lQ"> - By Avelx - </Card> - <Card title="A Beginner's Guide to Javascript's Prototype" icon="video" href="https://www.youtube.com/watch?v=XskMWBXNbp0"> - By Tyler Mcginnis - </Card> -</CardGroup> - -- [JavaScript Prototype Inheritance Explained pt. I — techsith](https://www.youtube.com/watch?v=7oNWNlMrkpc) -- [JavaScript Prototype Inheritance Explained pt. II — techsith](https://www.youtube.com/watch?v=uIlj6_z_wL8) -- [JavaScript Prototype Inheritance Explained — Kyle Robinson](https://www.youtube.com/watch?v=qMO-LTOrJaE) -- [Advanced Javascript - Prototypal Inheritance In 1 Minute](https://www.youtube.com/watch?v=G6l5CHl67HQ) -- [An Overview Of Classical Javascript Classes and Prototypal Inheritance — Pentacode](https://www.youtube.com/watch?v=phwzuiJJPpQ) -- [Object Oriented JavaScript - Prototype — The Net Ninja](https://www.youtube.com/watch?v=4jb4AYEyhRc) -- [Prototype in JavaScript — kudvenkat](https://www.youtube.com/watch?v=2rkEbcptR64) -- [JavaScript Using Prototypes — O'Reilly](https://www.youtube.com/watch?v=oCwCcNvaXAQ) -- [Prototypes in Javascript - p5.js Tutorial — The Coding Train](https://www.youtube.com/watch?v=hS_WqkyUah8) - -## Books - -<CardGroup cols={2}> - <Card title="You Don't Know JS, 1st Edition: this & Object Prototypes" icon="book" href="https://github.com/getify/You-Dont-Know-JS/tree/1st-ed"> - By Kyle Simpson - </Card> - <Card title="The Principles of Object-Oriented JavaScript" icon="book" href="https://www.google.com.pk/books/edition/The_Principles_of_Object_Oriented_JavaSc/rorlAwAAQBAJ"> - By Nicholas C. Zakas - </Card> -</CardGroup> From 03560ea5465e747dbd2112a918bc0f2b93f1f28d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Tue, 30 Dec 2025 14:44:15 -0300 Subject: [PATCH 064/128] test: add tests for object-creation-prototypes concept --- .../object-creation-prototypes.test.js | 440 ++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 tests/object-oriented/object-creation-prototypes/object-creation-prototypes.test.js diff --git a/tests/object-oriented/object-creation-prototypes/object-creation-prototypes.test.js b/tests/object-oriented/object-creation-prototypes/object-creation-prototypes.test.js new file mode 100644 index 00000000..3eb3e75d --- /dev/null +++ b/tests/object-oriented/object-creation-prototypes/object-creation-prototypes.test.js @@ -0,0 +1,440 @@ +import { describe, it, expect } from 'vitest' + +describe('Object Creation & Prototypes', () => { + describe('Opening Hook - Inherited Methods', () => { + it('should have inherited methods from Object.prototype', () => { + // You create a simple object + const player = { name: 'Alice', health: 100 } + + // But it has methods you never defined! + expect(typeof player.toString).toBe('function') + expect(player.toString()).toBe('[object Object]') + expect(player.hasOwnProperty('name')).toBe(true) + + // Where do these come from? + expect(Object.getPrototypeOf(player)).toBe(Object.prototype) + }) + }) + + describe('Prototype Chain', () => { + it('should look up properties through the prototype chain', () => { + const grandparent = { familyName: 'Smith' } + const parent = Object.create(grandparent) + parent.job = 'Engineer' + const child = Object.create(parent) + child.name = 'Alice' + + // Property lookup walks the chain + expect(child.name).toBe('Alice') // found on child + expect(child.job).toBe('Engineer') // found on parent + expect(child.familyName).toBe('Smith') // found on grandparent + }) + + it('should inherit methods from prototype (wizard/apprentice example)', () => { + // Create a simple object + const wizard = { + name: 'Gandalf', + castSpell() { + return `${this.name} casts a spell!` + } + } + + // Create another object that inherits from wizard + const apprentice = Object.create(wizard) + apprentice.name = 'Harry' + + // apprentice has its own 'name' property + expect(apprentice.name).toBe('Harry') + + // But castSpell comes from the prototype (wizard) + expect(apprentice.castSpell()).toBe('Harry casts a spell!') + + // The prototype chain: + // apprentice → wizard → Object.prototype → null + expect(Object.getPrototypeOf(apprentice)).toBe(wizard) + expect(Object.getPrototypeOf(wizard)).toBe(Object.prototype) + expect(Object.getPrototypeOf(Object.prototype)).toBeNull() + }) + + it('should return undefined when property is not found in chain', () => { + const obj = { name: 'test' } + expect(obj.nonexistent).toBeUndefined() + }) + + it('should end the chain at null', () => { + const obj = {} + expect(Object.getPrototypeOf(Object.prototype)).toBeNull() + }) + + it('should shadow inherited properties when set on object', () => { + const prototype = { greeting: 'Hello', count: 0 } + const obj = Object.create(prototype) + + // Before shadowing + expect(obj.greeting).toBe('Hello') + + // Shadow the property + obj.greeting = 'Hi' + + // obj has its own property now + expect(obj.greeting).toBe('Hi') + // Prototype is unchanged + expect(prototype.greeting).toBe('Hello') + expect(obj.hasOwnProperty('greeting')).toBe(true) + }) + }) + + describe('[[Prototype]], __proto__, and .prototype', () => { + it('should have Object.prototype as prototype for plain objects', () => { + const obj = {} + expect(Object.getPrototypeOf(obj)).toBe(Object.prototype) + }) + + it('should have .prototype property only on functions', () => { + function Player(name) { + this.name = name + } + const alice = new Player('Alice') + + // Functions have .prototype + expect(Player.prototype).toBeDefined() + expect(typeof Player.prototype).toBe('object') + + // Instances don't have .prototype + expect(alice.prototype).toBeUndefined() + + // Instance's [[Prototype]] is the constructor's .prototype + expect(Object.getPrototypeOf(alice)).toBe(Player.prototype) + }) + }) + + describe('Object Literals', () => { + it('should have Object.prototype as prototype', () => { + // Object literal — prototype is automatically Object.prototype + const player = { + name: 'Alice', + health: 100, + attack() { + return `${this.name} attacks!` + } + } + + expect(Object.getPrototypeOf(player)).toBe(Object.prototype) + expect(player.attack()).toBe('Alice attacks!') + }) + }) + + describe('Object.create()', () => { + it('should create object with specified prototype', () => { + const animalProto = { + speak() { + return `${this.name} makes a sound.` + } + } + + const dog = Object.create(animalProto) + dog.name = 'Rex' + + expect(Object.getPrototypeOf(dog)).toBe(animalProto) + expect(dog.speak()).toBe('Rex makes a sound.') + }) + + it('should create object with null prototype', () => { + const dict = Object.create(null) + + // No inherited properties + expect(dict.toString).toBeUndefined() + expect(dict.hasOwnProperty).toBeUndefined() + expect(Object.getPrototypeOf(dict)).toBeNull() + + // Can use any key without collision + dict['hasOwnProperty'] = 'safe!' + expect(dict['hasOwnProperty']).toBe('safe!') + }) + + it('should create object with property descriptors', () => { + const person = Object.create(Object.prototype, { + name: { + value: 'Alice', + writable: true, + enumerable: true, + configurable: true + }, + age: { + value: 30, + writable: false, + enumerable: true, + configurable: false + } + }) + + expect(person.name).toBe('Alice') + expect(person.age).toBe(30) + + // Can modify writable property + person.name = 'Bob' + expect(person.name).toBe('Bob') + + // Cannot modify non-writable property (throws in strict mode) + expect(() => { + person.age = 25 + }).toThrow(TypeError) + expect(person.age).toBe(30) // unchanged + }) + }) + + describe('new operator', () => { + it('should create object with correct prototype', () => { + function Player(name) { + this.name = name + } + Player.prototype.greet = function () { + return `Hello, ${this.name}!` + } + + const alice = new Player('Alice') + + expect(Object.getPrototypeOf(alice)).toBe(Player.prototype) + expect(alice.greet()).toBe('Hello, Alice!') + }) + + it('should bind this to the new object', () => { + function Counter() { + this.count = 0 + this.increment = function () { + this.count++ + } + } + + const counter = new Counter() + expect(counter.count).toBe(0) + counter.increment() + expect(counter.count).toBe(1) + }) + + it('should return the object unless constructor returns an object', () => { + function ReturnsNothing(name) { + this.name = name + // Implicitly returns the new object + } + + function ReturnsPrimitive(name) { + this.name = name + return 42 // Primitive is ignored + } + + function ReturnsObject(name) { + this.name = name + return { different: true } // Object is returned instead + } + + const obj1 = new ReturnsNothing('test') + expect(obj1.name).toBe('test') + + const obj2 = new ReturnsPrimitive('test') + expect(obj2.name).toBe('test') // Primitive return ignored + + const obj3 = new ReturnsObject('test') + expect(obj3.different).toBe(true) + expect(obj3.name).toBeUndefined() // Original object not returned + }) + + it('can be simulated with Object.create and apply', () => { + function myNew(Constructor, ...args) { + const obj = Object.create(Constructor.prototype) + const result = Constructor.apply(obj, args) + return result !== null && typeof result === 'object' ? result : obj + } + + function Player(name, health) { + this.name = name + this.health = health + } + Player.prototype.attack = function () { + return `${this.name} attacks!` + } + + const player1 = new Player('Alice', 100) + const player2 = myNew(Player, 'Bob', 100) + + expect(player1.name).toBe('Alice') + expect(player2.name).toBe('Bob') + expect(player1.attack()).toBe('Alice attacks!') + expect(player2.attack()).toBe('Bob attacks!') + expect(player1 instanceof Player).toBe(true) + expect(player2 instanceof Player).toBe(true) + }) + }) + + describe('Object.assign()', () => { + it('should copy enumerable own properties', () => { + const target = { a: 1 } + const source = { b: 2, c: 3 } + + const result = Object.assign(target, source) + + expect(result).toEqual({ a: 1, b: 2, c: 3 }) + expect(result).toBe(target) // Returns the target + }) + + it('should merge multiple objects (later sources overwrite)', () => { + const defaults = { theme: 'light', fontSize: 14 } + const userPrefs = { theme: 'dark' } + const session = { fontSize: 18 } + + const settings = Object.assign({}, defaults, userPrefs, session) + + expect(settings.theme).toBe('dark') + expect(settings.fontSize).toBe(18) + }) + + it('should perform shallow copy only', () => { + const original = { + name: 'Alice', + scores: [90, 85] + } + + const clone = Object.assign({}, original) + + // Primitive is copied by value + clone.name = 'Bob' + expect(original.name).toBe('Alice') + + // Array is copied by reference + clone.scores.push(100) + expect(original.scores).toEqual([90, 85, 100]) // Modified! + }) + + it('should not copy inherited or non-enumerable properties', () => { + const proto = { inherited: 'from prototype' } + const source = Object.create(proto) + source.own = 'my own property' + + Object.defineProperty(source, 'hidden', { + value: 'non-enumerable', + enumerable: false + }) + + const target = {} + Object.assign(target, source) + + expect(target.own).toBe('my own property') + expect(target.inherited).toBeUndefined() // Not copied + expect(target.hidden).toBeUndefined() // Not copied + }) + }) + + describe('Prototype inspection', () => { + it('Object.getPrototypeOf should return the prototype', () => { + const proto = { test: true } + const obj = Object.create(proto) + + expect(Object.getPrototypeOf(obj)).toBe(proto) + }) + + it('Object.setPrototypeOf should change the prototype', () => { + const swimmer = { swim: () => 'swimming' } + const flyer = { fly: () => 'flying' } + + const duck = { name: 'Donald' } + Object.setPrototypeOf(duck, swimmer) + + expect(duck.swim()).toBe('swimming') + + Object.setPrototypeOf(duck, flyer) + expect(duck.fly()).toBe('flying') + expect(duck.swim).toBeUndefined() + }) + + it('instanceof should check the prototype chain', () => { + function Animal(name) { + this.name = name + } + function Dog(name) { + Animal.call(this, name) + } + Dog.prototype = Object.create(Animal.prototype) + Dog.prototype.constructor = Dog + + const rex = new Dog('Rex') + + expect(rex instanceof Dog).toBe(true) + expect(rex instanceof Animal).toBe(true) + expect(rex instanceof Object).toBe(true) + expect(rex instanceof Array).toBe(false) + }) + + it('isPrototypeOf should check if object is in prototype chain', () => { + const animal = { eats: true } + const dog = Object.create(animal) + + expect(animal.isPrototypeOf(dog)).toBe(true) + expect(Object.prototype.isPrototypeOf(dog)).toBe(true) + expect(Array.prototype.isPrototypeOf(dog)).toBe(false) + }) + }) + + describe('Common prototype methods', () => { + it('hasOwnProperty should check only own properties', () => { + const proto = { inherited: true } + const obj = Object.create(proto) + obj.own = true + + expect(obj.hasOwnProperty('own')).toBe(true) + expect(obj.hasOwnProperty('inherited')).toBe(false) + + // 'in' checks the whole chain + expect('own' in obj).toBe(true) + expect('inherited' in obj).toBe(true) + }) + + it('Object.keys should return only own enumerable properties', () => { + const proto = { inherited: 'value' } + const obj = Object.create(proto) + obj.own1 = 'a' + obj.own2 = 'b' + + expect(Object.keys(obj)).toEqual(['own1', 'own2']) + expect(Object.keys(obj)).not.toContain('inherited') + }) + + it('Object.getOwnPropertyNames should return all own properties', () => { + const obj = { visible: true } + Object.defineProperty(obj, 'hidden', { + value: 'secret', + enumerable: false + }) + + expect(Object.keys(obj)).toEqual(['visible']) + expect(Object.getOwnPropertyNames(obj)).toEqual(['visible', 'hidden']) + }) + }) + + describe('Common mistakes', () => { + it('should not share reference types on prototype', () => { + // Wrong way - array on prototype is shared + function BadPlayer(name) { + this.name = name + } + BadPlayer.prototype.inventory = [] + + const alice = new BadPlayer('Alice') + const bob = new BadPlayer('Bob') + + alice.inventory.push('sword') + expect(bob.inventory).toContain('sword') // Bob has Alice's sword! + + // Correct way - array in constructor + function GoodPlayer(name) { + this.name = name + this.inventory = [] + } + + const charlie = new GoodPlayer('Charlie') + const dave = new GoodPlayer('Dave') + + charlie.inventory.push('shield') + expect(dave.inventory).not.toContain('shield') // Dave's inventory is separate + }) + }) +}) From 1c7c9cd09ee2fd17414f114eb2315a89a8548178 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 10:46:04 -0300 Subject: [PATCH 065/128] docs: add inheritance & polymorphism concept with tests - Rewrite inheritance-polymorphism.mdx with comprehensive coverage - Add 29 tests covering all code examples from documentation - Cover class inheritance, super keyword, method overriding - Cover polymorphism, prototype chain, composition patterns - Cover mixins and common mistakes --- docs/concepts/inheritance-polymorphism.mdx | 1287 ++++++++++++++++- .../inheritance-polymorphism.test.js | 657 +++++++++ 2 files changed, 1923 insertions(+), 21 deletions(-) create mode 100644 tests/object-oriented/inheritance-polymorphism/inheritance-polymorphism.test.js diff --git a/docs/concepts/inheritance-polymorphism.mdx b/docs/concepts/inheritance-polymorphism.mdx index fcde0f9e..1c8958da 100644 --- a/docs/concepts/inheritance-polymorphism.mdx +++ b/docs/concepts/inheritance-polymorphism.mdx @@ -4,47 +4,1292 @@ sidebarTitle: "Inheritance & Polymorphism: OOP Principles" description: "Learn inheritance and polymorphism in JavaScript — extending classes, prototype chains, method overriding, and code reuse patterns. Master object-oriented programming principles." --- -## Overview +How do game developers create hundreds of character types without copy-pasting the same code over and over? How can a Warrior, Mage, and Archer all "attack" differently but be treated the same way in battle? -**Inheritance** allows objects to inherit properties and methods from other objects. **Polymorphism** allows objects of different types to be treated as objects of a common type. These OOP principles, combined with effective code reuse strategies, help create maintainable and scalable JavaScript applications. +```javascript +// One base class, infinite possibilities +class Character { + constructor(name) { + this.name = name + this.health = 100 + } + + attack() { + return `${this.name} attacks!` + } +} + +class Warrior extends Character { + attack() { + return `${this.name} swings a mighty sword!` + } +} + +class Mage extends Character { + attack() { + return `${this.name} casts a fireball!` + } +} + +const hero = new Warrior("Aragorn") +const wizard = new Mage("Gandalf") + +console.log(hero.attack()) // "Aragorn swings a mighty sword!" +console.log(wizard.attack()) // "Gandalf casts a fireball!" +``` + +The answer lies in two powerful OOP principles: **[inheritance](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Classes_in_JavaScript#inheritance)** lets classes share code by extending other classes, and **polymorphism** lets different objects respond to the same method call in their own unique way. + +<Info> +**What you'll learn in this guide:** +- How inheritance lets child classes reuse parent class code +- Using the `extends` keyword to create class hierarchies +- The `super` keyword for calling parent constructors and methods +- Method overriding for specialized behavior +- Polymorphism: treating different object types through a common interface +- When to use composition instead of inheritance (the Gorilla-Banana problem) +- Mixins for sharing behavior across unrelated classes +</Info> + +<Warning> +**Prerequisites:** This guide assumes you understand [Factories & Classes](/concepts/factories-classes) and [Object Creation & Prototypes](/concepts/object-creation-prototypes). If you're not comfortable with creating classes in JavaScript, read those guides first! +</Warning> + +--- + +## What is Inheritance? + +**Inheritance** is a mechanism where a class (called a **child** or **subclass**) can inherit properties and methods from another class (called a **parent** or **superclass**). Instead of rewriting common functionality, the child class automatically gets everything the parent has — and can add or customize as needed. + +Think of it as the "IS-A" relationship: +- A **Warrior IS-A Character** — so it inherits all Character traits +- A **Mage IS-A Character** — same base, different specialization +- An **Archer IS-A Character** — you get the pattern + +```javascript +// The parent class — all characters share these basics +class Character { + constructor(name, health = 100) { + this.name = name + this.health = health + } + + introduce() { + return `I am ${this.name} with ${this.health} HP` + } + + attack() { + return `${this.name} attacks!` + } + + takeDamage(amount) { + this.health -= amount + return `${this.name} takes ${amount} damage! (${this.health} HP left)` + } +} + +// The child class — gets everything from Character automatically +class Warrior extends Character { + constructor(name) { + super(name, 150) // Warriors have more health! + this.rage = 0 + } + + // New method only Warriors have + battleCry() { + this.rage += 10 + return `${this.name} roars with fury! Rage: ${this.rage}` + } +} + +const conan = new Warrior("Conan") +console.log(conan.introduce()) // "I am Conan with 150 HP" (inherited!) +console.log(conan.battleCry()) // "Conan roars with fury! Rage: 10" (new!) +console.log(conan.attack()) // "Conan attacks!" (inherited!) +``` + +<Tip> +**The DRY Principle:** Inheritance helps you "Don't Repeat Yourself". Write common code once in the parent class, and all children automatically benefit — including bug fixes and improvements! +</Tip> + +--- + +## The Game Character Analogy + +Imagine you're building an RPG game. Every character — whether player or enemy — shares basic traits: a name, health points, the ability to attack and take damage. But each character *type* has unique abilities. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ GAME CHARACTER HIERARCHY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────────┐ │ +│ │ Character │ ← Parent (base class) │ +│ │ ───────── │ │ +│ │ name │ │ +│ │ health │ │ +│ │ attack() │ │ +│ │ takeDamage() │ │ +│ └───────┬───────┘ │ +│ │ │ +│ ┌────────────────────┼────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Warrior │ │ Mage │ │ Archer │ │ +│ │ ─────── │ │ ────── │ │ ────── │ │ +│ │ rage │ │ mana │ │ arrows │ │ +│ │ battleCry()│ │ castSpell()│ │ aim() │ │ +│ │ attack() ⚔ │ │ attack() ✨│ │ attack() 🏹│ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ +│ Each child INHERITS from Character but OVERRIDES attack() │ +│ to provide specialized behavior — that's POLYMORPHISM! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Without inheritance, you'd copy-paste `name`, `health`, `takeDamage()` into every character class. With inheritance, you write it once and *extend* it: + +```javascript +class Warrior extends Character { /* ... */ } +class Mage extends Character { /* ... */ } +class Archer extends Character { /* ... */ } +``` + +Each child class automatically has everything `Character` has, plus their own unique additions. + +--- + +## Class Inheritance with `extends` + +The **[`extends`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends)** keyword creates a class that is a child of another class. The syntax is straightforward: + +```javascript +class ChildClass extends ParentClass { + // Child-specific code here +} +``` + +<Steps> + <Step title="Define the Parent Class"> + Create the base class with shared properties and methods: + + ```javascript + class Character { + constructor(name) { + this.name = name + this.health = 100 + } + + attack() { + return `${this.name} attacks!` + } + } + ``` + </Step> + + <Step title="Create a Child Class with extends"> + Use `extends` to inherit from the parent: + + ```javascript + class Mage extends Character { + constructor(name) { + super(name) // Call parent constructor FIRST + this.mana = 100 // Then add child-specific properties + } + + castSpell(spell) { + this.mana -= 10 + return `${this.name} casts ${spell}!` + } + } + ``` + </Step> + + <Step title="Use the Child Class"> + Instances have both parent AND child capabilities: + + ```javascript + const gandalf = new Mage("Gandalf") + + // Inherited from Character + console.log(gandalf.name) // "Gandalf" + console.log(gandalf.health) // 100 + console.log(gandalf.attack()) // "Gandalf attacks!" + + // Unique to Mage + console.log(gandalf.mana) // 100 + console.log(gandalf.castSpell("Fireball")) // "Gandalf casts Fireball!" + ``` + </Step> +</Steps> + +### What the Child Automatically Gets + +When you use `extends`, the child class inherits: + +| Inherited | Example | +|-----------|---------| +| Instance properties | `this.name`, `this.health` | +| Instance methods | `attack()`, `takeDamage()` | +| Static methods | `Character.createRandom()` (if defined) | +| Getters/Setters | `get isAlive()`, `set health(val)` | + +```javascript +class Character { + constructor(name) { + this.name = name + this.health = 100 + } + + get isAlive() { + return this.health > 0 + } + + static createRandom() { + const names = ["Hero", "Villain", "Sidekick"] + return new this(names[Math.floor(Math.random() * names.length)]) + } +} + +class Warrior extends Character { + constructor(name) { + super(name) + this.rage = 0 + } +} + +// Child inherits the static method! +const randomWarrior = Warrior.createRandom() +console.log(randomWarrior.name) // Random name +console.log(randomWarrior.isAlive) // true (inherited getter) +console.log(randomWarrior.rage) // 0 (Warrior-specific) +``` + +--- + +## The `super` Keyword + +The **[`super`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super)** keyword is your lifeline when working with inheritance. It has two main uses: + +### 1. `super()` — Calling the Parent Constructor + +When a child class has a constructor, it **must** call `super()` before using `this`. This runs the parent's constructor to set up inherited properties. + +```javascript +class Character { + constructor(name, health) { + this.name = name + this.health = health + } +} + +class Warrior extends Character { + constructor(name) { + // MUST call super() first! + super(name, 150) // Pass arguments to parent constructor + + // Now we can use 'this' + this.rage = 0 + this.weapon = "Sword" + } +} + +const warrior = new Warrior("Conan") +console.log(warrior.name) // "Conan" (set by parent) +console.log(warrior.health) // 150 (passed to parent) +console.log(warrior.rage) // 0 (set by child) +``` + +<Warning> +**Critical Rule:** You MUST call `super()` before accessing `this` in a child constructor. If you don't, JavaScript throws a `ReferenceError`: + +```javascript +class Warrior extends Character { + constructor(name) { + this.rage = 0 // ❌ ReferenceError: Must call super constructor first! + super(name) + } +} +``` +</Warning> + +### 2. `super.method()` — Calling Parent Methods + +Use `super.methodName()` to call the parent's version of an overridden method. This is perfect when you want to *extend* behavior rather than *replace* it: + +```javascript +class Character { + constructor(name, health = 100) { + this.name = name + this.health = health + } + + attack() { + return `${this.name} attacks` + } + + describe() { + return `${this.name} (${this.health} HP)` + } +} + +class Warrior extends Character { + constructor(name) { + super(name, 150) // Pass name and custom health to parent + this.weapon = "Sword" + } + + attack() { + // Call parent's attack, then add to it + const baseAttack = super.attack() + return `${baseAttack} with a ${this.weapon}!` + } + + describe() { + // Extend parent's description + return `${super.describe()} - Warrior Class` + } +} + +const hero = new Warrior("Aragorn") +console.log(hero.attack()) // "Aragorn attacks with a Sword!" +console.log(hero.describe()) // "Aragorn (150 HP) - Warrior Class" +``` + +<Tip> +**Pattern: Extend, Don't Replace.** When overriding methods, consider calling `super.method()` first to preserve parent behavior, then add child-specific logic. This keeps your code DRY and ensures parent functionality isn't accidentally lost. +</Tip> + +--- + +## Method Overriding + +**Method overriding** occurs when a child class defines a method with the same name as one in its parent class. The child's version "shadows" the parent's version — when you call that method on a child instance, the child's implementation runs. + +```javascript +class Character { + attack() { + return `${this.name} attacks!` + } +} + +class Warrior extends Character { + attack() { + return `${this.name} swings a mighty sword for 25 damage!` + } +} + +class Mage extends Character { + attack() { + return `${this.name} hurls a fireball for 30 damage!` + } +} + +class Archer extends Character { + attack() { + return `${this.name} fires an arrow for 20 damage!` + } +} + +// Each class has the SAME method name, but DIFFERENT behavior +const warrior = new Warrior("Conan") +const mage = new Mage("Gandalf") +const archer = new Archer("Legolas") + +console.log(warrior.attack()) // "Conan swings a mighty sword for 25 damage!" +console.log(mage.attack()) // "Gandalf hurls a fireball for 30 damage!" +console.log(archer.attack()) // "Legolas fires an arrow for 20 damage!" +``` + +### Why Override Methods? + +| Reason | Example | +|--------|---------| +| **Specialization** | Each character type attacks differently | +| **Extension** | Add logging before calling `super.method()` | +| **Customization** | Change default values or behavior | +| **Performance** | Optimize for specific use case | + +### Extending vs Replacing + +You have two choices when overriding: + +<Tabs> + <Tab title="Replace Completely"> + ```javascript + class Warrior extends Character { + // Completely new implementation + attack() { + this.rage += 5 + const damage = 20 + this.rage + return `${this.name} rages and deals ${damage} damage!` + } + } + ``` + </Tab> + <Tab title="Extend Parent"> + ```javascript + class Warrior extends Character { + // Build on parent's behavior + attack() { + const base = super.attack() // "Conan attacks!" + this.rage += 5 + return `${base} Rage builds to ${this.rage}!` + } + } + ``` + </Tab> +</Tabs> + +--- + +## What is Polymorphism? + +**Polymorphism** (from Greek: "many forms") means that objects of different types can be treated through a common interface. In JavaScript, this primarily manifests as **subtype polymorphism**: child class instances can be used wherever a parent class instance is expected. + +The magic happens when you call the same method on different objects, and each responds in its own way: + +```javascript +class Character { + constructor(name) { + this.name = name + this.health = 100 + } + + attack() { + return `${this.name} attacks!` + } +} + +class Warrior extends Character { + attack() { + return `${this.name} swings a sword!` + } +} + +class Mage extends Character { + attack() { + return `${this.name} casts a spell!` + } +} + +class Archer extends Character { + attack() { + return `${this.name} shoots an arrow!` + } +} + +// THE POLYMORPHISM POWER MOVE +// This function works with ANY Character type! +function executeBattle(characters) { + console.log("⚔️ Battle begins!") + + characters.forEach(char => { + // Each character attacks in their OWN way + console.log(char.attack()) + }) +} + +// Mix of different types — polymorphism in action! +const party = [ + new Warrior("Conan"), + new Mage("Gandalf"), + new Archer("Legolas"), + new Character("Villager") // Even the base class works! +] + +executeBattle(party) +// ⚔️ Battle begins! +// "Conan swings a sword!" +// "Gandalf casts a spell!" +// "Legolas shoots an arrow!" +// "Villager attacks!" +``` + +### Why Polymorphism is Powerful + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ POLYMORPHISM: WRITE ONCE, USE MANY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ WITHOUT Polymorphism WITH Polymorphism │ +│ ───────────────────── ───────────────── │ +│ │ +│ function battle(char) { function battle(char) { │ +│ if (char instanceof Warrior) { char.attack() // That's it! │ +│ char.swingSword() } │ +│ } else if (char instanceof // Works with Warrior, Mage, │ +│ Mage) { // Archer, and ANY future type! │ +│ char.castSpell() │ +│ } else if (char instanceof │ +│ Archer) { │ +│ char.shootArrow() │ +│ } │ +│ // Need to add code for │ +│ // every new character type! │ +│ } │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +| Benefit | Explanation | +|---------|-------------| +| **Open for Extension** | Add new character types without changing battle logic | +| **Loose Coupling** | `executeBattle` doesn't need to know about specific types | +| **Cleaner Code** | No endless `if/else` or `switch` statements | +| **Easier Testing** | Test with mock objects that share the interface | + +### The `instanceof` Operator + +Use **[`instanceof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)** to check if an object is an instance of a class (or its parents): + +```javascript +const warrior = new Warrior("Conan") + +console.log(warrior instanceof Warrior) // true (direct) +console.log(warrior instanceof Character) // true (parent) +console.log(warrior instanceof Object) // true (all objects) +console.log(warrior instanceof Mage) // false (different branch) +``` + +--- + +## Under the Hood: Prototypes + +Here's a secret: ES6 `class` and `extends` are **syntactic sugar** over JavaScript's prototype-based inheritance. When you write `class Warrior extends Character`, JavaScript is really setting up a prototype chain behind the scenes. + +```javascript +// What you write (ES6 class syntax) +class Character { + constructor(name) { + this.name = name + } + attack() { + return `${this.name} attacks!` + } +} + +// Note: In this example, Warrior does NOT override attack() +// This lets us see how the prototype chain lookup works +class Warrior extends Character { + constructor(name) { + super(name) + this.rage = 0 + } + + // Warrior-specific method (not on Character) + battleCry() { + return `${this.name} roars!` + } +} + +// What JavaScript actually creates (simplified) +// Warrior.prototype.__proto__ === Character.prototype +``` + +When you call `warrior.attack()`, JavaScript walks up the prototype chain: +1. Looks for `attack` on the `warrior` instance itself — not found +2. Looks on `Warrior.prototype` — not found (Warrior didn't override it) +3. Follows the chain to `Character.prototype` — **found!** Executes it + +This is why inheritance "just works" — methods defined on parent classes are automatically available to child instances through the prototype chain. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PROTOTYPE CHAIN │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ warrior (instance) │ +│ ┌─────────────────┐ │ +│ │ name: "Conan" │ │ +│ │ rage: 0 │ │ +│ │ [[Prototype]] ──┼──┐ │ +│ └─────────────────┘ │ │ +│ ▼ │ +│ Warrior.prototype ┌─────────────────┐ │ +│ │ constructor │ │ +│ │ [[Prototype]] ──┼──┐ │ +│ └─────────────────┘ │ │ +│ ▼ │ +│ Character.prototype ┌─────────────────┐ │ +│ │ attack() │ ← Found here! │ +│ │ constructor │ │ +│ │ [[Prototype]] ──┼──► Object.prototype │ +│ └─────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Tip> +**Rule of Thumb:** Use ES6 `class` syntax for cleaner, more readable code. Understand prototypes for debugging and advanced patterns. For a deep dive, see our [Object Creation & Prototypes](/concepts/object-creation-prototypes) guide. +</Tip> + +--- + +## Composition vs Inheritance + +Inheritance is powerful, but it's not always the right tool. There's a famous saying in programming: + +> "You wanted a banana but got a gorilla holding the banana and the entire jungle." + +This is the **Gorilla-Banana Problem** — when you inherit from a class, you inherit *everything*, even the stuff you don't need. + +### When Inheritance Goes Wrong + +```javascript +// Inheritance nightmare — deep, rigid hierarchy +class Animal { } +class Mammal extends Animal { } +class WingedMammal extends Mammal { } +class Bat extends WingedMammal { } + +// Oh no! Now we need a FlyingFish... +// Fish aren't mammals! Do we create another branch? +// What about a Penguin (bird that can't fly)? + +// The hierarchy becomes fragile and hard to change +``` + +### The "IS-A" vs "HAS-A" Test + +| Question | If Yes... | Example | +|----------|-----------|---------| +| Is a Warrior **a type of** Character? | Use inheritance | `class Warrior extends Character` | +| Does a Character **have** inventory? | Use composition | `this.inventory = new Inventory()` | + +### Composition: Building with "HAS-A" + +Instead of inheriting behavior, you **compose** objects from smaller, reusable pieces: + +<Tabs> + <Tab title="Inheritance Approach"> + ```javascript + // Rigid hierarchy — what if we need a flying warrior? + class Character { } + class FlyingCharacter extends Character { + fly() { return `${this.name} flies!` } + } + class MagicCharacter extends Character { + castSpell() { return `${this.name} casts!` } + } + // Can't have a character that BOTH flies AND casts! + ``` + </Tab> + <Tab title="Composition Approach"> + ```javascript + // Flexible behaviors — mix and match! + const canFly = (state) => ({ + fly() { return `${state.name} soars through the sky!` } + }) + + const canCast = (state) => ({ + castSpell(spell) { + return `${state.name} casts ${spell}!` + } + }) + + const canFight = (state) => ({ + attack() { return `${state.name} attacks!` } + }) + + // Create a flying mage — compose the behaviors you need! + function createFlyingMage(name) { + const state = { name, health: 100, mana: 50 } + return { + ...state, + ...canFly(state), + ...canCast(state), + ...canFight(state) + } + } + + const merlin = createFlyingMage("Merlin") + console.log(merlin.fly()) // "Merlin soars through the sky!" + console.log(merlin.castSpell("Ice")) // "Merlin casts Ice!" + console.log(merlin.attack()) // "Merlin attacks!" + ``` + </Tab> +</Tabs> + +### When to Use Each + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ INHERITANCE vs COMPOSITION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Use INHERITANCE when: Use COMPOSITION when: │ +│ ───────────────────── ──────────────────── │ +│ │ +│ • Clear "IS-A" relationship • "HAS-A" relationship │ +│ (Warrior IS-A Character) (Character HAS inventory) │ +│ │ +│ • Child uses MOST of parent's • Only need SOME behaviors │ +│ functionality │ +│ │ +│ • Hierarchy is shallow • Behaviors need to be mixed │ +│ (2-3 levels max) freely │ +│ │ +│ • Relationships are stable • Requirements change frequently │ +│ and unlikely to change │ +│ │ +│ • You control the parent class • Inheriting from 3rd party code │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Warning> +**The Rule of Thumb:** "Favor composition over inheritance." Start with composition. Only use inheritance when you have a clear, stable "IS-A" relationship and the child truly needs most of the parent's behavior. +</Warning> + +--- + +## Mixins: Sharing Behavior Without Inheritance + +**Mixins** provide a way to add functionality to classes without using inheritance. They're like a toolkit of behaviors you can "mix in" to any class. + +### Basic Mixin Pattern + +```javascript +// Define behaviors as objects +const Swimmer = { + swim() { + return `${this.name} swims through the water!` + } +} + +const Flyer = { + fly() { + return `${this.name} soars through the sky!` + } +} + +const Walker = { + walk() { + return `${this.name} walks on land!` + } +} + +// A base class +class Animal { + constructor(name) { + this.name = name + } +} + +// Mix behaviors into classes as needed +class Duck extends Animal { } +Object.assign(Duck.prototype, Swimmer, Flyer, Walker) + +class Fish extends Animal { } +Object.assign(Fish.prototype, Swimmer) + +class Eagle extends Animal { } +Object.assign(Eagle.prototype, Flyer, Walker) + +// Use them! +const donald = new Duck("Donald") +console.log(donald.swim()) // "Donald swims through the water!" +console.log(donald.fly()) // "Donald soars through the sky!" +console.log(donald.walk()) // "Donald walks on land!" + +const nemo = new Fish("Nemo") +console.log(nemo.swim()) // "Nemo swims through the water!" +// nemo.fly() // ❌ Error: fly is not a function +``` + +### Functional Mixin Pattern + +A cleaner approach uses functions that take a class and return an enhanced class: + +```javascript +// Mixins as functions that enhance classes +const withLogging = (Base) => class extends Base { + log(message) { + console.log(`[${this.name}]: ${message}`) + } +} + +const withTimestamp = (Base) => class extends Base { + getTimestamp() { + return new Date().toISOString() + } +} + +// Apply mixins by wrapping the class +class Character { + constructor(name) { + this.name = name + } +} + +// Stack multiple mixins! +class LoggedCharacter extends withTimestamp(withLogging(Character)) { + doAction() { + this.log(`Action performed at ${this.getTimestamp()}`) + } +} + +const hero = new LoggedCharacter("Aragorn") +hero.doAction() // "[Aragorn]: Action performed at 2024-01-15T..." +``` + +### When to Use Mixins + +| Use Case | Example | +|----------|---------| +| Cross-cutting concerns | Logging, serialization, event handling | +| Multiple behaviors needed | A class that needs swimming AND flying | +| Third-party class extension | Adding methods to classes you don't control | +| Avoiding deep hierarchies | Instead of `FlyingSwimmingWalkingAnimal` | + +<Warning> +**Mixin Gotchas:** +- **Name collisions**: If two mixins define the same method, one overwrites the other +- **"this" confusion**: Mixins must work with whatever `this` they're mixed into +- **Hidden dependencies**: Mixins might expect certain properties to exist +- **Debugging difficulty**: Hard to trace where methods come from +</Warning> + +--- + +## Common Mistakes + +### 1. Forgetting to Call `super()` in Constructor + +```javascript +// ❌ WRONG — ReferenceError! +class Warrior extends Character { + constructor(name) { + this.rage = 0 // Error: must call super first! + super(name) + } +} + +// ✓ CORRECT — super() first, always +class Warrior extends Character { + constructor(name) { + super(name) // FIRST! + this.rage = 0 // Now this is safe + } +} +``` + +### 2. Using `this` Before `super()` + +```javascript +// ❌ WRONG — Can't use 'this' until super() is called +class Mage extends Character { + constructor(name, mana) { + this.mana = mana // ReferenceError! + super(name) + } +} + +// ✓ CORRECT +class Mage extends Character { + constructor(name, mana) { + super(name) + this.mana = mana // Works now! + } +} +``` + +### 3. Deep Inheritance Hierarchies + +```javascript +// ❌ BAD — Too deep, too fragile +class Entity { } +class LivingEntity extends Entity { } +class Animal extends LivingEntity { } +class Mammal extends Animal { } +class Canine extends Mammal { } +class Dog extends Canine { } +class Labrador extends Dog { } // 7 levels deep! 😱 + +// ✓ BETTER — Keep it shallow, use composition +class Dog { + constructor(breed) { + this.breed = breed + this.behaviors = { + ...canWalk, + ...canBark, + ...canFetch + } + } +} +``` + +### 4. Inheriting Just for Code Reuse + +```javascript +// ❌ WRONG — Stack is NOT an Array (violates IS-A) +class Stack extends Array { + peek() { return this[this.length - 1] } +} + +const stack = new Stack() +stack.push(1, 2, 3) +stack.shift() // 😱 Stacks shouldn't allow this! + +// ✓ CORRECT — Stack HAS-A array (composition) +class Stack { + #items = [] + + push(item) { this.#items.push(item) } + pop() { return this.#items.pop() } + peek() { return this.#items[this.#items.length - 1] } +} +``` + +### Inheritance Decision Flowchart + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ SHOULD I USE INHERITANCE? │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Is it an "IS-A" relationship? │ +│ (A Warrior IS-A Character?) │ +│ │ │ +│ YES │ NO │ +│ │ └──────► Use COMPOSITION ("HAS-A") │ +│ ▼ │ +│ Will child use MOST of parent's methods? │ +│ │ │ +│ YES │ NO │ +│ │ └──────► Use COMPOSITION or MIXINS │ +│ ▼ │ +│ Is hierarchy shallow (≤3 levels)? │ +│ │ │ +│ YES │ NO │ +│ │ └──────► REFACTOR! Flatten with composition │ +│ ▼ │ +│ Use INHERITANCE ✓ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Classic Interview Questions + +<AccordionGroup> + <Accordion title="What's the difference between inheritance and composition?"> + **Inheritance** establishes an "IS-A" relationship where a child class inherits all properties and methods from a parent class. It creates a tight coupling between classes. + + **Composition** establishes a "HAS-A" relationship where a class contains instances of other classes to reuse their functionality. It provides more flexibility and loose coupling. + + ```javascript + // Inheritance: Warrior IS-A Character + class Warrior extends Character { } + + // Composition: Character HAS-A weapon + class Character { + constructor() { + this.weapon = new Sword() // HAS-A + } + } + ``` + + **Rule of thumb:** Favor composition for flexibility, use inheritance for true type hierarchies. + </Accordion> + + <Accordion title="Explain polymorphism with an example"> + **Polymorphism** means "many forms" — the ability for different objects to respond to the same method call in different ways. + + ```javascript + class Shape { + area() { return 0 } + } + + class Rectangle extends Shape { + constructor(w, h) { super(); this.w = w; this.h = h } + area() { return this.w * this.h } + } + + class Circle extends Shape { + constructor(r) { super(); this.r = r } + area() { return Math.PI * this.r ** 2 } + } + + // Polymorphism in action — same method, different results + const shapes = [new Rectangle(4, 5), new Circle(3)] + shapes.forEach(s => console.log(s.area())) + // 20 + // 28.274... + ``` + + The `area()` method works differently based on the actual object type, but we can treat all shapes uniformly. + </Accordion> + + <Accordion title="What does the 'super' keyword do in JavaScript?"> + `super` has two main uses: + + 1. **`super()`** — Calls the parent class constructor (required in child constructors before using `this`) + 2. **`super.method()`** — Calls a method from the parent class + + ```javascript + class Parent { + constructor(name) { this.name = name } + greet() { return `Hello, I'm ${this.name}` } + } + + class Child extends Parent { + constructor(name, age) { + super(name) // Call parent constructor + this.age = age + } + + greet() { + return `${super.greet()} and I'm ${this.age}` // Call parent method + } + } + ``` + </Accordion> + + <Accordion title="Why might deep inheritance hierarchies be problematic?"> + Deep hierarchies (more than 3 levels) create several problems: + + 1. **Fragile Base Class Problem**: Changes to a parent class can break many descendants + 2. **Tight Coupling**: Child classes become dependent on implementation details + 3. **Inflexibility**: Hard to reuse code outside the hierarchy + 4. **Complexity**: Difficult to understand and debug method resolution + 5. **The Gorilla-Banana Problem**: You inherit everything, even what you don't need + + **Solution:** Keep hierarchies shallow (2-3 levels max) and prefer composition for sharing behavior. + </Accordion> + + <Accordion title="How does JavaScript inheritance differ from classical OOP languages?"> + JavaScript uses **prototype-based inheritance** rather than class-based: + + | Classical OOP (Java, C++) | JavaScript | + |---------------------------|------------| + | Classes are blueprints | "Classes" are functions with prototypes | + | Objects are instances of classes | Objects inherit from other objects | + | Static class hierarchy | Dynamic prototype chain | + | Multiple inheritance via interfaces | Single prototype chain (use mixins for multiple) | + + ES6 `class` syntax is syntactic sugar — under the hood, it's still prototypes: + + ```javascript + class Dog extends Animal { } + + // Is equivalent to setting up: + // Dog.prototype.__proto__ === Animal.prototype + ``` + </Accordion> +</AccordionGroup> + +--- + +## Key Takeaways + +<Info> +**Remember these essential points about Inheritance & Polymorphism:** + +1. **Inheritance lets child classes reuse parent code** — use `extends` to create class hierarchies + +2. **Always call `super()` first in child constructors** — before using `this` + +3. **`super.method()` calls the parent's version** — useful for extending rather than replacing behavior + +4. **Method overriding = same name, different behavior** — the child's method shadows the parent's + +5. **Polymorphism = "many forms"** — treat different object types through a common interface + +6. **ES6 classes are syntactic sugar over prototypes** — understand prototypes for debugging + +7. **"IS-A" → inheritance, "HAS-A" → composition** — use the right tool for the relationship + +8. **The Gorilla-Banana problem is real** — deep hierarchies inherit too much baggage + +9. **Favor composition over inheritance** — it's more flexible and maintainable + +10. **Keep inheritance hierarchies shallow** — 2-3 levels maximum + +11. **Mixins share behavior without inheritance chains** — useful for cross-cutting concerns + +12. **`instanceof` checks the entire prototype chain** — `warrior instanceof Character` is `true` +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="1. What happens if you forget to call super() in a child constructor?"> + **Answer:** JavaScript throws a `ReferenceError` with the message "Must call super constructor in derived class before accessing 'this' or returning from derived constructor". + + ```javascript + class Child extends Parent { + constructor() { + this.name = "test" // ❌ ReferenceError! + } + } + ``` + + The `super()` call is mandatory because it initializes the parent part of the object, which must happen before the child can add its own properties. + </Accordion> + + <Accordion title="2. How does method overriding enable polymorphism?"> + **Answer:** Method overriding allows different classes to provide their own implementation of the same method name. This enables polymorphism because code can call that method on any object without knowing its specific type — each object responds appropriately. + + ```javascript + function makeSound(animal) { + console.log(animal.speak()) // Works with ANY animal type + } + + class Dog { speak() { return "Woof!" } } + class Cat { speak() { return "Meow!" } } + + makeSound(new Dog()) // "Woof!" + makeSound(new Cat()) // "Meow!" + ``` + </Accordion> + + <Accordion title="3. When should you prefer composition over inheritance?"> + **Answer:** Prefer composition when: + + - The relationship is "HAS-A" rather than "IS-A" + - You only need some of the parent's functionality + - Behaviors need to be mixed freely (e.g., flying + swimming) + - Requirements change frequently + - You're working with third-party code you don't control + - The inheritance hierarchy would exceed 3 levels + + ```javascript + // Use composition: Character HAS abilities + class Character { + constructor() { + this.abilities = [canAttack, canDefend, canHeal] + } + } + ``` + </Accordion> + + <Accordion title="4. What's a mixin and when would you use one?"> + **Answer:** A mixin is a way to add functionality to classes without using inheritance. It's an object (or function) containing methods that can be "mixed into" multiple classes. + + Use mixins for: + - Cross-cutting concerns (logging, serialization) + - When a class needs behaviors from multiple sources + - Avoiding the diamond problem of multiple inheritance + + ```javascript + const Serializable = { + toJSON() { return JSON.stringify(this) } + } + + class User { constructor(name) { this.name = name } } + Object.assign(User.prototype, Serializable) + + new User("Alice").toJSON() // '{"name":"Alice"}' + ``` + </Accordion> + + <Accordion title="5. How can you call a parent's method from an overriding method?"> + **Answer:** Use `super.methodName()` to call the parent's version of an overridden method: + + ```javascript + class Parent { + greet() { return "Hello" } + } + + class Child extends Parent { + greet() { + const parentGreeting = super.greet() // "Hello" + return `${parentGreeting} from Child!` + } + } + + new Child().greet() // "Hello from Child!" + ``` + + This is useful when you want to extend behavior rather than completely replace it. + </Accordion> + + <Accordion title="6. What's the 'IS-A' test for inheritance?"> + **Answer:** The "IS-A" test determines if inheritance is appropriate by asking: "Is the child truly a specialized type of the parent?" + + - **Passes:** "A Warrior IS-A Character" ✓ + - **Passes:** "A Dog IS-A Animal" ✓ + - **Fails:** "A Stack IS-A Array" ✗ (Stack has different behavior) + - **Fails:** "A Car IS-A Engine" ✗ (Car HAS-A Engine) + + If it fails the IS-A test, use composition instead. This prevents the Liskov Substitution Principle violations where child instances can't properly substitute for parent instances. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Factories & Classes" icon="hammer" href="/concepts/factories-classes"> + Learn the fundamentals of creating objects with factory functions and ES6 classes + </Card> + <Card title="Object Creation & Prototypes" icon="sitemap" href="/concepts/object-creation-prototypes"> + Understand the prototype chain that powers JavaScript inheritance + </Card> + <Card title="this, call, apply, bind" icon="bullseye" href="/concepts/this-call-apply-bind"> + Master context binding — essential for understanding method inheritance + </Card> + <Card title="Design Patterns" icon="compass-drafting" href="/concepts/design-patterns"> + Learn patterns like Strategy and Decorator that use polymorphism + </Card> +</CardGroup> + +--- ## Reference <CardGroup cols={2}> - <Card title="Inheritance in JavaScript — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance"> - MDN documentation + <Card title="Classes — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes"> + Complete guide to ES6 classes in JavaScript + </Card> + <Card title="extends — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends"> + Official documentation for the extends keyword </Card> - <Card title="Class inheritance, super — JavaScript.Info" icon="book" href="https://javascript.info/class-inheritance"> - JavaScript.Info + <Card title="super — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super"> + How to use super for parent class access + </Card> + <Card title="Inheritance and the prototype chain — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain"> + Deep dive into how inheritance really works in JavaScript </Card> </CardGroup> ## Articles <CardGroup cols={2}> - <Card title="Inheritance in JavaScript" icon="newspaper" href="https://hackernoon.com/inheritance-in-javascript-21d2b82ffa6f"> - By Rupesh Mishra + <Card title="Class Inheritance — JavaScript.info" icon="newspaper" href="https://javascript.info/class-inheritance"> + A comprehensive guide to class inheritance with extends and super </Card> - <Card title="Simple Inheritance with JavaScript" icon="newspaper" href="https://www.sitepoint.com/simple-inheritance-javascript/"> - By David Catuhe + <Card title="Understanding Classes in JavaScript — DigitalOcean" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-classes-in-javascript"> + Deep exploration of ES6 class syntax and OOP principles </Card> -</CardGroup> -- [JavaScript — Inheritance, delegation patterns and Object linking — NC Patro](https://codeburst.io/javascript-inheritance-25fe61ab9f85) -- [Object Oriented JavaScript: Polymorphism with examples — Knoldus Blogs](https://blog.knoldus.com/object-oriented-javascript-polymorphism-with-examples/) -- [Program Like Proteus — A beginner's guide to polymorphism in Javascript — Sam Galson](https://medium.com/yld-blog/program-like-proteus-a-beginners-guide-to-polymorphism-in-javascript-867bea7c8be2) -- [Object-oriented JavaScript: A Deep Dive into ES6 Classes — Jeff Mott](https://www.sitepoint.com/object-oriented-javascript-deep-dive-es6-classes/) -- [Unlocking the Power of Polymorphism in JavaScript: A Deep Dive](https://prototypr.io/post/unlocking-the-power-of-polymorphism-in-javascript-a-deep-dive) + <Card title="The Gorilla-Banana Problem" icon="newspaper" href="https://www.johndcook.com/blog/2011/07/19/you-wanted-banana/"> + The origin of the famous inheritance criticism + </Card> +</CardGroup> ## Videos <CardGroup cols={2}> + <Card title="JavaScript ES6 Classes and Inheritance" icon="video" href="https://www.youtube.com/watch?v=RBLIm5LMrmc"> + Traversy Media's tutorial on ES6 class inheritance + </Card> <Card title="Inheritance in JavaScript" icon="video" href="https://www.youtube.com/watch?v=yXlFR81tDBM"> - By kudvenkat + Detailed walkthrough of inheritance concepts by kudvenkat </Card> - <Card title="JavaScript ES6 Classes and Inheritance" icon="video" href="https://www.youtube.com/watch?v=RBLIm5LMrmc"> - By Traversy Media + <Card title="Composition over Inheritance" icon="video" href="https://www.youtube.com/watch?v=wfMtDGfHWpA"> + Fun Fun Function explains why composition is often better + </Card> + <Card title="Polymorphism in JavaScript" icon="video" href="https://www.youtube.com/watch?v=zdovG9cuEBA"> + Clear explanation of polymorphism with practical examples </Card> </CardGroup> - -- [Polymorphism in JavaScript — kudvenkat](https://www.youtube.com/watch?v=zdovG9cuEBA) diff --git a/tests/object-oriented/inheritance-polymorphism/inheritance-polymorphism.test.js b/tests/object-oriented/inheritance-polymorphism/inheritance-polymorphism.test.js new file mode 100644 index 00000000..8e0431c7 --- /dev/null +++ b/tests/object-oriented/inheritance-polymorphism/inheritance-polymorphism.test.js @@ -0,0 +1,657 @@ +import { describe, it, expect } from 'vitest' + +describe('Inheritance & Polymorphism', () => { + // ============================================================ + // BASE CLASSES FOR TESTING + // ============================================================ + + class Character { + constructor(name, health = 100) { + this.name = name + this.health = health + } + + introduce() { + return `I am ${this.name} with ${this.health} HP` + } + + attack() { + return `${this.name} attacks!` + } + + takeDamage(amount) { + this.health -= amount + return `${this.name} takes ${amount} damage! (${this.health} HP left)` + } + + get isAlive() { + return this.health > 0 + } + + static createRandom() { + const names = ['Hero', 'Villain', 'Sidekick'] + return new this(names[Math.floor(Math.random() * names.length)]) + } + } + + class Warrior extends Character { + constructor(name) { + super(name, 150) // Warriors have more health + this.rage = 0 + this.weapon = 'Sword' + } + + attack() { + return `${this.name} swings a mighty sword!` + } + + battleCry() { + this.rage += 10 + return `${this.name} roars with fury! Rage: ${this.rage}` + } + } + + class Mage extends Character { + constructor(name) { + super(name, 80) // Mages have less health + this.mana = 100 + } + + attack() { + return `${this.name} casts a fireball!` + } + + castSpell(spell) { + this.mana -= 10 + return `${this.name} casts ${spell}!` + } + } + + class Archer extends Character { + constructor(name) { + super(name, 90) + this.arrows = 20 + } + + attack() { + this.arrows-- + return `${this.name} fires an arrow!` + } + } + + // ============================================================ + // CLASS INHERITANCE WITH EXTENDS + // ============================================================ + + describe('Class Inheritance with extends', () => { + it('should inherit properties from parent class', () => { + const warrior = new Warrior('Conan') + + // Inherited from Character + expect(warrior.name).toBe('Conan') + expect(warrior.health).toBe(150) // Custom value passed to super() + + // Unique to Warrior + expect(warrior.rage).toBe(0) + expect(warrior.weapon).toBe('Sword') + }) + + it('should inherit methods from parent class', () => { + const warrior = new Warrior('Conan') + + // Inherited method works + expect(warrior.introduce()).toBe('I am Conan with 150 HP') + expect(warrior.takeDamage(20)).toBe('Conan takes 20 damage! (130 HP left)') + }) + + it('should inherit getters from parent class', () => { + const warrior = new Warrior('Conan') + + expect(warrior.isAlive).toBe(true) + warrior.health = 0 + expect(warrior.isAlive).toBe(false) + }) + + it('should inherit static methods from parent class', () => { + const randomWarrior = Warrior.createRandom() + + expect(randomWarrior).toBeInstanceOf(Warrior) + expect(randomWarrior).toBeInstanceOf(Character) + expect(['Hero', 'Villain', 'Sidekick']).toContain(randomWarrior.name) + }) + + it('should allow child classes to have unique methods', () => { + const warrior = new Warrior('Conan') + const mage = new Mage('Gandalf') + + // Warrior-specific method + expect(warrior.battleCry()).toBe('Conan roars with fury! Rage: 10') + expect(typeof mage.battleCry).toBe('undefined') + + // Mage-specific method + expect(mage.castSpell('Fireball')).toBe('Gandalf casts Fireball!') + expect(typeof warrior.castSpell).toBe('undefined') + }) + }) + + // ============================================================ + // THE SUPER KEYWORD + // ============================================================ + + describe('The super Keyword', () => { + it('super() should call parent constructor with arguments', () => { + const warrior = new Warrior('Conan') + + // super(name, 150) was called in Warrior constructor + expect(warrior.name).toBe('Conan') + expect(warrior.health).toBe(150) + }) + + it('super.method() should call parent method', () => { + class ExtendedWarrior extends Character { + constructor(name) { + super(name, 150) + this.weapon = 'Axe' + } + + attack() { + const baseAttack = super.attack() // "Name attacks!" + return `${baseAttack} With an ${this.weapon}!` + } + + describe() { + return `${super.introduce()} - Warrior Class` + } + } + + const hero = new ExtendedWarrior('Gimli') + + expect(hero.attack()).toBe('Gimli attacks! With an Axe!') + expect(hero.describe()).toBe('I am Gimli with 150 HP - Warrior Class') + }) + + it('should throw ReferenceError if super() is not called before this', () => { + // This would cause an error - we test the concept + expect(() => { + class BrokenWarrior extends Character { + constructor(name) { + // Intentionally not calling super() first + // this.rage = 0 // Would throw ReferenceError + super(name) + } + } + new BrokenWarrior('Test') + }).not.toThrow() // The fixed version doesn't throw + }) + }) + + // ============================================================ + // METHOD OVERRIDING + // ============================================================ + + describe('Method Overriding', () => { + it('should override parent method with child implementation', () => { + const character = new Character('Generic') + const warrior = new Warrior('Conan') + const mage = new Mage('Gandalf') + const archer = new Archer('Legolas') + + // Each class has different attack() implementation + expect(character.attack()).toBe('Generic attacks!') + expect(warrior.attack()).toBe('Conan swings a mighty sword!') + expect(mage.attack()).toBe('Gandalf casts a fireball!') + expect(archer.attack()).toBe('Legolas fires an arrow!') + }) + + it('should allow extending parent behavior with super.method()', () => { + class VerboseWarrior extends Character { + attack() { + return `${super.attack()} POWERFULLY!` + } + } + + const hero = new VerboseWarrior('Hero') + expect(hero.attack()).toBe('Hero attacks! POWERFULLY!') + }) + + it('should allow complete replacement of parent behavior', () => { + class SilentWarrior extends Character { + attack() { + return '...' // Completely different, no super.attack() + } + } + + const ninja = new SilentWarrior('Shadow') + expect(ninja.attack()).toBe('...') + }) + }) + + // ============================================================ + // POLYMORPHISM + // ============================================================ + + describe('Polymorphism', () => { + it('should treat different types uniformly through common interface', () => { + const party = [ + new Warrior('Conan'), + new Mage('Gandalf'), + new Archer('Legolas'), + new Character('Villager') + ] + + // All can attack(), each in their own way + const attacks = party.map(char => char.attack()) + + expect(attacks).toEqual([ + 'Conan swings a mighty sword!', + 'Gandalf casts a fireball!', + 'Legolas fires an arrow!', + 'Villager attacks!' + ]) + }) + + it('should allow functions to work with any subtype', () => { + function executeBattle(characters) { + return characters.map(char => char.attack()) + } + + const team1 = [new Warrior('W1'), new Warrior('W2')] + const team2 = [new Mage('M1'), new Archer('A1')] + const mixedTeam = [new Warrior('W'), new Mage('M'), new Archer('A')] + + // Same function works with any combination + expect(executeBattle(team1)).toHaveLength(2) + expect(executeBattle(team2)).toHaveLength(2) + expect(executeBattle(mixedTeam)).toHaveLength(3) + }) + + it('instanceof should check entire prototype chain', () => { + const warrior = new Warrior('Conan') + + expect(warrior instanceof Warrior).toBe(true) + expect(warrior instanceof Character).toBe(true) + expect(warrior instanceof Object).toBe(true) + expect(warrior instanceof Mage).toBe(false) + }) + + it('should enable the Open/Closed principle', () => { + // We can add new character types without changing existing code + class Healer extends Character { + attack() { + return `${this.name} heals the party!` + } + } + + // Existing function works with new type + function getAttacks(chars) { + return chars.map(c => c.attack()) + } + + const team = [new Warrior('W'), new Healer('H')] + const attacks = getAttacks(team) + + expect(attacks).toContain('W swings a mighty sword!') + expect(attacks).toContain('H heals the party!') + }) + }) + + // ============================================================ + // PROTOTYPE CHAIN (Under the Hood) + // ============================================================ + + describe('Prototype Chain', () => { + it('should set up prototype chain correctly with extends', () => { + const warrior = new Warrior('Conan') + + // Instance -> Warrior.prototype -> Character.prototype -> Object.prototype + expect(Object.getPrototypeOf(warrior)).toBe(Warrior.prototype) + expect(Object.getPrototypeOf(Warrior.prototype)).toBe(Character.prototype) + expect(Object.getPrototypeOf(Character.prototype)).toBe(Object.prototype) + }) + + it('should find methods by walking up the prototype chain', () => { + const warrior = new Warrior('Conan') + + // attack() is on Warrior.prototype (overridden) + expect(Warrior.prototype.hasOwnProperty('attack')).toBe(true) + + // introduce() is on Character.prototype (inherited) + expect(Warrior.prototype.hasOwnProperty('introduce')).toBe(false) + expect(Character.prototype.hasOwnProperty('introduce')).toBe(true) + + // Both work on the instance + expect(warrior.attack()).toContain('sword') + expect(warrior.introduce()).toContain('Conan') + }) + }) + + // ============================================================ + // COMPOSITION PATTERN + // ============================================================ + + describe('Composition Pattern', () => { + it('should compose behaviors instead of inheriting', () => { + // Behavior factories + const canFly = (state) => ({ + fly() { return `${state.name} soars through the sky!` } + }) + + const canCast = (state) => ({ + castSpell(spell) { return `${state.name} casts ${spell}!` } + }) + + const canFight = (state) => ({ + attack() { return `${state.name} attacks!` } + }) + + // Compose a flying mage + function createFlyingMage(name) { + const state = { name, health: 100, mana: 50 } + return { + ...state, + ...canFly(state), + ...canCast(state), + ...canFight(state) + } + } + + const merlin = createFlyingMage('Merlin') + + expect(merlin.fly()).toBe('Merlin soars through the sky!') + expect(merlin.castSpell('Ice')).toBe('Merlin casts Ice!') + expect(merlin.attack()).toBe('Merlin attacks!') + expect(merlin.health).toBe(100) + expect(merlin.mana).toBe(50) + }) + + it('should allow mixing and matching behaviors freely', () => { + const canSwim = (state) => ({ + swim() { return `${state.name} swims!` } + }) + + const canFly = (state) => ({ + fly() { return `${state.name} flies!` } + }) + + // Duck can both swim and fly + function createDuck(name) { + const state = { name } + return { ...state, ...canSwim(state), ...canFly(state) } + } + + // Fish can only swim + function createFish(name) { + const state = { name } + return { ...state, ...canSwim(state) } + } + + const duck = createDuck('Donald') + const fish = createFish('Nemo') + + expect(duck.swim()).toBe('Donald swims!') + expect(duck.fly()).toBe('Donald flies!') + expect(fish.swim()).toBe('Nemo swims!') + expect(fish.fly).toBeUndefined() + }) + }) + + // ============================================================ + // MIXINS + // ============================================================ + + describe('Mixins', () => { + it('should mix behavior into class prototype with Object.assign', () => { + const Swimmer = { + swim() { return `${this.name} swims!` } + } + + const Flyer = { + fly() { return `${this.name} flies!` } + } + + class Animal { + constructor(name) { + this.name = name + } + } + + class Duck extends Animal {} + Object.assign(Duck.prototype, Swimmer, Flyer) + + const donald = new Duck('Donald') + + expect(donald.swim()).toBe('Donald swims!') + expect(donald.fly()).toBe('Donald flies!') + }) + + it('should support functional mixin pattern', () => { + const withLogging = (Base) => class extends Base { + log(message) { + return `[${this.name}]: ${message}` + } + } + + const withTimestamp = (Base) => class extends Base { + getTimestamp() { + return '2024-01-15' + } + } + + class Character { + constructor(name) { + this.name = name + } + } + + // Stack mixins + class LoggedCharacter extends withTimestamp(withLogging(Character)) { + doAction() { + return this.log(`Action at ${this.getTimestamp()}`) + } + } + + const hero = new LoggedCharacter('Aragorn') + + expect(hero.log('Hello')).toBe('[Aragorn]: Hello') + expect(hero.getTimestamp()).toBe('2024-01-15') + expect(hero.doAction()).toBe('[Aragorn]: Action at 2024-01-15') + }) + + it('should handle mixin name collisions (last one wins)', () => { + const MixinA = { + greet() { return 'Hello from A' } + } + + const MixinB = { + greet() { return 'Hello from B' } + } + + class Base {} + Object.assign(Base.prototype, MixinA, MixinB) + + const instance = new Base() + + // MixinB's greet() overwrites MixinA's + expect(instance.greet()).toBe('Hello from B') + }) + }) + + // ============================================================ + // COMMON MISTAKES + // ============================================================ + + describe('Common Mistakes', () => { + it('should demonstrate that inherited methods can be accidentally lost', () => { + class Parent { + method() { return 'parent' } + } + + class Child extends Parent { + method() { return 'child' } // Completely replaces parent + } + + const child = new Child() + expect(child.method()).toBe('child') + + // To preserve parent behavior, use super.method() + class BetterChild extends Parent { + method() { return `${super.method()} + child` } + } + + const betterChild = new BetterChild() + expect(betterChild.method()).toBe('parent + child') + }) + + it('should show the problem with inheriting for code reuse only', () => { + // BAD: Stack is NOT an Array (violates IS-A) + // A Stack should only allow push/pop, not shift/unshift + class BadStack extends Array { + peek() { return this[this.length - 1] } + } + + const badStack = new BadStack() + badStack.push(1, 2, 3) + + // Problem: Array methods we DON'T want are available + expect(badStack.shift()).toBe(1) // Stacks shouldn't allow this! + + // GOOD: Composition - Stack HAS-A array + class GoodStack { + #items = [] + + push(item) { this.#items.push(item) } + pop() { return this.#items.pop() } + peek() { return this.#items[this.#items.length - 1] } + } + + const goodStack = new GoodStack() + goodStack.push(1) + goodStack.push(2) + + expect(goodStack.peek()).toBe(2) + expect(typeof goodStack.shift).toBe('undefined') // Correctly unavailable + }) + }) + + // ============================================================ + // SHAPE POLYMORPHISM (Interview Question Example) + // ============================================================ + + describe('Shape Polymorphism (Interview Example)', () => { + class Shape { + area() { return 0 } + } + + class Rectangle extends Shape { + constructor(width, height) { + super() + this.width = width + this.height = height + } + area() { return this.width * this.height } + } + + class Circle extends Shape { + constructor(radius) { + super() + this.radius = radius + } + area() { return Math.PI * this.radius ** 2 } + } + + it('should calculate area differently for each shape type', () => { + const rectangle = new Rectangle(4, 5) + const circle = new Circle(3) + + expect(rectangle.area()).toBe(20) + expect(circle.area()).toBeCloseTo(28.274, 2) // Math.PI * 9 + }) + + it('should treat all shapes uniformly through common interface', () => { + const shapes = [new Rectangle(4, 5), new Circle(3), new Shape()] + const areas = shapes.map(s => s.area()) + + expect(areas[0]).toBe(20) + expect(areas[1]).toBeCloseTo(28.274, 2) + expect(areas[2]).toBe(0) // Base shape + }) + + it('should verify instanceof for shape hierarchy', () => { + const rect = new Rectangle(2, 3) + const circle = new Circle(5) + + expect(rect instanceof Rectangle).toBe(true) + expect(rect instanceof Shape).toBe(true) + expect(circle instanceof Circle).toBe(true) + expect(circle instanceof Shape).toBe(true) + expect(rect instanceof Circle).toBe(false) + }) + }) + + // ============================================================ + // MULTI-LEVEL INHERITANCE + // ============================================================ + + describe('Multi-level Inheritance', () => { + it('should support multi-level inheritance (keep shallow!)', () => { + class Entity { + constructor(id) { + this.id = id + } + } + + class Character extends Entity { + constructor(id, name) { + super(id) + this.name = name + } + } + + class Warrior extends Character { + constructor(id, name) { + super(id, name) + this.class = 'Warrior' + } + } + + const hero = new Warrior(1, 'Conan') + + expect(hero.id).toBe(1) + expect(hero.name).toBe('Conan') + expect(hero.class).toBe('Warrior') + + expect(hero instanceof Warrior).toBe(true) + expect(hero instanceof Character).toBe(true) + expect(hero instanceof Entity).toBe(true) + }) + + it('should call super() chain correctly', () => { + const calls = [] + + class A { + constructor() { + calls.push('A') + } + } + + class B extends A { + constructor() { + super() + calls.push('B') + } + } + + class C extends B { + constructor() { + super() + calls.push('C') + } + } + + new C() + + // Constructors called from parent to child + expect(calls).toEqual(['A', 'B', 'C']) + }) + }) +}) From 1c0339915c667ef5876355bca1f080ef0d6325d6 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 11:23:49 -0300 Subject: [PATCH 066/128] docs: add callbacks concept with synced tests - Complete callbacks documentation covering sync/async callbacks, error-first pattern, callback hell, and escape strategies - 42 tests in main file matching doc examples exactly with line refs - 10 DOM event handler tests in separate JSDOM file - Total: 52 tests covering all documentation examples --- docs/concepts/callbacks.mdx | 1436 +++++++++++++++- .../callbacks/callbacks.dom.test.js | 239 +++ .../callbacks/callbacks.test.js | 1490 +++++++++++++++++ 3 files changed, 3143 insertions(+), 22 deletions(-) create mode 100644 tests/async-javascript/callbacks/callbacks.dom.test.js create mode 100644 tests/async-javascript/callbacks/callbacks.test.js diff --git a/docs/concepts/callbacks.mdx b/docs/concepts/callbacks.mdx index 900f33ab..62e2c21b 100644 --- a/docs/concepts/callbacks.mdx +++ b/docs/concepts/callbacks.mdx @@ -1,54 +1,1446 @@ --- title: "Callbacks: The Foundation of Async JavaScript" sidebarTitle: "Callbacks: The Foundation of Async" -description: "Learn JavaScript callbacks — functions passed to other functions to be called later. Understand async patterns, callback hell, error-first callbacks, and why Promises were invented." +description: "Learn JavaScript callbacks — functions passed to other functions to be called later. Master sync vs async callbacks, error-first patterns, callback hell, and why Promises were invented." --- -## Overview +Why doesn't JavaScript wait? When you set a timer, make a network request, or listen for a click — how does your code keep running instead of freezing until that operation completes? -A **callback** is a function passed as an argument to another function, to be executed later — either synchronously or asynchronously. Callbacks are the foundation of asynchronous JavaScript: event handlers, timers, and Node.js APIs all rely on this pattern. +```javascript +console.log('Before timer') -<Warning> -**Prerequisites:** Before diving into async JavaScript, make sure you understand [the Event Loop](/concepts/event-loop) — it's the mechanism that makes all async code work! The Event Loop explains *how* JavaScript handles async operations; callbacks are *what* gets executed when those operations complete. -</Warning> +setTimeout(function() { + console.log('Timer fired!') +}, 1000) + +console.log('After timer') + +// Output: +// Before timer +// After timer +// Timer fired! (1 second later) +``` + +The answer is **callbacks** — functions you pass to other functions, saying "call me back when you're done." Callbacks power everything async in JavaScript. Every event handler, every timer, every network request — they all rely on them. <Info> **What you'll learn in this guide:** - What callbacks are and why JavaScript uses them -- Synchronous vs asynchronous callbacks +- The difference between synchronous and asynchronous callbacks +- How callbacks connect to higher-order functions +- Common callback patterns (event handlers, timers, array methods) - The error-first callback pattern (Node.js convention) - Callback hell and the "pyramid of doom" +- How to escape callback hell - Why Promises were invented to solve callback problems </Info> +<Warning> +**Prerequisites:** This guide assumes familiarity with [the Event Loop](/concepts/event-loop) — it's the mechanism that makes async callbacks work! You should also understand [higher-order functions](/concepts/higher-order-functions), since callbacks are higher-order functions in action. +</Warning> + +--- + +## What is a Callback? + +A **[callback](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)** is a function passed as an argument to another function, that gets called later. The other function decides when (or if) to run it. + +```javascript +// greet is a callback function +function greet(name) { + console.log(`Hello, ${name}!`) +} + +// processUserInput accepts a callback +function processUserInput(callback) { + const name = 'Alice' + callback(name) // "calling back" the function we received +} + +processUserInput(greet) // "Hello, Alice!" +``` + +The term "callback" comes from the idea of being **called back** — like getting a buzzer at a restaurant: "We'll buzz you when your table is ready." + +<Tip> +**Here's the thing:** A callback is just a regular function. Nothing magical about it. What makes it a "callback" is *how it's used* — passed to another function to be executed later. +</Tip> + +### Callbacks Can Be Anonymous + +You don't have to define callbacks as named functions. Anonymous functions (and arrow functions) work just as well: + +```javascript +// Named function as callback +function handleClick() { + console.log('Clicked!') +} +button.addEventListener('click', handleClick) + +// Anonymous function as callback +button.addEventListener('click', function() { + console.log('Clicked!') +}) + +// Arrow function as callback +button.addEventListener('click', () => { + console.log('Clicked!') +}) +``` + +All three do the same thing. Named functions are easier to debug though, and you can reuse them. + +--- + +## The Restaurant Buzzer Analogy + +Callbacks work like the buzzer you get at a busy restaurant: + +1. **You place an order** — You call a function and pass it a callback +2. **You get a buzzer** — The function registers your callback +3. **You go sit down** — Your code continues running (non-blocking) +4. **The buzzer goes off** — The async operation completes +5. **You pick up your food** — Your callback is executed + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE RESTAURANT BUZZER ANALOGY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ YOU (Your Code) RESTAURANT (JavaScript Runtime) │ +│ │ +│ ┌──────────────┐ ┌─────────────────────────────────┐ │ +│ │ │ │ KITCHEN │ │ +│ │ "I'd like │ ────────► │ (Web APIs) │ │ +│ │ a burger" │ ORDER │ │ │ +│ │ │ │ [setTimeout: 5 min] │ │ +│ └──────────────┘ │ [fetch: waiting...] │ │ +│ │ │ [click: listening...] │ │ +│ │ └─────────────────────────────────┘ │ +│ │ │ │ +│ │ You get a buzzer │ When ready... │ +│ │ and go sit down ▼ │ +│ │ ┌─────────────────────────────────┐ │ +│ │ │ PICKUP COUNTER │ │ +│ ▼ │ (Callback Queue) │ │ +│ ┌──────────────┐ │ │ │ +│ │ │ │ [Your callback waiting here] │ │ +│ │ 📱 BUZZ! │ ◄──────── │ │ │ +│ │ │ READY! └─────────────────────────────────┘ │ +│ │ Time to │ │ +│ │ eat! │ The Event Loop calls your callback │ +│ └──────────────┘ when the kitchen (Web API) is done │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +The key insight: **you don't wait at the counter**. You give them a way to reach you (the callback), and you go do other things. That's how JavaScript stays fast — it never sits around waiting. + +```javascript +// You place your order (start async operation) +setTimeout(function eatBurger() { + console.log('Eating my burger!') // This is the callback +}, 5000) + +// You go sit down (your code continues) +console.log('Sitting down, checking my phone...') +console.log('Chatting with friends...') +console.log('Reading the menu...') + +// Output: +// Sitting down, checking my phone... +// Chatting with friends... +// Reading the menu... +// Eating my burger! (5 seconds later) +``` + +--- + +## Callbacks and Higher-Order Functions + +Callbacks and [higher-order functions](/concepts/higher-order-functions) go hand in hand: + +- A **higher-order function** is a function that accepts functions as arguments or returns them +- A **callback** is the function being passed to a higher-order function + +```javascript +// forEach is a HIGHER-ORDER FUNCTION (it accepts a function) +// The arrow function is the CALLBACK (it's being passed in) + +const numbers = [1, 2, 3] + +numbers.forEach((num) => { // ← This is the callback + console.log(num * 2) +}) +// 2, 4, 6 +``` + +Every time you use `map`, `filter`, `forEach`, `reduce`, `sort`, or `find`, you're passing callbacks to higher-order functions: + +```javascript +const users = [ + { name: 'Alice', age: 25 }, + { name: 'Bob', age: 17 }, + { name: 'Charlie', age: 30 } +] + +// filter accepts a callback that returns true/false +const adults = users.filter(user => user.age >= 18) + +// map accepts a callback that transforms each element +const names = users.map(user => user.name) + +// find accepts a callback that returns true when found +const bob = users.find(user => user.name === 'Bob') + +// sort accepts a callback that compares two elements +const byAge = users.sort((a, b) => a.age - b.age) +``` + +<Note> +**The connection:** Understanding higher-order functions helps you understand callbacks. If you're comfortable with `map` and `filter`, you already understand callbacks! The only difference with async callbacks is *when* they execute. +</Note> + +--- + +## Synchronous vs Asynchronous Callbacks + +Some callbacks run right away. Others run later. Getting this wrong will bite you. + +### Synchronous Callbacks + +**Synchronous callbacks** are executed immediately, during the function call. They block until complete. + +```javascript +const numbers = [1, 2, 3, 4, 5] + +console.log('Before map') + +const doubled = numbers.map(num => { + console.log(`Doubling ${num}`) + return num * 2 +}) + +console.log('After map') +console.log(doubled) + +// Output (all synchronous, in order): +// Before map +// Doubling 1 +// Doubling 2 +// Doubling 3 +// Doubling 4 +// Doubling 5 +// After map +// [2, 4, 6, 8, 10] +``` + +The callback runs for each element **before** `map` returns. Nothing else happens until it's done. + +**Common synchronous callbacks:** +- Array methods: `map`, `filter`, `forEach`, `reduce`, `find`, `sort`, `every`, `some` +- String methods: `replace` (with function) +- Object methods: `Object.keys().forEach()` + +### Asynchronous Callbacks + +**Asynchronous callbacks** are executed later, after the current code finishes. They don't block. + +```javascript +console.log('Before setTimeout') + +setTimeout(() => { + console.log('Inside setTimeout') +}, 0) // Even with 0ms delay! + +console.log('After setTimeout') + +// Output: +// Before setTimeout +// After setTimeout +// Inside setTimeout (runs AFTER all sync code) +``` + +Even with a 0ms delay, the callback runs **after** the synchronous code. This is because async callbacks go through the [event loop](/concepts/event-loop). + +**Common asynchronous callbacks:** +- Timers: `setTimeout`, `setInterval` +- Events: `addEventListener`, `onclick` +- Network: `XMLHttpRequest.onload`, `fetch().then()` +- Node.js I/O: `fs.readFile`, `http.get` + +### Comparison Table + +| Aspect | Synchronous Callbacks | Asynchronous Callbacks | +|--------|----------------------|------------------------| +| **When executed** | Immediately, during the function call | Later, via the event loop | +| **Blocking** | Yes — code waits for completion | No — code continues immediately | +| **Examples** | `map`, `filter`, `forEach`, `sort` | `setTimeout`, `addEventListener`, `fetch` | +| **Use case** | Data transformation, iteration | I/O, user interaction, timers | +| **Error handling** | Regular `try/catch` works | `try/catch` won't catch errors! | +| **Return value** | Can return values | Return values usually ignored | + +### The Critical Difference: Error Handling + +This trips up almost everyone: + +```javascript +// Synchronous callback - try/catch WORKS +try { + [1, 2, 3].forEach(num => { + if (num === 2) throw new Error('Found 2!') + }) +} catch (error) { + console.log('Caught:', error.message) // "Caught: Found 2!" +} + +// Asynchronous callback - try/catch DOES NOT WORK! +try { + setTimeout(() => { + throw new Error('Async error!') // This error escapes! + }, 100) +} catch (error) { + // This will NEVER run + console.log('Caught:', error.message) +} +// The error crashes your program! +``` + +Why? The `try/catch` runs immediately. By the time the async callback executes, the `try/catch` is long gone. The callback runs in a different "turn" of the event loop. + +--- + +## How Callbacks Work with the Event Loop + +To really get async callbacks, you need to see how they work with the [event loop](/concepts/event-loop). + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ASYNC CALLBACK LIFECYCLE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. YOUR CODE RUNS │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ console.log('Start') │ │ +│ │ setTimeout(callback, 1000) // Register callback with Web API │ │ +│ │ console.log('End') │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ 2. WEB API HANDLES THE ASYNC OPERATION │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Timer starts counting... │ │ +│ │ (Your code continues running - it doesn't wait!) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ (after 1000ms) │ +│ 3. CALLBACK QUEUED │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Timer done! Callback added to Task Queue │ │ +│ │ [callback] ← waiting here │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ (when call stack is empty) │ +│ 4. EVENT LOOP EXECUTES CALLBACK │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Event Loop: "Call stack empty? Let me grab that callback..." │ │ +│ │ callback() runs! │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Let's trace through a real example: + +```javascript +console.log('1: Script start') + +setTimeout(function first() { + console.log('2: First timeout') +}, 0) + +setTimeout(function second() { + console.log('3: Second timeout') +}, 0) + +console.log('4: Script end') +``` + +**Execution order:** + +1. `console.log('1: Script start')` — runs immediately → "1: Script start" +2. `setTimeout(first, 0)` — registers `first` callback with Web APIs +3. `setTimeout(second, 0)` — registers `second` callback with Web APIs +4. `console.log('4: Script end')` — runs immediately → "4: Script end" +5. Call stack is now empty +6. Event Loop checks Task Queue — finds `first` +7. `first()` runs → "2: First timeout" +8. Event Loop checks Task Queue — finds `second` +9. `second()` runs → "3: Second timeout" + +**Output:** +``` +1: Script start +4: Script end +2: First timeout +3: Second timeout +``` + +Even with a 0ms delay, the callbacks still run **after** all the synchronous code finishes. + +<Tip> +**Read more:** Our [Event Loop guide](/concepts/event-loop) goes deep into tasks, microtasks, and rendering. If you want to understand *why* `Promise.then()` runs before `setTimeout(..., 0)`, check it out! +</Tip> + +--- + +## Common Callback Patterns + +Here are the most common ways you'll see callbacks in the wild. + +### Pattern 1: Event Handlers + +The most common use of callbacks in browser JavaScript: + +```javascript +// DOM events +const button = document.getElementById('myButton') + +button.addEventListener('click', function handleClick(event) { + console.log('Button clicked!') + console.log('Event type:', event.type) // "click" + console.log('Target:', event.target) // the button element +}) + +// The callback receives an Event object with details about what happened +``` + +You can also use named functions for reusability: + +```javascript +function handleClick(event) { + console.log('Clicked:', event.target.id) +} + +function handleMouseOver(event) { + event.target.style.backgroundColor = 'yellow' +} + +button.addEventListener('click', handleClick) +button.addEventListener('mouseover', handleMouseOver) + +// Later, you can remove them: +button.removeEventListener('click', handleClick) +``` + +### Pattern 2: Timers + +`setTimeout` and `setInterval` both accept callbacks: + +```javascript +// setTimeout - runs once after delay +const timeoutId = setTimeout(function() { + console.log('This runs once after 2 seconds') +}, 2000) + +// Cancel it before it runs +clearTimeout(timeoutId) + +// setInterval - runs repeatedly +let count = 0 +const intervalId = setInterval(function() { + count++ + console.log(`Count: ${count}`) + + if (count >= 5) { + clearInterval(intervalId) // Stop after 5 times + console.log('Done!') + } +}, 1000) +``` + +**Passing arguments to timer callbacks:** + +```javascript +// Method 1: Closure (most common) +const name = 'Alice' +setTimeout(function() { + console.log(`Hello, ${name}!`) +}, 1000) + +// Method 2: setTimeout's extra arguments +setTimeout(function(greeting, name) { + console.log(`${greeting}, ${name}!`) +}, 1000, 'Hello', 'Bob') // Extra args passed to callback + +// Method 3: Arrow function with closure +const user = { name: 'Charlie' } +setTimeout(() => console.log(`Hi, ${user.name}!`), 1000) +``` + +### Pattern 3: Array Iteration + +These are synchronous callbacks, but they're everywhere: + +```javascript +const products = [ + { name: 'Laptop', price: 999, inStock: true }, + { name: 'Phone', price: 699, inStock: false }, + { name: 'Tablet', price: 499, inStock: true } +] + +// forEach - do something with each item +products.forEach(product => { + console.log(`${product.name}: $${product.price}`) +}) + +// map - transform each item into something new +const productNames = products.map(product => product.name) +// ['Laptop', 'Phone', 'Tablet'] + +// filter - keep only items that pass a test +const available = products.filter(product => product.inStock) +// [{ name: 'Laptop', ... }, { name: 'Tablet', ... }] + +// find - get the first item that passes a test +const phone = products.find(product => product.name === 'Phone') +// { name: 'Phone', price: 699, inStock: false } + +// reduce - combine all items into a single value +const totalValue = products.reduce((sum, product) => sum + product.price, 0) +// 2197 +``` + +### Pattern 4: Custom Callbacks + +You can create your own functions that accept callbacks: + +```javascript +// A function that does something and then calls you back +function fetchUserData(userId, callback) { + // Simulate async operation + setTimeout(function() { + const user = { id: userId, name: 'Alice', email: 'alice@example.com' } + callback(user) + }, 1000) +} + +// Using the function +fetchUserData(123, function(user) { + console.log('Got user:', user.name) +}) +console.log('Fetching user...') + +// Output: +// Fetching user... +// Got user: Alice (1 second later) +``` + +--- + +## The Error-First Callback Pattern + +When Node.js came along, developers needed a standard way to handle errors in async callbacks. They landed on **error-first callbacks** (also called "Node-style callbacks" or "errbacks"). + +### The Convention + +```javascript +// Error-first callback signature +function callback(error, result) { + // error: null/undefined if success, Error object if failure + // result: the data if success, usually undefined if failure +} +``` + +The first parameter is **always** reserved for an error. If the operation succeeds, `error` is `null` or `undefined`. If it fails, `error` contains an Error object. + +### Reading a File (Node.js Example) + +```javascript +const fs = require('fs') + +fs.readFile('config.json', 'utf8', function(error, data) { + // ALWAYS check for error first! + if (error) { + console.error('Failed to read file:', error.message) + return // Important: stop execution! + } + + // If we get here, error is null/undefined + console.log('File contents:', data) + const config = JSON.parse(data) + console.log('Config loaded:', config) +}) +``` + +### Why Put Error First? + +1. **Consistency** — Every callback has the same signature +2. **Can't be ignored** — The error is the first thing you see +3. **Early return** — Check for error, return early, then handle success +4. **No exceptions** — Async errors can't be caught with try/catch + +### Creating Your Own Error-First Functions + +```javascript +function divideAsync(a, b, callback) { + // Simulate async operation + setTimeout(function() { + // Check for errors + if (typeof a !== 'number' || typeof b !== 'number') { + callback(new Error('Both arguments must be numbers')) + return + } + + if (b === 0) { + callback(new Error('Cannot divide by zero')) + return + } + + // Success! Error is null, result is the value + const result = a / b + callback(null, result) + }, 100) +} + +// Using it +divideAsync(10, 2, function(error, result) { + if (error) { + console.error('Division failed:', error.message) + return + } + console.log('Result:', result) // Result: 5 +}) + +divideAsync(10, 0, function(error, result) { + if (error) { + console.error('Division failed:', error.message) // "Cannot divide by zero" + return + } + console.log('Result:', result) +}) +``` + +### Common Mistake: Forgetting to Return + +```javascript +// ❌ WRONG - code continues after error callback! +function processData(data, callback) { + if (!data) { + callback(new Error('No data provided')) + // Oops! Execution continues... + } + + // This runs even when there's an error! + const processed = transform(data) // Crash! data is undefined + callback(null, processed) +} + +// ✓ CORRECT - return after error callback +function processData(data, callback) { + if (!data) { + return callback(new Error('No data provided')) + // Or: callback(new Error(...)); return; + } + + // This only runs if data exists + const processed = transform(data) + callback(null, processed) +} +``` + +<Warning> +**Always return after calling an error callback!** Otherwise, your code continues executing with invalid data. +</Warning> + +--- + +## Callback Hell: The Pyramid of Doom + +When you have multiple async operations that depend on each other, callbacks nest inside callbacks. This creates the infamous "callback hell" or "pyramid of doom." + +### The Problem + +Imagine a user authentication flow: + +1. Get user from database +2. Verify password +3. Get user's profile +4. Get user's settings +5. Render the dashboard + +With callbacks, this becomes: + +```javascript +getUser(userId, function(error, user) { + if (error) { + handleError(error) + return + } + + verifyPassword(user, password, function(error, isValid) { + if (error) { + handleError(error) + return + } + + if (!isValid) { + handleError(new Error('Invalid password')) + return + } + + getProfile(user.id, function(error, profile) { + if (error) { + handleError(error) + return + } + + getSettings(user.id, function(error, settings) { + if (error) { + handleError(error) + return + } + + renderDashboard(user, profile, settings, function(error) { + if (error) { + handleError(error) + return + } + + console.log('Dashboard rendered!') + }) + }) + }) + }) +}) +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CALLBACK HELL │ +│ (The Pyramid of Doom) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ getUser(id, function(err, user) { │ +│ verifyPassword(user, pw, function(err, valid) { │ +│ getProfile(user.id, function(err, profile) { │ +│ getSettings(user.id, function(err, settings) { │ +│ renderDashboard(user, profile, settings, function(err) { │ +│ // Finally! But look at this indentation... │ +│ }) │ +│ }) │ +│ }) │ +│ }) │ +│ }) │ +│ │ +│ Problems: │ +│ • Hard to read (horizontal scrolling) │ +│ • Hard to debug (which callback failed?) │ +│ • Hard to maintain (adding a step means more nesting) │ +│ • Error handling repeated at every level │ +│ • Variables from outer callbacks hard to track │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Why This Hurts + +1. **Readability** — Code flows right instead of down, requiring horizontal scrolling +2. **Error handling** — Must be duplicated at every level +3. **Debugging** — Stack traces become confusing +4. **Maintenance** — Adding or removing steps is painful +5. **Variable scope** — Variables from outer callbacks are hard to track +6. **Testing** — Nearly impossible to unit test individual steps + +<Note> +Want to dive deeper into callback hell? Check out [callbackhell.com](http://callbackhell.com/) — a dedicated guide to understanding and escaping the pyramid of doom. +</Note> + +--- + +## Escaping Callback Hell + +Here's how to escape the pyramid of doom. + +### Strategy 1: Named Functions + +Extract anonymous callbacks into named functions: + +```javascript +// Before: Anonymous callback hell +getData(function(err, data) { + processData(data, function(err, processed) { + saveData(processed, function(err) { + console.log('Done!') + }) + }) +}) + +// After: Named functions +function handleData(err, data) { + if (err) return handleError(err) + processData(data, handleProcessed) +} + +function handleProcessed(err, processed) { + if (err) return handleError(err) + saveData(processed, handleSaved) +} + +function handleSaved(err) { + if (err) return handleError(err) + console.log('Done!') +} + +function handleError(err) { + console.error('Error:', err.message) +} + +// Start the chain +getData(handleData) +``` + +**Benefits:** +- Code flows vertically (easier to read) +- Functions can be reused +- Easier to debug (named functions in stack traces) +- Easier to test individually + +### Strategy 2: Early Returns and Guard Clauses + +Keep the happy path at the lowest indentation level: + +```javascript +// Instead of nested if/else +function processUser(user, callback) { + validateUser(user, function(err, isValid) { + if (err) { + callback(err) + } else { + if (isValid) { + saveUser(user, function(err, savedUser) { + if (err) { + callback(err) + } else { + callback(null, savedUser) + } + }) + } else { + callback(new Error('Invalid user')) + } + } + }) +} + +// Use early returns +function processUser(user, callback) { + validateUser(user, function(err, isValid) { + if (err) return callback(err) + if (!isValid) return callback(new Error('Invalid user')) + + saveUser(user, function(err, savedUser) { + if (err) return callback(err) + callback(null, savedUser) + }) + }) +} +``` + +### Strategy 3: Modularization + +Split your code into smaller, focused modules: + +```javascript +// auth.js +function authenticateUser(credentials, callback) { + getUser(credentials.email, function(err, user) { + if (err) return callback(err) + + verifyPassword(user, credentials.password, function(err, isValid) { + if (err) return callback(err) + if (!isValid) return callback(new Error('Invalid password')) + callback(null, user) + }) + }) +} + +// profile.js +function loadUserProfile(userId, callback) { + getProfile(userId, function(err, profile) { + if (err) return callback(err) + + getSettings(userId, function(err, settings) { + if (err) return callback(err) + callback(null, { profile, settings }) + }) + }) +} + +// main.js +authenticateUser(credentials, function(err, user) { + if (err) return handleError(err) + + loadUserProfile(user.id, function(err, data) { + if (err) return handleError(err) + renderDashboard(user, data.profile, data.settings) + }) +}) +``` + +### Strategy 4: Control Flow Libraries (Historical) + +Before Promises, libraries like [async.js](https://caolan.github.io/async/) helped manage callback flow: + +```javascript +// Using async.js waterfall (each step passes result to next) +async.waterfall([ + function(callback) { + getUser(userId, callback) + }, + function(user, callback) { + verifyPassword(user, password, function(err, isValid) { + callback(err, user, isValid) + }) + }, + function(user, isValid, callback) { + if (!isValid) return callback(new Error('Invalid password')) + getProfile(user.id, function(err, profile) { + callback(err, user, profile) + }) + }, + function(user, profile, callback) { + getSettings(user.id, function(err, settings) { + callback(err, user, profile, settings) + }) + } +], function(err, user, profile, settings) { + if (err) return handleError(err) + renderDashboard(user, profile, settings) +}) +``` + +### Strategy 5: Promises (The Modern Solution) + +[Promises](/concepts/promises) were invented specifically to solve callback hell: + +```javascript +// The same flow with Promises +getUser(userId) + .then(user => verifyPassword(user, password)) + .then(({ user, isValid }) => { + if (!isValid) throw new Error('Invalid password') + return getProfile(user.id).then(profile => ({ user, profile })) + }) + .then(({ user, profile }) => { + return getSettings(user.id).then(settings => ({ user, profile, settings })) + }) + .then(({ user, profile, settings }) => { + renderDashboard(user, profile, settings) + }) + .catch(handleError) +``` + +<Note> +This Promise chain is intentionally verbose to show how callbacks nest differently with Promises. For cleaner patterns and best practices, check out our [Promises guide](/concepts/promises). +</Note> + +Or with [async/await](/concepts/async-await): + +```javascript +// The same flow with async/await +async function initDashboard(userId, password) { + try { + const user = await getUser(userId) + const isValid = await verifyPassword(user, password) + + if (!isValid) throw new Error('Invalid password') + + const profile = await getProfile(user.id) + const settings = await getSettings(user.id) + + renderDashboard(user, profile, settings) + } catch (error) { + handleError(error) + } +} +``` + +<Tip> +**Promises and async/await are built on callbacks.** They don't replace callbacks — they provide a cleaner abstraction over them. Under the hood, Promise `.then()` handlers are still callbacks! +</Tip> + +--- + +## Common Callback Mistakes + +### Mistake 1: Calling a Callback Multiple Times + +A callback should typically be called exactly once — either with an error or with a result: + +```javascript +// ❌ WRONG - callback called multiple times! +function fetchData(url, callback) { + fetch(url) + .then(response => { + callback(null, response) // Called on success + }) + .catch(error => { + callback(error) // Called on error + }) + .finally(() => { + callback(null, 'done') // Called ALWAYS — even after success or error! + }) +} + +// ✓ CORRECT - callback called exactly once +function fetchData(url, callback) { + fetch(url) + .then(response => callback(null, response)) + .catch(error => callback(error)) +} +``` + +### Mistake 2: Synchronous and Asynchronous Mixing (Zalgo) + +A function should be consistently sync or async, never both. This inconsistency is nicknamed "releasing Zalgo" — a reference to an internet meme about unleashing chaos. And chaos is exactly what you get when code behaves unpredictably: + +```javascript +// ❌ WRONG - sometimes sync, sometimes async (Zalgo!) +function getData(cache, callback) { + if (cache.has('data')) { + callback(null, cache.get('data')) // Sync! + return + } + + fetchFromServer(function(err, data) { + callback(err, data) // Async! + }) +} + +// This causes unpredictable behavior: +let value = 'initial' +getData(cache, function(err, data) { + value = data +}) +console.log(value) // "initial" or the data? Depends on cache! + +// ✓ CORRECT - always async +function getData(cache, callback) { + if (cache.has('data')) { + // Use setImmediate, process.nextTick, or setTimeout to make it async + setImmediate(function() { + callback(null, cache.get('data')) + }) + return + } + + fetchFromServer(function(err, data) { + callback(err, data) + }) +} +``` + +### Mistake 3: Losing `this` Context + +Regular functions lose their `this` binding when used as callbacks: + +```javascript +// ❌ WRONG - this is undefined/global +const user = { + name: 'Alice', + greetLater: function() { + setTimeout(function() { + console.log(`Hello, ${this.name}!`) // this.name is undefined! + }, 1000) + } +} +user.greetLater() // "Hello, undefined!" + +// ✓ CORRECT - Use arrow function (inherits this) +const user = { + name: 'Alice', + greetLater: function() { + setTimeout(() => { + console.log(`Hello, ${this.name}!`) // Arrow function keeps this + }, 1000) + } +} +user.greetLater() // "Hello, Alice!" + +// ✓ CORRECT - Use bind +const user = { + name: 'Alice', + greetLater: function() { + setTimeout(function() { + console.log(`Hello, ${this.name}!`) + }.bind(this), 1000) // Explicitly bind this + } +} +user.greetLater() // "Hello, Alice!" + +// ✓ CORRECT - Save reference to this +const user = { + name: 'Alice', + greetLater: function() { + const self = this // Save reference + setTimeout(function() { + console.log(`Hello, ${self.name}!`) + }, 1000) + } +} +user.greetLater() // "Hello, Alice!" +``` + +### Mistake 4: Not Handling Errors + +Always handle errors in async callbacks — unhandled errors can crash your application: + +```javascript +// ❌ WRONG - error ignored +fs.readFile('config.json', function(err, data) { + const config = JSON.parse(data) // Crashes if err exists! + startApp(config) +}) + +// ✓ CORRECT - error handled +fs.readFile('config.json', function(err, data) { + if (err) { + console.error('Could not read config:', err.message) + process.exit(1) + return + } + + try { + const config = JSON.parse(data) + startApp(config) + } catch (parseError) { + console.error('Invalid JSON in config:', parseError.message) + process.exit(1) + } +}) +``` + +--- + +## Historical Context: Why JavaScript Uses Callbacks + +Understanding *why* JavaScript uses callbacks helps everything click into place. + +### The Birth of JavaScript (1995) + +JavaScript was created by Brendan Eich at Netscape in just 10 days. Its primary purpose was to make web pages interactive — responding to user clicks, form submissions, and other events. + +### The Single-Threaded Design + +JavaScript was designed to be **single-threaded** — one thing at a time. Why? + +1. **Simplicity** — No race conditions, deadlocks, or complex synchronization +2. **DOM Safety** — Multiple threads modifying the DOM would cause chaos +3. **Browser Reality** — Early browsers couldn't handle multi-threaded scripts + +But single-threaded means a problem: **you can't block waiting for things.** + +If JavaScript waited for a network request to complete, the entire page would freeze. Users couldn't click, scroll, or do anything. That's unacceptable for a UI language. + +### The Callback Solution + +Callbacks solved this problem neatly: + +1. **Register interest** — "When this happens, call this function" +2. **Continue immediately** — Don't block, keep the UI responsive +3. **React later** — When the event occurs, the callback runs + +```javascript +// This pattern was there from day one +element.onclick = function() { + alert('Clicked!') +} + +// The page doesn't freeze waiting for a click +// JavaScript registers the callback and moves on +// When clicked, the callback runs +``` + +### The Evolution + +| Year | Development | +|------|-------------| +| 1995 | JavaScript created with event callbacks | +| 1999 | XMLHttpRequest (AJAX) — async HTTP with callbacks | +| 2009 | Node.js — callbacks for server-side I/O | +| 2012 | Callback hell becomes a recognized problem | +| 2015 | ES6 Promises — official solution to callback hell | +| 2017 | ES8 async/await — syntactic sugar for Promises | + +### Callbacks Are Still Fundamental + +Even with Promises and async/await, callbacks are everywhere: + +- **Event handlers** still use callbacks +- **Array methods** still use callbacks +- **Promises** use callbacks internally (`.then(callback)`) +- **async/await** is syntactic sugar over Promise callbacks + +Callbacks aren't obsolete — they're the foundation that everything else builds upon. + +--- + +## Key Takeaways + +<Info> +**Remember these essential points about callbacks:** + +1. **A callback is a function passed to another function** to be executed later — nothing magical + +2. **Callbacks can be synchronous or asynchronous** — array methods are sync, timers and events are async + +3. **Higher-order functions and callbacks are two sides of the same coin** — one accepts, one is passed + +4. **Async callbacks go through the event loop** — they never run until all sync code finishes + +5. **Error-first callbacks: `callback(error, result)`** — always check error first, return after handling + +6. **You can't use try/catch for async callbacks** — the catch is gone by the time the callback runs + +7. **Callback hell is real** — deeply nested callbacks become unreadable and unmaintainable + +8. **Escape callback hell with:** named functions, modularization, early returns, or Promises + +9. **Promises were invented to solve callback problems** — but they still use callbacks under the hood + +10. **Callbacks are the foundation** — events, Promises, async/await all build on callbacks +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between synchronous and asynchronous callbacks?"> + **Answer:** + + **Synchronous callbacks** execute immediately, during the function call. They block until complete. Examples: `map`, `filter`, `forEach`. + + ```javascript + [1, 2, 3].forEach(n => console.log(n)) // Runs immediately, blocks + console.log('Done') // Runs after forEach completes + ``` + + **Asynchronous callbacks** execute later, via the event loop. They don't block. Examples: `setTimeout`, `addEventListener`, `fs.readFile`. + + ```javascript + setTimeout(() => console.log('Timer'), 0) // Registers, doesn't block + console.log('Done') // Runs BEFORE the timer callback + ``` + </Accordion> + + <Accordion title="Question 2: Why is error the first parameter in Node.js-style callbacks?"> + **Answer:** + + The error-first convention exists because: + + 1. **Consistency** — Every async callback has the same signature: `(error, result)` + 2. **Can't be ignored** — The error is the first thing you must deal with + 3. **Forces handling** — You naturally check for errors before using results + 4. **No exceptions** — Async errors can't be caught with try/catch, so they must be passed + + ```javascript + fs.readFile('file.txt', (error, data) => { + if (error) { + // Handle error FIRST + console.error(error) + return + } + // Safe to use data + console.log(data) + }) + ``` + </Accordion> + + <Accordion title="Question 3: What's the output of this code?"> + ```javascript + console.log('A') + + setTimeout(() => console.log('B'), 0) + + console.log('C') + + setTimeout(() => console.log('D'), 0) + + console.log('E') + ``` + + **Answer:** `A`, `C`, `E`, `B`, `D` + + **Explanation:** + 1. `console.log('A')` — sync, runs immediately → "A" + 2. `setTimeout(..., 0)` — registers callback B, continues + 3. `console.log('C')` — sync, runs immediately → "C" + 4. `setTimeout(..., 0)` — registers callback D, continues + 5. `console.log('E')` — sync, runs immediately → "E" + 6. Call stack empty → event loop runs callback B → "B" + 7. Event loop runs callback D → "D" + + Even with 0ms delay, setTimeout callbacks run after all sync code. + </Accordion> + + <Accordion title="Question 4: How can you preserve `this` context in a callback?"> + **Answer:** Three common approaches: + + **1. Arrow functions** (recommended — they inherit `this` from enclosing scope): + ```javascript + const obj = { + name: 'Alice', + greet() { + setTimeout(() => { + console.log(this.name) // "Alice" + }, 100) + } + } + ``` + + **2. Using `bind()`**: + ```javascript + setTimeout(function() { + console.log(this.name) + }.bind(this), 100) + ``` + + **3. Saving a reference**: + ```javascript + const self = this + setTimeout(function() { + console.log(self.name) + }, 100) + ``` + </Accordion> + + <Accordion title="Question 5: Why can't you use try/catch with async callbacks?"> + **Answer:** + + The `try/catch` block executes **synchronously**. By the time an async callback runs, the try/catch is long gone — it's on a different "turn" of the event loop. + + ```javascript + try { + setTimeout(() => { + throw new Error('Async error!') // This escapes! + }, 100) + } catch (e) { + // This NEVER catches the error + console.log('Caught:', e) + } + + // The error crashes the program because: + // 1. try/catch runs immediately + // 2. setTimeout registers callback and returns + // 3. try/catch completes (nothing thrown yet!) + // 4. 100ms later, callback runs and throws + // 5. No try/catch exists at that point + ``` + + This is why we use error-first callbacks or Promise `.catch()` for async error handling. + </Accordion> + + <Accordion title="Question 6: What are three ways to avoid callback hell?"> + **Answer:** + + **1. Named functions** — Extract callbacks into named functions: + ```javascript + function handleUser(err, user) { + if (err) return handleError(err) + getProfile(user.id, handleProfile) + } + getUser(userId, handleUser) + ``` + + **2. Modularization** — Split into separate modules/functions: + ```javascript + // auth.js exports authenticateUser() + // profile.js exports loadProfile() + // main.js composes them + ``` + + **3. Promises/async-await** — Use modern async patterns: + ```javascript + const user = await getUser(userId) + const profile = await getProfile(user.id) + ``` + + Other approaches: control flow libraries (async.js), early returns, keeping nesting shallow. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + How JavaScript schedules and executes async callbacks + </Card> + <Card title="Promises" icon="handshake" href="/concepts/promises"> + Modern solution to callback hell + </Card> + <Card title="async/await" icon="hourglass" href="/concepts/async-await"> + Cleaner syntax for Promise-based async code + </Card> + <Card title="Higher-Order Functions" icon="layer-group" href="/concepts/higher-order-functions"> + Functions that accept or return other functions + </Card> +</CardGroup> + +--- + +## Reference + +<CardGroup cols={2}> + <Card title="Callback function — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Callback_function"> + Official MDN glossary definition of callback functions + </Card> + <Card title="setTimeout — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/setTimeout"> + Documentation for the setTimeout timer function + </Card> + <Card title="EventTarget.addEventListener — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener"> + How to register event callbacks on DOM elements + </Card> + <Card title="Array.prototype.forEach — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach"> + Synchronous callback pattern with array iteration + </Card> +</CardGroup> + ## Articles <CardGroup cols={2}> <Card title="JavaScript Callbacks Explained" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-callback-functions-what-are-callbacks-in-js-and-how-to-use-them/"> - By freeCodeCamp + freeCodeCamp's beginner-friendly guide to understanding callback functions with practical examples. + </Card> + <Card title="Callback Functions in JavaScript" icon="newspaper" href="https://javascript.info/callbacks"> + JavaScript.info's in-depth guide with interactive examples and explanations. </Card> - <Card title="Understanding Callbacks in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-callbacks-in-javascript"> - By DigitalOcean + <Card title="Callback Hell" icon="newspaper" href="http://callbackhell.com/"> + The definitive guide to understanding and avoiding callback hell, with practical solutions. + </Card> + <Card title="Understand Callback Functions in JavaScript" icon="newspaper" href="https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced"> + Brandon Morelli's clear explanation of what callbacks are and how they work. </Card> </CardGroup> -- [Callback Functions in JavaScript — JavaScript.info](https://javascript.info/callbacks) -- [What is a Callback Function in JavaScript? — MDN](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) -- [JavaScript Callbacks — W3Schools](https://www.w3schools.com/js/js_callback.asp) -- [Understand Callback Functions in JavaScript — Brandon Morelli](https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced) -- [Callback Hell — callbackhell.com](http://callbackhell.com/) - ## Videos <CardGroup cols={2}> <Card title="Callbacks in JavaScript Explained!" icon="video" href="https://www.youtube.com/watch?v=cNjIUSDnb9k"> - By Mosh Hamedani + Mosh Hamedani's clear explanation of callbacks with visual examples. </Card> <Card title="JavaScript Callbacks" icon="video" href="https://www.youtube.com/watch?v=QRq2zMHlBz4"> - By Web Dev Simplified + Web Dev Simplified's beginner-friendly callback tutorial. + </Card> + <Card title="Callback Functions" icon="video" href="https://www.youtube.com/watch?v=Nau-iEEgEoM"> + Fun Fun Function's entertaining deep-dive into callbacks. + </Card> + <Card title="Asynchronous JavaScript Tutorial" icon="video" href="https://www.youtube.com/watch?v=PoRJizFvM7s"> + Traversy Media's comprehensive guide covering callbacks, Promises, and async/await. + </Card> + <Card title="JavaScript Callback Functions" icon="video" href="https://www.youtube.com/watch?v=pTbSfCT42_M"> + techsith's practical tutorial on callback functions with examples. </Card> </CardGroup> - -- [Callback Functions — Fun Fun Function](https://www.youtube.com/watch?v=Nau-iEEgEoM) -- [JavaScript Callback Functions — techsith](https://www.youtube.com/watch?v=pTbSfCT42_M) -- [Asynchronous JavaScript Tutorial - Callbacks, Promises, Async/Await — Traversy Media](https://www.youtube.com/watch?v=PoRJizFvM7s) diff --git a/tests/async-javascript/callbacks/callbacks.dom.test.js b/tests/async-javascript/callbacks/callbacks.dom.test.js new file mode 100644 index 00000000..b841bee0 --- /dev/null +++ b/tests/async-javascript/callbacks/callbacks.dom.test.js @@ -0,0 +1,239 @@ +/** + * @vitest-environment jsdom + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +// ============================================================ +// DOM EVENT HANDLER CALLBACKS +// From callbacks.mdx lines 401-434 +// Pattern 1: Event Handlers +// ============================================================ + +describe('DOM Event Handler Callbacks', () => { + let button + + beforeEach(() => { + // Create a fresh button element for each test + button = document.createElement('button') + button.id = 'myButton' + document.body.appendChild(button) + }) + + afterEach(() => { + // Clean up + document.body.innerHTML = '' + }) + + // From lines 405-416: DOM events with addEventListener + it('should execute callback when button is clicked', () => { + const output = [] + + // DOM events + const button = document.getElementById('myButton') + + button.addEventListener('click', function handleClick(event) { + output.push('Button clicked!') + output.push(`Event type: ${event.type}`) // "click" + output.push(`Target id: ${event.target.id}`) // "myButton" + }) + + // The callback receives an Event object with details about what happened + + // Simulate click + button.click() + + expect(output).toEqual([ + 'Button clicked!', + 'Event type: click', + 'Target id: myButton' + ]) + }) + + // From lines 420-434: Named functions for reusability and removal + it('should use named functions for reusability', () => { + const output = [] + + function handleClick(event) { + output.push(`Clicked: ${event.target.id}`) + } + + function handleMouseOver(event) { + output.push(`Mouseover: ${event.target.id}`) + } + + button.addEventListener('click', handleClick) + button.addEventListener('mouseover', handleMouseOver) + + // Simulate events + button.click() + button.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })) + + expect(output).toEqual([ + 'Clicked: myButton', + 'Mouseover: myButton' + ]) + }) + + it('should remove event listeners with removeEventListener', () => { + const output = [] + + function handleClick(event) { + output.push('Clicked!') + } + + button.addEventListener('click', handleClick) + + // First click - handler is attached + button.click() + expect(output).toEqual(['Clicked!']) + + // Later, you can remove them: + button.removeEventListener('click', handleClick) + + // Second click - handler is removed + button.click() + expect(output).toEqual(['Clicked!']) // Still just one, handler was removed + }) + + it('should demonstrate multiple event listeners on same element', () => { + const output = [] + + button.addEventListener('click', () => output.push('Handler 1')) + button.addEventListener('click', () => output.push('Handler 2')) + button.addEventListener('click', () => output.push('Handler 3')) + + button.click() + + // All handlers execute in order of registration + expect(output).toEqual(['Handler 1', 'Handler 2', 'Handler 3']) + }) + + it('should demonstrate event object properties in callback', () => { + const eventData = {} + + button.addEventListener('click', function(event) { + eventData.type = event.type + eventData.target = event.target + eventData.currentTarget = event.currentTarget + eventData.bubbles = event.bubbles + eventData.cancelable = event.cancelable + }) + + button.click() + + expect(eventData.type).toBe('click') + expect(eventData.target).toBe(button) + expect(eventData.currentTarget).toBe(button) + expect(eventData.bubbles).toBe(true) + expect(eventData.cancelable).toBe(true) + }) + + it('should demonstrate event delegation pattern with callbacks', () => { + // Create a list with items + const list = document.createElement('ul') + list.id = 'myList' + + const item1 = document.createElement('li') + item1.textContent = 'Item 1' + item1.dataset.id = '1' + + const item2 = document.createElement('li') + item2.textContent = 'Item 2' + item2.dataset.id = '2' + + list.appendChild(item1) + list.appendChild(item2) + document.body.appendChild(list) + + const clickedItems = [] + + // Event delegation - single handler on parent + list.addEventListener('click', function(event) { + if (event.target.tagName === 'LI') { + clickedItems.push(event.target.dataset.id) + } + }) + + item1.click() + item2.click() + + expect(clickedItems).toEqual(['1', '2']) + }) + + it('should demonstrate this context in event handler callbacks', () => { + const results = [] + + // Regular function - 'this' is the element + button.addEventListener('click', function(event) { + results.push(`Regular: ${this.id}`) + }) + + // Arrow function - 'this' is NOT the element (inherited from outer scope) + button.addEventListener('click', (event) => { + // In this context, 'this' would be the module/global scope + results.push(`Arrow target: ${event.target.id}`) + }) + + button.click() + + expect(results).toEqual([ + 'Regular: myButton', + 'Arrow target: myButton' + ]) + }) + + it('should demonstrate once option for single-fire callbacks', () => { + const output = [] + + button.addEventListener('click', () => { + output.push('Clicked!') + }, { once: true }) + + button.click() + button.click() + button.click() + + // Handler only fires once + expect(output).toEqual(['Clicked!']) + }) + + it('should demonstrate preventing default with callbacks', () => { + const form = document.createElement('form') + const submitEvents = [] + let defaultPrevented = false + + form.addEventListener('submit', function(event) { + event.preventDefault() + defaultPrevented = event.defaultPrevented + submitEvents.push('Form submitted') + }) + + // Dispatch a submit event + const submitEvent = new Event('submit', { cancelable: true }) + form.dispatchEvent(submitEvent) + + expect(submitEvents).toEqual(['Form submitted']) + expect(defaultPrevented).toBe(true) + }) + + it('should demonstrate stopping propagation in callbacks', () => { + const output = [] + + // Create nested elements + const outer = document.createElement('div') + const inner = document.createElement('div') + outer.appendChild(inner) + document.body.appendChild(outer) + + outer.addEventListener('click', () => output.push('Outer clicked')) + inner.addEventListener('click', (event) => { + event.stopPropagation() + output.push('Inner clicked') + }) + + inner.click() + + // Only inner handler fires due to stopPropagation + expect(output).toEqual(['Inner clicked']) + }) +}) diff --git a/tests/async-javascript/callbacks/callbacks.test.js b/tests/async-javascript/callbacks/callbacks.test.js new file mode 100644 index 00000000..86e4c37d --- /dev/null +++ b/tests/async-javascript/callbacks/callbacks.test.js @@ -0,0 +1,1490 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +describe('Callbacks', () => { + + // ============================================================ + // OPENING EXAMPLES + // From callbacks.mdx lines 9-22, 139-155 + // ============================================================ + + describe('Opening Examples', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // From lines 9-22: Why doesn't JavaScript wait? + it('should demonstrate setTimeout non-blocking behavior', async () => { + const output = [] + + output.push('Before timer') + + setTimeout(function() { + output.push('Timer fired!') + }, 1000) + + output.push('After timer') + + // Before timer advances, only sync code has run + expect(output).toEqual(['Before timer', 'After timer']) + + // After 1 second + await vi.advanceTimersByTimeAsync(1000) + + // Output: + // Before timer + // After timer + // Timer fired! (1 second later) + expect(output).toEqual(['Before timer', 'After timer', 'Timer fired!']) + }) + + // From lines 139-155: Restaurant buzzer analogy + it('should demonstrate restaurant buzzer analogy with eatBurger callback', async () => { + const output = [] + + // You place your order (start async operation) + setTimeout(function eatBurger() { + output.push('Eating my burger!') // This is the callback + }, 5000) + + // You go sit down (your code continues) + output.push('Sitting down, checking my phone...') + output.push('Chatting with friends...') + output.push('Reading the menu...') + + // Before timer fires + expect(output).toEqual([ + 'Sitting down, checking my phone...', + 'Chatting with friends...', + 'Reading the menu...' + ]) + + // After 5 seconds + await vi.advanceTimersByTimeAsync(5000) + + // Output: + // Sitting down, checking my phone... + // Chatting with friends... + // Reading the menu... + // Eating my burger! (5 seconds later) + expect(output).toEqual([ + 'Sitting down, checking my phone...', + 'Chatting with friends...', + 'Reading the menu...', + 'Eating my burger!' + ]) + }) + }) + + // ============================================================ + // WHAT IS A CALLBACK + // From callbacks.mdx lines 48-91 + // ============================================================ + + describe('What is a Callback', () => { + // From lines 48-61: greet and processUserInput example + it('should execute greet callback passed to processUserInput', () => { + const output = [] + + // greet is a callback function + function greet(name) { + output.push(`Hello, ${name}!`) + } + + // processUserInput accepts a callback + function processUserInput(callback) { + const name = 'Alice' + callback(name) // "calling back" the function we received + } + + processUserInput(greet) // "Hello, Alice!" + + expect(output).toEqual(['Hello, Alice!']) + }) + + // From lines 73-91: Callbacks can be anonymous + it('should work with anonymous function callbacks', () => { + const output = [] + + // Simulating addEventListener behavior for testing + function simulateAddEventListener(event, callback) { + callback() + } + + // Named function as callback + function handleClick() { + output.push('Clicked!') + } + simulateAddEventListener('click', handleClick) + + // Anonymous function as callback + simulateAddEventListener('click', function() { + output.push('Clicked!') + }) + + // Arrow function as callback + simulateAddEventListener('click', () => { + output.push('Clicked!') + }) + + // All three do the same thing + expect(output).toEqual(['Clicked!', 'Clicked!', 'Clicked!']) + }) + }) + + // ============================================================ + // CALLBACKS AND HIGHER-ORDER FUNCTIONS + // From callbacks.mdx lines 166-198 + // ============================================================ + + describe('Callbacks and Higher-Order Functions', () => { + // From lines 166-176: forEach is a higher-order function + it('should demonstrate forEach as a higher-order function', () => { + const output = [] + + // forEach is a HIGHER-ORDER FUNCTION (it accepts a function) + // The arrow function is the CALLBACK (it's being passed in) + + const numbers = [1, 2, 3] + + numbers.forEach((num) => { // <- This is the callback + output.push(num * 2) + }) + // 2, 4, 6 + + expect(output).toEqual([2, 4, 6]) + }) + + // From lines 180-198: filter, map, find, sort with users array + it('should demonstrate filter, map, find, sort with users array', () => { + const users = [ + { name: 'Alice', age: 25 }, + { name: 'Bob', age: 17 }, + { name: 'Charlie', age: 30 } + ] + + // filter accepts a callback that returns true/false + const adults = users.filter(user => user.age >= 18) + expect(adults).toEqual([ + { name: 'Alice', age: 25 }, + { name: 'Charlie', age: 30 } + ]) + + // map accepts a callback that transforms each element + const names = users.map(user => user.name) + expect(names).toEqual(['Alice', 'Bob', 'Charlie']) + + // find accepts a callback that returns true when found + const bob = users.find(user => user.name === 'Bob') + expect(bob).toEqual({ name: 'Bob', age: 17 }) + + // sort accepts a callback that compares two elements + const byAge = [...users].sort((a, b) => a.age - b.age) + expect(byAge).toEqual([ + { name: 'Bob', age: 17 }, + { name: 'Alice', age: 25 }, + { name: 'Charlie', age: 30 } + ]) + }) + }) + + // ============================================================ + // SYNCHRONOUS VS ASYNCHRONOUS CALLBACKS + // From callbacks.mdx lines 214-310 + // ============================================================ + + describe('Synchronous Callbacks', () => { + // From lines 214-236: Synchronous callbacks execute immediately + it('should execute map callbacks synchronously and in order', () => { + const output = [] + + const numbers = [1, 2, 3, 4, 5] + + output.push('Before map') + + const doubled = numbers.map(num => { + output.push(`Doubling ${num}`) + return num * 2 + }) + + output.push('After map') + output.push(JSON.stringify(doubled)) + + // Output (all synchronous, in order): + // Before map + // Doubling 1 + // Doubling 2 + // Doubling 3 + // Doubling 4 + // Doubling 5 + // After map + // [2, 4, 6, 8, 10] + expect(output).toEqual([ + 'Before map', + 'Doubling 1', + 'Doubling 2', + 'Doubling 3', + 'Doubling 4', + 'Doubling 5', + 'After map', + '[2,4,6,8,10]' + ]) + + expect(doubled).toEqual([2, 4, 6, 8, 10]) + }) + + // From lines 287-295: Synchronous callback - try/catch WORKS + it('should catch errors in synchronous callbacks with try/catch', () => { + let caughtMessage = null + + // Synchronous callback - try/catch WORKS + try { + [1, 2, 3].forEach(num => { + if (num === 2) throw new Error('Found 2!') + }) + } catch (error) { + caughtMessage = error.message // "Caught: Found 2!" + } + + expect(caughtMessage).toBe('Found 2!') + }) + }) + + describe('Asynchronous Callbacks', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // From lines 249-262: Even with 0ms delay, callback runs after sync code + it('should execute setTimeout callbacks after synchronous code even with 0ms delay', async () => { + const output = [] + + output.push('Before setTimeout') + + setTimeout(() => { + output.push('Inside setTimeout') + }, 0) // Even with 0ms delay! + + output.push('After setTimeout') + + // Before timer fires + expect(output).toEqual(['Before setTimeout', 'After setTimeout']) + + await vi.advanceTimersByTimeAsync(0) + + // Output: + // Before setTimeout + // After setTimeout + // Inside setTimeout (runs AFTER all sync code) + expect(output).toEqual([ + 'Before setTimeout', + 'After setTimeout', + 'Inside setTimeout' + ]) + }) + + // From lines 297-306: Asynchronous callback - try/catch DOES NOT WORK! + it('should demonstrate that try/catch cannot catch async callback errors', async () => { + // This test verifies the concept that try/catch doesn't work for async callbacks + // In real code, the error would crash the program + + let tryCatchRan = false + const asyncCallback = vi.fn() + + // Asynchronous callback - try/catch DOES NOT WORK! + try { + setTimeout(() => { + asyncCallback() + // throw new Error('Async error!') // This error escapes! + }, 100) + } catch (error) { + // This will NEVER run + tryCatchRan = true + } + + // The try/catch completes immediately, before the callback even runs + expect(tryCatchRan).toBe(false) + expect(asyncCallback).not.toHaveBeenCalled() + + await vi.advanceTimersByTimeAsync(100) + + // Now the callback has run, but the try/catch is long gone + expect(asyncCallback).toHaveBeenCalled() + }) + }) + + // ============================================================ + // HOW CALLBACKS WORK WITH THE EVENT LOOP + // From callbacks.mdx lines 355-393 + // ============================================================ + + describe('Event Loop Examples', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // From lines 355-387: Event loop trace example + it('should demonstrate event loop execution order', async () => { + const output = [] + + output.push('1: Script start') + + setTimeout(function first() { + output.push('2: First timeout') + }, 0) + + setTimeout(function second() { + output.push('3: Second timeout') + }, 0) + + output.push('4: Script end') + + // Execution order: + // 1. console.log('1: Script start') - runs immediately + // 2. setTimeout(first, 0) - registers first callback with Web APIs + // 3. setTimeout(second, 0) - registers second callback with Web APIs + // 4. console.log('4: Script end') - runs immediately + // 5. Call stack is now empty + // 6. Event Loop checks Task Queue - finds first + // 7. first() runs -> "2: First timeout" + // 8. Event Loop checks Task Queue - finds second + // 9. second() runs -> "3: Second timeout" + + // Before timers fire - only sync code has run + expect(output).toEqual(['1: Script start', '4: Script end']) + + await vi.advanceTimersByTimeAsync(0) + + // Output: + // 1: Script start + // 4: Script end + // 2: First timeout + // 3: Second timeout + expect(output).toEqual([ + '1: Script start', + '4: Script end', + '2: First timeout', + '3: Second timeout' + ]) + }) + }) + + // ============================================================ + // COMMON CALLBACK PATTERNS + // From callbacks.mdx lines 397-537 + // ============================================================ + + describe('Common Callback Patterns', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // Pattern 2: Timers (lines 436-479) + + // From lines 440-447: setTimeout runs once after delay + it('should run setTimeout callback once after delay', async () => { + const output = [] + + // setTimeout - runs once after delay + const timeoutId = setTimeout(function() { + output.push('This runs once after 2 seconds') + }, 2000) + + expect(output).toEqual([]) + + await vi.advanceTimersByTimeAsync(2000) + + expect(output).toEqual(['This runs once after 2 seconds']) + }) + + // From lines 447: Cancel timeout before it runs + it('should cancel setTimeout with clearTimeout', async () => { + const callback = vi.fn() + + // Cancel it before it runs + const timeoutId = setTimeout(function() { + callback() + }, 2000) + + clearTimeout(timeoutId) + + await vi.advanceTimersByTimeAsync(2000) + + expect(callback).not.toHaveBeenCalled() + }) + + // From lines 449-459: setInterval runs repeatedly + it('should run setInterval callback repeatedly until cleared', async () => { + const output = [] + + // setInterval - runs repeatedly + let count = 0 + const intervalId = setInterval(function() { + count++ + output.push(`Count: ${count}`) + + if (count >= 5) { + clearInterval(intervalId) // Stop after 5 times + output.push('Done!') + } + }, 1000) + + await vi.advanceTimersByTimeAsync(5000) + + expect(output).toEqual([ + 'Count: 1', + 'Count: 2', + 'Count: 3', + 'Count: 4', + 'Count: 5', + 'Done!' + ]) + }) + + // From lines 464-479: Passing arguments to timer callbacks + it('should pass arguments to setTimeout callbacks using closure', async () => { + const output = [] + + // Method 1: Closure (most common) + const name = 'Alice' + setTimeout(function() { + output.push(`Hello, ${name}!`) + }, 1000) + + await vi.advanceTimersByTimeAsync(1000) + + expect(output).toEqual(['Hello, Alice!']) + }) + + it('should pass arguments to setTimeout callbacks using extra arguments', async () => { + const output = [] + + // Method 2: setTimeout's extra arguments + setTimeout(function(greeting, name) { + output.push(`${greeting}, ${name}!`) + }, 1000, 'Hello', 'Bob') // Extra args passed to callback + + await vi.advanceTimersByTimeAsync(1000) + + expect(output).toEqual(['Hello, Bob!']) + }) + + it('should pass arguments to setTimeout callbacks using arrow function with closure', async () => { + const output = [] + + // Method 3: Arrow function with closure + const user = { name: 'Charlie' } + setTimeout(() => output.push(`Hi, ${user.name}!`), 1000) + + await vi.advanceTimersByTimeAsync(1000) + + expect(output).toEqual(['Hi, Charlie!']) + }) + + // Pattern 3: Array Iteration (lines 481-512) + + // From lines 485-512: products array examples + it('should demonstrate array iteration callbacks with products array', () => { + const products = [ + { name: 'Laptop', price: 999, inStock: true }, + { name: 'Phone', price: 699, inStock: false }, + { name: 'Tablet', price: 499, inStock: true } + ] + + // forEach - do something with each item + const forEachOutput = [] + products.forEach(product => { + forEachOutput.push(`${product.name}: $${product.price}`) + }) + expect(forEachOutput).toEqual([ + 'Laptop: $999', + 'Phone: $699', + 'Tablet: $499' + ]) + + // map - transform each item into something new + const productNames = products.map(product => product.name) + // ['Laptop', 'Phone', 'Tablet'] + expect(productNames).toEqual(['Laptop', 'Phone', 'Tablet']) + + // filter - keep only items that pass a test + const available = products.filter(product => product.inStock) + // [{ name: 'Laptop', ... }, { name: 'Tablet', ... }] + expect(available).toEqual([ + { name: 'Laptop', price: 999, inStock: true }, + { name: 'Tablet', price: 499, inStock: true } + ]) + + // find - get the first item that passes a test + const phone = products.find(product => product.name === 'Phone') + // { name: 'Phone', price: 699, inStock: false } + expect(phone).toEqual({ name: 'Phone', price: 699, inStock: false }) + + // reduce - combine all items into a single value + const totalValue = products.reduce((sum, product) => sum + product.price, 0) + // 2197 + expect(totalValue).toBe(2197) + }) + + // Pattern 4: Custom Callbacks (lines 514-537) + + // From lines 518-537: fetchUserData custom callback + it('should demonstrate custom callback pattern with fetchUserData', async () => { + const output = [] + + // A function that does something and then calls you back + function fetchUserData(userId, callback) { + // Simulate async operation + setTimeout(function() { + const user = { id: userId, name: 'Alice', email: 'alice@example.com' } + callback(user) + }, 1000) + } + + // Using the function + fetchUserData(123, function(user) { + output.push(`Got user: ${user.name}`) + }) + output.push('Fetching user...') + + // Before timer fires + expect(output).toEqual(['Fetching user...']) + + await vi.advanceTimersByTimeAsync(1000) + + // Output: + // Fetching user... + // Got user: Alice (1 second later) + expect(output).toEqual(['Fetching user...', 'Got user: Alice']) + }) + }) + + // ============================================================ + // THE ERROR-FIRST CALLBACK PATTERN + // From callbacks.mdx lines 541-654 + // ============================================================ + + describe('Error-First Callback Pattern', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // From lines 547-553: Error-first callback signature + it('should demonstrate error-first callback signature', () => { + // Error-first callback signature + // function callback(error, result) { + // // error: null/undefined if success, Error object if failure + // // result: the data if success, usually undefined if failure + // } + + let receivedError = 'not called' + let receivedResult = 'not called' + + function callback(error, result) { + receivedError = error + receivedResult = result + } + + // Success case + callback(null, 'success data') + expect(receivedError).toBeNull() + expect(receivedResult).toBe('success data') + + // Error case + callback(new Error('something failed'), undefined) + expect(receivedError).toBeInstanceOf(Error) + expect(receivedError.message).toBe('something failed') + expect(receivedResult).toBeUndefined() + }) + + // From lines 586-622: divideAsync error-first example + it('should demonstrate divideAsync error-first callback pattern', async () => { + function divideAsync(a, b, callback) { + // Simulate async operation + setTimeout(function() { + // Check for errors + if (typeof a !== 'number' || typeof b !== 'number') { + callback(new Error('Both arguments must be numbers')) + return + } + + if (b === 0) { + callback(new Error('Cannot divide by zero')) + return + } + + // Success! Error is null, result is the value + const result = a / b + callback(null, result) + }, 100) + } + + // Test success case + let successError = 'not called' + let successResult = 'not called' + + divideAsync(10, 2, function(error, result) { + successError = error + successResult = result + }) + + await vi.advanceTimersByTimeAsync(100) + + expect(successError).toBeNull() + expect(successResult).toBe(5) // Result: 5 + + // Test error case - divide by zero + let errorError = 'not called' + let errorResult = 'not called' + + divideAsync(10, 0, function(error, result) { + errorError = error + errorResult = result + }) + + await vi.advanceTimersByTimeAsync(100) + + expect(errorError).toBeInstanceOf(Error) + expect(errorError.message).toBe('Cannot divide by zero') + }) + + // From lines 627-650: Common Mistake - Forgetting to Return + it('should demonstrate the importance of returning after error callback', () => { + const results = [] + + // Wrong - doesn't return after error + function processDataWrong(data, callback) { + if (!data) { + callback(new Error('No data provided')) + // Oops! Execution continues... + } + + // This runs even when there's an error! + results.push('This should not run if error') + callback(null, 'processed') + } + + // Correct - return after error callback + function processDataCorrect(data, callback) { + if (!data) { + return callback(new Error('No data provided')) + // Or: callback(new Error(...)); return; + } + + // This only runs if data exists + results.push('This only runs on success') + callback(null, 'processed') + } + + // Test wrong way + processDataWrong(null, () => {}) + expect(results).toContain('This should not run if error') // Bug! + + results.length = 0 // Clear results + + // Test correct way + processDataCorrect(null, () => {}) + expect(results).not.toContain('This only runs on success') // Correct! + }) + }) + + // ============================================================ + // CALLBACK HELL: THE PYRAMID OF DOOM + // From callbacks.mdx lines 658-757 + // ============================================================ + + describe('Callback Hell', () => { + // From lines 674-715: Nested callback example + it('should demonstrate the pyramid of doom pattern', () => { + return new Promise((resolve) => { + const steps = [] + + // Simulated async operations + function getUser(userId, callback) { + setTimeout(() => { + steps.push('getUser') + callback(null, { id: userId, name: 'Alice' }) + }, 0) + } + + function verifyPassword(user, password, callback) { + setTimeout(() => { + steps.push('verifyPassword') + callback(null, password === 'correct') + }, 0) + } + + function getProfile(userId, callback) { + setTimeout(() => { + steps.push('getProfile') + callback(null, { bio: 'Developer' }) + }, 0) + } + + function getSettings(userId, callback) { + setTimeout(() => { + steps.push('getSettings') + callback(null, { theme: 'dark' }) + }, 0) + } + + function renderDashboard(user, profile, settings, callback) { + setTimeout(() => { + steps.push('renderDashboard') + callback(null) + }, 0) + } + + function handleError(error) { + steps.push(`Error: ${error.message}`) + } + + const userId = 123 + const password = 'correct' + + // Callback hell - nested callbacks (pyramid of doom) + getUser(userId, function(error, user) { + if (error) { + handleError(error) + return + } + + verifyPassword(user, password, function(error, isValid) { + if (error) { + handleError(error) + return + } + + if (!isValid) { + handleError(new Error('Invalid password')) + return + } + + getProfile(user.id, function(error, profile) { + if (error) { + handleError(error) + return + } + + getSettings(user.id, function(error, settings) { + if (error) { + handleError(error) + return + } + + renderDashboard(user, profile, settings, function(error) { + if (error) { + handleError(error) + return + } + + steps.push('Dashboard rendered!') + + expect(steps).toEqual([ + 'getUser', + 'verifyPassword', + 'getProfile', + 'getSettings', + 'renderDashboard', + 'Dashboard rendered!' + ]) + resolve() + }) + }) + }) + }) + }) + }) + }) + }) + + // ============================================================ + // ESCAPING CALLBACK HELL + // From callbacks.mdx lines 761-970 + // ============================================================ + + describe('Escaping Callback Hell', () => { + // From lines 769-801: Strategy 1 - Named Functions + it('should demonstrate named functions to escape callback hell', () => { + return new Promise((resolve) => { + const steps = [] + let rejected = false + + function getData(callback) { + setTimeout(() => { + steps.push('getData') + callback(null, 'data') + }, 0) + } + + function processData(data, callback) { + setTimeout(() => { + steps.push(`processData: ${data}`) + callback(null, 'processed') + }, 0) + } + + function saveData(processed, callback) { + setTimeout(() => { + steps.push(`saveData: ${processed}`) + callback(null) + }, 0) + } + + function handleError(err) { + steps.push(`Error: ${err.message}`) + rejected = true + } + + // After: Named functions + function handleData(err, data) { + if (err) return handleError(err) + processData(data, handleProcessed) + } + + function handleProcessed(err, processed) { + if (err) return handleError(err) + saveData(processed, handleSaved) + } + + function handleSaved(err) { + if (err) return handleError(err) + steps.push('Done!') + + expect(steps).toEqual([ + 'getData', + 'processData: data', + 'saveData: processed', + 'Done!' + ]) + resolve() + } + + // Start the chain + getData(handleData) + }) + }) + + // From lines 813-847: Strategy 2 - Early Returns + it('should demonstrate early returns to reduce nesting', () => { + return new Promise((resolve) => { + const results = [] + + function validateUser(user, callback) { + setTimeout(() => { + callback(null, user.name !== '') + }, 0) + } + + function saveUser(user, callback) { + setTimeout(() => { + callback(null, { ...user, saved: true }) + }, 0) + } + + // Use early returns + function processUser(user, callback) { + validateUser(user, function(err, isValid) { + if (err) return callback(err) + if (!isValid) return callback(new Error('Invalid user')) + + saveUser(user, function(err, savedUser) { + if (err) return callback(err) + callback(null, savedUser) + }) + }) + } + + processUser({ name: 'Alice' }, function(err, result) { + expect(err).toBeNull() + expect(result).toEqual({ name: 'Alice', saved: true }) + + // Test invalid user + processUser({ name: '' }, function(err, result) { + expect(err).toBeInstanceOf(Error) + expect(err.message).toBe('Invalid user') + resolve() + }) + }) + }) + }) + + // From lines 853-888: Strategy 3 - Modularization + it('should demonstrate modularization to break up callback hell', () => { + return new Promise((resolve) => { + const steps = [] + + // auth.js + function getUser(email, callback) { + setTimeout(() => callback(null, { id: 1, email }), 0) + } + + function verifyPassword(user, password, callback) { + setTimeout(() => callback(null, password === 'secret'), 0) + } + + function authenticateUser(credentials, callback) { + getUser(credentials.email, function(err, user) { + if (err) return callback(err) + + verifyPassword(user, credentials.password, function(err, isValid) { + if (err) return callback(err) + if (!isValid) return callback(new Error('Invalid password')) + callback(null, user) + }) + }) + } + + // profile.js + function getProfile(userId, callback) { + setTimeout(() => callback(null, { bio: 'Developer' }), 0) + } + + function getSettings(userId, callback) { + setTimeout(() => callback(null, { theme: 'dark' }), 0) + } + + function loadUserProfile(userId, callback) { + getProfile(userId, function(err, profile) { + if (err) return callback(err) + + getSettings(userId, function(err, settings) { + if (err) return callback(err) + callback(null, { profile, settings }) + }) + }) + } + + function handleError(err) { + steps.push(`Error: ${err.message}`) + } + + function renderDashboard(user, profile, settings) { + steps.push(`Rendered dashboard for ${user.email}`) + } + + // main.js + const credentials = { email: 'alice@example.com', password: 'secret' } + + authenticateUser(credentials, function(err, user) { + if (err) return handleError(err) + + loadUserProfile(user.id, function(err, data) { + if (err) return handleError(err) + renderDashboard(user, data.profile, data.settings) + + expect(steps).toEqual(['Rendered dashboard for alice@example.com']) + resolve() + }) + }) + }) + }) + }) + + // ============================================================ + // COMMON CALLBACK MISTAKES + // From callbacks.mdx lines 974-1121 + // ============================================================ + + describe('Common Callback Mistakes', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // From lines 981-1001: Mistake 1 - Calling a Callback Multiple Times + it('should demonstrate the problem of calling callbacks multiple times', () => { + const results = [] + + // Wrong - callback called multiple times! + function fetchDataWrong(url, callback) { + // Simulating the wrong pattern from the docs + callback(null, 'response') // Called on success + // In the wrong code, .finally() would also call callback + callback(null, 'done') // Called ALWAYS - even after success or error! + } + + fetchDataWrong('http://example.com', (err, data) => { + results.push(data) + }) + + // Bug: callback was called twice! + expect(results).toEqual(['response', 'done']) + expect(results.length).toBe(2) + }) + + // From lines 1003-1041: Mistake 2 - Zalgo (sync/async inconsistency) + it('should demonstrate the Zalgo problem (inconsistent sync/async)', async () => { + const cache = new Map() + const order = [] + + // Wrong - sometimes sync, sometimes async (Zalgo!) + function getData(key, callback) { + if (cache.has(key)) { + callback(null, cache.get(key)) // Sync! + return + } + + setTimeout(() => { + const data = `data for ${key}` + cache.set(key, data) + callback(null, data) // Async! + }, 0) + } + + // This causes unpredictable behavior: + let value = 'initial' + getData('key1', function(err, data) { + value = data + }) + order.push(`After first call: ${value}`) + + await vi.advanceTimersByTimeAsync(0) + order.push(`After timer: ${value}`) + + // Second call - from cache (sync) + getData('key1', function(err, data) { + value = 'from cache' + }) + order.push(`After second call: ${value}`) + + // Inconsistent! First call: value changed after timer + // Second call: value changed immediately + expect(order).toEqual([ + 'After first call: initial', // First call was async + 'After timer: data for key1', // Value updated after timer + 'After second call: from cache' // Second call was sync - immediate! + ]) + }) + + // From lines 1027-1041: Solution to Zalgo - always be async + it('should demonstrate the solution to Zalgo - always async', async () => { + const cache = new Map() + const order = [] + + // Correct - always async + function getData(key, callback) { + if (cache.has(key)) { + // Use setTimeout to make it async even when cached + setTimeout(function() { + callback(null, cache.get(key)) + }, 0) + return + } + + setTimeout(() => { + const data = `data for ${key}` + cache.set(key, data) + callback(null, data) + }, 0) + } + + let value = 'initial' + + // First call + getData('key1', function(err, data) { + value = data + order.push(`callback1: ${value}`) + }) + order.push('after first call') + + await vi.advanceTimersByTimeAsync(0) + + // Second call (from cache, but still async) + getData('key1', function(err, data) { + value = 'from cache' + order.push(`callback2: ${value}`) + }) + order.push('after second call') + + await vi.advanceTimersByTimeAsync(0) + + // Consistent ordering! Both callbacks run after their respective calls + expect(order).toEqual([ + 'after first call', + 'callback1: data for key1', + 'after second call', + 'callback2: from cache' + ]) + }) + + // From lines 1043-1092: Mistake 3 - Losing `this` Context + it('should demonstrate losing this context with regular function callbacks', async () => { + // Wrong - this is undefined/global + const user = { + name: 'Alice', + greetLater: function() { + return new Promise(resolve => { + setTimeout(function() { + // 'this' is undefined in strict mode + resolve(this?.name) // this.name is undefined! + }, 1000) + }) + } + } + + const promise = user.greetLater() + await vi.advanceTimersByTimeAsync(1000) + const result = await promise + + expect(result).toBeUndefined() // "Hello, undefined!" + }) + + it('should preserve this context with arrow function callbacks', async () => { + // Correct - Use arrow function (inherits this) + const user = { + name: 'Alice', + greetLater: function() { + return new Promise(resolve => { + setTimeout(() => { + resolve(`Hello, ${this.name}!`) // Arrow function keeps this + }, 1000) + }) + } + } + + const promise = user.greetLater() + await vi.advanceTimersByTimeAsync(1000) + const result = await promise + + expect(result).toBe('Hello, Alice!') + }) + + it('should preserve this context with bind', async () => { + // Correct - Use bind + const user = { + name: 'Alice', + greetLater: function() { + return new Promise(resolve => { + setTimeout(function() { + resolve(`Hello, ${this.name}!`) + }.bind(this), 1000) // Explicitly bind this + }) + } + } + + const promise = user.greetLater() + await vi.advanceTimersByTimeAsync(1000) + const result = await promise + + expect(result).toBe('Hello, Alice!') + }) + + it('should preserve this context by saving reference', async () => { + // Correct - Save reference to this + const user = { + name: 'Alice', + greetLater: function() { + const self = this // Save reference + return new Promise(resolve => { + setTimeout(function() { + resolve(`Hello, ${self.name}!`) + }, 1000) + }) + } + } + + const promise = user.greetLater() + await vi.advanceTimersByTimeAsync(1000) + const result = await promise + + expect(result).toBe('Hello, Alice!') + }) + }) + + // ============================================================ + // TEST YOUR KNOWLEDGE + // From callbacks.mdx lines 1260-1371 + // ============================================================ + + describe('Test Your Knowledge', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + // From lines 1260-1285: Question 3 - What's the output of this code? + it('Question 3: What is the output order? A, C, E, B, D', async () => { + const output = [] + + output.push('A') + + setTimeout(() => output.push('B'), 0) + + output.push('C') + + setTimeout(() => output.push('D'), 0) + + output.push('E') + + // Before timers: A, C, E + expect(output).toEqual(['A', 'C', 'E']) + + await vi.advanceTimersByTimeAsync(0) + + // Answer: A, C, E, B, D + // + // Explanation: + // 1. console.log('A') - sync, runs immediately -> "A" + // 2. setTimeout(..., 0) - registers callback B, continues + // 3. console.log('C') - sync, runs immediately -> "C" + // 4. setTimeout(..., 0) - registers callback D, continues + // 5. console.log('E') - sync, runs immediately -> "E" + // 6. Call stack empty -> event loop runs callback B -> "B" + // 7. Event loop runs callback D -> "D" + // + // Even with 0ms delay, setTimeout callbacks run after all sync code. + expect(output).toEqual(['A', 'C', 'E', 'B', 'D']) + }) + + // From lines 1287-1316: Question 4 - How can you preserve `this` context? + it('Question 4: Three ways to preserve this context', async () => { + // 1. Arrow functions (recommended) + const obj1 = { + name: 'Alice', + greet() { + return new Promise(resolve => { + setTimeout(() => { + resolve(this.name) // "Alice" + }, 100) + }) + } + } + + // 2. Using bind() + const obj2 = { + name: 'Alice', + greet() { + return new Promise(resolve => { + setTimeout(function() { + resolve(this.name) + }.bind(this), 100) + }) + } + } + + // 3. Saving a reference + const obj3 = { + name: 'Alice', + greet() { + const self = this + return new Promise(resolve => { + setTimeout(function() { + resolve(self.name) + }, 100) + }) + } + } + + const promise1 = obj1.greet() + const promise2 = obj2.greet() + const promise3 = obj3.greet() + + await vi.advanceTimersByTimeAsync(100) + + expect(await promise1).toBe('Alice') + expect(await promise2).toBe('Alice') + expect(await promise3).toBe('Alice') + }) + + // From lines 1318-1342: Question 5 - Why can't you use try/catch with async callbacks? + it('Question 5: try/catch cannot catch async callback errors', async () => { + // The try/catch block executes synchronously. By the time an async + // callback runs, the try/catch is long gone - it's on a different + // "turn" of the event loop. + + let tryCatchExecuted = false + const callbackExecuted = vi.fn() + + try { + setTimeout(() => { + callbackExecuted() + // throw new Error('Async error!') // This escapes! + }, 100) + } catch (e) { + // This NEVER catches the error + tryCatchExecuted = true + } + + // The error crashes the program because: + // 1. try/catch runs immediately + // 2. setTimeout registers callback and returns + // 3. try/catch completes (nothing thrown yet!) + // 4. 100ms later, callback runs and throws + // 5. No try/catch exists at that point + + expect(tryCatchExecuted).toBe(false) + expect(callbackExecuted).not.toHaveBeenCalled() + + await vi.advanceTimersByTimeAsync(100) + + // Callback ran, but try/catch is long gone + expect(callbackExecuted).toHaveBeenCalled() + expect(tryCatchExecuted).toBe(false) // Still false! + }) + + // From lines 1344-1370: Question 6 - Three ways to avoid callback hell + it('Question 6: Three ways to avoid callback hell', async () => { + const steps = [] + + function getUser(userId, callback) { + setTimeout(() => callback(null, { id: userId, name: 'Alice' }), 0) + } + + function getProfile(userId, callback) { + setTimeout(() => callback(null, { bio: 'Developer' }), 0) + } + + function handleError(err) { + steps.push(`Error: ${err.message}`) + } + + // 1. Named functions - Extract callbacks into named functions + function handleUser(err, user) { + if (err) return handleError(err) + getProfile(user.id, handleProfile) + } + + function handleProfile(err, profile) { + if (err) return handleError(err) + steps.push(`Got profile: ${profile.bio}`) + } + + // Start the chain + getUser(123, handleUser) + + // Advance timers to let callbacks execute + // Need to run all pending timers (nested setTimeouts) + await vi.runAllTimersAsync() + + expect(steps).toEqual(['Got profile: Developer']) + + // Other approaches mentioned in docs: + // 2. Modularization - Split into separate modules/functions + // 3. Promises/async-await - Use modern async patterns + }) + }) + + // ============================================================ + // ADDITIONAL EDGE CASES + // ============================================================ + + describe('Additional Edge Cases', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should handle nested setTimeout callbacks', async () => { + const order = [] + + setTimeout(() => { + order.push('first') + setTimeout(() => { + order.push('second') + setTimeout(() => { + order.push('third') + }, 100) + }, 100) + }, 100) + + await vi.advanceTimersByTimeAsync(100) + expect(order).toEqual(['first']) + + await vi.advanceTimersByTimeAsync(100) + expect(order).toEqual(['first', 'second']) + + await vi.advanceTimersByTimeAsync(100) + expect(order).toEqual(['first', 'second', 'third']) + }) + + it('should demonstrate callback with multiple array methods chained', () => { + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + // Each method accepts a callback + const result = numbers + .filter(n => n % 2 === 0) // Keep evens: [2, 4, 6, 8, 10] + .map(n => n * 2) // Double: [4, 8, 12, 16, 20] + .reduce((sum, n) => sum + n, 0) // Sum: 60 + + expect(result).toBe(60) + }) + + it('should demonstrate once-only callback pattern', () => { + function once(callback) { + let called = false + return function(...args) { + if (called) return + called = true + return callback.apply(this, args) + } + } + + let callCount = 0 + const onceCallback = once(() => { + callCount++ + return 'result' + }) + + expect(onceCallback()).toBe('result') + expect(onceCallback()).toBeUndefined() + expect(onceCallback()).toBeUndefined() + expect(callCount).toBe(1) + }) + + it('should demonstrate closure issues with var in loops', async () => { + const results = [] + + // Wrong with var - all callbacks see final value + for (var i = 0; i < 3; i++) { + setTimeout(() => results.push(`var: ${i}`), 100) + } + + await vi.advanceTimersByTimeAsync(100) + + // All see i = 3 (the final value after loop completes) + expect(results).toEqual(['var: 3', 'var: 3', 'var: 3']) + }) + + it('should demonstrate closure fix with let in loops', async () => { + const results = [] + + // Correct with let - each iteration gets its own i + for (let i = 0; i < 3; i++) { + setTimeout(() => results.push(`let: ${i}`), 100) + } + + await vi.advanceTimersByTimeAsync(100) + + expect(results).toEqual(['let: 0', 'let: 1', 'let: 2']) + }) + }) +}) From b659e6d902fda4a6b00b950518ed86a63e1f1454 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 12:05:08 -0300 Subject: [PATCH 067/128] docs: add comprehensive promises concept page with tests - Complete rewrite of promises.mdx (1,645 lines) - Cover Promise states, fate, creation, and consumption - Explain chaining, error handling, and all static methods - Add common patterns (sequential, parallel, batching, retry) - Document 6 common mistakes with examples - Include ASCII diagrams, Q&A section, and references - Add 50 passing tests for code examples --- docs/concepts/promises.mdx | 1658 ++++++++++++++++- .../promises/promises.test.js | 529 ++++++ 2 files changed, 2147 insertions(+), 40 deletions(-) create mode 100644 tests/functions-execution/promises/promises.test.js diff --git a/docs/concepts/promises.mdx b/docs/concepts/promises.mdx index 4cc8ca83..b9795629 100644 --- a/docs/concepts/promises.mdx +++ b/docs/concepts/promises.mdx @@ -1,66 +1,1644 @@ --- title: "Promises: Managing Async Operations in JavaScript" sidebarTitle: "Promises: Managing Async Operations" -description: "Learn JavaScript Promises — objects representing eventual completion or failure of async operations. Master then/catch/finally, Promise.all, Promise.race, and error handling patterns." +description: "Learn JavaScript Promises for handling async operations. Understand how to create, chain, and combine Promises, handle errors properly, and avoid common pitfalls." --- -## Overview +What if you could represent a value that doesn't exist yet? What if instead of deeply nested callbacks, you could write asynchronous code that reads almost like synchronous code? -A **Promise** is an object representing the eventual completion or failure of an asynchronous operation. It allows you to attach callbacks to handle the success or failure of the operation, rather than passing callbacks into a function. +```javascript +// Instead of callback hell... +getUser(userId, function(user) { + getPosts(user.id, function(posts) { + getComments(posts[0].id, function(comments) { + console.log(comments) + }) + }) +}) + +// ...Promises give you this: +getUser(userId) + .then(user => getPosts(user.id)) + .then(posts => getComments(posts[0].id)) + .then(comments => console.log(comments)) +``` + +A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** is an object representing the eventual completion or failure of an asynchronous operation. It's basically a placeholder for a value that will show up later — like an order ticket at a restaurant that you'll trade for food when it's ready. + +<Info> +**What you'll learn in this guide:** +- What Promises are and why they were invented +- The three states of a Promise: pending, fulfilled, rejected +- How to create Promises with the Promise constructor +- How to consume Promises with `.then()`, `.catch()`, and `.finally()` +- How Promise chaining works and why it's powerful +- All the Promise static methods: `all`, `allSettled`, `race`, `any`, `resolve`, `reject` +- Common patterns and mistakes to avoid +</Info> + +<Warning> +**Prerequisite:** This guide assumes you understand [Callbacks](/concepts/callbacks). Promises were invented to solve problems with callbacks, so understanding callbacks will help you appreciate why Promises exist and how they improve async code. +</Warning> + +--- + +## What is a Promise? + +A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** is a JavaScript object that represents the eventual result of an asynchronous operation. When you create a Promise, you're basically saying: "I don't have the value right now, but I *promise* to give you a value (or an error) later." + +```javascript +// A Promise that resolves after 1 second +const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve('Hello from the future!') + }, 1000) +}) + +// Consuming the Promise +promise.then(value => { + console.log(value) // "Hello from the future!" (after 1 second) +}) +``` + +Unlike callbacks that you pass *into* functions, Promises are objects you get *back* from functions. This small change unlocks some really useful patterns like chaining, composition, and unified error handling. + +<CardGroup cols={2}> + <Card title="Promise — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"> + Official MDN documentation for the Promise object + </Card> + <Card title="Using Promises — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises"> + MDN guide on how to use Promises effectively + </Card> +</CardGroup> + +--- + +## The Restaurant Order Analogy + +Let's make this concrete. Imagine you're at a busy restaurant: + +1. **You place an order** — The waiter gives you an order ticket (a Promise) +2. **You wait** — The kitchen is cooking (the async operation is pending) +3. **One of two things happens:** + - **Food is ready** — You exchange your ticket for food (Promise fulfilled) + - **Kitchen ran out of ingredients** — You get an apology instead (Promise rejected) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE PROMISE LIFECYCLE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ YOU KITCHEN │ +│ ┌──────────┐ ┌──────────────┐ │ +│ │ │ "I'll have the │ │ │ +│ │ :) │ ─────pasta!─────► │ [chef] │ │ +│ │ │ │ │ │ +│ └──────────┘ └──────────────┘ │ +│ │ │ │ +│ │ Here's your │ │ +│ │ ORDER TICKET │ Cooking... │ +│ │ (Promise) │ (Pending) │ +│ ▼ │ │ +│ ┌──────────┐ │ │ +│ │ TICKET │ │ │ +│ │ #42 │◄───────────────────────────┘ │ +│ │ PENDING │ │ +│ └──────────┘ │ +│ │ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ OUTCOME │ │ +│ ├─────────────────────────┬───────────────────────────────┤ │ +│ │ │ │ │ +│ │ FULFILLED │ REJECTED │ │ +│ │ ┌──────────┐ │ ┌──────────┐ │ │ +│ │ │ PASTA │ │ │ SORRY! │ │ │ +│ │ │ :D │ │ │ No more │ │ │ +│ │ │ │ │ │ pasta │ │ │ +│ │ └──────────┘ │ └──────────┘ │ │ +│ │ You got what │ Something went │ │ +│ │ you ordered! │ wrong │ │ +│ │ │ │ │ +│ └─────────────────────────┴───────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Here's how this maps to JavaScript: + +| Restaurant | Promise | Code | +|------------|---------|------| +| Order ticket | Promise object | `const promise = fetch(url)` | +| Waiting for food | Pending state | Promise exists but hasn't settled | +| Food arrives | Fulfilled state | `resolve(value)` was called | +| Out of ingredients | Rejected state | `reject(error)` was called | +| Picking up food | `.then()` handler | `promise.then(food => eat(food))` | +| Handling problems | `.catch()` handler | `promise.catch(err => complain(err))` | + +Here's the important part: **once your order is fulfilled or rejected, it doesn't change**. You can't un-eat the pasta or un-reject the apology. Similarly, once a Promise settles, its state is permanent. + +--- + +## Why Promises? The Callback Problem + +Before we go further, let's quickly look at why Promises were invented. If you've read the [Callbacks guide](/concepts/callbacks), you know about "callback hell" — the deeply nested, hard-to-read code that happens when you chain multiple async operations: + +```javascript +// Callback Hell - The Pyramid of Doom +getUserData(userId, function(error, user) { + if (error) { + handleError(error) + return + } + getOrderHistory(user.id, function(error, orders) { + if (error) { + handleError(error) + return + } + getOrderDetails(orders[0].id, function(error, details) { + if (error) { + handleError(error) + return + } + getShippingStatus(details.shipmentId, function(error, status) { + if (error) { + handleError(error) + return + } + console.log(status) + }) + }) + }) +}) +``` + +The same logic with Promises: + +```javascript +// Promises - Flat and Readable +getUserData(userId) + .then(user => getOrderHistory(user.id)) + .then(orders => getOrderDetails(orders[0].id)) + .then(details => getShippingStatus(details.shipmentId)) + .then(status => console.log(status)) + .catch(error => handleError(error)) // One place for ALL errors! +``` + +<Tip> +**Why Promises are better:** +- **Flat structure** — No more pyramid of doom +- **Unified error handling** — One `.catch()` handles all errors in the chain +- **Composition** — Promises can be combined with `Promise.all()`, `Promise.race()`, etc. +- **Guaranteed async** — `.then()` callbacks always run asynchronously (on the microtask queue) +- **Return values** — Promises are objects you can store, pass around, and return from functions +</Tip> + +--- + +## Promise States and Fate + +Every Promise is in one of three **states**: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PROMISE STATES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────┐ │ +│ │ PENDING │ │ +│ │ │ │ +│ │ Waiting │ │ +│ │ for │ │ +│ │ result │ │ +│ └─────┬─────┘ │ +│ │ │ +│ ┌─────────────────┴─────────────────┐ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌───────────────┐ ┌───────────────┐ │ +│ │ FULFILLED │ │ REJECTED │ │ +│ │ │ │ │ │ +│ │ Success! │ │ Failed! │ │ +│ │ Has value │ │ Has reason │ │ +│ └───────────────┘ └───────────────┘ │ +│ │ +│ ◄─────────────── SETTLED (final state) ───────────────► │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +| State | Description | Can Change? | +|-------|-------------|-------------| +| **Pending** | Initial state. The async operation is still in progress. | Yes | +| **Fulfilled** | The operation completed successfully. The Promise has a value. | No | +| **Rejected** | The operation failed. The Promise has a reason (error). | No | + +A Promise that is either fulfilled or rejected is called **settled**. Once settled, a Promise's state is locked in — it never changes again. + +```javascript +const promise = new Promise((resolve, reject) => { + resolve('first') // Promise is now FULFILLED with value 'first' + resolve('second') // Ignored! Promise already settled + reject('error') // Also ignored! Promise already settled +}) + +promise.then(value => { + console.log(value) // "first" +}) +``` + +<Warning> +**Important:** Calling `resolve()` or `reject()` multiple times does nothing after the first call. The Promise settles once and only once. +</Warning> + +### Promise Fate: Resolved vs Unresolved + +There's a subtle but useful distinction between a Promise's **state** and its **fate**: + +- **State** = pending, fulfilled, or rejected +- **Fate** = resolved or unresolved + +Think of it like this: when you place your restaurant order, your fate is "sealed" the moment the waiter writes it down — even though you haven't received your food yet (still pending). You can't change your order anymore. + +A Promise is **resolved** when its fate is sealed — either because it's already settled, or because it's "locked in" to follow another Promise: + +```javascript +const innerPromise = new Promise(resolve => { + setTimeout(() => resolve('inner value'), 1000) +}) + +const outerPromise = new Promise(resolve => { + resolve(innerPromise) // Resolving with another Promise! +}) + +// outerPromise is now "resolved" (its fate is locked to innerPromise) +// but it's still "pending" (its state hasn't settled yet) + +outerPromise.then(value => { + console.log(value) // "inner value" (after 1 second) +}) +``` + +When you resolve a Promise with another Promise, the outer Promise "adopts" the state of the inner one. This is called **Promise unwrapping** — the outer Promise automatically follows whatever happens to the inner Promise. + +--- + +## Creating Promises + +### The Promise Constructor + +You create a new Promise using the `Promise` constructor, which takes an **executor function**: + +```javascript +const promise = new Promise((resolve, reject) => { + // Your async code here + // Call resolve(value) on success + // Call reject(error) on failure +}) +``` + +The executor receives two arguments: +- **`resolve(value)`** — Call this to fulfill the Promise with a value +- **`reject(reason)`** — Call this to reject the Promise with an error + +<Warning> +**Heads up:** The executor function runs **immediately and synchronously** when you create the Promise. Only the `.then()` callbacks are asynchronous. + +```javascript +console.log('Before Promise') + +const promise = new Promise((resolve, reject) => { + console.log('Inside executor (synchronous!)') + resolve('done') +}) + +console.log('After Promise') + +promise.then(value => { + console.log('Inside then (asynchronous)') +}) + +console.log('After then') + +// Output: +// Before Promise +// Inside executor (synchronous!) +// After Promise +// After then +// Inside then (asynchronous) +``` +</Warning> + +### Wrapping setTimeout in a Promise + +You'll often use the Promise constructor to wrap old callback-style code. Let's create a handy `delay` function: + +```javascript +// Create a Promise that resolves after ms milliseconds +function delay(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms) + }) +} + +// Usage +console.log('Starting...') + +delay(2000).then(() => { + console.log('2 seconds have passed!') +}) + +// Or with a value +function delayedValue(value, ms) { + return new Promise(resolve => { + setTimeout(() => resolve(value), ms) + }) +} + +delayedValue('Hello!', 1000).then(message => { + console.log(message) // "Hello!" (after 1 second) +}) +``` + +### Wrapping Callback-Based APIs + +Here's a real-world example — turning a callback-based image loader into a Promise: + +```javascript +// Original callback-based function +function loadImageCallback(url, onSuccess, onError) { + const img = new Image() + img.onload = () => onSuccess(img) + img.onerror = () => onError(new Error(`Failed to load ${url}`)) + img.src = url +} + +// Promise-based wrapper +function loadImage(url) { + return new Promise((resolve, reject) => { + const img = new Image() + img.onload = () => resolve(img) + img.onerror = () => reject(new Error(`Failed to load ${url}`)) + img.src = url + }) +} + +// Now you can use it with .then() or async/await! +loadImage('https://example.com/photo.jpg') + .then(img => { + console.log(`Loaded image: ${img.width}x${img.height}`) + document.body.appendChild(img) + }) + .catch(error => { + console.error('Failed to load image:', error.message) + }) +``` + +### Handling Errors in the Executor + +If an error is thrown inside the executor, the Promise is automatically rejected: + +```javascript +const promise = new Promise((resolve, reject) => { + throw new Error('Something went wrong!') + // No need to call reject() — the throw does it automatically +}) + +promise.catch(error => { + console.log(error.message) // "Something went wrong!" +}) +``` + +This is equivalent to: + +```javascript +const promise = new Promise((resolve, reject) => { + reject(new Error('Something went wrong!')) +}) +``` + +--- + +## Consuming Promises: then, catch, finally + +Once you have a Promise, you need to actually *do* something with it when it finishes. JavaScript gives you three methods for this. + +### .then() — The Core Method + +The **[`.then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)** method is the primary way to handle Promise results. It takes up to two callbacks: + +```javascript +promise.then(onFulfilled, onRejected) +``` + +- **`onFulfilled(value)`** — Called when the Promise is fulfilled +- **`onRejected(reason)`** — Called when the Promise is rejected + +```javascript +const promise = new Promise((resolve, reject) => { + const random = Math.random() + if (random > 0.5) { + resolve(`Success! Random was ${random}`) + } else { + reject(new Error(`Failed! Random was ${random}`)) + } +}) + +promise.then( + value => console.log('Fulfilled:', value), + error => console.log('Rejected:', error.message) +) +``` + +Most commonly, you'll only pass the first callback and use `.catch()` for errors: + +```javascript +promise.then(value => { + console.log('Got value:', value) +}) +``` + +### .catch() — Handling Rejections + +The **[`.catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)** method is syntactic sugar for `.then(undefined, onRejected)`: + +```javascript +// These are equivalent: +promise.catch(error => handleError(error)) +promise.then(undefined, error => handleError(error)) +``` + +Using `.catch()` is cleaner and more readable: + +```javascript +fetchUserData(userId) + .then(user => processUser(user)) + .then(result => saveResult(result)) + .catch(error => { + // Catches errors from fetchUserData, processUser, OR saveResult + console.error('Something went wrong:', error.message) + }) +``` + +### .finally() — Cleanup Code + +The **[`.finally()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally)** method runs code no matter if the Promise was fulfilled or rejected — great for cleanup: + +```javascript +let isLoading = true + +fetchData(url) + .then(data => { + displayData(data) + }) + .catch(error => { + displayError(error) + }) + .finally(() => { + // This runs no matter what! + isLoading = false + hideLoadingSpinner() + }) +``` + +<Note> +**How `.finally()` works:** +- It receives no arguments (it doesn't know if the Promise fulfilled or rejected) +- It returns a Promise that "passes through" the original value/error +- If you throw or return a rejected Promise in `.finally()`, that error propagates +</Note> + +```javascript +Promise.resolve('hello') + .finally(() => { + console.log('Cleanup!') + // Return value is ignored + return 'ignored' + }) + .then(value => { + console.log(value) // "hello" (not "ignored"!) + }) +``` + +### Every Handler Returns a New Promise + +This is **key** to understand: `.then()`, `.catch()`, and `.finally()` all return **new Promises**. This is what makes chaining possible: + +```javascript +const promise1 = Promise.resolve(1) +const promise2 = promise1.then(x => x + 1) +const promise3 = promise2.then(x => x + 1) + +// promise1, promise2, and promise3 are THREE DIFFERENT Promises! + +console.log(promise1 === promise2) // false +console.log(promise2 === promise3) // false + +promise3.then(value => console.log(value)) // 3 +``` + +--- + +## Promise Chaining + +Promise chaining is where Promises really shine. Since each `.then()` returns a new Promise, you can chain them together: + +```javascript +Promise.resolve(1) + .then(x => { + console.log(x) // 1 + return x + 1 + }) + .then(x => { + console.log(x) // 2 + return x + 1 + }) + .then(x => { + console.log(x) // 3 + return x + 1 + }) + .then(x => { + console.log(x) // 4 + }) +``` + +### How Chaining Works + +The value returned from a `.then()` callback becomes the fulfillment value of the Promise returned by `.then()`: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PROMISE CHAINING FLOW │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Promise.resolve(1) │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .then(x => x * 2) │ │ +│ │ │ │ +│ │ Input: 1 │ │ +│ │ Return: 2 │ │ +│ │ Output Promise: fulfilled with 2 │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .then(x => x + 10) │ │ +│ │ │ │ +│ │ Input: 2 │ │ +│ │ Return: 12 │ │ +│ │ Output Promise: fulfilled with 12 │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .then(x => console.log(x)) │ │ +│ │ │ │ +│ │ Input: 12 │ │ +│ │ Console: "12" │ │ +│ │ Return: undefined │ │ +│ │ Output Promise: fulfilled with undefined │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Returning Promises in Chains + +If you return a Promise from a `.then()` callback, the chain waits for it to finish: + +```javascript +function fetchUser(id) { + return new Promise(resolve => { + setTimeout(() => resolve({ id, name: 'Alice' }), 100) + }) +} + +function fetchPosts(userId) { + return new Promise(resolve => { + setTimeout(() => resolve([ + { id: 1, title: 'First Post' }, + { id: 2, title: 'Second Post' } + ]), 100) + }) +} + +// Chain of async operations +fetchUser(1) + .then(user => { + console.log('Got user:', user.name) + return fetchPosts(user.id) // Return a Promise + }) + .then(posts => { + // This waits for fetchPosts to complete! + console.log('Got posts:', posts.length) + }) + +// Output: +// Got user: Alice +// Got posts: 2 +``` + +<Tip> +**The #1 Rule of Chaining:** Always `return` from your `.then()` callbacks! Forgetting to return is the most common Promise mistake. + +```javascript +// ❌ WRONG - forgot to return +fetchUser(1) + .then(user => { + fetchPosts(user.id) // Oops! Not returned + }) + .then(posts => { + console.log(posts) // undefined! The Promise wasn't returned + }) + +// ✓ CORRECT - return the Promise +fetchUser(1) + .then(user => { + return fetchPosts(user.id) // Explicitly return + }) + .then(posts => { + console.log(posts) // [{ id: 1, ... }, { id: 2, ... }] + }) + +// ✓ ALSO CORRECT - arrow function implicit return +fetchUser(1) + .then(user => fetchPosts(user.id)) // Implicit return + .then(posts => console.log(posts)) +``` +</Tip> + +### Transforming Values Through the Chain + +Each step in the chain can transform the value: + +```javascript +Promise.resolve('hello') + .then(str => str.toUpperCase()) // 'HELLO' + .then(str => str + '!') // 'HELLO!' + .then(str => str.repeat(3)) // 'HELLO!HELLO!HELLO!' + .then(str => str.split('!')) // ['HELLO', 'HELLO', 'HELLO', ''] + .then(arr => arr.filter(s => s.length)) // ['HELLO', 'HELLO', 'HELLO'] + .then(arr => arr.length) // 3 + .then(count => console.log(count)) // Logs: 3 +``` + +--- + +## Error Handling + +Error handling is where Promises really shine. Errors automatically flow down the chain until something catches them. + +### Error Propagation + +When a Promise is rejected or an error is thrown, it "skips" all `.then()` callbacks until it finds a `.catch()`: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ERROR PROPAGATION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Promise.reject(new Error('Oops!')) │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .then(x => x * 2) │ ◄── SKIPPED │ +│ └─────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .then(x => x + 10) │ ◄── SKIPPED │ +│ └─────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .catch(err => console.log(err.message)) │ ◄── CAUGHT HERE! │ +│ │ │ │ +│ │ Output: "Oops!" │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ .then(() => console.log('Recovered!')) │ ◄── RUNS (chain │ +│ └─────────────────────────────────────────────┘ continues) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +```javascript +Promise.reject(new Error('Oops!')) + .then(x => { + console.log('This never runs') + return x * 2 + }) + .then(x => { + console.log('This never runs either') + return x + 10 + }) + .catch(error => { + console.log('Caught:', error.message) // "Caught: Oops!" + return 'recovered' + }) + .then(value => { + console.log('Continued with:', value) // "Continued with: recovered" + }) +``` + +### Throwing Errors in .then() + +If you throw an error in a `.then()` callback (or return a rejected Promise), the chain rejects: + +```javascript +Promise.resolve('start') + .then(value => { + console.log(value) // "start" + throw new Error('Something went wrong!') + }) + .then(value => { + console.log('This is skipped') + }) + .catch(error => { + console.log('Caught:', error.message) // "Caught: Something went wrong!" + }) +``` + +### Re-throwing Errors + +Sometimes you want to log an error but still let it bubble up: + +```javascript +fetchData(url) + .catch(error => { + // Log the error + console.error('Error fetching data:', error.message) + + // Re-throw to continue propagating + throw error + }) + .then(data => { + // This won't run if there was an error + processData(data) + }) + .catch(error => { + // Handle at a higher level + showUserError('Failed to load data') + }) +``` + +### Multiple .catch() Handlers + +You can have multiple `.catch()` handlers in a chain for different error handling strategies: + +```javascript +fetchUser(userId) + .then(user => { + if (!user.isActive) { + throw new Error('User is inactive') + } + return fetchUserPosts(user.id) + }) + .catch(error => { + // Handle user-related errors + if (error.message === 'User is inactive') { + return [] // Return empty posts for inactive users + } + throw error // Re-throw other errors + }) + .then(posts => renderPosts(posts)) + .catch(error => { + // Handle all other errors (network, rendering, etc.) + console.error('Failed:', error) + showFallbackUI() + }) +``` + +### The Unhandled Rejection Problem + +<Warning> +**Always handle Promise rejections!** If a Promise is rejected and there's no `.catch()` handler, modern JavaScript environments will warn you about an "unhandled promise rejection": + +```javascript +// ❌ BAD - Unhandled rejection +Promise.reject(new Error('Oops!')) + +// ❌ BAD - Error in .then() with no .catch() +Promise.resolve('data') + .then(data => { + throw new Error('Processing failed!') + }) +// UnhandledPromiseRejection warning! + +// ✓ GOOD - Always have a .catch() +Promise.reject(new Error('Oops!')) + .catch(error => console.error('Handled:', error.message)) +``` + +In Node.js, unhandled rejections can crash your application in future versions. In browsers, they're logged as errors. +</Warning> + +--- + +## Promise Static Methods + +The `Promise` class has several static methods for creating and combining Promises. These are super useful in practice. + +### Promise.resolve() and Promise.reject() + +The simplest static methods — they create already-settled Promises: + +```javascript +// Create a fulfilled Promise +const fulfilled = Promise.resolve('success') +fulfilled.then(value => console.log(value)) // "success" + +// Create a rejected Promise +const rejected = Promise.reject(new Error('failure')) +rejected.catch(error => console.log(error.message)) // "failure" +``` + +**When are these useful?** +- Converting a regular value to a Promise for consistency +- Starting a Promise chain +- Testing Promise-based code + +```javascript +// Useful for normalizing values to Promises +function fetchData(cached) { + if (cached) { + return Promise.resolve(cached) // Return cached data as Promise + } + return fetch('/api/data').then(r => r.json()) // Fetch fresh data +} + +// Both code paths return Promises, so callers can use .then() consistently +fetchData(cachedData).then(data => render(data)) +``` + +### Promise.all() — Wait for All + +**[`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)** takes an iterable of Promises and returns a single Promise that: +- **Fulfills** when ALL input Promises fulfill (with an array of values) +- **Rejects** when ANY input Promise rejects (with that error, immediately) + +```javascript +const promise1 = Promise.resolve(1) +const promise2 = Promise.resolve(2) +const promise3 = Promise.resolve(3) + +Promise.all([promise1, promise2, promise3]) + .then(values => { + console.log(values) // [1, 2, 3] + }) +``` + +**Real example — loading a dashboard:** + +```javascript +async function loadDashboard(userId) { + // All three requests start simultaneously! + const [user, posts, notifications] = await Promise.all([ + fetchUser(userId), + fetchPosts(userId), + fetchNotifications(userId) + ]) + + return { user, posts, notifications } +} +``` + +**The short-circuit behavior:** + +```javascript +Promise.all([ + Promise.resolve('A'), + Promise.reject(new Error('B failed!')), // This rejects! + Promise.resolve('C') +]) + .then(values => { + console.log('Success:', values) // Never runs + }) + .catch(error => { + console.log('Failed:', error.message) // "Failed: B failed!" + // We don't get 'A' or 'C' — the whole thing fails + }) +``` + +<Tip> +**Use `Promise.all()` when:** +- You need ALL results to proceed +- Any single failure should abort the whole operation +- You want to run Promises in parallel and wait for all +</Tip> + +### Promise.allSettled() — Wait for All (No Short-Circuit) + +**[`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)** waits for ALL Promises to settle, regardless of whether they fulfill or reject. It never rejects: + +```javascript +Promise.allSettled([ + Promise.resolve('A'), + Promise.reject(new Error('B failed!')), + Promise.resolve('C') +]) + .then(results => { + console.log(results) + // [ + // { status: 'fulfilled', value: 'A' }, + // { status: 'rejected', reason: Error: B failed! }, + // { status: 'fulfilled', value: 'C' } + // ] + }) +``` + +**Real example — sending notifications to multiple users:** + +```javascript +async function sendNotificationsToAll(userIds, message) { + const results = await Promise.allSettled( + userIds.map(id => sendNotification(id, message)) + ) + + const succeeded = results.filter(r => r.status === 'fulfilled') + const failed = results.filter(r => r.status === 'rejected') + + console.log(`Sent: ${succeeded.length}, Failed: ${failed.length}`) + + // Log failures for debugging + failed.forEach(f => console.error('Failed:', f.reason)) + + return { succeeded: succeeded.length, failed: failed.length } +} +``` + +<Tip> +**Use `Promise.allSettled()` when:** +- You want to attempt ALL operations regardless of individual failures +- You need to know which succeeded and which failed +- Partial success is acceptable +</Tip> + +### Promise.race() — First to Settle Wins + +**[`Promise.race()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race)** returns a Promise that settles as soon as ANY input Promise settles (fulfilled or rejected): + +```javascript +const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 200)) +const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 100)) + +Promise.race([slow, fast]) + .then(winner => console.log(winner)) // "fast" +``` + +**Real example — adding a timeout:** + +```javascript +function fetchWithTimeout(url, timeout = 5000) { + const fetchPromise = fetch(url) + + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(`Request timed out after ${timeout}ms`)) + }, timeout) + }) + + return Promise.race([fetchPromise, timeoutPromise]) +} + +// Usage +fetchWithTimeout('https://api.example.com/data', 3000) + .then(response => response.json()) + .catch(error => { + console.error(error.message) // "Request timed out after 3000ms" + }) +``` + +<Warning> +**Watch out:** `Promise.race()` settles on the first Promise to settle, whether it fulfills OR rejects. If the fastest Promise rejects, the race rejects: + +```javascript +Promise.race([ + new Promise((_, reject) => setTimeout(() => reject(new Error('Fast failure')), 50)), + new Promise(resolve => setTimeout(() => resolve('Slow success'), 100)) +]) + .catch(error => console.log(error.message)) // "Fast failure" +``` +</Warning> + +### Promise.any() — First to Fulfill Wins + +**[`Promise.any()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any)** returns a Promise that fulfills as soon as ANY input Promise fulfills. It ignores rejections unless ALL Promises reject: + +```javascript +Promise.any([ + Promise.reject(new Error('Error 1')), + Promise.resolve('Success!'), + Promise.reject(new Error('Error 2')) +]) + .then(value => console.log(value)) // "Success!" +``` + +**If ALL Promises reject, you get an AggregateError:** + +```javascript +Promise.any([ + Promise.reject(new Error('Error 1')), + Promise.reject(new Error('Error 2')), + Promise.reject(new Error('Error 3')) +]) + .catch(error => { + console.log(error.name) // "AggregateError" + console.log(error.errors) // [Error: Error 1, Error: Error 2, Error: Error 3] + }) +``` + +**Real example — trying multiple CDN mirrors:** + +```javascript +async function fetchFromFastestMirror(mirrors) { + try { + // Returns data from whichever mirror responds first + const data = await Promise.any( + mirrors.map(mirror => fetch(mirror).then(r => r.json())) + ) + return data + } catch (error) { + // All mirrors failed + throw new Error('All mirrors failed: ' + error.errors.map(e => e.message).join(', ')) + } +} + +const mirrors = [ + 'https://mirror1.example.com/data', + 'https://mirror2.example.com/data', + 'https://mirror3.example.com/data' +] + +fetchFromFastestMirror(mirrors) + .then(data => console.log('Got data:', data)) + .catch(error => console.error(error.message)) +``` + +<Tip> +**Use `Promise.any()` when:** +- You only need one successful result +- You have multiple sources/fallbacks and want the first success +- Rejections should be ignored unless everything fails +</Tip> + +### Comparison Table + +| Method | Fulfills when... | Rejects when... | Use case | +|--------|-----------------|-----------------|----------| +| `Promise.all()` | ALL fulfill | ANY rejects | Need all results, fail-fast | +| `Promise.allSettled()` | ALL settle | Never | Need all results, tolerate failures | +| `Promise.race()` | First settles | First settles (if rejection) | Timeout, fastest response | +| `Promise.any()` | First fulfills | ALL reject | First success, ignore failures | + +### Promise.withResolvers() + +**[`Promise.withResolvers()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers)** (ES2024) returns an object containing a new Promise and the functions to resolve/reject it. This is useful when you need to resolve a Promise from outside its executor: + +```javascript +const { promise, resolve, reject } = Promise.withResolvers() + +// Resolve it later from anywhere +setTimeout(() => resolve('Done!'), 1000) + +promise.then(value => console.log(value)) // "Done!" (after 1 second) +``` + +**Before `withResolvers()`, you had to do this:** + +```javascript +let resolve, reject +const promise = new Promise((res, rej) => { + resolve = res + reject = rej +}) + +// Now resolve/reject are available outside +``` + +--- + +## Common Patterns + +### Sequential Execution + +When you need to run things one at a time (not in parallel). Use this when each step depends on the previous result—like database transactions, or when processing order matters (uploading files in a specific sequence). + +```javascript +// Process items one at a time +async function processSequentially(items) { + const results = [] + + for (const item of items) { + const result = await processItem(item) // Wait for each + results.push(result) + } + + return results +} + +// Or with reduce (pure Promises, no async/await): +function processSequentiallyWithReduce(items) { + return items.reduce((chain, item) => { + return chain.then(results => { + return processItem(item).then(result => { + return [...results, result] + }) + }) + }, Promise.resolve([])) +} +``` + +### Parallel Execution + +When operations don't depend on each other. Great for independent fetches like loading a dashboard where you need user data, notifications, and settings all at once—much faster than doing them one by one. + +```javascript +// Process all items in parallel +async function processInParallel(items) { + const promises = items.map(item => processItem(item)) + return Promise.all(promises) +} + +// Example: Fetch multiple URLs at once +try { + const urls = ['/api/users', '/api/posts', '/api/comments'] + const responses = await Promise.all(urls.map(url => fetch(url))) + const data = await Promise.all(responses.map(r => r.json())) +} catch (error) { + console.error('One of the requests failed:', error) +} +``` + +### Parallel with Limit (Batching) + +When you want parallelism but don't want to hammer a server with 100 requests at once. Essential for API rate limits (e.g., "max 10 requests/second") or when processing large datasets without exhausting memory or connections. + +```javascript +async function processInBatches(items, batchSize = 3) { + const results = [] + + for (let i = 0; i < items.length; i += batchSize) { + const batch = items.slice(i, i + batchSize) + const batchResults = await Promise.all( + batch.map(item => processItem(item)) + ) + results.push(...batchResults) + } + + return results +} + +// Process 10 items, 3 at a time +const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +const results = await processInBatches(items, 3) +// Batch 1: [1, 2, 3] (parallel) +// Batch 2: [4, 5, 6] (parallel, after batch 1) +// Batch 3: [7, 8, 9] (parallel, after batch 2) +// Batch 4: [10] (after batch 3) +``` + +### Retry Pattern + +Automatically retry when things fail. Perfect for flaky network connections, unreliable third-party APIs, or temporary server issues. For production, consider adding exponential backoff (doubling the delay each attempt). + +```javascript +async function retry(fn, maxAttempts = 3, delay = 1000) { + let lastError + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn() + } catch (error) { + lastError = error + console.log(`Attempt ${attempt} failed: ${error.message}`) + + if (attempt < maxAttempts) { + await new Promise(resolve => setTimeout(resolve, delay)) + } + } + } + + throw lastError +} + +// Usage +const data = await retry( + () => fetch('/api/flaky-endpoint').then(r => r.json()), + 3, // max attempts + 1000 // delay between attempts +) +``` + +### Converting Callbacks to Promises (Promisification) + +A helper to convert old callback-style functions to Promises. Useful when working with older Node.js APIs or third-party libraries that still use callbacks but you want clean async/await syntax. + +```javascript +function promisify(fn) { + return function(...args) { + return new Promise((resolve, reject) => { + fn(...args, (error, result) => { + if (error) { + reject(error) + } else { + resolve(result) + } + }) + }) + } +} + +// Usage example (Node.js - fs uses callbacks) +const readFile = promisify(fs.readFile) +const data = await readFile('file.txt', 'utf8') +``` + +<Note> +Node.js has this built-in: `const { promisify } = require('util')` +</Note> + +--- + +## Common Mistakes + +### Mistake 1: Forgetting to Return + +The #1 Promise mistake — forgetting to return from `.then()`: + +```javascript +// ❌ WRONG - Promise not returned, chain breaks +fetchUser(1) + .then(user => { + fetchPosts(user.id) // This Promise floats away! + }) + .then(posts => { + console.log(posts) // undefined! + }) + +// ✓ CORRECT - Return the Promise +fetchUser(1) + .then(user => { + return fetchPosts(user.id) + }) + .then(posts => { + console.log(posts) // Array of posts + }) + +// ✓ EVEN BETTER - Arrow function implicit return +fetchUser(1) + .then(user => fetchPosts(user.id)) + .then(posts => console.log(posts)) +``` + +### Mistake 2: Nesting Instead of Chaining + +Don't accidentally recreate callback hell with Promises: + +```javascript +// ❌ WRONG - Promise hell (nesting) +fetchUser(1).then(user => { + fetchPosts(user.id).then(posts => { + fetchComments(posts[0].id).then(comments => { + console.log(comments) + }) + }) +}) + +// ✓ CORRECT - Flat chain +fetchUser(1) + .then(user => fetchPosts(user.id)) + .then(posts => fetchComments(posts[0].id)) + .then(comments => console.log(comments)) +``` + +### Mistake 3: The Promise Constructor Anti-Pattern + +Don't wrap existing Promises in `new Promise()`: + +```javascript +// ❌ WRONG - Unnecessary Promise wrapper +function getUser(id) { + return new Promise((resolve, reject) => { + fetch(`/api/users/${id}`) + .then(response => response.json()) + .then(user => resolve(user)) + .catch(error => reject(error)) + }) +} + +// ✓ CORRECT - Just return the Promise! +function getUser(id) { + return fetch(`/api/users/${id}`) + .then(response => response.json()) +} +``` + +<Warning> +**The Promise constructor anti-pattern** is when you wrap something that's already a Promise. You're just adding complexity for no reason. Only use `new Promise()` when you're wrapping callback-based APIs. +</Warning> + +### Mistake 4: Forgetting Error Handling + +```javascript +// ❌ WRONG - No error handling +fetchData() + .then(data => processData(data)) + .then(result => saveResult(result)) +// If anything fails, you get an unhandled rejection! + +// ✓ CORRECT - Always have a .catch() +fetchData() + .then(data => processData(data)) + .then(result => saveResult(result)) + .catch(error => { + console.error('Operation failed:', error) + // Handle the error appropriately + }) +``` + +### Mistake 5: Using forEach with Async Operations + +```javascript +// ❌ WRONG - forEach doesn't wait for Promises +async function processAll(items) { + items.forEach(async item => { + await processItem(item) // These run in parallel, not sequentially! + }) + console.log('Done!') // Logs immediately, before processing completes +} + +// ✓ CORRECT - Use for...of for sequential +async function processAllSequential(items) { + for (const item of items) { + await processItem(item) + } + console.log('Done!') // Logs after all items processed +} + +// ✓ CORRECT - Use Promise.all for parallel +async function processAllParallel(items) { + await Promise.all(items.map(item => processItem(item))) + console.log('Done!') // Logs after all items processed +} +``` + +### Mistake 6: Microtask Timing Gotcha + +```javascript +console.log('1') + +Promise.resolve().then(() => console.log('2')) + +console.log('3') + +// Output: 1, 3, 2 (NOT 1, 2, 3!) +``` + +Promise callbacks are scheduled as **microtasks**, which run after the current synchronous code but before the next macrotask. See the [Event Loop guide](/concepts/event-loop) for details. + +--- + +## Key Takeaways <Info> -Promises have three states: **pending** (initial state), **fulfilled** (operation completed successfully), and **rejected** (operation failed). +**Remember these essential points about Promises:** + +1. **A Promise is a placeholder** — It represents a value that will show up later (or an error if something goes wrong). + +2. **Three states, one transition** — Promises go from `pending` to either `fulfilled` or `rejected`, and never change after that. + +3. **`.then()` returns a NEW Promise** — This is what enables chaining. The value you return becomes the next Promise's value. + +4. **Always return from `.then()`** — Forgetting to return is the #1 Promise mistake. Use arrow functions for implicit returns. + +5. **Errors propagate down the chain** — A rejection skips all `.then()` handlers until it hits a `.catch()`. + +6. **Always handle rejections** — Use `.catch()` at the end of chains. Unhandled rejections are bugs. + +7. **`Promise.all()` for parallel + fail-fast** — Runs Promises in parallel, fails immediately if any rejects. + +8. **`Promise.allSettled()` for partial success** — Waits for all to settle, gives you results for each. + +9. **`Promise.race()` for timeouts** — First to settle wins (fulfill OR reject). + +10. **`Promise.any()` for first success** — First to fulfill wins, ignores rejections unless all fail. </Info> +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What are the three states of a Promise?"> + **Answer:** + + 1. **Pending** — Initial state, the async operation is still in progress + 2. **Fulfilled** — The operation completed successfully, the Promise has a value + 3. **Rejected** — The operation failed, the Promise has a reason (error) + + Once a Promise is fulfilled or rejected (we call this "settled"), its state is locked in forever. + </Accordion> + + <Accordion title="Question 2: What does .then() return?"> + **Answer:** + + `.then()` always returns a **new Promise**. The value returned from the `.then()` callback becomes the fulfillment value of this new Promise. + + ```javascript + const p1 = Promise.resolve(1) + const p2 = p1.then(x => x + 1) + + console.log(p1 === p2) // false - different Promises! + + p2.then(x => console.log(x)) // 2 + ``` + + If you return a Promise from the callback, the new Promise "adopts" its state. + </Accordion> + + <Accordion title="Question 3: What's the difference between Promise.all() and Promise.allSettled()?"> + **Answer:** + + | `Promise.all()` | `Promise.allSettled()` | + |-----------------|------------------------| + | Rejects immediately if ANY Promise rejects | Never rejects — waits for ALL to settle | + | Returns array of values on success | Returns array of `{status, value/reason}` objects | + | Use when all must succeed | Use when you want results regardless of failures | + + ```javascript + // Promise.all - fails fast + Promise.all([Promise.resolve(1), Promise.reject('error')]) + .catch(e => console.log(e)) // "error" + + // Promise.allSettled - gets all results + Promise.allSettled([Promise.resolve(1), Promise.reject('error')]) + .then(results => console.log(results)) + // [{status:'fulfilled',value:1}, {status:'rejected',reason:'error'}] + ``` + </Accordion> + + <Accordion title="Question 4: What happens if you resolve a Promise with another Promise?"> + **Answer:** + + The outer Promise "adopts" the state of the inner Promise. This is called Promise unwrapping or assimilation: + + ```javascript + const inner = new Promise(resolve => { + setTimeout(() => resolve('inner value'), 1000) + }) + + const outer = Promise.resolve(inner) + + // outer is now "locked in" to follow inner + // It won't fulfill until inner fulfills + + outer.then(value => console.log(value)) // "inner value" (after 1 second) + ``` + + This happens automatically — you can't have a Promise that fulfills with another Promise as its value. + </Accordion> + + <Accordion title="Question 5: What's wrong with this code?"> + ```javascript + function getData() { + return new Promise((resolve, reject) => { + fetch('/api/data') + .then(response => response.json()) + .then(data => resolve(data)) + .catch(error => reject(error)) + }) + } + ``` + + **Answer:** + + This is the **Promise constructor anti-pattern**. You're wrapping a Promise (`fetch`) inside `new Promise()` unnecessarily. Just return the Promise directly: + + ```javascript + function getData() { + return fetch('/api/data') + .then(response => response.json()) + } + ``` + + The original code: + - Adds unnecessary complexity + - Could lose stack trace information + - Might swallow errors if you forget the `.catch()` + + Only use `new Promise()` when wrapping callback-based APIs. + </Accordion> + + <Accordion title="Question 6: What's the output order?"> + ```javascript + console.log('A') + + Promise.resolve().then(() => console.log('B')) + + Promise.resolve().then(() => { + console.log('C') + Promise.resolve().then(() => console.log('D')) + }) + + console.log('E') + ``` + + **Answer:** `A`, `E`, `B`, `C`, `D` + + **Explanation:** + 1. `'A'` — Synchronous, runs first + 2. First `.then()` callback queued as microtask + 3. Second `.then()` callback queued as microtask + 4. `'E'` — Synchronous, runs next + 5. Synchronous code done → process microtask queue + 6. `'B'` — First microtask runs + 7. `'C'` — Second microtask runs, queues another microtask + 8. `'D'` — Third microtask runs (microtask queue is drained before any macrotask) + + Promise callbacks always run as microtasks, after the current synchronous code but before macrotasks like `setTimeout`. See [Event Loop](/concepts/event-loop) for more. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Callbacks" icon="phone" href="/concepts/callbacks"> + The predecessor to Promises — understand what Promises improve upon + </Card> + <Card title="async/await" icon="hourglass" href="/concepts/async-await"> + Modern syntax built on top of Promises — makes async code look synchronous + </Card> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + How Promise callbacks are scheduled via the microtask queue + </Card> + <Card title="Fetch API" icon="globe" href="/concepts/http-fetch"> + The most common Promise-based API — making HTTP requests + </Card> +</CardGroup> + +--- + ## Reference -<Card title="Promise — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"> - Official MDN documentation -</Card> +<CardGroup cols={2}> + <Card title="Promise — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"> + Complete reference for the Promise object and all its methods + </Card> + <Card title="Using Promises — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises"> + MDN guide covering Promise fundamentals and patterns + </Card> + <Card title="Promise.all() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all"> + Documentation for Promise.all() with examples + </Card> + <Card title="Promise.allSettled() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled"> + Documentation for Promise.allSettled() with examples + </Card> +</CardGroup> ## Articles <CardGroup cols={2}> - <Card title="JavaScript Promises for Dummies" icon="newspaper" href="https://scotch.io/tutorials/javascript-promises-for-dummies"> - By Jecelyn Yeen + <Card title="JavaScript Promises: An Introduction" icon="newspaper" href="https://web.dev/promises/"> + Google's comprehensive guide to Promises with interactive examples and best practices. + </Card> + <Card title="JavaScript Visualized: Promises & Async/Await" icon="newspaper" href="https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke"> + Lydia Hallie's visual explanation with animated GIFs showing exactly how Promises work. + </Card> + <Card title="Promise Basics — JavaScript.info" icon="newspaper" href="https://javascript.info/promise-basics"> + Clear tutorial covering Promise fundamentals with practical examples. + </Card> + <Card title="Promise Chaining — JavaScript.info" icon="newspaper" href="https://javascript.info/promise-chaining"> + In-depth guide to Promise chaining with diagrams and gotchas. + </Card> + <Card title="We Have a Problem with Promises" icon="newspaper" href="https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html"> + Nolan Lawson's classic article on common Promise mistakes developers make. </Card> - <Card title="Master the JavaScript Interview: What is a Promise?" icon="newspaper" href="https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261"> - By Eric Elliott + <Card title="The Complete JavaScript Promise Guide" icon="newspaper" href="https://blog.webdevsimplified.com/2021-09/javascript-promises"> + Web Dev Simplified's comprehensive tutorial with clear explanations. </Card> </CardGroup> -- [Understanding promises in JavaScript — Gokul N K](https://hackernoon.com/understanding-promises-in-javascript-13d99df067c1) -- [An Overview of JavaScript Promises — Sandeep Panda](https://www.sitepoint.com/overview-javascript-promises/) -- [How to use Promises in JavaScript — Prashant Ram](https://medium.freecodecamp.org/promises-in-javascript-explained-277b98850de) -- [Implementing Promises In JavaScript — Maciej Cieslar](https://medium.freecodecamp.org/how-to-implement-promises-in-javascript-1ce2680a7f51) -- [JavaScript: Promises explained with simple real life analogies — Shruti Kapoor](https://codeburst.io/javascript-promises-explained-with-simple-real-life-analogies-dd6908092138) -- [Promises for Asynchronous Programming — Exploring JS](http://exploringjs.com/es6/ch_promises.html) -- [JavaScript Promises Explained By Gambling At A Casino — Kevin Kononenko](https://blog.codeanalogies.com/2018/08/26/javascript-promises-explained-by-gambling-at-a-casino/) -- [ES6 Promises: Patterns and Anti-Patterns — Bobby Brennan](https://medium.com/datafire-io/es6-promises-patterns-and-anti-patterns-bbb21a5d0918) -- [A Simple Guide to ES6 Promises — Brandon Morelli](https://codeburst.io/a-simple-guide-to-es6-promises-d71bacd2e13a) -- [ES6 Promises in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-promises-in-depth) -- [How to Write a JavaScript Promise — Brandon Wozniewicz](https://medium.freecodecamp.org/how-to-write-a-javascript-promise-4ed8d44292b8) -- [JavaScript Visualized: Promises & Async/Await — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke) -- [Best Practices for ES6 Promises — Basti Ortiz](https://dev.to/somedood/best-practices-for-es6-promises-36da) -- [Promise Basics - javascript.info](https://javascript.info/promise-basics) -- [The Complete JavaScript Promise Guide](https://blog.webdevsimplified.com/2021-09/javascript-promises) -- [Promise Chaining - javascript.info](https://javascript.info/promise-chaining) - ## Videos <CardGroup cols={2}> - <Card title="Let's Learn ES6 - Promises" icon="video" href="https://www.youtube.com/watch?v=vQ3MoXnKfuQ"> - By Ryan Christiani + <Card title="JavaScript Promises In 10 Minutes" icon="video" href="https://www.youtube.com/watch?v=DHvZLI7Db8E"> + Web Dev Simplified's quick and practical introduction to Promises. + </Card> + <Card title="Promises — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=2d7s3spWAzo"> + MPJ's entertaining and thorough explanation of Promises with great analogies. </Card> - <Card title="JavaScript ES6 / ES2015 Promises" icon="video" href="https://www.youtube.com/watch?v=XJEHuBZQ5dU"> - By Traversy Media + <Card title="JavaScript Promise in 100 Seconds" icon="video" href="https://www.youtube.com/watch?v=RvYYCGs45L4"> + Fireship's ultra-concise overview of Promise fundamentals. + </Card> + <Card title="Promises | Namaste JavaScript" icon="video" href="https://youtu.be/ap-6PPAuK1Y"> + Akshay Saini's deep dive into how Promises work under the hood. </Card> </CardGroup> - -- [Promises — Fun Fun Function](https://www.youtube.com/watch?v=2d7s3spWAzo) -- [Error Handling Promises in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=f8IgdnYIwOU) -- [Promises Part 1 - Topics of JavaScript/ES6 — The Coding Train](https://www.youtube.com/watch?v=QO4NXhWo_NM) -- [JavaScript Promise in 100 Seconds](https://www.youtube.com/watch?v=RvYYCGs45L4) -- [JavaScript Promise in 9 Minutes](https://youtu.be/3NjdOtHpcBM) -- [JavaScript Promises In 10 Minutes — Web Dev Simplified](https://www.youtube.com/watch?v=DHvZLI7Db8E) -- [Promises | Ep 02 Season 02 - Namaste JavaScript - Akshay Saini](https://youtu.be/ap-6PPAuK1Y?si=Ri1fopXeYjlrHzpf) diff --git a/tests/functions-execution/promises/promises.test.js b/tests/functions-execution/promises/promises.test.js new file mode 100644 index 00000000..1ca0ff2f --- /dev/null +++ b/tests/functions-execution/promises/promises.test.js @@ -0,0 +1,529 @@ +import { describe, it, expect, vi } from 'vitest' + +describe('Promises', () => { + describe('Basic Promise Creation', () => { + it('should create a fulfilled Promise with resolve()', async () => { + const promise = new Promise((resolve) => { + resolve('success') + }) + + const result = await promise + expect(result).toBe('success') + }) + + it('should create a rejected Promise with reject()', async () => { + const promise = new Promise((_, reject) => { + reject(new Error('failure')) + }) + + await expect(promise).rejects.toThrow('failure') + }) + + it('should execute the executor function synchronously', () => { + const order = [] + + order.push('before') + + new Promise((resolve) => { + order.push('inside executor') + resolve('done') + }) + + order.push('after') + + expect(order).toEqual(['before', 'inside executor', 'after']) + }) + + it('should ignore subsequent resolve/reject calls after first settlement', async () => { + const promise = new Promise((resolve, reject) => { + resolve('first') + resolve('second') // Ignored + reject(new Error('error')) // Ignored + }) + + const result = await promise + expect(result).toBe('first') + }) + + it('should automatically reject if executor throws', async () => { + const promise = new Promise(() => { + throw new Error('thrown error') + }) + + await expect(promise).rejects.toThrow('thrown error') + }) + }) + + describe('Promise.resolve() and Promise.reject()', () => { + it('should create fulfilled Promise with Promise.resolve()', async () => { + const promise = Promise.resolve(42) + expect(await promise).toBe(42) + }) + + it('should create rejected Promise with Promise.reject()', async () => { + const promise = Promise.reject(new Error('rejected')) + await expect(promise).rejects.toThrow('rejected') + }) + + it('should return the same Promise if resolving with a Promise', async () => { + const original = Promise.resolve('original') + const wrapped = Promise.resolve(original) + + // Promise.resolve returns the same Promise if given a native Promise + expect(wrapped).toBe(original) + }) + }) + + describe('.then() method', () => { + it('should receive the fulfilled value', async () => { + const result = await Promise.resolve(10).then(x => x * 2) + expect(result).toBe(20) + }) + + it('should return a new Promise', () => { + const p1 = Promise.resolve(1) + const p2 = p1.then(x => x) + + expect(p2).toBeInstanceOf(Promise) + expect(p1).not.toBe(p2) + }) + + it('should chain values through multiple .then() calls', async () => { + const result = await Promise.resolve(1) + .then(x => x + 1) + .then(x => x * 2) + .then(x => x + 10) + + expect(result).toBe(14) // ((1 + 1) * 2) + 10 + }) + + it('should unwrap returned Promises', async () => { + const result = await Promise.resolve(1) + .then(x => Promise.resolve(x + 1)) + .then(x => x * 2) + + expect(result).toBe(4) // (1 + 1) * 2 + }) + + it('should skip .then() when Promise is rejected', async () => { + const thenCallback = vi.fn() + + await Promise.reject(new Error('error')) + .then(thenCallback) + .catch(() => {}) // Handle the rejection + + expect(thenCallback).not.toHaveBeenCalled() + }) + }) + + describe('.catch() method', () => { + it('should catch rejected Promises', async () => { + const result = await Promise.reject(new Error('error')) + .catch(error => `caught: ${error.message}`) + + expect(result).toBe('caught: error') + }) + + it('should catch errors thrown in .then()', async () => { + const result = await Promise.resolve('ok') + .then(() => { + throw new Error('thrown') + }) + .catch(error => `caught: ${error.message}`) + + expect(result).toBe('caught: thrown') + }) + + it('should allow chain to continue after catching', async () => { + const result = await Promise.reject(new Error('error')) + .catch(() => 'recovered') + .then(value => value.toUpperCase()) + + expect(result).toBe('RECOVERED') + }) + + it('should propagate errors through the chain until caught', async () => { + const thenCallback1 = vi.fn() + const thenCallback2 = vi.fn() + const catchCallback = vi.fn(e => e.message) + + await Promise.reject(new Error('original error')) + .then(thenCallback1) + .then(thenCallback2) + .catch(catchCallback) + + expect(thenCallback1).not.toHaveBeenCalled() + expect(thenCallback2).not.toHaveBeenCalled() + expect(catchCallback).toHaveBeenCalledWith(expect.any(Error)) + }) + }) + + describe('.finally() method', () => { + it('should run on fulfillment', async () => { + const finallyCallback = vi.fn() + + await Promise.resolve('value').finally(finallyCallback) + + expect(finallyCallback).toHaveBeenCalled() + }) + + it('should run on rejection', async () => { + const finallyCallback = vi.fn() + + await Promise.reject(new Error('error')) + .catch(() => {}) // Handle rejection + .finally(finallyCallback) + + expect(finallyCallback).toHaveBeenCalled() + }) + + it('should not receive any arguments', async () => { + const finallyCallback = vi.fn() + + await Promise.resolve('value').finally(finallyCallback) + + expect(finallyCallback).toHaveBeenCalledWith() // No arguments + }) + + it('should pass through the original value', async () => { + const result = await Promise.resolve('original') + .finally(() => 'ignored') + + expect(result).toBe('original') + }) + + it('should pass through the original error', async () => { + await expect( + Promise.reject(new Error('original')) + .finally(() => 'ignored') + ).rejects.toThrow('original') + }) + }) + + describe('Promise Chaining', () => { + it('should maintain chain with undefined return', async () => { + const result = await Promise.resolve('start') + .then(() => { + // No explicit return = undefined + }) + .then(value => value) + + expect(result).toBeUndefined() + }) + + it('should handle async operations in sequence', async () => { + const delay = (ms, value) => + new Promise(resolve => setTimeout(() => resolve(value), ms)) + + const result = await delay(10, 'first') + .then(value => delay(10, value + ' second')) + .then(value => delay(10, value + ' third')) + + expect(result).toBe('first second third') + }) + }) + + describe('Promise.all()', () => { + it('should resolve with array of values when all fulfill', async () => { + const result = await Promise.all([ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3) + ]) + + expect(result).toEqual([1, 2, 3]) + }) + + it('should maintain order regardless of resolution order', async () => { + const result = await Promise.all([ + new Promise(resolve => setTimeout(() => resolve('slow'), 30)), + new Promise(resolve => setTimeout(() => resolve('fast'), 10)), + Promise.resolve('instant') + ]) + + expect(result).toEqual(['slow', 'fast', 'instant']) + }) + + it('should reject immediately if any Promise rejects', async () => { + await expect( + Promise.all([ + Promise.resolve('A'), + Promise.reject(new Error('B failed')), + Promise.resolve('C') + ]) + ).rejects.toThrow('B failed') + }) + + it('should work with non-Promise values', async () => { + const result = await Promise.all([1, 'two', Promise.resolve(3)]) + expect(result).toEqual([1, 'two', 3]) + }) + + it('should resolve immediately with empty array', async () => { + const result = await Promise.all([]) + expect(result).toEqual([]) + }) + }) + + describe('Promise.allSettled()', () => { + it('should return status objects for all Promises', async () => { + const results = await Promise.allSettled([ + Promise.resolve('success'), + Promise.reject(new Error('failure')), + Promise.resolve(42) + ]) + + expect(results).toEqual([ + { status: 'fulfilled', value: 'success' }, + { status: 'rejected', reason: expect.any(Error) }, + { status: 'fulfilled', value: 42 } + ]) + }) + + it('should never reject', async () => { + const results = await Promise.allSettled([ + Promise.reject(new Error('error 1')), + Promise.reject(new Error('error 2')) + ]) + + expect(results).toHaveLength(2) + expect(results[0].status).toBe('rejected') + expect(results[1].status).toBe('rejected') + }) + + it('should wait for all to settle', async () => { + const start = Date.now() + + await Promise.allSettled([ + new Promise(resolve => setTimeout(resolve, 50)), + new Promise((_, reject) => setTimeout(() => reject(new Error()), 30)), + new Promise(resolve => setTimeout(resolve, 40)) + ]) + + const elapsed = Date.now() - start + expect(elapsed).toBeGreaterThanOrEqual(45) // Waited for slowest + }) + }) + + describe('Promise.race()', () => { + it('should resolve with first settled value', async () => { + const result = await Promise.race([ + new Promise(resolve => setTimeout(() => resolve('slow'), 50)), + new Promise(resolve => setTimeout(() => resolve('fast'), 10)) + ]) + + expect(result).toBe('fast') + }) + + it('should reject if first settled is rejection', async () => { + await expect( + Promise.race([ + new Promise((_, reject) => setTimeout(() => reject(new Error('fast error')), 10)), + new Promise(resolve => setTimeout(() => resolve('slow success'), 50)) + ]) + ).rejects.toThrow('fast error') + }) + + it('should never settle with empty array', () => { + // Promise.race([]) returns a forever-pending Promise + const promise = Promise.race([]) + + // We can't really test this without timing out, + // but we can verify it returns a Promise + expect(promise).toBeInstanceOf(Promise) + }) + }) + + describe('Promise.any()', () => { + it('should resolve with first fulfilled value', async () => { + const result = await Promise.any([ + Promise.reject(new Error('error 1')), + Promise.resolve('success'), + Promise.reject(new Error('error 2')) + ]) + + expect(result).toBe('success') + }) + + it('should wait for first fulfillment, ignoring rejections', async () => { + const result = await Promise.any([ + new Promise((_, reject) => setTimeout(() => reject(new Error()), 10)), + new Promise(resolve => setTimeout(() => resolve('winner'), 30)), + new Promise((_, reject) => setTimeout(() => reject(new Error()), 20)) + ]) + + expect(result).toBe('winner') + }) + + it('should reject with AggregateError if all reject', async () => { + try { + await Promise.any([ + Promise.reject(new Error('error 1')), + Promise.reject(new Error('error 2')), + Promise.reject(new Error('error 3')) + ]) + expect.fail('Should have rejected') + } catch (error) { + expect(error.name).toBe('AggregateError') + expect(error.errors).toHaveLength(3) + } + }) + }) + + describe('Microtask Queue Timing', () => { + it('should run .then() callbacks asynchronously', () => { + const order = [] + + order.push('1') + + Promise.resolve().then(() => { + order.push('3') + }) + + order.push('2') + + // Synchronously, only 1 and 2 are in the array + expect(order).toEqual(['1', '2']) + }) + + it('should demonstrate microtask priority over macrotasks', async () => { + const order = [] + + // Macrotask (setTimeout) + setTimeout(() => order.push('timeout'), 0) + + // Microtask (Promise) + Promise.resolve().then(() => order.push('promise')) + + // Wait for both to complete + await new Promise(resolve => setTimeout(resolve, 10)) + + // Promise (microtask) runs before setTimeout (macrotask) + expect(order).toEqual(['promise', 'timeout']) + }) + + it('should process nested microtasks before macrotasks', async () => { + const order = [] + + setTimeout(() => order.push('timeout'), 0) + + Promise.resolve().then(() => { + order.push('promise 1') + Promise.resolve().then(() => { + order.push('promise 2') + }) + }) + + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(order).toEqual(['promise 1', 'promise 2', 'timeout']) + }) + }) + + describe('Common Patterns', () => { + it('should wrap setTimeout in a Promise (delay pattern)', async () => { + const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)) + + const start = Date.now() + await delay(50) + const elapsed = Date.now() - start + + expect(elapsed).toBeGreaterThanOrEqual(45) + }) + + it('should handle sequential execution', async () => { + const results = [] + const items = [1, 2, 3] + + for (const item of items) { + const result = await Promise.resolve(item * 2) + results.push(result) + } + + expect(results).toEqual([2, 4, 6]) + }) + + it('should handle parallel execution', async () => { + const items = [1, 2, 3] + const results = await Promise.all( + items.map(item => Promise.resolve(item * 2)) + ) + + expect(results).toEqual([2, 4, 6]) + }) + }) + + describe('Common Mistakes', () => { + it('should demonstrate forgotten return issue', async () => { + // This is what happens when you forget to return + const result = await Promise.resolve('start') + .then(value => { + Promise.resolve(value + ' middle') // Forgot return! + }) + .then(value => value) + + expect(result).toBeUndefined() // Lost the value! + }) + + it('should demonstrate correct return', async () => { + const result = await Promise.resolve('start') + .then(value => { + return Promise.resolve(value + ' middle') // Correct! + }) + .then(value => value) + + expect(result).toBe('start middle') + }) + + it('should demonstrate Promise constructor anti-pattern', async () => { + // Anti-pattern: unnecessary wrapper + const antiPattern = () => { + return new Promise((resolve, reject) => { + Promise.resolve('data') + .then(data => resolve(data)) + .catch(error => reject(error)) + }) + } + + // Correct: just return the Promise + const correct = () => { + return Promise.resolve('data') + } + + // Both work, but correct is cleaner + expect(await antiPattern()).toBe('data') + expect(await correct()).toBe('data') + }) + }) + + describe('Error Handling Patterns', () => { + it('should catch errors anywhere in the chain', async () => { + const error = await Promise.resolve('start') + .then(() => { + throw new Error('middle error') + }) + .then(() => 'never reached') + .catch(e => e.message) + + expect(error).toBe('middle error') + }) + + it('should allow recovery from errors', async () => { + const result = await Promise.reject(new Error('initial error')) + .catch(() => 'recovered value') + .then(value => value.toUpperCase()) + + expect(result).toBe('RECOVERED VALUE') + }) + + it('should allow re-throwing errors', async () => { + await expect( + Promise.reject(new Error('original')) + .catch(error => { + // Log it, then re-throw + throw error + }) + ).rejects.toThrow('original') + }) + }) +}) From f0ac46be42e62889d41a2009ed660b922740477d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 12:43:57 -0300 Subject: [PATCH 068/128] docs: add comprehensive async/await concept page with tests - Complete rewrite from resource collection to educational guide - Covers async keyword, await keyword, error handling, parallel execution - Includes restaurant analogy with ASCII diagrams - 52 tests covering all documented concepts - Improved copywriting: removed AI-generated language patterns - Fixed em dash overuse, replaced stiff phrases with conversational tone - Curated resources with specific 2-sentence descriptions --- docs/concepts/async-await.mdx | 1609 ++++++++++++++++- .../async-await/async-await.test.js | 1042 +++++++++++ 2 files changed, 2603 insertions(+), 48 deletions(-) create mode 100644 tests/functions-execution/async-await/async-await.test.js diff --git a/docs/concepts/async-await.mdx b/docs/concepts/async-await.mdx index 05503374..bcb3a203 100644 --- a/docs/concepts/async-await.mdx +++ b/docs/concepts/async-await.mdx @@ -1,78 +1,1591 @@ --- -title: "async/await: Modern Async Syntax in JavaScript" -sidebarTitle: "async/await: Modern Async Syntax" -description: "Learn async/await in JavaScript — syntactic sugar over Promises that makes async code look synchronous. Master error handling with try/catch, parallel execution, and common pitfalls." +title: "async/await: Writing Async Code That Looks Synchronous in JavaScript" +sidebarTitle: "async/await: Writing Async Code That Looks Synchronous" +description: "Learn async/await in JavaScript. Syntactic sugar over Promises that makes async code readable. Covers error handling with try/catch, parallel execution with Promise.all, and common pitfalls." --- -## Overview +Why does asynchronous code have to look so complicated? What if you could write code that fetches data from a server, waits for user input, or reads files, all while looking as clean and readable as regular synchronous code? -**async/await** is syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous code. An `async` function always returns a Promise, and `await` pauses the execution until the Promise resolves. +```javascript +// This is async code that reads like sync code +async function getUserData(userId) { + const response = await fetch(`/api/users/${userId}`) + const user = await response.json() + return user +} + +// Using the async function +(async () => { + const user = await getUserData(123) + console.log(user.name) // "Alice" +})() +``` + +That's the magic of **[async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)**. It's syntactic sugar introduced in ES2017 that makes asynchronous JavaScript look and behave like synchronous code, while still being non-blocking under the hood. <Info> -`async/await` was introduced in ES2017 (ES8) and has become the preferred way to handle asynchronous operations due to its readability and cleaner error handling with try/catch blocks. +**What you'll learn in this guide:** +- What async/await actually is (and why it's "just" Promises underneath) +- How the `async` keyword transforms functions into Promise-returning functions +- How `await` pauses execution without blocking the main thread +- Error handling with try/catch (finally, a sane way to handle async errors!) +- The critical difference between sequential and parallel execution +- The most common async/await mistakes and how to avoid them +- How async/await relates to the event loop and microtasks </Info> -## Reference +<Warning> +**Prerequisites:** This guide assumes you understand [Promises](/concepts/promises). async/await is built entirely on top of them. You should also be familiar with the [Event Loop](/concepts/event-loop) to understand why code after `await` behaves like a microtask. +</Warning> + +--- + +## What is async/await? + +Think of **async/await** as a friendlier way to write [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). You mark a function with `async`, use `await` to pause until a Promise resolves, and your async code suddenly reads like regular synchronous code. The best part? JavaScript stays non-blocking under the hood. + +Here's the same operation written three ways: + +<Tabs> + <Tab title="Callbacks (Old Way)"> + ```javascript + // Callback hell - nested so deep you need a flashlight + function getUserPosts(userId, callback) { + fetchUser(userId, (err, user) => { + if (err) return callback(err) + + fetchPosts(user.id, (err, posts) => { + if (err) return callback(err) + + fetchComments(posts[0].id, (err, comments) => { + if (err) return callback(err) + + callback(null, { user, posts, comments }) + }) + }) + }) + } + ``` + </Tab> + <Tab title="Promises"> + ```javascript + // Promise chains - better, but still nested + function getUserPosts(userId) { + return fetchUser(userId) + .then(user => { + return fetchPosts(user.id) + .then(posts => { + return fetchComments(posts[0].id) + .then(comments => ({ user, posts, comments })) + }) + }) + } + ``` + </Tab> + <Tab title="async/await (Modern)"> + ```javascript + // async/await - reads like synchronous code! + async function getUserPosts(userId) { + const user = await fetchUser(userId) + const posts = await fetchPosts(user.id) + const comments = await fetchComments(posts[0].id) + return { user, posts, comments } + } + ``` + </Tab> +</Tabs> + +The async/await version is much easier to read. Each line clearly shows what happens next, error handling uses familiar try/catch, and there's no nesting or callback pyramids. + +<Tip> +**Don't forget:** async/await doesn't replace Promises. It's built on top of them. Every `async` function returns a Promise, and `await` works with any Promise. The better you understand Promises, the better you'll be at async/await. +</Tip> + +--- + +## The Restaurant Analogy + +Think of async/await like ordering food at a restaurant with table service versus a fast-food counter. + +**Without async/await (callback style):** You order at the counter, then stand there awkwardly blocking everyone behind you until your food is ready. If you need multiple items, you wait for each one before ordering the next. + +**With async/await:** You sit at a table and place your order. The waiter takes it to the kitchen (starts the async operation), but you're free to chat, check your phone, or do other things (the main thread isn't blocked). When the food is ready, the waiter brings it to you (the Promise resolves) and you continue from where you left off. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE RESTAURANT ANALOGY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ async function dinner() { │ +│ │ +│ ┌──────────┐ "I'll have the ┌─────────────┐ │ +│ │ YOU │ ──────────────────────► │ KITCHEN │ │ +│ │ (code) │ pasta please" │ (server) │ │ +│ └──────────┘ await order() └─────────────┘ │ +│ │ │ │ +│ │ You're free to do │ Kitchen is │ +│ │ other things while │ preparing... │ +│ │ waiting! │ │ +│ │ │ │ +│ │ "Your pasta!" │ │ +│ ┌──────────┐ ◄────────────────────── ┌─────────────┐ │ +│ │ YOU │ Promise resolved │ KITCHEN │ │ +│ │ resume │ │ done │ │ +│ └──────────┘ └─────────────┘ │ +│ │ +│ return enjoyMeal(pasta) │ +│ } │ +│ │ +│ The KEY: You (the main thread) are NOT blocked while waiting! │ +│ Other customers (other code) can be served. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Here's the clever part: `await` makes your code *look* like it's waiting, but JavaScript is actually free to do other work. When the Promise resolves, your function resumes exactly where it left off. + +--- + +## The `async` Keyword + +The [`async`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) keyword does one simple thing: **it makes a function return a Promise**. + +```javascript +// Regular function +function greet() { + return 'Hello' +} +console.log(greet()) // "Hello" + +// Async function - automatically returns a Promise +async function greetAsync() { + return 'Hello' +} +console.log(greetAsync()) // Promise {<fulfilled>: "Hello"} +``` + +### What Happens to Return Values? + +When you return a value from an async function, it gets automatically wrapped in `Promise.resolve()`: + +```javascript +async function getValue() { + return 42 +} + +// The above is equivalent to: +function getValuePromise() { + return Promise.resolve(42) +} + +// Both work the same way: +getValue().then(value => console.log(value)) // 42 +``` + +### What Happens When You Throw? + +When you throw an error in an async function, it becomes a rejected Promise: + +```javascript +async function failingFunction() { + throw new Error('Something went wrong!') +} + +// The above is equivalent to: +function failingPromise() { + return Promise.reject(new Error('Something went wrong!')) +} + +// Both are caught the same way: +failingFunction().catch(err => console.log(err.message)) // "Something went wrong!" +``` + +### Return a Promise? No Double-Wrapping + +If you return a Promise from an async function, it doesn't get double-wrapped: + +```javascript +async function fetchData() { + // Returning a Promise directly - it's NOT double-wrapped + return fetch('/api/data') +} + +// This returns Promise<Response>, NOT Promise<Promise<Response>> +const response = await fetchData() +``` + +### Async Function Expressions and Arrow Functions + +You can use `async` with function expressions and arrow functions too: + +```javascript +// Async function expression +const fetchData = async function() { + return await fetch('/api/data') +} + +// Async arrow function +const loadData = async () => { + return await fetch('/api/data') +} + +// Async arrow function (concise body) +const getData = async () => fetch('/api/data') + +// Async method in an object +const api = { + async fetchUser(id) { + return await fetch(`/api/users/${id}`) + } +} + +// Async method in a class +class UserService { + async getUser(id) { + const response = await fetch(`/api/users/${id}`) + return response.json() + } +} +``` + +<Warning> +**Common misconception:** Making a function `async` doesn't make it run in a separate thread or "in the background." JavaScript is still single-threaded. The `async` keyword simply enables the use of `await` inside the function and ensures it returns a Promise. +</Warning> + +--- + +## The `await` Keyword + +The [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) keyword is where things get interesting. It **pauses the execution of an async function** until a Promise settles (fulfills or rejects), then resumes with the resolved value. + +```javascript +async function example() { + console.log('Before await') + + const result = await somePromise() // Execution pauses here + + console.log('After await:', result) // Resumes when Promise resolves +} +``` + +### Where Can You Use await? + +`await` can only be used in two places: + +1. **Inside an async function** +2. **At the top level of an ES module** (top-level await, covered later) + +```javascript +// ✓ Inside async function +async function fetchUser() { + const response = await fetch('/api/user') + return response.json() +} + +// ✓ Top-level await in ES modules +// (in a .mjs file or with "type": "module" in package.json) +const config = await fetch('/config.json').then(r => r.json()) + +// ❌ NOT in regular functions +function regularFunction() { + const data = await fetch('/api/data') // SyntaxError! +} + +// ❌ NOT in global scope of scripts (non-modules) +await fetch('/api/data') // SyntaxError in non-module scripts +``` + +### What Can You await? + +You can `await` any value, but it's most useful with Promises: + +```javascript +// Awaiting a Promise (the normal case) +const response = await fetch('/api/data') + +// Awaiting Promise.resolve() +const value = await Promise.resolve(42) +console.log(value) // 42 + +// Awaiting a non-Promise value (works, but pointless) +const num = await 42 +console.log(num) // 42 (immediately, no actual waiting) + +// Awaiting a thenable (object with .then method) +const thenable = { + then(resolve) { + setTimeout(() => resolve('thenable value'), 1000) + } +} +const result = await thenable +console.log(result) // "thenable value" (after 1 second) +``` + +<Tip> +**Pro tip:** Only use `await` when you're actually waiting for a Promise. Awaiting non-Promise values works but adds unnecessary overhead and confuses anyone reading your code. +</Tip> + +### await Pauses the Function, Not the Thread + +This trips people up: `await` pauses only the async function it's in, not the entire JavaScript thread. Other code can run while waiting: + +```javascript +async function slowOperation() { + console.log('Starting slow operation') + await new Promise(resolve => setTimeout(resolve, 2000)) + console.log('Slow operation complete') +} + +console.log('Before calling slowOperation') +slowOperation() // Starts but doesn't block +console.log('After calling slowOperation') + +// Output: +// "Before calling slowOperation" +// "Starting slow operation" +// "After calling slowOperation" +// (2 seconds later) +// "Slow operation complete" +``` + +Notice that "After calling slowOperation" prints before "Slow operation complete". The main thread wasn't blocked. + +--- + +## How await Works Under the Hood + +Let's peek under the hood at what actually happens. When you `await` a Promise, **the code after the await becomes a microtask** that runs when the Promise resolves. + +```javascript +async function example() { + console.log('1. Before await') // Runs synchronously + await Promise.resolve() + console.log('2. After await') // Runs as a microtask +} + +console.log('A. Before call') +example() +console.log('B. After call') + +// Output: +// A. Before call +// 1. Before await +// B. After call +// 2. After await +``` + +Let's trace through this step by step: + +<Steps> + <Step title="Synchronous code starts"> + `console.log('A. Before call')` executes → prints "A. Before call" + </Step> + + <Step title="Call example()"> + The function starts executing synchronously. + `console.log('1. Before await')` executes → prints "1. Before await" + </Step> + + <Step title="Hit the await"> + `await Promise.resolve()`. The Promise is already resolved, but the code after `await` is still scheduled as a **microtask**. The function pauses and returns control to the caller. + </Step> + + <Step title="Continue after the call"> + `console.log('B. After call')` executes → prints "B. After call" + </Step> + + <Step title="Call stack empties, microtasks run"> + The event loop processes the microtask queue. The continuation of `example()` runs. + `console.log('2. After await')` executes → prints "2. After await" + </Step> +</Steps> + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ await SPLITS THE FUNCTION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ async function example() { │ +│ console.log('Before') ──────► Runs SYNCHRONOUSLY │ +│ │ +│ await somePromise() ──────► PAUSE: Schedule continuation │ +│ as microtask, return to caller │ +│ │ +│ console.log('After') ──────► Runs as MICROTASK when │ +│ } Promise resolves │ +│ │ +│ ───────────────────────────────────────────────────────────────────── │ +│ │ +│ Think of it like this - await transforms the function into: │ +│ │ +│ function example() { │ +│ console.log('Before') │ +│ return somePromise().then(() => { │ +│ console.log('After') │ +│ }) │ +│ } │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Note> +This is why understanding the [Event Loop](/concepts/event-loop) is so important for async/await. The `await` keyword effectively registers a microtask, which has priority over setTimeout callbacks (macrotasks). +</Note> + +--- + +## Error Handling with try/catch + +Finally, **error handling that doesn't make you want to flip a table**. Instead of chaining `.catch()` after `.then()` after `.catch()`, you get to use good old try/catch blocks. + +### Basic try/catch Pattern + +```javascript +async function fetchUserData(userId) { + try { + const response = await fetch(`/api/users/${userId}`) + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`) + } + + const user = await response.json() + return user + + } catch (error) { + console.error('Failed to fetch user:', error.message) + throw error // Re-throw if you want callers to handle it + } +} +``` + +### Catching Different Types of Errors + +```javascript +async function processOrder(orderId) { + try { + const order = await fetchOrder(orderId) + const payment = await processPayment(order) + const shipment = await createShipment(order) + return { order, payment, shipment } + + } catch (error) { + // You can check error types + if (error.name === 'NetworkError') { + console.log('Network issue - please check your connection') + } else if (error.name === 'PaymentError') { + console.log('Payment failed - please try again') + } else { + console.log('Unexpected error:', error.message) + } + throw error + } +} +``` + +### The finally Block + +The `finally` block always runs, whether the try succeeded or failed: + +```javascript +async function fetchWithLoading(url) { + showLoadingSpinner() + + try { + const response = await fetch(url) + const data = await response.json() + return data + + } catch (error) { + showErrorMessage(error.message) + throw error + + } finally { + // This ALWAYS runs - perfect for cleanup + hideLoadingSpinner() + } +} +``` + +### try/catch vs .catch() + +Both approaches work, but they have different use cases: + +<Tabs> + <Tab title="try/catch (Preferred)"> + ```javascript + // Good for: Multiple awaits where any could fail + async function getFullProfile(userId) { + try { + const user = await fetchUser(userId) + const posts = await fetchPosts(userId) + const friends = await fetchFriends(userId) + return { user, posts, friends } + } catch (error) { + // Catches any of the three failures + console.error('Profile fetch failed:', error) + return null + } + } + ``` + </Tab> + <Tab title=".catch() (Sometimes Better)"> + ```javascript + // Good for: Handling errors for specific operations + async function getProfileWithFallback(userId) { + const user = await fetchUser(userId) + + // Only this operation has fallback behavior + const posts = await fetchPosts(userId).catch(() => []) + + // This will still throw if it fails + const friends = await fetchFriends(userId) + + return { user, posts, friends } + } + ``` + </Tab> +</Tabs> + +### Common Error Handling Mistake + +<Warning> +**The Trap:** If you catch an error but don't re-throw it, the Promise resolves successfully (with undefined), not rejects! +</Warning> + +```javascript +// ❌ WRONG - Error is swallowed, returns undefined +async function fetchData() { + try { + const response = await fetch('/api/data') + return await response.json() + } catch (error) { + console.error('Error:', error) + // Missing: throw error + } +} + +const data = await fetchData() // undefined if there was an error! + +// ✓ CORRECT - Re-throw or return a meaningful value +async function fetchData() { + try { + const response = await fetch('/api/data') + return await response.json() + } catch (error) { + console.error('Error:', error) + throw error // Re-throw to let caller handle it + // OR: return null // Return explicit fallback value + // OR: return { error: error.message } // Return error object + } +} +``` + +--- + +## Sequential vs Parallel Execution + +This is a big one. **By default, await makes operations sequential**, but often you want them to run in parallel. + +### The Problem: Unnecessary Sequential Execution + +```javascript +// ❌ SLOW - Each request waits for the previous one +async function getUserDashboard(userId) { + const user = await fetchUser(userId) // Wait ~500ms + const posts = await fetchPosts(userId) // Wait ~500ms + const notifications = await fetchNotifications(userId) // Wait ~500ms + + return { user, posts, notifications } + // Total time: ~1500ms (sequential) +} +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ SEQUENTIAL EXECUTION (SLOW) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Time: 0ms 500ms 1000ms 1500ms │ +│ │ │ │ │ │ +│ ├─────────┤ │ │ │ +│ │ user │ │ │ Total: 1500ms │ +│ │ fetch │ │ │ │ +│ └─────────┼─────────┤ │ │ +│ │ posts │ │ │ +│ │ fetch │ │ │ +│ └─────────┼─────────┤ │ +│ │ notifs │ │ +│ │ fetch │ │ +│ └─────────┘ │ +│ │ +│ Each request WAITS for the previous one to complete! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### The Solution: Promise.all for Parallel Execution + +When operations are independent, run them in parallel: + +```javascript +// ✓ FAST - All requests run simultaneously +async function getUserDashboard(userId) { + const [user, posts, notifications] = await Promise.all([ + fetchUser(userId), // Starts immediately + fetchPosts(userId), // Starts immediately + fetchNotifications(userId) // Starts immediately + ]) + + return { user, posts, notifications } + // Total time: ~500ms (parallel - time of slowest request) +} +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PARALLEL EXECUTION (FAST) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Time: 0ms 500ms │ +│ │ │ │ +│ ├─────────┤ │ +│ │ user │ │ +│ │ fetch │ │ +│ ├─────────┤ Total: 500ms (3x faster!) │ +│ │ posts │ │ +│ │ fetch │ │ +│ ├─────────┤ │ +│ │ notifs │ │ +│ │ fetch │ │ +│ └─────────┘ │ +│ │ +│ All requests start at the SAME TIME! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### When to Use Sequential vs Parallel + +| Use Sequential When | Use Parallel When | +|---------------------|-------------------| +| Each operation depends on the previous result | Operations are independent | +| Order of execution matters | Order doesn't matter | +| You need to stop on first failure | All results are needed | + +### Promise.all vs Promise.allSettled + +**[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all):** Fails fast. If any Promise rejects, the whole thing rejects. + +**[Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled):** Waits for all Promises, gives you results for each (fulfilled or rejected). + +```javascript +// Promise.all - fails fast +async function getAllOrNothing() { + try { + const results = await Promise.all([ + fetchUser(1), + fetchUser(999), // This one fails + fetchUser(3) + ]) + return results + } catch (error) { + // If ANY request fails, we end up here + console.log('At least one request failed') + } +} + +// Promise.allSettled - get all results regardless of failures +async function getAllResults() { + const results = await Promise.allSettled([ + fetchUser(1), + fetchUser(999), // This one fails + fetchUser(3) + ]) + + // results = [ + // { status: 'fulfilled', value: user1 }, + // { status: 'rejected', reason: Error }, + // { status: 'fulfilled', value: user3 } + // ] + + const successful = results + .filter(r => r.status === 'fulfilled') + .map(r => r.value) + + const failed = results + .filter(r => r.status === 'rejected') + .map(r => r.reason) + + return { successful, failed } +} +``` + +### Mixed Pattern: Some Sequential, Some Parallel + +Sometimes you need a mix: some operations depend on others, but independent ones can run in parallel: + +```javascript +async function processOrder(orderId) { + // Step 1: Must fetch order first + const order = await fetchOrder(orderId) + + // Step 2: These can run in parallel (both depend on order, not each other) + const [inventory, pricing] = await Promise.all([ + checkInventory(order.items), + calculatePricing(order.items) + ]) + + // Step 3: Must wait for both before charging + const payment = await processPayment(order, pricing) + + // Step 4: These can run in parallel (both depend on payment) + const [receipt, notification] = await Promise.all([ + generateReceipt(payment), + sendConfirmationEmail(order, payment) + ]) + + return { order, payment, receipt } +} +``` + +--- + +## The 5 Most Common async/await Mistakes + +### Mistake #1: Forgetting await + +Without `await`, you get a Promise object instead of the resolved value: + +```javascript +// ❌ WRONG - response is a Promise, not a Response! +async function fetchUser() { + const response = fetch('/api/user') // Missing await! + const data = response.json() // Error: response.json is not a function + return data +} + +// ✓ CORRECT +async function fetchUser() { + const response = await fetch('/api/user') + const data = await response.json() + return data +} +``` + +<Warning> +**The silent bug:** Sometimes forgetting `await` doesn't throw an error. You just get unexpected results. If you see `[object Promise]` in your output or undefined where you expected data, check for missing awaits. +</Warning> + +### Mistake #2: Using await in forEach + +`forEach` and async don't play well together — it just fires and forgets: + +```javascript +// ❌ WRONG - forEach doesn't await! +async function processUsers(userIds) { + userIds.forEach(async (id) => { + const user = await fetchUser(id) + console.log(user.name) + }) + console.log('Done!') // Prints BEFORE users are fetched! +} + +// ✓ CORRECT - Use for...of for sequential +async function processUsersSequential(userIds) { + for (const id of userIds) { + const user = await fetchUser(id) + console.log(user.name) + } + console.log('Done!') // Prints after all users +} + +// ✓ CORRECT - Use Promise.all for parallel +async function processUsersParallel(userIds) { + await Promise.all( + userIds.map(async (id) => { + const user = await fetchUser(id) + console.log(user.name) + }) + ) + console.log('Done!') // Prints after all users +} +``` + +### Mistake #3: Sequential await When Parallel is Better + +We covered this above, but it's worth repeating: + +```javascript +// ❌ SLOW - 3 seconds total +async function getData() { + const a = await fetchA() // 1 second + const b = await fetchB() // 1 second + const c = await fetchC() // 1 second + return { a, b, c } +} + +// ✓ FAST - 1 second total +async function getData() { + const [a, b, c] = await Promise.all([ + fetchA(), + fetchB(), + fetchC() + ]) + return { a, b, c } +} +``` + +### Mistake #4: Not Handling Errors + +Unhandled Promise rejections can crash your application: + +```javascript +// ❌ WRONG - No error handling +async function riskyOperation() { + const data = await fetch('/api/might-fail') + return data.json() +} -<Card title="async/await — JavaScript.Info" icon="book" href="https://javascript.info/async-await"> - JavaScript.Info documentation -</Card> +// If fetch fails, we get an unhandled rejection +riskyOperation() // No .catch(), no try/catch -## Books +// ✓ CORRECT - Handle errors +async function safeOperation() { + try { + const data = await fetch('/api/might-fail') + return data.json() + } catch (error) { + console.error('Operation failed:', error) + return null // Or throw, or return error object + } +} + +// Or catch at the call site +riskyOperation().catch(err => console.error('Failed:', err)) +``` + +### Mistake #5: await Inside try Block Returns + +Be careful with where you place `await` relative to `return`: + +```javascript +// ❌ SUBTLE BUG - The await is pointless here +async function fetchData() { + try { + return await fetch('/api/data') // await is unnecessary + } catch (error) { + console.error('Error:', error) + } +} + +// This is equivalent and cleaner: +async function fetchData() { + try { + return fetch('/api/data') // No await needed + } catch (error) { + // BUT: This catch won't catch fetch errors! + console.error('Error:', error) + } +} + +// ✓ CORRECT - If you want to catch errors, process the result +async function fetchData() { + try { + const response = await fetch('/api/data') + return response // Now errors ARE caught + } catch (error) { + console.error('Error:', error) + throw error + } +} +``` + +<Tip> +**Quick check:** If you have a try/catch and want to catch errors from a Promise, make sure to `await` it on a separate line before returning. +</Tip> + +--- + +## async/await vs Promise Chains + +Both async/await and Promise chains achieve the same thing. The choice often comes down to readability and personal preference. + +### Comparison Table + +| Aspect | async/await | Promise Chains | +|--------|-------------|----------------| +| **Readability** | Looks like sync code | Nested callbacks | +| **Error Handling** | try/catch | .catch() | +| **Debugging** | Better stack traces | Harder to trace | +| **Conditionals** | Natural if/else | Nested .then() | +| **Early Returns** | Just use return | Have to throw or nest | +| **Loops** | for/for...of work naturally | Need recursion or reduce | + +### When Promise Chains Might Be Better + +```javascript +// Promise chain is more concise for simple transformations +fetchUser(id) + .then(user => user.profileId) + .then(fetchProfile) + .then(profile => profile.avatarUrl) + +// async/await equivalent - more verbose +async function getAvatarUrl(id) { + const user = await fetchUser(id) + const profile = await fetchProfile(user.profileId) + return profile.avatarUrl +} + +// Promise.race is cleaner with raw Promises +const result = await Promise.race([ + fetch('/api/main'), + timeout(5000) +]) + +// Promise chain for "fire and forget" +saveAnalytics(data).catch(console.error) // Don't await, just catch errors +``` + +### When async/await Shines + +```javascript +// Complex conditional logic +async function processOrder(order) { + const inventory = await checkInventory(order.items) + + if (!inventory.available) { + await notifyBackorder(order) + return { status: 'backordered' } + } + + const payment = await processPayment(order) + + if (payment.requiresVerification) { + await requestVerification(payment) + return { status: 'pending_verification' } + } + + await shipOrder(order) + return { status: 'shipped' } +} + +// Loops with async operations +async function migrateUsers(users) { + for (const user of users) { + await migrateUser(user) + await delay(100) // Rate limiting + } +} + +// Complex error handling +async function robustFetch(url, retries = 3) { + for (let i = 0; i < retries; i++) { + try { + return await fetch(url) + } catch (error) { + if (i === retries - 1) throw error + await delay(1000 * (i + 1)) // Exponential backoff + } + } +} +``` + +--- + +## Top-Level await + +[Top-level await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top-level-await) allows you to use `await` outside of async functions, but only in ES modules. + +```javascript +// config.js (ES module) +const response = await fetch('/config.json') +export const config = await response.json() + +// main.js +import { config } from './config.js' +console.log(config) // Config is already loaded! +``` + +### Where Top-Level await Works + +- **ES Modules** (files with `.mjs` extension or `"type": "module"` in package.json) +- **Browser `<script type="module">`** +- **Dynamic imports** + +```html +<!-- In browser --> +<script type="module"> + const data = await fetch('/api/data').then(r => r.json()) + console.log(data) +</script> +``` + +### Use Cases + +```javascript +// 1. Loading configuration before app starts +export const config = await loadConfig() + +// 2. Dynamic imports +const module = await import(`./locales/${language}.js`) + +// 3. Database connection +export const db = await connectToDatabase() + +// 4. Feature detection +export const supportsWebGL = await checkWebGLSupport() +``` + +<Warning> +**Careful:** Top-level await blocks the loading of the module and any modules that import it. Use it sparingly and only when you truly need the value before the module can be used. +</Warning> + +--- + +## Advanced Patterns + +### Retry with Exponential Backoff + +```javascript +async function fetchWithRetry(url, options = {}) { + const { retries = 3, backoff = 1000 } = options + + for (let attempt = 0; attempt < retries; attempt++) { + try { + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + return response + + } catch (error) { + const isLastAttempt = attempt === retries - 1 + + if (isLastAttempt) { + throw error + } + + // Wait with exponential backoff: 1s, 2s, 4s, 8s... + const delay = backoff * Math.pow(2, attempt) + console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`) + + await new Promise(resolve => setTimeout(resolve, delay)) + } + } +} + +// Usage +const response = await fetchWithRetry('/api/flaky-endpoint', { + retries: 5, + backoff: 500 +}) +``` + +### Timeout Wrapper + +```javascript +async function withTimeout(promise, ms) { + const timeout = new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms) + }) + + return Promise.race([promise, timeout]) +} + +// Usage +try { + const response = await withTimeout(fetch('/api/slow'), 5000) + console.log('Success:', response) +} catch (error) { + console.log('Failed:', error.message) // "Timeout after 5000ms" +} +``` + +### Cancellation with AbortController + +```javascript +async function fetchWithCancellation(url, signal) { + try { + const response = await fetch(url, { signal }) + return await response.json() + } catch (error) { + if (error.name === 'AbortError') { + console.log('Fetch was cancelled') + return null + } + throw error + } +} + +// Usage +const controller = new AbortController() + +// Start the fetch +const dataPromise = fetchWithCancellation('/api/data', controller.signal) + +// Cancel after 2 seconds if not done +setTimeout(() => controller.abort(), 2000) + +const data = await dataPromise +``` + +### Async Iterators (for await...of) + +For working with streams of async data: + +```javascript +async function* generateAsyncNumbers() { + for (let i = 1; i <= 5; i++) { + await new Promise(resolve => setTimeout(resolve, 1000)) + yield i + } +} + +// Consume the async iterator +async function processNumbers() { + for await (const num of generateAsyncNumbers()) { + console.log(num) // Prints 1, 2, 3, 4, 5 (one per second) + } +} +``` + +### Converting Callback APIs to async/await + +```javascript +// Original callback-based API +function readFileCallback(path, callback) { + fs.readFile(path, 'utf8', (err, data) => { + if (err) callback(err) + else callback(null, data) + }) +} + +// Promisified version +function readFileAsync(path) { + return new Promise((resolve, reject) => { + fs.readFile(path, 'utf8', (err, data) => { + if (err) reject(err) + else resolve(data) + }) + }) +} + +// Now you can use async/await +async function processFile(path) { + const content = await readFileAsync(path) + return content.toUpperCase() +} + +// Or use util.promisify (Node.js) +const { promisify } = require('util') +const readFileAsync = promisify(fs.readFile) +``` + +--- + +## Interview Questions + +### Question 1: What's the Output? + +```javascript +async function test() { + console.log('1') + await Promise.resolve() + console.log('2') +} + +console.log('A') +test() +console.log('B') +``` + +<Accordion title="Answer"> +**Output:** `A`, `1`, `B`, `2` + +**Explanation:** +1. `console.log('A')` — synchronous → "A" +2. `test()` is called: + - `console.log('1')` — synchronous → "1" + - `await Promise.resolve()` — pauses test(), schedules continuation as microtask + - Returns to caller +3. `console.log('B')` — synchronous → "B" +4. Call stack empty → microtask runs → `console.log('2')` → "2" + +The pattern: Code before `await` runs synchronously. Code after `await` becomes a microtask. +</Accordion> + +### Question 2: Sequential vs Parallel + +```javascript +// Version A +async function versionA() { + const start = Date.now() + const a = await delay(1000) + const b = await delay(1000) + console.log(`Time: ${Date.now() - start}ms`) +} + +// Version B +async function versionB() { + const start = Date.now() + const [a, b] = await Promise.all([delay(1000), delay(1000)]) + console.log(`Time: ${Date.now() - start}ms`) +} + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} +``` + +<Accordion title="Answer"> +**versionA:** ~2000ms (sequential — waits 1s, then another 1s) + +**versionB:** ~1000ms (parallel — both delays run simultaneously) + +This is the classic "sequential vs parallel" interview question. In versionA, each `await` must complete before the next line runs. In versionB, both Promises are created immediately, then `Promise.all` waits for both to complete while they run in parallel. +</Accordion> + +### Question 3: Error Handling + +```javascript +async function outer() { + try { + await inner() + console.log('After inner') + } catch (e) { + console.log('Caught:', e.message) + } +} + +async function inner() { + throw new Error('Oops!') +} + +outer() +``` + +<Accordion title="Answer"> +**Output:** `Caught: Oops!` + +"After inner" is never printed because `inner()` throws, which causes the `await inner()` to reject, which jumps to the catch block. + +This demonstrates that async/await error handling works like synchronous try/catch. Errors "propagate up" naturally. +</Accordion> + +### Question 4: The forEach Trap + +```javascript +async function processItems() { + const items = [1, 2, 3] + + items.forEach(async (item) => { + await delay(100) + console.log(item) + }) + + console.log('Done') +} + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +processItems() +``` + +<Accordion title="Answer"> +**Output:** +``` +Done +1 +2 +3 +``` + +(Not `1`, `2`, `3`, `Done` as you might expect!) + +**Why:** `forEach` doesn't wait for async callbacks. It fires off all three async functions and immediately continues to `console.log('Done')`. The numbers print later when their delays complete. + +**Fix:** Use `for...of` for sequential or `Promise.all` with `map` for parallel. +</Accordion> + +### Question 5: What's Wrong Here? + +```javascript +async function getData() { + return await fetch('/api/data') +} +``` + +<Accordion title="Answer"> +**Issue:** The `await` is unnecessary. + +Since `getData` is already async, it will return a Promise. You don't need to `await` before returning. The Promise will be returned directly. + +```javascript +// Equivalent, simpler: +async function getData() { + return fetch('/api/data') +} + +// The 'await' only matters if you need to do something with the result +// or if you're in a try/catch and want to catch errors: +async function getData() { + try { + return await fetch('/api/data') // await IS needed here to catch errors + } catch (error) { + console.error(error) + throw error + } +} +``` +</Accordion> + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **async/await is syntactic sugar over Promises** — it doesn't change how async works, just how you write it + +2. **async functions always return Promises** — even if you return a plain value, it's wrapped in Promise.resolve() + +3. **await pauses the function, not the thread** — other code can run while waiting; JavaScript stays non-blocking + +4. **Code after await becomes a microtask** — it runs after the current synchronous code completes, but before setTimeout callbacks + +5. **Use try/catch for error handling** — it works just like synchronous code and catches both sync errors and Promise rejections + +6. **await in forEach doesn't work as expected** — use for...of for sequential or Promise.all with map for parallel + +7. **Prefer parallel over sequential** — use Promise.all when operations are independent; it's often 2-10x faster + +8. **Don't forget await** — without it, you get a Promise object instead of the resolved value + +9. **Top-level await only works in ES modules** — not in regular scripts or CommonJS + +10. **async/await and Promises are interchangeable** — choose based on readability for your specific use case +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What does the async keyword do to a function?"> + **Answer:** + + The `async` keyword does two things: + + 1. Makes the function **always return a Promise** — even if you return a non-Promise value, it gets wrapped in `Promise.resolve()` + 2. Enables the use of `await` inside the function + + ```javascript + async function example() { + return 42 + } + + example().then(value => console.log(value)) // 42 + console.log(example()) // Promise {<fulfilled>: 42} + ``` + </Accordion> + + <Accordion title="Question 2: What's the difference between these two?"> + ```javascript + // Version A + const data = await fetchData() + + // Version B + const data = fetchData() + ``` + + **Answer:** + + - **Version A:** `data` contains the resolved value (e.g., the actual JSON object) + - **Version B:** `data` contains a Promise object, not the resolved value + + Version B is a common mistake that leads to bugs like seeing `[object Promise]` or getting undefined properties. + </Accordion> + + <Accordion title="Question 3: How do you run async operations in parallel?"> + **Answer:** + + Use `Promise.all()` to run multiple async operations simultaneously: + + ```javascript + // ❌ Sequential (slow) + const a = await fetchA() + const b = await fetchB() + const c = await fetchC() + + // ✓ Parallel (fast) + const [a, b, c] = await Promise.all([ + fetchA(), + fetchB(), + fetchC() + ]) + ``` + + For cases where you want all results even if some fail, use `Promise.allSettled()`. + </Accordion> + + <Accordion title="Question 4: Why doesn't await work inside forEach?"> + **Answer:** + + `forEach` is not async-aware. It doesn't wait for the callback's Promise to resolve before continuing. It just fires off all the async callbacks and moves on. + + ```javascript + // ❌ Doesn't wait + items.forEach(async item => { + await processItem(item) + }) + console.log('Done') // Prints before items are processed! + + // ✓ Sequential - use for...of + for (const item of items) { + await processItem(item) + } + console.log('Done') // Prints after all items + + // ✓ Parallel - use Promise.all with map + await Promise.all(items.map(item => processItem(item))) + console.log('Done') // Prints after all items + ``` + </Accordion> + + <Accordion title="Question 5: How do you handle errors in async functions?"> + **Answer:** + + Use `try/catch` blocks, which work just like synchronous error handling: + + ```javascript + async function fetchData() { + try { + const response = await fetch('/api/data') + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + return await response.json() + } catch (error) { + console.error('Fetch failed:', error) + throw error // Re-throw if caller should handle it + } finally { + // Cleanup code that always runs + } + } + ``` + + You can also use `.catch()` at the call site: `fetchData().catch(handleError)` + </Accordion> + + <Accordion title="Question 6: What's the output order and why?"> + ```javascript + console.log('1') + setTimeout(() => console.log('2'), 0) + Promise.resolve().then(() => console.log('3')) + async function test() { + console.log('4') + await Promise.resolve() + console.log('5') + } + test() + console.log('6') + ``` + + **Answer:** `1`, `4`, `6`, `3`, `5`, `2` + + **Explanation:** + 1. `'1'` — synchronous + 2. `setTimeout` callback → task queue + 3. `.then` callback → microtask queue + 4. `test()` called → `'4'` — synchronous part of async function + 5. `await` → schedules `'5'` as microtask, returns to caller + 6. `'6'` — synchronous + 7. Call stack empty → process microtasks: `'3'` then `'5'` + 8. Microtasks done → process task queue: `'2'` + + Key: Microtasks (Promises, await continuations) run before macrotasks (setTimeout). + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="Eloquent JavaScript, 3rd Edition: Ch. 11 - Asynchronous Programming" icon="book" href="https://eloquentjavascript.net/11_async.html"> - By Marijn Haverbeke + <Card title="Promises" icon="handshake" href="/concepts/promises"> + async/await is built on Promises. Knowing Promises well makes async/await easier </Card> - <Card title="Exploring JS: Asynchronous Programming" icon="book" href="http://exploringjs.com/es6/ch_async.html"> - By Dr. Axel Rauschmayer + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + Learn how JavaScript handles async operations and why await creates microtasks + </Card> + <Card title="Callbacks" icon="phone" href="/concepts/callbacks"> + The original async pattern that async/await replaced + </Card> + <Card title="Fetch API" icon="cloud" href="/concepts/http-fetch"> + The most common use case for async/await: making HTTP requests </Card> </CardGroup> -## Articles +--- + +## Reference <CardGroup cols={2}> - <Card title="Understanding async/await in Javascript" icon="newspaper" href="https://hackernoon.com/understanding-async-await-in-javascript-1d81bb079b2c"> - By Gokul N K + <Card title="async function — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"> + Complete reference for async function declarations and expressions + </Card> + <Card title="await — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await"> + Documentation for the await operator and its behavior </Card> - <Card title="Modern Asynchronous JavaScript with async/await" icon="newspaper" href="https://flaviocopes.com/javascript-async-await/"> - By Flavio Copes + <Card title="Promise — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"> + The foundation that async/await is built on + </Card> + <Card title="try...catch — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"> + Error handling syntax used with async/await </Card> </CardGroup> -- [Asynchronous Javascript using async/await — Joy Warugu](https://scotch.io/tutorials/asynchronous-javascript-using-async-await) -- [Javascript — ES8 Introducing async/await Functions — Ben Garrison](https://medium.com/@_bengarrison/javascript-es8-introducing-async-await-functions-7a471ec7de8a) -- [How to escape async/await hell — Aditya Agarwal](https://medium.freecodecamp.org/avoiding-the-async-await-hell-c77a0fb71c4c) -- [Understanding JavaScript's async await — Nicolás Bevacqua](https://ponyfoo.com/articles/understanding-javascript-async-await) -- [JavaScript Async/Await: Serial, Parallel and Complex Flow — TechBrij](https://techbrij.com/javascript-async-await-parallel-sequence) -- [From JavaScript Promises to Async/Await: why bother? — Chris Nwamba](https://blog.pusher.com/promises-async-await/) -- [Flow Control in Modern JS: Callbacks to Promises to Async/Await — Craig Buckler](https://www.sitepoint.com/flow-control-callbacks-promises-async-await/) -- [How to improve your asynchronous Javascript code with async and await — Indrek Lasn](https://medium.freecodecamp.org/improve-your-asynchronous-javascript-code-with-async-and-await-c02fc3813eda) -- [Making Fetches Easy With Async Await — Mickey Sheridan](https://medium.com/@micksheridan.24/making-fetches-easy-with-async-await-8a1246efa1f6) -- [7 Reasons Why JavaScript Async/Await Is Better Than Plain Promises — Mostafa Gaafar](https://dev.to/gafi/7-reasons-to-always-use-async-await-over-plain-promises-tutorial-4ej9) -- [JavaScript: Promises or async-await — Gokul N K](https://medium.com/better-programming/should-i-use-promises-or-async-await-126ab5c98789) -- [Async / Await: From Zero to Hero — Zhi Yuan](https://dev.to/zhiyuanamos/async-await-from-zero-to-hero-a22) -- [JavaScript Visualized: Promises & Async/Await — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke) -- [Making asynchronous programming easier with async and await — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) -- [JavaScript Async/Await Tutorial – Learn Callbacks, Promises, and Async/Await by Making Ice Cream](https://www.freecodecamp.org/news/javascript-async-await-tutorial-learn-callbacks-promises-async-await-by-making-icecream/) -- [Better Than Promises - JavaScript Async/Await](https://blog.webdevsimplified.com/2021-11/async-await/) +## Articles + +<CardGroup cols={2}> + <Card title="JavaScript Async/Await Tutorial" icon="newspaper" href="https://javascript.info/async-await"> + The go-to reference for async/await fundamentals. Includes exercises at the end to test your understanding of rewriting promise chains. + </Card> + <Card title="How to Use Async/Await in JavaScript" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-async-await-tutorial-learn-callbacks-promises-async-await-by-making-icecream/"> + Learn async patterns by building a virtual ice cream shop. The GIFs comparing sync vs async execution are worth the visit alone. + </Card> + + <Card title="7 Reasons Why Async/Await Is Better Than Promises" icon="newspaper" href="https://dev.to/gafi/7-reasons-to-always-use-async-await-over-plain-promises-tutorial-4ej9"> + Side-by-side code comparisons that show exactly how async/await cleans up promise chains. The debugging section alone is worth bookmarking. + </Card> + <Card title="JavaScript Visualized: Promises & Async/Await" icon="newspaper" href="https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke"> + Animated GIFs that show the call stack, microtask queue, and event loop in action. This is how async/await finally "clicked" for thousands of developers. + </Card> + <Card title="How to Escape Async/Await Hell" icon="newspaper" href="https://medium.freecodecamp.org/avoiding-the-async-await-hell-c77a0fb71c4c"> + The pizza-and-drinks ordering example makes parallel vs sequential execution crystal clear. Essential reading once you know the basics. + </Card> +</CardGroup> ## Videos <CardGroup cols={2}> + <Card title="JavaScript Async/Await" icon="video" href="https://www.youtube.com/watch?v=V_Kr9OSfDeU"> + Web Dev Simplified breaks down async/await in 12 minutes. Perfect if you learn better from watching code being written live. + </Card> + <Card title="Async + Await in JavaScript" icon="video" href="https://www.youtube.com/watch?v=9YkUCxvaLEk"> + Wes Bos at dotJS 2017. An energetic talk that covers async/await patterns with real API calls. The crowd reactions tell you which parts trip people up. + </Card> <Card title="Asynchronous JavaScript Crash Course" icon="video" href="https://www.youtube.com/watch?v=exBgWAIeIeg"> - By Traversy Media + Traversy Media's full async journey from callbacks through promises to async/await. Great if you want to see how we got here historically. </Card> - <Card title="Async + Await" icon="video" href="https://www.youtube.com/watch?v=9YkUCxvaLEk"> - By Wes Bos + <Card title="Async Await in JavaScript" icon="video" href="https://youtu.be/Gjbr21JLfgg"> + Hitesh Choudhary's hands-on walkthrough with coding examples. Hindi and English explanations make concepts accessible to a wider audience. </Card> </CardGroup> - -- [Asynchrony: Under the Hood — Shelley Vohr](https://www.youtube.com/watch?v=SrNQS8J67zc) -- [async/await in JavaScript - What, Why and How — Fun Fun Function](https://www.youtube.com/watch?v=568g8hxJJp4&index=3&list=PL0zVEGEvSaeHJppaRLrqjeTPnCH6) -- [async/await Part 1 - Topics of JavaScript/ES8 — The Coding Train](https://www.youtube.com/watch?v=XO77Fib9tSI&index=3&list=PLRqwX-V7Uu6bKLPQvPRNNE65kBL62mVfx) -- [async/await Part 2 - Topics of JavaScript/ES8 — The Coding Train](https://www.youtube.com/watch?v=chavThlNz3s&index=4&list=PLRqwX-V7Uu6bKLPQvPRNNE65kBL62mVfx) -- [Complete Guide to JS Async & Await ES2017/ES8 — Colt Steele](https://www.youtube.com/watch?v=krAYA4rvbdA) -- [Tips for using async/await in JavaScript — James Q Quick](https://www.youtube.com/watch?v=_9vgd9XKlDQ) -- [JavaScript Async Await — Web Dev Simplified](https://www.youtube.com/watch?v=V_Kr9OSfDeU) -- [Promise async and await in javascript — Hitesh Choudhary](https://youtu.be/Gjbr21JLfgg?si=SDCVKr9ONw2GsNdT) diff --git a/tests/functions-execution/async-await/async-await.test.js b/tests/functions-execution/async-await/async-await.test.js new file mode 100644 index 00000000..50251aa9 --- /dev/null +++ b/tests/functions-execution/async-await/async-await.test.js @@ -0,0 +1,1042 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +describe('async/await', () => { + + // ============================================================ + // THE async KEYWORD + // ============================================================ + + describe('The async Keyword', () => { + it('should make a function return a Promise', () => { + // From: async function always returns a Promise + async function getValue() { + return 42 + } + + const result = getValue() + + expect(result).toBeInstanceOf(Promise) + }) + + it('should wrap return values in Promise.resolve()', async () => { + // From: return values are wrapped in Promise.resolve() + async function getValue() { + return 42 + } + + const result = await getValue() + + expect(result).toBe(42) + }) + + it('should convert thrown errors to rejected Promises', async () => { + // From: when you throw in an async function, it becomes a rejected Promise + async function failingFunction() { + throw new Error('Something went wrong!') + } + + await expect(failingFunction()).rejects.toThrow('Something went wrong!') + }) + + it('should not double-wrap returned Promises', async () => { + // From: return a Promise? No double-wrapping + async function getPromise() { + return Promise.resolve(42) + } + + const result = await getPromise() + + // If it double-wrapped, result would be a Promise, not 42 + expect(result).toBe(42) + expect(typeof result).toBe('number') + }) + + it('should work with async arrow functions', async () => { + // From: async arrow function + const getData = async () => { + return 'data' + } + + expect(await getData()).toBe('data') + }) + + it('should work with async methods in objects', async () => { + // From: async method in an object + const api = { + async fetchData() { + return 'fetched' + } + } + + expect(await api.fetchData()).toBe('fetched') + }) + + it('should work with async methods in classes', async () => { + // From: async method in a class + class DataService { + async getData() { + return 'class data' + } + } + + const service = new DataService() + expect(await service.getData()).toBe('class data') + }) + }) + + // ============================================================ + // THE await KEYWORD + // ============================================================ + + describe('The await Keyword', () => { + it('should pause execution until Promise resolves', async () => { + const order = [] + + async function example() { + order.push('before await') + await Promise.resolve() + order.push('after await') + } + + await example() + + expect(order).toEqual(['before await', 'after await']) + }) + + it('should return the resolved value of a Promise', async () => { + async function example() { + const value = await Promise.resolve(42) + return value + } + + expect(await example()).toBe(42) + }) + + it('should work with non-Promise values (though pointless)', async () => { + // From: awaiting a non-Promise value + async function example() { + const num = await 42 + return num + } + + expect(await example()).toBe(42) + }) + + it('should work with thenable objects', async () => { + // From: awaiting a thenable + const thenable = { + then(resolve) { + resolve('thenable value') + } + } + + async function example() { + return await thenable + } + + expect(await example()).toBe('thenable value') + }) + + it('should not block the main thread - other code runs while waiting', async () => { + // From: await pauses the function, not the thread + const order = [] + + async function slowOperation() { + order.push('Starting slow operation') + await Promise.resolve() + order.push('Slow operation complete') + } + + order.push('Before calling slowOperation') + const promise = slowOperation() + order.push('After calling slowOperation') + + // At this point, slowOperation is paused at await + expect(order).toEqual([ + 'Before calling slowOperation', + 'Starting slow operation', + 'After calling slowOperation' + ]) + + await promise + + expect(order).toEqual([ + 'Before calling slowOperation', + 'Starting slow operation', + 'After calling slowOperation', + 'Slow operation complete' + ]) + }) + }) + + // ============================================================ + // HOW await WORKS UNDER THE HOOD + // ============================================================ + + describe('How await Works Under the Hood', () => { + it('should run code before await synchronously', async () => { + // From: code before await is synchronous + const order = [] + + async function example() { + order.push('1. Before await') + await Promise.resolve() + order.push('2. After await') + } + + order.push('A. Before call') + example() + order.push('B. After call') + + // Before microtasks run + expect(order).toEqual([ + 'A. Before call', + '1. Before await', + 'B. After call' + ]) + + // Let microtasks run + await Promise.resolve() + + expect(order).toEqual([ + 'A. Before call', + '1. Before await', + 'B. After call', + '2. After await' + ]) + }) + + it('should treat code after await as a microtask', async () => { + // From: await splits the function diagram + const order = [] + + async function asyncFn() { + order.push('async start') + await Promise.resolve() + order.push('async after await') + } + + order.push('script start') + asyncFn() + order.push('script end') + + // Await hasn't resolved yet + expect(order).toEqual(['script start', 'async start', 'script end']) + + await Promise.resolve() + + expect(order).toEqual(['script start', 'async start', 'script end', 'async after await']) + }) + + it('should handle multiple await statements', async () => { + const order = [] + + async function multipleAwaits() { + order.push('start') + await Promise.resolve() + order.push('after first await') + await Promise.resolve() + order.push('after second await') + } + + multipleAwaits() + order.push('sync after call') + + expect(order).toEqual(['start', 'sync after call']) + + await Promise.resolve() + expect(order).toEqual(['start', 'sync after call', 'after first await']) + + await Promise.resolve() + expect(order).toEqual(['start', 'sync after call', 'after first await', 'after second await']) + }) + }) + + // ============================================================ + // ERROR HANDLING WITH try/catch + // ============================================================ + + describe('Error Handling with try/catch', () => { + it('should catch rejected Promises with try/catch', async () => { + // From: Basic try/catch pattern + async function fetchData() { + try { + await Promise.reject(new Error('Network error')) + return 'success' + } catch (error) { + return `caught: ${error.message}` + } + } + + expect(await fetchData()).toBe('caught: Network error') + }) + + it('should catch errors thrown in async functions', async () => { + async function mightFail(shouldFail) { + if (shouldFail) { + throw new Error('Failed!') + } + return 'Success' + } + + expect(await mightFail(false)).toBe('Success') + await expect(mightFail(true)).rejects.toThrow('Failed!') + }) + + it('should run finally block regardless of success or failure', async () => { + // From: The finally block + const results = [] + + async function withFinally(shouldFail) { + try { + if (shouldFail) { + throw new Error('error') + } + results.push('success') + } catch (error) { + results.push('caught') + } finally { + results.push('finally') + } + } + + await withFinally(false) + expect(results).toEqual(['success', 'finally']) + + results.length = 0 + await withFinally(true) + expect(results).toEqual(['caught', 'finally']) + }) + + it('should demonstrate the swallowed error mistake', async () => { + // From: The Trap - if you catch but don't re-throw, Promise resolves with undefined + async function swallowsError() { + try { + throw new Error('Oops') + } catch (error) { + console.error('Error:', error) + // Missing: throw error + } + } + + // This resolves (not rejects!) with undefined + const result = await swallowsError() + expect(result).toBeUndefined() + }) + + it('should propagate errors when re-thrown', async () => { + async function rethrowsError() { + try { + throw new Error('Oops') + } catch (error) { + throw error // Re-throw + } + } + + await expect(rethrowsError()).rejects.toThrow('Oops') + }) + + it('should catch errors from nested async calls', async () => { + // From: Interview Question 3 - Error Handling + async function inner() { + throw new Error('Oops!') + } + + async function outer() { + try { + await inner() + return 'success' + } catch (e) { + return `caught: ${e.message}` + } + } + + expect(await outer()).toBe('caught: Oops!') + }) + }) + + // ============================================================ + // SEQUENTIAL VS PARALLEL EXECUTION + // ============================================================ + + describe('Sequential vs Parallel Execution', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should demonstrate slow sequential execution', async () => { + // From: The Problem - Unnecessary Sequential Execution + const delay = (ms, value) => new Promise(resolve => + setTimeout(() => resolve(value), ms) + ) + + async function sequential() { + const start = Date.now() + const a = await delay(100, 'a') + const b = await delay(100, 'b') + const c = await delay(100, 'c') + return { a, b, c, time: Date.now() - start } + } + + const promise = sequential() + + // Advance through all three delays + await vi.advanceTimersByTimeAsync(100) + await vi.advanceTimersByTimeAsync(100) + await vi.advanceTimersByTimeAsync(100) + + const result = await promise + expect(result.a).toBe('a') + expect(result.b).toBe('b') + expect(result.c).toBe('c') + expect(result.time).toBeGreaterThanOrEqual(300) // Sequential: 100+100+100 + }) + + it('should demonstrate fast parallel execution with Promise.all', async () => { + // From: The Solution - Promise.all for Parallel Execution + const delay = (ms, value) => new Promise(resolve => + setTimeout(() => resolve(value), ms) + ) + + async function parallel() { + const start = Date.now() + const [a, b, c] = await Promise.all([ + delay(100, 'a'), + delay(100, 'b'), + delay(100, 'c') + ]) + return { a, b, c, time: Date.now() - start } + } + + const promise = parallel() + + // All three start at once, so only need 100ms total + await vi.advanceTimersByTimeAsync(100) + + const result = await promise + expect(result.a).toBe('a') + expect(result.b).toBe('b') + expect(result.c).toBe('c') + expect(result.time).toBe(100) // Parallel: max(100,100,100) = 100 + }) + + it('should fail fast with Promise.all when any Promise rejects', async () => { + // From: Promise.all - fails fast + const results = await Promise.allSettled([ + Promise.resolve('success'), + Promise.reject(new Error('fail')), + Promise.resolve('also success') + ]) + + expect(results[0]).toEqual({ status: 'fulfilled', value: 'success' }) + expect(results[1].status).toBe('rejected') + expect(results[1].reason.message).toBe('fail') + expect(results[2]).toEqual({ status: 'fulfilled', value: 'also success' }) + }) + + it('should get all results with Promise.allSettled', async () => { + // From: Promise.allSettled - waits for all + const results = await Promise.allSettled([ + Promise.resolve('a'), + Promise.reject(new Error('b failed')), + Promise.resolve('c') + ]) + + const successful = results + .filter(r => r.status === 'fulfilled') + .map(r => r.value) + + const failed = results + .filter(r => r.status === 'rejected') + .map(r => r.reason.message) + + expect(successful).toEqual(['a', 'c']) + expect(failed).toEqual(['b failed']) + }) + }) + + // ============================================================ + // COMMON MISTAKES + // ============================================================ + + describe('Common Mistakes', () => { + it('Mistake #1: Forgetting await gives Promise instead of value', async () => { + // From: Without await, you get a Promise object instead of the resolved value + async function withoutAwait() { + const value = Promise.resolve(42) // Missing await! + return value + } + + async function withAwait() { + const value = await Promise.resolve(42) + return value + } + + const withoutResult = await withoutAwait() + const withResult = await withAwait() + + // Both eventually resolve to 42, but withoutAwait returns a Promise + expect(withoutResult).toBe(42) // Works because we await the function + expect(withResult).toBe(42) + }) + + it('Mistake #2: forEach does not wait for async callbacks', async () => { + // From: forEach doesn't wait for async callbacks + const order = [] + const items = [1, 2, 3] + + // This is the WRONG way + async function wrongWay() { + items.forEach(async (item) => { + await Promise.resolve() + order.push(item) + }) + order.push('done') + } + + await wrongWay() + // 'done' appears before the items because forEach doesn't wait + expect(order[0]).toBe('done') + + // Let microtasks complete + await Promise.resolve() + await Promise.resolve() + await Promise.resolve() + + expect(order).toEqual(['done', 1, 2, 3]) + }) + + it('Mistake #2 Fix: Use for...of for sequential processing', async () => { + // From: Use for...of for sequential + const order = [] + const items = [1, 2, 3] + + async function rightWay() { + for (const item of items) { + await Promise.resolve() + order.push(item) + } + order.push('done') + } + + await rightWay() + expect(order).toEqual([1, 2, 3, 'done']) + }) + + it('Mistake #2 Fix: Use Promise.all with map for parallel processing', async () => { + // From: Use Promise.all for parallel + const results = [] + const items = [1, 2, 3] + + async function parallelWay() { + await Promise.all( + items.map(async (item) => { + await Promise.resolve() + results.push(item) + }) + ) + results.push('done') + } + + await parallelWay() + // Items may be in any order (parallel), but 'done' is always last + expect(results).toContain(1) + expect(results).toContain(2) + expect(results).toContain(3) + expect(results[results.length - 1]).toBe('done') + }) + + it('Mistake #4: Not handling errors leads to unhandled rejections', async () => { + // From: Not Handling Errors + async function riskyOperation() { + throw new Error('Unhandled!') + } + + // Without error handling, this would be an unhandled rejection + await expect(riskyOperation()).rejects.toThrow('Unhandled!') + }) + }) + + // ============================================================ + // ADVANCED PATTERNS + // ============================================================ + + describe('Advanced Patterns', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should implement retry with exponential backoff', async () => { + // From: Retry with Exponential Backoff + let attempts = 0 + + async function flakyOperation() { + attempts++ + if (attempts < 3) { + throw new Error('Temporary failure') + } + return 'success' + } + + async function withRetry(operation, retries = 3, backoff = 100) { + for (let attempt = 0; attempt < retries; attempt++) { + try { + return await operation() + } catch (error) { + if (attempt === retries - 1) throw error + await new Promise(resolve => setTimeout(resolve, backoff * Math.pow(2, attempt))) + } + } + } + + const promise = withRetry(flakyOperation, 3, 100) + + // First attempt fails, wait 100ms + await vi.advanceTimersByTimeAsync(0) + expect(attempts).toBe(1) + + // Second attempt after 100ms, fails, wait 200ms + await vi.advanceTimersByTimeAsync(100) + expect(attempts).toBe(2) + + // Third attempt after 200ms, succeeds + await vi.advanceTimersByTimeAsync(200) + + const result = await promise + expect(result).toBe('success') + expect(attempts).toBe(3) + }) + + it('should implement timeout wrapper', async () => { + // From: Timeout Wrapper + async function withTimeout(promise, ms) { + const timeout = new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms) + }) + return Promise.race([promise, timeout]) + } + + // Test successful case + const fastPromise = withTimeout(Promise.resolve('fast'), 1000) + expect(await fastPromise).toBe('fast') + + // Test timeout case + const slowPromise = new Promise(resolve => setTimeout(() => resolve('slow'), 2000)) + const timeoutPromise = withTimeout(slowPromise, 100) + + // Advance time to trigger timeout + vi.advanceTimersByTime(100) + + await expect(timeoutPromise).rejects.toThrow('Timeout after 100ms') + }) + + it('should implement cancellation with AbortController', async () => { + // From: Cancellation with AbortController + async function fetchWithCancellation(signal) { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => resolve('data'), 1000) + + signal.addEventListener('abort', () => { + clearTimeout(timeoutId) + reject(new DOMException('Aborted', 'AbortError')) + }) + }) + } + + const controller = new AbortController() + const promise = fetchWithCancellation(controller.signal) + + // Cancel before it completes + controller.abort() + + await expect(promise).rejects.toThrow('Aborted') + }) + + it('should convert callback API to async/await', async () => { + // From: Converting Callback APIs to async/await + function callbackApi(value, callback) { + setTimeout(() => { + if (value < 0) { + callback(new Error('Negative value')) + } else { + callback(null, value * 2) + } + }, 100) + } + + // Promisified version + function asyncApi(value) { + return new Promise((resolve, reject) => { + callbackApi(value, (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + + // Test success + const successPromise = asyncApi(21) + await vi.advanceTimersByTimeAsync(100) + expect(await successPromise).toBe(42) + + // Test failure - must attach handler BEFORE advancing time + const failPromise = asyncApi(-1) + const failHandler = expect(failPromise).rejects.toThrow('Negative value') + await vi.advanceTimersByTimeAsync(100) + await failHandler + }) + }) + + // ============================================================ + // INTERVIEW QUESTIONS + // ============================================================ + + describe('Interview Questions', () => { + it('Question 1: What is the output order?', async () => { + // From: Interview Question 1 + const order = [] + + async function test() { + order.push('1') + await Promise.resolve() + order.push('2') + } + + order.push('A') + test() + order.push('B') + + // Before microtasks + expect(order).toEqual(['A', '1', 'B']) + + await Promise.resolve() + + // After microtasks + expect(order).toEqual(['A', '1', 'B', '2']) + }) + + it('Question 2: Sequential vs Parallel timing', async () => { + // From: Interview Question 2 + vi.useFakeTimers() + + function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + // Version A - Sequential + async function versionA() { + const start = Date.now() + await delay(100) + await delay(100) + return Date.now() - start + } + + // Version B - Parallel + async function versionB() { + const start = Date.now() + await Promise.all([delay(100), delay(100)]) + return Date.now() - start + } + + const promiseA = versionA() + await vi.advanceTimersByTimeAsync(200) + const timeA = await promiseA + + const promiseB = versionB() + await vi.advanceTimersByTimeAsync(100) + const timeB = await promiseB + + expect(timeA).toBe(200) // Sequential: 100 + 100 + expect(timeB).toBe(100) // Parallel: max(100, 100) + + vi.useRealTimers() + }) + + it('Question 4: The forEach trap', async () => { + // From: Interview Question 4 - The forEach Trap + vi.useFakeTimers() + + const order = [] + + async function processItems() { + const items = [1, 2, 3] + + items.forEach(async (item) => { + await new Promise(resolve => setTimeout(resolve, 100)) + order.push(item) + }) + + order.push('Done') + } + + processItems() + + // 'Done' appears immediately because forEach doesn't wait + expect(order).toEqual(['Done']) + + // After delays complete + await vi.advanceTimersByTimeAsync(100) + await Promise.resolve() + await Promise.resolve() + await Promise.resolve() + + expect(order).toEqual(['Done', 1, 2, 3]) + + vi.useRealTimers() + }) + + it('Question 5: Unnecessary await before return', async () => { + // From: Interview Question 5 - What is wrong here? + + // This await is unnecessary (but not wrong) + async function unnecessaryAwait() { + return await Promise.resolve(42) + } + + // This is equivalent and cleaner + async function noAwait() { + return Promise.resolve(42) + } + + expect(await unnecessaryAwait()).toBe(42) + expect(await noAwait()).toBe(42) + }) + + it('Question 6: Complex output order with async/await, Promises, and setTimeout', async () => { + // From: Test Your Knowledge Question 6 + vi.useFakeTimers() + + const order = [] + + order.push('1') + setTimeout(() => order.push('2'), 0) + Promise.resolve().then(() => order.push('3')) + + async function test() { + order.push('4') + await Promise.resolve() + order.push('5') + } + + test() + order.push('6') + + // Synchronous code completes: 1, 4, 6 + expect(order).toEqual(['1', '4', '6']) + + // Microtasks run: 3, 5 + await Promise.resolve() + await Promise.resolve() + expect(order).toEqual(['1', '4', '6', '3', '5']) + + // Macrotask runs: 2 + await vi.advanceTimersByTimeAsync(0) + expect(order).toEqual(['1', '4', '6', '3', '5', '2']) + + vi.useRealTimers() + }) + }) + + // ============================================================ + // ASYNC/AWAIT WITH PROMISES INTEROPERABILITY + // ============================================================ + + describe('async/await and Promises Interoperability', () => { + it('should work with .then() on async function results', async () => { + async function getData() { + return 42 + } + + const result = await getData().then(x => x * 2) + expect(result).toBe(84) + }) + + it('should work with .catch() on async function results', async () => { + async function failingFn() { + throw new Error('failed') + } + + const result = await failingFn().catch(err => `caught: ${err.message}`) + expect(result).toBe('caught: failed') + }) + + it('should allow mixing async/await and Promise chains', async () => { + async function step1() { + return 'step1' + } + + function step2(prev) { + return Promise.resolve(`${prev} -> step2`) + } + + async function step3(prev) { + return `${prev} -> step3` + } + + const result = await step1() + .then(step2) + .then(step3) + + expect(result).toBe('step1 -> step2 -> step3') + }) + + it('should handle Promise.race with async functions', async () => { + vi.useFakeTimers() + + async function fast() { + await new Promise(resolve => setTimeout(resolve, 50)) + return 'fast' + } + + async function slow() { + await new Promise(resolve => setTimeout(resolve, 200)) + return 'slow' + } + + const racePromise = Promise.race([fast(), slow()]) + + await vi.advanceTimersByTimeAsync(50) + + expect(await racePromise).toBe('fast') + + vi.useRealTimers() + }) + }) + + // ============================================================ + // EDGE CASES + // ============================================================ + + describe('Edge Cases', () => { + it('should handle async function that returns undefined', async () => { + async function returnsNothing() { + await Promise.resolve() + // No return statement + } + + const result = await returnsNothing() + expect(result).toBeUndefined() + }) + + it('should handle async function that returns null', async () => { + async function returnsNull() { + return null + } + + const result = await returnsNull() + expect(result).toBeNull() + }) + + it('should handle nested async functions', async () => { + async function outer() { + async function inner() { + return await Promise.resolve('inner value') + } + return await inner() + } + + expect(await outer()).toBe('inner value') + }) + + it('should handle async IIFE', async () => { + const result = await (async () => { + return 'IIFE result' + })() + + expect(result).toBe('IIFE result') + }) + + it('should handle await in conditional', async () => { + async function conditionalAwait(condition) { + if (condition) { + return await Promise.resolve('true branch') + } else { + return await Promise.resolve('false branch') + } + } + + expect(await conditionalAwait(true)).toBe('true branch') + expect(await conditionalAwait(false)).toBe('false branch') + }) + + it('should handle await in try-catch-finally', async () => { + const order = [] + + async function withTryCatchFinally() { + try { + order.push('try start') + await Promise.resolve() + order.push('try end') + throw new Error('test') + } catch (e) { + order.push('catch') + await Promise.resolve() + order.push('catch after await') + } finally { + order.push('finally') + await Promise.resolve() + order.push('finally after await') + } + } + + await withTryCatchFinally() + + expect(order).toEqual([ + 'try start', + 'try end', + 'catch', + 'catch after await', + 'finally', + 'finally after await' + ]) + }) + + it('should handle await in loop', async () => { + async function loopWithAwait() { + const results = [] + for (let i = 0; i < 3; i++) { + results.push(await Promise.resolve(i)) + } + return results + } + + expect(await loopWithAwait()).toEqual([0, 1, 2]) + }) + + it('should handle rejected Promise without await in try block', async () => { + // This is a subtle bug - the catch won't catch the rejection + // because the Promise is returned, not awaited + async function subtleBug() { + try { + return Promise.reject(new Error('not caught')) + } catch (e) { + return 'caught' + } + } + + // The error is NOT caught by the try-catch + await expect(subtleBug()).rejects.toThrow('not caught') + }) + + it('should catch rejected Promise when awaited in try block', async () => { + async function fixedVersion() { + try { + return await Promise.reject(new Error('is caught')) + } catch (e) { + return 'caught' + } + } + + // Now the error IS caught + expect(await fixedVersion()).toBe('caught') + }) + }) +}) From e1d67d47ea16e3720cbe85cb9f5ada06f8cc9394 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 12:44:06 -0300 Subject: [PATCH 069/128] docs: add comprehensive generators-iterators concept page with tests - Complete rewrite covering generators, iterators, and iteration protocols - Includes analogies, ASCII diagrams, and practical patterns - 32 tests covering all documented concepts --- docs/concepts/generators-iterators.mdx | 1401 ++++++++++++++++- .../generators-iterators.test.js | 593 +++++++ 2 files changed, 1966 insertions(+), 28 deletions(-) create mode 100644 tests/functions-execution/generators-iterators/generators-iterators.test.js diff --git a/docs/concepts/generators-iterators.mdx b/docs/concepts/generators-iterators.mdx index a189ae1f..747791f7 100644 --- a/docs/concepts/generators-iterators.mdx +++ b/docs/concepts/generators-iterators.mdx @@ -4,53 +4,1398 @@ sidebarTitle: "Generators & Iterators: Pausable Functions" description: "Learn JavaScript generators and iterators — functions that can pause and resume execution. Master the yield keyword, iteration protocols, lazy evaluation, and async generators." --- -## Overview +What if a function could pause mid-execution, return a value, and then resume right where it left off? What if you could create a sequence of values that are computed only when you ask for them — not all at once? -**Generators** are special functions that can be paused and resumed, yielding multiple values over time. Combined with **iterators** (objects that define a sequence), they enable powerful patterns like lazy evaluation, infinite sequences, and custom iteration — and form the foundation for async/await under the hood. +```javascript +// This function can PAUSE and RESUME +function* countToThree() { + yield 1 // Pause here, return 1 + yield 2 // Resume, pause here, return 2 + yield 3 // Resume, pause here, return 3 +} + +const counter = countToThree() + +console.log(counter.next().value) // 1 +console.log(counter.next().value) // 2 +console.log(counter.next().value) // 3 +``` + +This is the power of **[generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)** — functions that can pause with `yield` and pick up where they left off. Combined with **[iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)** (objects that define how to step through a sequence), they open up patterns like lazy evaluation, infinite sequences, and clean data pipelines. <Info> **What you'll learn in this guide:** -- Generator functions and the `yield` keyword -- The iteration protocol (`Symbol.iterator`) -- Creating custom iterables -- Lazy evaluation and infinite sequences -- Async generators and `for await...of` -- Real-world use cases (Redux-Saga, data streaming) +- What iterators are and how the iteration protocol works +- Generator functions with `function*` and `yield` (they're lazier than you think) +- The difference between `yield` and `return` (it trips people up!) +- How to make any object iterable with `Symbol.iterator` +- Lazy evaluation — why generators are so memory-efficient +- Practical patterns: pagination, ID generation, state machines +- Async generators and `for await...of` for streaming data </Info> +<Warning> +**Prerequisites:** This guide assumes you're comfortable with [closures](/concepts/scope-and-closures) and [higher-order functions](/concepts/higher-order-functions). If those concepts are new to you, read those guides first! +</Warning> + +--- + +## What is an Iterator? + +Before getting into generators, we need to cover **iterators** — the foundation that makes generators work. + +An **[iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol)** is an object that defines a sequence and provides a way to access values one at a time. It must have a `.next()` method that returns an object with two properties: + +- `value` — the next value in the sequence +- `done` — `true` if the sequence is finished, `false` otherwise + +```javascript +// Creating an iterator manually +function createCounterIterator(max) { + let count = 0 + + return { + next() { + if (count < max) { + return { value: count++, done: false } + } else { + return { value: undefined, done: true } + } + } + } +} + +const counter = createCounterIterator(3) + +console.log(counter.next()) // { value: 0, done: false } +console.log(counter.next()) // { value: 1, done: false } +console.log(counter.next()) // { value: 2, done: false } +console.log(counter.next()) // { value: undefined, done: true } +``` + +### Why Iterators? + +Why not just use an array? Two reasons: + +1. **Lazy evaluation** — Values are computed only when you ask for them, not upfront +2. **Memory efficiency** — You don't need to hold the entire sequence in memory + +Say you need to process a million records. With an array, you'd load all million into memory. With an iterator, you process one at a time — memory stays flat. + +### Built-in Iterables + +Many JavaScript built-ins are already **iterable** (they have iterators built in): + +| Type | Example | What it iterates over | +|------|---------|----------------------| +| **Array** | `[1, 2, 3]` | Each element | +| **String** | `"hello"` | Each character | +| **Map** | `new Map([['a', 1]])` | Each `[key, value]` pair | +| **Set** | `new Set([1, 2, 3])` | Each unique value | +| **arguments** | `arguments` object | Each argument passed to a function | +| **NodeList** | `document.querySelectorAll('div')` | Each DOM node | + +You can access their iterator using `Symbol.iterator`: + +```javascript +const arr = [10, 20, 30] +const iterator = arr[Symbol.iterator]() + +console.log(iterator.next()) // { value: 10, done: false } +console.log(iterator.next()) // { value: 20, done: false } +console.log(iterator.next()) // { value: 30, done: false } +console.log(iterator.next()) // { value: undefined, done: true } +``` + +<Note> +**`for...of` uses iterators under the hood.** When you write `for (const item of array)`, JavaScript is actually calling the iterator's `.next()` method repeatedly until `done` is `true`. +</Note> + +--- + +## The Vending Machine Analogy + +Generators click when you have the right mental picture. Think of them like a **vending machine**: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ GENERATOR AS A VENDING MACHINE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ YOU VENDING MACHINE │ +│ (caller) (generator) │ +│ │ +│ ┌─────────┐ ┌─────────────────┐ │ +│ │ │ │ ┌───────────┐ │ │ +│ │ "I'll │ ──── Press button ─────────► │ │ Snack A │ │ │ +│ │ have │ (call .next()) │ ├───────────┤ │ │ +│ │ one" │ │ │ Snack B │ │ │ +│ │ │ ◄─── Dispense one item ───── │ ├───────────┤ │ │ +│ │ │ (yield value) │ │ Snack C │ │ │ +│ │ │ │ └───────────┘ │ │ +│ │ │ * Machine PAUSES * │ │ │ +│ │ │ * Waits for next * │ [ PAUSED ] │ │ +│ │ │ * button press * │ │ │ +│ └─────────┘ └─────────────────┘ │ +│ │ +│ KEY INSIGHT: The machine remembers where it stopped! │ +│ When you press the button again, it gives you the NEXT item, │ +│ not the first one again. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Here's how this maps to generator concepts: + +| Vending Machine | Generator | +|-----------------|-----------| +| Press the button | Call `.next()` | +| Machine dispenses one item | `yield` returns a value | +| Machine pauses, waits | Generator pauses at `yield` | +| Press button again | Call `.next()` again | +| Machine remembers position | Generator remembers its state | +| Machine is empty | `done: true` | + +A generator works the same way — one value at a time, pausing between each. + +--- + +## What is a Generator? + +A **[generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)** is a function that can stop mid-execution, hand you a value, and pick up where it left off later. You create one using `function*` (note the asterisk) and pause it with the `yield` keyword. + +```javascript +// The asterisk (*) makes this a generator function +function* myGenerator() { + console.log('Starting...') + yield 'First value' + + console.log('Resuming...') + yield 'Second value' + + console.log('Finishing...') + return 'Done!' +} +``` + +When you call a generator function, the code inside doesn't run yet — you just get back a **generator object** (which is an iterator): + +```javascript +const gen = myGenerator() // Nothing logs yet! + +console.log(gen) // Object [Generator] {} +``` + +The code only runs when you call `.next()`: + +```javascript +const gen = myGenerator() + +// First .next() — runs until first yield +console.log(gen.next()) +// Logs: "Starting..." +// Returns: { value: 'First value', done: false } + +// Second .next() — resumes and runs until second yield +console.log(gen.next()) +// Logs: "Resuming..." +// Returns: { value: 'Second value', done: false } + +// Third .next() — resumes and runs to the end +console.log(gen.next()) +// Logs: "Finishing..." +// Returns: { value: 'Done!', done: true } + +// Fourth .next() — generator is exhausted +console.log(gen.next()) +// Returns: { value: undefined, done: true } +``` + +### Generators are Iterators + +Because generator objects follow the iterator protocol, you can use them with `for...of`: + +```javascript +function* colors() { + yield 'red' + yield 'green' + yield 'blue' +} + +for (const color of colors()) { + console.log(color) +} +// Output: +// red +// green +// blue +``` + +You can also spread them into arrays: + +```javascript +function* numbers() { + yield 1 + yield 2 + yield 3 +} + +const arr = [...numbers()] +console.log(arr) // [1, 2, 3] +``` + +<CardGroup cols={2}> + <Card title="Generator — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"> + Official MDN documentation for Generator objects + </Card> + <Card title="function* — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*"> + Documentation for the generator function syntax + </Card> +</CardGroup> + +--- + +## The `yield` Keyword Deep Dive + +`yield` is what makes generators tick. It pauses the function and sends a value back to the caller. When you call `.next()` again, execution picks up right after the `yield`. + +### Basic `yield` + +```javascript +function* countdown() { + yield 3 + yield 2 + yield 1 + yield 'Liftoff!' +} + +const rocket = countdown() + +console.log(rocket.next().value) // 3 +console.log(rocket.next().value) // 2 +console.log(rocket.next().value) // 1 +console.log(rocket.next().value) // "Liftoff!" +``` + +### `yield` vs `return` + +Both `yield` and `return` can return values, but they behave very differently: + +| `yield` | `return` | +|---------|----------| +| Pauses the generator | Ends the generator | +| `done: false` | `done: true` | +| Can have multiple | Only one matters | +| Value accessible in `for...of` | Value NOT accessible in `for...of` | + +```javascript +function* example() { + yield 'A' // Pauses, done: false + yield 'B' // Pauses, done: false + return 'C' // Ends, done: true +} + +// With for...of — return value is ignored! +for (const val of example()) { + console.log(val) +} +// Output: A, B (no C!) + +// With .next() — you can see the return value +const gen = example() +console.log(gen.next()) // { value: 'A', done: false } +console.log(gen.next()) // { value: 'B', done: false } +console.log(gen.next()) // { value: 'C', done: true } +``` + +<Warning> +**Common gotcha:** The value from `return` is not included when iterating with `for...of`, spread syntax, or `Array.from()`. Use `yield` for all values you want to iterate over. +</Warning> + +### `yield*` — Delegating to Other Iterables + +When you want to pass through all values from another iterable, use `yield*`: + +```javascript +function* inner() { + yield 'a' + yield 'b' +} + +function* outer() { + yield 1 + yield* inner() // Delegates to inner generator + yield 2 +} + +console.log([...outer()]) // [1, 'a', 'b', 2] +``` + +`yield*` shines when flattening nested structures: + +```javascript +function* flatten(arr) { + for (const item of arr) { + if (Array.isArray(item)) { + yield* flatten(item) // Recursively delegate + } else { + yield item + } + } +} + +const nested = [1, [2, 3, [4, 5]], 6] +console.log([...flatten(nested)]) // [1, 2, 3, 4, 5, 6] +``` + +### Passing Values INTO Generators + +You can also send values *into* a generator by passing them to `.next(value)`. The value becomes the result of the `yield` expression inside the generator: + +```javascript +function* conversation() { + const name = yield 'What is your name?' + const color = yield `Hello, ${name}! What's your favorite color?` + yield `${color} is a great color, ${name}!` +} + +const chat = conversation() + +// First .next() — no value needed, just starts the generator +console.log(chat.next().value) +// "What is your name?" + +// Second .next() — pass in the answer +console.log(chat.next('Alice').value) +// "Hello, Alice! What's your favorite color?" + +// Third .next() — pass in another answer +console.log(chat.next('Blue').value) +// "Blue is a great color, Alice!" +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DATA FLOW WITH yield │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ CALLER GENERATOR │ +│ │ +│ .next() ─────────────────────► starts execution │ +│ ◄───────────────────── yield 'question' │ +│ │ +│ .next('Alice') ─────────────────────► const name = 'Alice' │ +│ ◄───────────────────── yield 'Hello Alice' │ +│ │ +│ .next('Blue') ─────────────────────► const color = 'Blue' │ +│ ◄───────────────────── yield 'Blue is great' │ +│ │ +│ The value passed to .next() becomes the RESULT of the yield │ +│ expression inside the generator. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Note> +**Why no value in the first `.next()`?** The first call starts the generator and runs until the first `yield`. There's no `yield` waiting to receive a value yet, so anything you pass gets ignored. +</Note> + +--- + +## The Iteration Protocol (`Symbol.iterator`) + +Now for the fun part — making your own objects work with `for...of`. An object is **iterable** if it has a `[Symbol.iterator]` method that returns an iterator. + +### Making a Custom Object Iterable + +```javascript +const myCollection = { + items: ['apple', 'banana', 'cherry'], + + // This makes the object iterable + [Symbol.iterator]() { + let index = 0 + const items = this.items + + return { + next() { + if (index < items.length) { + return { value: items[index++], done: false } + } else { + return { value: undefined, done: true } + } + } + } + } +} + +// Now we can use for...of! +for (const item of myCollection) { + console.log(item) +} +// Output: apple, banana, cherry + +// And spread syntax! +console.log([...myCollection]) // ['apple', 'banana', 'cherry'] +``` + +### Using Generators to Simplify Iterators + +All that manual iterator code? Generators cut it down to almost nothing: + +```javascript +const myCollection = { + items: ['apple', 'banana', 'cherry'], + + // Generator as the Symbol.iterator method + *[Symbol.iterator]() { + for (const item of this.items) { + yield item + } + } +} + +for (const item of myCollection) { + console.log(item) +} +// Output: apple, banana, cherry +``` + +### Example: Creating an Iterable Range + +Here's a `Range` class you can loop over with `for...of`: + +```javascript +class Range { + constructor(start, end, step = 1) { + this.start = start + this.end = end + this.step = step + } + + // Generator makes this easy! + *[Symbol.iterator]() { + for (let i = this.start; i <= this.end; i += this.step) { + yield i + } + } +} + +const oneToFive = new Range(1, 5) +console.log([...oneToFive]) // [1, 2, 3, 4, 5] + +const evens = new Range(0, 10, 2) +console.log([...evens]) // [0, 2, 4, 6, 8, 10] + +// Works with for...of +for (const n of new Range(1, 3)) { + console.log(n) // 1, 2, 3 +} +``` + +### What `for...of` Really Does + +When you write a `for...of` loop, JavaScript does this behind the scenes: + +```javascript +// This: +for (const item of iterable) { + console.log(item) +} + +// Is equivalent to this: +const iterator = iterable[Symbol.iterator]() +let result = iterator.next() + +while (!result.done) { + const item = result.value + console.log(item) + result = iterator.next() +} +``` + +<Tip> +**When to make something iterable:** If your object represents a collection or sequence of values, making it iterable allows it to work with `for...of`, spread syntax, `Array.from()`, destructuring, and more. +</Tip> + +--- + +## Lazy Evaluation & Infinite Sequences + +The killer feature of generators is **lazy evaluation** — values are computed only when you ask for them, not ahead of time. + +### Memory Efficiency + +Compare these two approaches for creating a range of numbers: + +```javascript +// Eager evaluation — creates entire array in memory +function rangeArray(start, end) { + const result = [] + for (let i = start; i <= end; i++) { + result.push(i) + } + return result +} + +// Lazy evaluation — computes values on demand +function* rangeGenerator(start, end) { + for (let i = start; i <= end; i++) { + yield i + } +} + +// For small ranges, both work fine +console.log(rangeArray(1, 5)) // [1, 2, 3, 4, 5] +console.log([...rangeGenerator(1, 5)]) // [1, 2, 3, 4, 5] + +// For large ranges, generators shine +// rangeArray(1, 1000000) — Creates array of 1 million numbers! +// rangeGenerator(1, 1000000) — Creates nothing until you iterate +``` + +### Infinite Sequences + +Because generators are lazy, you can create **infinite sequences** — something impossible with arrays: + +```javascript +// Infinite sequence of natural numbers +function* naturalNumbers() { + let n = 1 + while (true) { // Infinite loop! + yield n++ + } +} + +// This would crash with an array, but generators are lazy +const numbers = naturalNumbers() + +console.log(numbers.next().value) // 1 +console.log(numbers.next().value) // 2 +console.log(numbers.next().value) // 3 +// We can keep going forever... +``` + +### Fibonacci Sequence + +A classic example — the infinite Fibonacci sequence: + +```javascript +function* fibonacci() { + let prev = 0 + let curr = 1 + + while (true) { + yield curr + const next = prev + curr + prev = curr + curr = next + } +} + +const fib = fibonacci() + +console.log(fib.next().value) // 1 +console.log(fib.next().value) // 1 +console.log(fib.next().value) // 2 +console.log(fib.next().value) // 3 +console.log(fib.next().value) // 5 +console.log(fib.next().value) // 8 +``` + +### Taking N Items from an Infinite Generator + +You'll often want to take a limited number of items from an infinite generator: + +```javascript +// Helper function to take N items from any iterable +function* take(n, iterable) { + let count = 0 + for (const item of iterable) { + if (count >= n) return + yield item + count++ + } +} + +// Get first 10 Fibonacci numbers +const firstTenFib = [...take(10, fibonacci())] +console.log(firstTenFib) // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + +// Get first 5 natural numbers +const firstFive = [...take(5, naturalNumbers())] +console.log(firstFive) // [1, 2, 3, 4, 5] +``` + +<Warning> +**Be careful with infinite generators!** Never use `[...infiniteGenerator()]` or `for...of` on an infinite generator without a break condition — your program will hang trying to iterate forever. + +```javascript +// ❌ DANGER — This will hang/crash! +const all = [...naturalNumbers()] // Trying to collect infinite items + +// ✓ SAFE — Use take() or break early +const some = [...take(100, naturalNumbers())] +``` +</Warning> + +--- + +## Common Patterns + +Here are some patterns that make generators worth knowing. + +### Pattern 1: Unique ID Generator + +Generate unique IDs without tracking global state: + +```javascript +function* createIdGenerator(prefix = 'id') { + let id = 1 + while (true) { + yield `${prefix}_${id++}` + } +} + +const userIds = createIdGenerator('user') +const orderIds = createIdGenerator('order') + +console.log(userIds.next().value) // "user_1" +console.log(userIds.next().value) // "user_2" +console.log(orderIds.next().value) // "order_1" +console.log(userIds.next().value) // "user_3" +console.log(orderIds.next().value) // "order_2" +``` + +### Pattern 2: Pagination / Chunking Data + +Process large datasets in manageable chunks: + +```javascript +function* chunk(array, size) { + for (let i = 0; i < array.length; i += size) { + yield array.slice(i, i + size) + } +} + +const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +for (const batch of chunk(data, 3)) { + console.log('Processing batch:', batch) +} +// Output: +// Processing batch: [1, 2, 3] +// Processing batch: [4, 5, 6] +// Processing batch: [7, 8, 9] +// Processing batch: [10] +``` + +This is great for batch processing, API rate limiting, or breaking up heavy computations: + +```javascript +function* processInBatches(items, batchSize) { + for (const batch of chunk(items, batchSize)) { + // Process each batch + const results = batch.map(item => heavyComputation(item)) + yield results + } +} + +// Process 1000 items in batches of 100 +const allItems = new Array(1000).fill(null).map((_, i) => i) + +for (const batchResults of processInBatches(allItems, 100)) { + console.log(`Processed ${batchResults.length} items`) + // Could add delay here to avoid blocking the main thread +} +``` + +### Pattern 3: Filtering and Transforming Data + +Create composable data pipelines: + +```javascript +function* filter(iterable, predicate) { + for (const item of iterable) { + if (predicate(item)) { + yield item + } + } +} + +function* map(iterable, transform) { + for (const item of iterable) { + yield transform(item) + } +} + +// Compose them together +function* range(start, end) { + for (let i = start; i <= end; i++) { + yield i + } +} + +// Pipeline: numbers 1-10 → filter evens → double them +const result = map( + filter(range(1, 10), n => n % 2 === 0), + n => n * 2 +) + +console.log([...result]) // [4, 8, 12, 16, 20] +``` + +### Pattern 4: Simple State Machine + +Generators naturally model state machines because they remember their position: + +```javascript +function* trafficLight() { + while (true) { + yield 'green' + yield 'yellow' + yield 'red' + } +} + +const light = trafficLight() + +console.log(light.next().value) // "green" +console.log(light.next().value) // "yellow" +console.log(light.next().value) // "red" +console.log(light.next().value) // "green" (cycles back) +console.log(light.next().value) // "yellow" +``` + +A more complex example with different wait times: + +```javascript +function* trafficLightWithDurations() { + while (true) { + yield { color: 'green', duration: 30000 } // 30 seconds + yield { color: 'yellow', duration: 5000 } // 5 seconds + yield { color: 'red', duration: 25000 } // 25 seconds + } +} + +const light = trafficLightWithDurations() + +function changeLight() { + const { color, duration } = light.next().value + console.log(`Light is now ${color} for ${duration / 1000} seconds`) + setTimeout(changeLight, duration) +} + +// changeLight() // Uncomment to run +``` + +### Pattern 5: Tree Traversal + +Generators are excellent for traversing tree structures: + +```javascript +function* traverseTree(node) { + yield node.value + + if (node.children) { + for (const child of node.children) { + yield* traverseTree(child) // Recursive delegation + } + } +} + +const tree = { + value: 'root', + children: [ + { + value: 'child1', + children: [ + { value: 'grandchild1' }, + { value: 'grandchild2' } + ] + }, + { + value: 'child2', + children: [ + { value: 'grandchild3' } + ] + } + ] +} + +console.log([...traverseTree(tree)]) +// ['root', 'child1', 'grandchild1', 'grandchild2', 'child2', 'grandchild3'] +``` + +--- + +## Async Generators & `for await...of` + +What about yielding values from async operations — API calls, file reads, that kind of thing? That's what **async generators** are for. + +### The Problem with Regular Generators + +Regular generators are synchronous. If you try to yield a Promise, you get the Promise object itself, not its resolved value: + +```javascript +function* fetchUsers() { + yield fetch('/api/user/1').then(r => r.json()) + yield fetch('/api/user/2').then(r => r.json()) +} + +const gen = fetchUsers() +console.log(gen.next().value) // Promise { <pending> } — not the user! +``` + +### Async Generator Syntax + +An **[async generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*)** combines `async` functions with generators. You can `await` inside them, and you iterate with `for await...of`: + +```javascript +async function* fetchUsersAsync() { + const user1 = await fetch('/api/user/1').then(r => r.json()) + yield user1 + + const user2 = await fetch('/api/user/2').then(r => r.json()) + yield user2 +} + +// Use for await...of to consume +async function displayUsers() { + for await (const user of fetchUsersAsync()) { + console.log(user.name) + } +} +``` + +### Practical Example: Paginated API + +Fetch all pages of data from a paginated API: + +```javascript +async function* fetchAllPages(baseUrl) { + let page = 1 + let hasMore = true + + while (hasMore) { + const response = await fetch(`${baseUrl}?page=${page}`) + const data = await response.json() + + yield data.items // Yield this page's items + + hasMore = data.hasNextPage + page++ + } +} + +// Process all pages +async function processAllUsers() { + for await (const pageOfUsers of fetchAllPages('/api/users')) { + console.log(`Processing ${pageOfUsers.length} users...`) + + for (const user of pageOfUsers) { + // Process each user + await saveToDatabase(user) + } + } +} +``` + +### Async Generator vs Promise.all + +When do you reach for an async generator over `Promise.all`? + +```javascript +// Promise.all — All requests in parallel, wait for ALL to complete +async function fetchAllAtOnce(userIds) { + const users = await Promise.all( + userIds.map(id => fetch(`/api/user/${id}`).then(r => r.json())) + ) + return users // Returns all users at once +} + +// Async generator — Process as each completes +async function* fetchOneByOne(userIds) { + for (const id of userIds) { + const user = await fetch(`/api/user/${id}`).then(r => r.json()) + yield user // Yield each user as it's fetched + } +} +``` + +| Approach | Best for | +|----------|----------| +| `Promise.all` | When you need all results before proceeding | +| Async generator | When you want to process results as they arrive | +| Async generator | When fetching everything at once would be too memory-intensive | +| Async generator | When you might want to stop early | + +### Reading Lines from a Stream + +Here's a real pattern for processing a stream line by line: + +```javascript +async function* readLines(reader) { + const decoder = new TextDecoder() + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + + if (done) { + if (buffer) yield buffer // Yield any remaining content + return + } + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split('\n') + buffer = lines.pop() // Keep incomplete line in buffer + + for (const line of lines) { + yield line + } + } +} + +// Usage with fetch +async function processLogFile(url) { + const response = await fetch(url) + const reader = response.body.getReader() + + for await (const line of readLines(reader)) { + console.log('Log entry:', line) + } +} +``` + +<CardGroup cols={2}> + <Card title="async function* — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*"> + Documentation for async generator functions + </Card> + <Card title="for await...of — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of"> + Documentation for async iteration + </Card> +</CardGroup> + +--- + +## Common Mistakes + +<AccordionGroup> + <Accordion title="Mistake 1: Forgetting the asterisk in function*"> + ```javascript + // ❌ WRONG — This is a regular function, not a generator + function myGenerator() { + yield 1 // SyntaxError: Unexpected number + } + + // ✓ CORRECT — Note the asterisk + function* myGenerator() { + yield 1 + } + ``` + + The asterisk can go next to `function` or next to the name — both work: + + ```javascript + function* foo() {} // ✓ + function *foo() {} // ✓ + function * foo() {} // ✓ + ``` + </Accordion> + + <Accordion title="Mistake 2: Expecting generator to run immediately"> + ```javascript + // ❌ WRONG — Nothing happens when you call a generator function + function* greet() { + console.log('Hello!') + yield 'Hi' + } + + greet() // Nothing logged! Returns generator object + + // ✓ CORRECT — You must call .next() or iterate + const gen = greet() + gen.next() // NOW it logs "Hello!" + + // Or use for...of + for (const val of greet()) { + console.log(val) + } + ``` + </Accordion> + + <Accordion title="Mistake 3: Using return instead of yield for iteration values"> + ```javascript + // ❌ WRONG — return value won't appear in for...of + function* letters() { + yield 'a' + yield 'b' + return 'c' // This won't be iterated! + } + + console.log([...letters()]) // ['a', 'b'] — no 'c'! + + // ✓ CORRECT — Use yield for all iteration values + function* letters() { + yield 'a' + yield 'b' + yield 'c' + } + + console.log([...letters()]) // ['a', 'b', 'c'] + ``` + </Accordion> + + <Accordion title="Mistake 4: Reusing an exhausted generator"> + ```javascript + // ❌ WRONG — Generators can only be iterated once + function* nums() { + yield 1 + yield 2 + } + + const gen = nums() + console.log([...gen]) // [1, 2] + console.log([...gen]) // [] — generator is exhausted! + + // ✓ CORRECT — Create a new generator each time + console.log([...nums()]) // [1, 2] + console.log([...nums()]) // [1, 2] + ``` + </Accordion> + + <Accordion title="Mistake 5: Infinite loop without break condition"> + ```javascript + // ❌ DANGER — This will hang your program + function* forever() { + let i = 0 + while (true) { + yield i++ + } + } + + const all = [...forever()] // Infinite loop trying to collect all values! + + // ✓ SAFE — Use take() or break early + function* take(n, gen) { + let count = 0 + for (const val of gen) { + if (count++ >= n) return + yield val + } + } + + const firstHundred = [...take(100, forever())] // Safe! + ``` + </Accordion> + + <Accordion title="Mistake 6: Using generators when arrays would be simpler"> + ```javascript + // ❌ OVERKILL — If you're just returning a fixed list, use an array + function* getDaysOfWeek() { + yield 'Monday' + yield 'Tuesday' + yield 'Wednesday' + yield 'Thursday' + yield 'Friday' + yield 'Saturday' + yield 'Sunday' + } + + // ✓ SIMPLER — Just use an array + const daysOfWeek = [ + 'Monday', 'Tuesday', 'Wednesday', 'Thursday', + 'Friday', 'Saturday', 'Sunday' + ] + ``` + + **Use generators when:** + - Values are computed on-demand (lazy) + - Sequence is infinite or very large + - You need to pause/resume execution + - Values come from async operations + + **Use arrays when:** + - You have a fixed, known set of values + - Values are already computed + - You need random access (`array[5]`) + </Accordion> +</AccordionGroup> + +--- + +## Key Takeaways + +<Info> +**The short version:** + +1. **Iterators** are objects with a `.next()` method that returns `{ value, done }` + +2. **Generators** are functions that pause at `yield` and resume at `.next()` + +3. **Don't forget the asterisk** — it's `function*`, not `function` + +4. **`yield` pauses, `return` ends** — and `return` values don't show up in `for...of` + +5. **`yield*` passes through** all values from another iterable + +6. **Generators are lazy** — nothing runs until you ask for it + +7. **Infinite sequences work** because generators compute on-demand + +8. **`Symbol.iterator`** is how you make objects work with `for...of` + +9. **Async generators** (`async function*`) let you `await` inside and iterate with `for await...of` + +10. **Generators are single-use** — once done, you need a fresh one +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between yield and return in a generator?"> + **Answer:** + + - `yield` **pauses** the generator and returns `{ value, done: false }`. The generator can resume from where it paused. + - `return` **ends** the generator and returns `{ value, done: true }`. The generator cannot resume. + + Important: Values from `return` are NOT included when using `for...of`, spread syntax, or `Array.from()`. + + ```javascript + function* example() { + yield 'A' // Included in iteration + yield 'B' // Included in iteration + return 'C' // NOT included in for...of! + } + + console.log([...example()]) // ['A', 'B'] + ``` + </Accordion> + + <Accordion title="Question 2: How do you make a custom object iterable?"> + **Answer:** + + Add a `[Symbol.iterator]` method that returns an iterator (an object with a `.next()` method): + + ```javascript + const myObject = { + data: [1, 2, 3], + + // Method 1: Return an iterator object + [Symbol.iterator]() { + let index = 0 + const data = this.data + return { + next() { + if (index < data.length) { + return { value: data[index++], done: false } + } + return { done: true } + } + } + } + } + + // Method 2: Use a generator (simpler!) + const myObject2 = { + data: [1, 2, 3], + + *[Symbol.iterator]() { + yield* this.data + } + } + ``` + </Accordion> + + <Accordion title="Question 3: What will this code output?"> + ```javascript + function* gen() { + console.log('A') + yield 1 + console.log('B') + yield 2 + console.log('C') + } + + const g = gen() + console.log('Start') + console.log(g.next().value) + console.log('Middle') + console.log(g.next().value) + ``` + + **Answer:** + + ``` + Start + A + 1 + Middle + B + 2 + ``` + + **Explanation:** + 1. `gen()` creates the generator but doesn't run any code + 2. `'Start'` logs + 3. First `g.next()` runs until first `yield` — logs `'A'`, returns `{ value: 1, done: false }` + 4. We log the value `1` + 5. `'Middle'` logs + 6. Second `g.next()` resumes and runs until second `yield` — logs `'B'`, returns `{ value: 2, done: false }` + 7. We log the value `2` + 8. `'C'` never logs because we didn't call `g.next()` a third time + </Accordion> + + <Accordion title="Question 4: How can you pass values INTO a generator?"> + **Answer:** + + Pass values as arguments to `.next(value)`. The value becomes the result of the `yield` expression: + + ```javascript + function* adder() { + const a = yield 'Enter first number' + const b = yield 'Enter second number' + yield `Sum: ${a + b}` + } + + const gen = adder() + console.log(gen.next().value) // "Enter first number" + console.log(gen.next(10).value) // "Enter second number" (a = 10) + console.log(gen.next(5).value) // "Sum: 15" (b = 5) + ``` + + Note: The first `.next()` starts the generator — any value passed to it is ignored because there's no `yield` waiting to receive it yet. + </Accordion> + + <Accordion title="Question 5: When would you use an async generator?"> + **Answer:** + + Use async generators when you need to yield values from asynchronous operations: + + - **Paginated APIs** — Fetch and yield page by page + - **Streaming data** — Process chunks as they arrive + - **Database cursors** — Iterate through large result sets + - **File processing** — Read and yield lines from large files + + ```javascript + async function* fetchPages(url) { + let page = 1 + while (true) { + const response = await fetch(`${url}?page=${page}`) + const data = await response.json() + + if (data.items.length === 0) return + + yield data.items + page++ + } + } + + // Consume with for await...of + for await (const items of fetchPages('/api/products')) { + processItems(items) + } + ``` + </Accordion> + + <Accordion title="Question 6: Why can't you use [...infiniteGenerator()]?"> + **Answer:** + + Spread syntax (`...`) tries to collect ALL values into an array. With an infinite generator, this means infinite iteration — your program will hang trying to collect infinite values. + + ```javascript + function* forever() { + let i = 0 + while (true) yield i++ + } + + // ❌ DANGER — Hangs forever! + const all = [...forever()] + + // ✓ SAFE — Limit how many you take + function* take(n, gen) { + let i = 0 + for (const val of gen) { + if (i++ >= n) return + yield val + } + } + + const first100 = [...take(100, forever())] + ``` + + Always use a limiting function like `take()`, or manually call `.next()` a specific number of times. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Higher-Order Functions" icon="layer-group" href="/concepts/higher-order-functions"> + Generators pair nicely with map, filter, and reduce patterns + </Card> + <Card title="Promises" icon="handshake" href="/concepts/promises"> + Async generators are built on Promises + </Card> + <Card title="async/await" icon="clock" href="/concepts/async-await"> + The other half of async generators — you'll use both together + </Card> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + How async generators fit into JavaScript's execution model + </Card> +</CardGroup> + +--- + ## Reference -<Card title="Generator — MDN web docs" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"> - MDN documentation for Generator objects -</Card> +<CardGroup cols={2}> + <Card title="Generator — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"> + The Generator object and its methods — `.next()`, `.return()`, `.throw()` + </Card> + <Card title="Iteration Protocols — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols"> + The spec for iterators and iterables. Good for understanding what's really going on. + </Card> + <Card title="function* — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*"> + Generator function syntax and behavior + </Card> + <Card title="yield — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield"> + Everything about the yield operator + </Card> +</CardGroup> ## Articles <CardGroup cols={2}> - <Card title="What are JavaScript Generators and how to use them" icon="newspaper" href="https://codeburst.io/what-are-javascript-generators-and-how-to-use-them-c6f2713fd12e"> - By Vladislav Stepanov + <Card title="The Basics of ES6 Generators" icon="newspaper" href="https://davidwalsh.name/es6-generators"> + Kyle Simpson (You Don't Know JS) breaks down how generators work under the hood. + </Card> + <Card title="Generators — JavaScript.info" icon="newspaper" href="https://javascript.info/generators"> + Interactive tutorial with runnable examples. Great for hands-on learning. </Card> - <Card title="Understanding Generators in ES6 JavaScript with Examples" icon="newspaper" href="https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5"> - By Arfat Salman + <Card title="Async Iterators and Generators — JavaScript.info" icon="newspaper" href="https://javascript.info/async-iterators-generators"> + Picks up where the sync guide leaves off — async generators and `for await...of`. + </Card> + <Card title="Iterators and Generators — MDN" icon="newspaper" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators"> + The official MDN walkthrough. Solid reference for both concepts. </Card> </CardGroup> -- [The Basics of ES6 Generators — Kyle Simpson](https://davidwalsh.name/es6-generators) -- [An Introduction to JavaScript Generators — Alice Kallaugher](https://dev.to/kallaugher/an-introduction-to-javascript-generators-1224) -- [Iterators and Generators — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) -- [A Simple Guide to ES6 Iterators in JavaScript with Examples — Brandon Morelli](https://codeburst.io/a-simple-guide-to-es6-iterators-in-javascript-with-examples-189d052c3d8e) -- [Generators — JavaScript.info](https://javascript.info/generators) - ## Videos <CardGroup cols={2}> - <Card title="JavaScript ES6 / ES2015 - Generators" icon="video" href="https://www.youtube.com/watch?v=dcP039DYzmE"> - By Traversy Media + <Card title="Generators in JavaScript — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=ategZqxHkz4"> + Mattias Petter Johansson makes generators fun. Seriously. </Card> - <Card title="JavaScript Generators" icon="video" href="https://www.youtube.com/watch?v=QO07THdLWQo"> - By LearnCode.academy + <Card title="JavaScript Iterators and Generators — Fireship" icon="video" href="https://www.youtube.com/watch?v=IJ6EgdiI_wU"> + The fast version. 100 seconds and you'll get the gist. + </Card> + <Card title="JavaScript ES6 Generators — Traversy Media" icon="video" href="https://www.youtube.com/watch?v=dcP039DYzmE"> + Brad Traversy's walkthrough. Great if you like to code along. </Card> </CardGroup> - -- [Generators in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=ategZqxHkz4) -- [JavaScript Iterators and Generators — Fireship](https://www.youtube.com/watch?v=IJ6EgdiI_wU) diff --git a/tests/functions-execution/generators-iterators/generators-iterators.test.js b/tests/functions-execution/generators-iterators/generators-iterators.test.js new file mode 100644 index 00000000..030dc66d --- /dev/null +++ b/tests/functions-execution/generators-iterators/generators-iterators.test.js @@ -0,0 +1,593 @@ +import { describe, it, expect } from 'vitest' + +describe('Generators & Iterators', () => { + describe('Basic Iterator Protocol', () => { + it('should follow the iterator protocol with { value, done }', () => { + function createIterator(arr) { + let index = 0 + return { + next() { + if (index < arr.length) { + return { value: arr[index++], done: false } + } + return { value: undefined, done: true } + } + } + } + + const iterator = createIterator([1, 2, 3]) + + expect(iterator.next()).toEqual({ value: 1, done: false }) + expect(iterator.next()).toEqual({ value: 2, done: false }) + expect(iterator.next()).toEqual({ value: 3, done: false }) + expect(iterator.next()).toEqual({ value: undefined, done: true }) + }) + + it('should allow accessing array iterator via Symbol.iterator', () => { + const arr = [10, 20, 30] + const iterator = arr[Symbol.iterator]() + + expect(iterator.next()).toEqual({ value: 10, done: false }) + expect(iterator.next()).toEqual({ value: 20, done: false }) + expect(iterator.next()).toEqual({ value: 30, done: false }) + expect(iterator.next()).toEqual({ value: undefined, done: true }) + }) + }) + + describe('Basic Generator Syntax', () => { + it('should create a generator function with function*', () => { + function* simpleGenerator() { + yield 1 + yield 2 + yield 3 + } + + const gen = simpleGenerator() + + expect(gen.next()).toEqual({ value: 1, done: false }) + expect(gen.next()).toEqual({ value: 2, done: false }) + expect(gen.next()).toEqual({ value: 3, done: false }) + expect(gen.next()).toEqual({ value: undefined, done: true }) + }) + + it('should not execute until .next() is called', () => { + let executed = false + + function* trackExecution() { + executed = true + yield 'done' + } + + const gen = trackExecution() + expect(executed).toBe(false) // Not executed yet! + + gen.next() + expect(executed).toBe(true) // Now it's executed + }) + + it('should work with for...of loops', () => { + function* colors() { + yield 'red' + yield 'green' + yield 'blue' + } + + const result = [] + for (const color of colors()) { + result.push(color) + } + + expect(result).toEqual(['red', 'green', 'blue']) + }) + + it('should work with spread operator', () => { + function* numbers() { + yield 1 + yield 2 + yield 3 + } + + expect([...numbers()]).toEqual([1, 2, 3]) + }) + }) + + describe('yield vs return', () => { + it('should pause execution at yield and allow resuming', () => { + const executionOrder = [] + + function* trackOrder() { + executionOrder.push('before first yield') + yield 'A' + executionOrder.push('after first yield') + yield 'B' + executionOrder.push('after second yield') + } + + const gen = trackOrder() + + expect(executionOrder).toEqual([]) + + gen.next() + expect(executionOrder).toEqual(['before first yield']) + + gen.next() + expect(executionOrder).toEqual(['before first yield', 'after first yield']) + + gen.next() + expect(executionOrder).toEqual([ + 'before first yield', + 'after first yield', + 'after second yield' + ]) + }) + + it('should mark done: true on return', () => { + function* withReturn() { + yield 'A' + return 'B' + yield 'C' // This never executes + } + + const gen = withReturn() + + expect(gen.next()).toEqual({ value: 'A', done: false }) + expect(gen.next()).toEqual({ value: 'B', done: true }) + expect(gen.next()).toEqual({ value: undefined, done: true }) + }) + + it('should NOT include return value in for...of', () => { + function* withReturn() { + yield 'A' + yield 'B' + return 'C' // Not included in iteration! + } + + expect([...withReturn()]).toEqual(['A', 'B']) // No 'C'! + }) + }) + + describe('yield* delegation', () => { + it('should delegate to another iterable', () => { + function* inner() { + yield 'a' + yield 'b' + } + + function* outer() { + yield 1 + yield* inner() + yield 2 + } + + expect([...outer()]).toEqual([1, 'a', 'b', 2]) + }) + + it('should delegate to arrays', () => { + function* withArray() { + yield 'start' + yield* [1, 2, 3] + yield 'end' + } + + expect([...withArray()]).toEqual(['start', 1, 2, 3, 'end']) + }) + + it('should flatten nested arrays recursively', () => { + function* flatten(arr) { + for (const item of arr) { + if (Array.isArray(item)) { + yield* flatten(item) + } else { + yield item + } + } + } + + const nested = [1, [2, 3, [4, 5]], 6] + expect([...flatten(nested)]).toEqual([1, 2, 3, 4, 5, 6]) + }) + }) + + describe('Passing values to generators', () => { + it('should receive values via .next(value)', () => { + function* adder() { + const a = yield 'Enter first number' + const b = yield 'Enter second number' + yield a + b + } + + const gen = adder() + + expect(gen.next().value).toBe('Enter first number') + expect(gen.next(10).value).toBe('Enter second number') + expect(gen.next(5).value).toBe(15) + }) + + it('should ignore value passed to first .next()', () => { + function* capture() { + const first = yield 'ready' + yield first + } + + const gen = capture() + + // First .next() value is ignored because no yield is waiting + gen.next('IGNORED') + expect(gen.next('captured').value).toBe('captured') + }) + }) + + describe('Symbol.iterator - Custom Iterables', () => { + it('should make object iterable with Symbol.iterator', () => { + const myCollection = { + items: ['apple', 'banana', 'cherry'], + [Symbol.iterator]() { + let index = 0 + const items = this.items + return { + next() { + if (index < items.length) { + return { value: items[index++], done: false } + } + return { value: undefined, done: true } + } + } + } + } + + expect([...myCollection]).toEqual(['apple', 'banana', 'cherry']) + }) + + it('should make object iterable with generator', () => { + const myCollection = { + items: [1, 2, 3], + *[Symbol.iterator]() { + yield* this.items + } + } + + const result = [] + for (const item of myCollection) { + result.push(item) + } + + expect(result).toEqual([1, 2, 3]) + }) + + it('should create an iterable Range class', () => { + class Range { + constructor(start, end, step = 1) { + this.start = start + this.end = end + this.step = step + } + + *[Symbol.iterator]() { + for (let i = this.start; i <= this.end; i += this.step) { + yield i + } + } + } + + expect([...new Range(1, 5)]).toEqual([1, 2, 3, 4, 5]) + expect([...new Range(0, 10, 2)]).toEqual([0, 2, 4, 6, 8, 10]) + expect([...new Range(5, 1)]).toEqual([]) // Empty when start > end + }) + }) + + describe('Lazy Evaluation', () => { + it('should compute values on demand', () => { + let computeCount = 0 + + function* lazyComputation() { + while (true) { + computeCount++ + yield computeCount + } + } + + const gen = lazyComputation() + + expect(computeCount).toBe(0) // Nothing computed yet + + gen.next() + expect(computeCount).toBe(1) + + gen.next() + expect(computeCount).toBe(2) + + // Only computed twice, not infinitely + }) + + it('should handle infinite sequences safely with take()', () => { + function* naturalNumbers() { + let n = 1 + while (true) { + yield n++ + } + } + + function* take(n, iterable) { + let count = 0 + for (const item of iterable) { + if (count >= n) return + yield item + count++ + } + } + + expect([...take(5, naturalNumbers())]).toEqual([1, 2, 3, 4, 5]) + }) + + it('should generate Fibonacci sequence lazily', () => { + function* fibonacci() { + let prev = 0 + let curr = 1 + + while (true) { + yield curr + const next = prev + curr + prev = curr + curr = next + } + } + + function* take(n, iterable) { + let count = 0 + for (const item of iterable) { + if (count >= n) return + yield item + count++ + } + } + + expect([...take(10, fibonacci())]).toEqual([ + 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 + ]) + }) + }) + + describe('Common Patterns', () => { + it('should create a unique ID generator', () => { + function* createIdGenerator(prefix = 'id') { + let id = 1 + while (true) { + yield `${prefix}_${id++}` + } + } + + const userIds = createIdGenerator('user') + const orderIds = createIdGenerator('order') + + expect(userIds.next().value).toBe('user_1') + expect(userIds.next().value).toBe('user_2') + expect(orderIds.next().value).toBe('order_1') + expect(userIds.next().value).toBe('user_3') + expect(orderIds.next().value).toBe('order_2') + }) + + it('should chunk arrays into batches', () => { + function* chunk(array, size) { + for (let i = 0; i < array.length; i += size) { + yield array.slice(i, i + size) + } + } + + const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + expect([...chunk(data, 3)]).toEqual([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10] + ]) + }) + + it('should implement filter and map with generators', () => { + function* filter(iterable, predicate) { + for (const item of iterable) { + if (predicate(item)) { + yield item + } + } + } + + function* map(iterable, transform) { + for (const item of iterable) { + yield transform(item) + } + } + + function* range(start, end) { + for (let i = start; i <= end; i++) { + yield i + } + } + + // Pipeline: 1-10 -> filter evens -> double them + const result = map( + filter(range(1, 10), n => n % 2 === 0), + n => n * 2 + ) + + expect([...result]).toEqual([4, 8, 12, 16, 20]) + }) + + it('should implement a simple state machine', () => { + function* trafficLight() { + while (true) { + yield 'green' + yield 'yellow' + yield 'red' + } + } + + const light = trafficLight() + + expect(light.next().value).toBe('green') + expect(light.next().value).toBe('yellow') + expect(light.next().value).toBe('red') + expect(light.next().value).toBe('green') // Cycles back + expect(light.next().value).toBe('yellow') + }) + + it('should traverse a tree structure', () => { + function* traverseTree(node) { + yield node.value + + if (node.children) { + for (const child of node.children) { + yield* traverseTree(child) + } + } + } + + const tree = { + value: 'root', + children: [ + { + value: 'child1', + children: [{ value: 'grandchild1' }, { value: 'grandchild2' }] + }, + { + value: 'child2', + children: [{ value: 'grandchild3' }] + } + ] + } + + expect([...traverseTree(tree)]).toEqual([ + 'root', + 'child1', + 'grandchild1', + 'grandchild2', + 'child2', + 'grandchild3' + ]) + }) + }) + + describe('Async Generators', () => { + it('should create async generators with async function*', async () => { + async function* asyncNumbers() { + yield await Promise.resolve(1) + yield await Promise.resolve(2) + yield await Promise.resolve(3) + } + + const results = [] + for await (const num of asyncNumbers()) { + results.push(num) + } + + expect(results).toEqual([1, 2, 3]) + }) + + it('should handle delayed async values', async () => { + const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + + async function* delayedNumbers() { + await delay(10) + yield 1 + await delay(10) + yield 2 + await delay(10) + yield 3 + } + + const results = [] + for await (const num of delayedNumbers()) { + results.push(num) + } + + expect(results).toEqual([1, 2, 3]) + }) + + it('should simulate paginated API fetching', async () => { + // Mock paginated data + const mockPages = [ + { items: ['a', 'b'], hasNextPage: true }, + { items: ['c', 'd'], hasNextPage: true }, + { items: ['e'], hasNextPage: false } + ] + + async function* fetchAllPages() { + let page = 0 + let hasMore = true + + while (hasMore) { + // Simulate API call + const data = await Promise.resolve(mockPages[page]) + yield data.items + hasMore = data.hasNextPage + page++ + } + } + + const allItems = [] + for await (const pageItems of fetchAllPages()) { + allItems.push(...pageItems) + } + + expect(allItems).toEqual(['a', 'b', 'c', 'd', 'e']) + }) + }) + + describe('Generator Exhaustion', () => { + it('should only be iterable once', () => { + function* nums() { + yield 1 + yield 2 + } + + const gen = nums() + + expect([...gen]).toEqual([1, 2]) + expect([...gen]).toEqual([]) // Exhausted! + }) + + it('should create fresh generator for each iteration', () => { + function* nums() { + yield 1 + yield 2 + } + + expect([...nums()]).toEqual([1, 2]) + expect([...nums()]).toEqual([1, 2]) // Fresh generator each time + }) + }) + + describe('Error Handling', () => { + it('should allow throwing errors into generator with .throw()', () => { + function* gen() { + try { + yield 'A' + yield 'B' + } catch (e) { + yield `Error: ${e.message}` + } + } + + const g = gen() + + expect(g.next().value).toBe('A') + expect(g.throw(new Error('Something went wrong')).value).toBe( + 'Error: Something went wrong' + ) + }) + + it('should allow early termination with .return()', () => { + function* gen() { + yield 1 + yield 2 + yield 3 + } + + const g = gen() + + expect(g.next().value).toBe(1) + expect(g.return('early exit')).toEqual({ value: 'early exit', done: true }) + expect(g.next()).toEqual({ value: undefined, done: true }) + }) + }) +}) From d24b624a2fa519d5f3c2940d8f8ed8b7846a894d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:07:45 -0300 Subject: [PATCH 070/128] docs: improve promises page copywriting and resource descriptions - Remove AI filler words: 'basically' (2x), 'really' (2x) - Change 'essential points' to 'key things to remember' - Replace body text em dashes with periods/commas/colons (17 fixes) - Rewrite 6 resource descriptions to be specific 2-sentence format - All 52 remaining em dashes are in acceptable contexts (Key Takeaways, card titles, structured lists, H3 headings) --- docs/concepts/promises.mdx | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/concepts/promises.mdx b/docs/concepts/promises.mdx index b9795629..15e7d51f 100644 --- a/docs/concepts/promises.mdx +++ b/docs/concepts/promises.mdx @@ -23,7 +23,7 @@ getUser(userId) .then(comments => console.log(comments)) ``` -A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** is an object representing the eventual completion or failure of an asynchronous operation. It's basically a placeholder for a value that will show up later — like an order ticket at a restaurant that you'll trade for food when it's ready. +A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** is an object representing the eventual completion or failure of an asynchronous operation. It's a placeholder for a value that will show up later. Think of it like an order ticket at a restaurant that you'll trade for food when it's ready. <Info> **What you'll learn in this guide:** @@ -44,7 +44,7 @@ A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ## What is a Promise? -A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** is a JavaScript object that represents the eventual result of an asynchronous operation. When you create a Promise, you're basically saying: "I don't have the value right now, but I *promise* to give you a value (or an error) later." +A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** is a JavaScript object that represents the eventual result of an asynchronous operation. When you create a Promise, you're saying: "I don't have the value right now, but I *promise* to give you a value (or an error) later." ```javascript // A Promise that resolves after 1 second @@ -60,7 +60,7 @@ promise.then(value => { }) ``` -Unlike callbacks that you pass *into* functions, Promises are objects you get *back* from functions. This small change unlocks some really useful patterns like chaining, composition, and unified error handling. +Unlike callbacks that you pass *into* functions, Promises are objects you get *back* from functions. This small change unlocks useful patterns like chaining, composition, and unified error handling. <CardGroup cols={2}> <Card title="Promise — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"> @@ -142,7 +142,7 @@ Here's the important part: **once your order is fulfilled or rejected, it doesn' ## Why Promises? The Callback Problem -Before we go further, let's quickly look at why Promises were invented. If you've read the [Callbacks guide](/concepts/callbacks), you know about "callback hell" — the deeply nested, hard-to-read code that happens when you chain multiple async operations: +Before we go further, let's quickly look at why Promises were invented. If you've read the [Callbacks guide](/concepts/callbacks), you know about "callback hell": the deeply nested, hard-to-read code that happens when you chain multiple async operations: ```javascript // Callback Hell - The Pyramid of Doom @@ -234,7 +234,7 @@ Every Promise is in one of three **states**: | **Fulfilled** | The operation completed successfully. The Promise has a value. | No | | **Rejected** | The operation failed. The Promise has a reason (error). | No | -A Promise that is either fulfilled or rejected is called **settled**. Once settled, a Promise's state is locked in — it never changes again. +A Promise that is either fulfilled or rejected is called **settled**. Once settled, a Promise's state is locked in and never changes. ```javascript const promise = new Promise((resolve, reject) => { @@ -259,9 +259,9 @@ There's a subtle but useful distinction between a Promise's **state** and its ** - **State** = pending, fulfilled, or rejected - **Fate** = resolved or unresolved -Think of it like this: when you place your restaurant order, your fate is "sealed" the moment the waiter writes it down — even though you haven't received your food yet (still pending). You can't change your order anymore. +Think of it like this: when you place your restaurant order, your fate is "sealed" the moment the waiter writes it down, even though you haven't received your food yet (still pending). You can't change your order anymore. -A Promise is **resolved** when its fate is sealed — either because it's already settled, or because it's "locked in" to follow another Promise: +A Promise is **resolved** when its fate is sealed, either because it's already settled, or because it's "locked in" to follow another Promise: ```javascript const innerPromise = new Promise(resolve => { @@ -280,7 +280,7 @@ outerPromise.then(value => { }) ``` -When you resolve a Promise with another Promise, the outer Promise "adopts" the state of the inner one. This is called **Promise unwrapping** — the outer Promise automatically follows whatever happens to the inner Promise. +When you resolve a Promise with another Promise, the outer Promise "adopts" the state of the inner one. This is called **Promise unwrapping**. The outer Promise automatically follows whatever happens to the inner Promise. --- @@ -363,7 +363,7 @@ delayedValue('Hello!', 1000).then(message => { ### Wrapping Callback-Based APIs -Here's a real-world example — turning a callback-based image loader into a Promise: +Here's a real-world example: turning a callback-based image loader into a Promise: ```javascript // Original callback-based function @@ -483,7 +483,7 @@ fetchUserData(userId) ### .finally() — Cleanup Code -The **[`.finally()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally)** method runs code no matter if the Promise was fulfilled or rejected — great for cleanup: +The **[`.finally()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally)** method runs code no matter if the Promise was fulfilled or rejected. It's great for cleanup: ```javascript let isLoading = true @@ -542,7 +542,7 @@ promise3.then(value => console.log(value)) // 3 ## Promise Chaining -Promise chaining is where Promises really shine. Since each `.then()` returns a new Promise, you can chain them together: +Promise chaining is where Promises shine. Since each `.then()` returns a new Promise, you can chain them together: ```javascript Promise.resolve(1) @@ -689,7 +689,7 @@ Promise.resolve('hello') ## Error Handling -Error handling is where Promises really shine. Errors automatically flow down the chain until something catches them. +Error handling is where Promises shine. Errors automatically flow down the chain until something catches them. ### Error Propagation @@ -846,7 +846,7 @@ The `Promise` class has several static methods for creating and combining Promis ### Promise.resolve() and Promise.reject() -The simplest static methods — they create already-settled Promises: +The simplest static methods. They create already-settled Promises: ```javascript // Create a fulfilled Promise @@ -893,7 +893,7 @@ Promise.all([promise1, promise2, promise3]) }) ``` -**Real example — loading a dashboard:** +**Real example: loading a dashboard** ```javascript async function loadDashboard(userId) { @@ -952,7 +952,7 @@ Promise.allSettled([ }) ``` -**Real example — sending notifications to multiple users:** +**Real example: sending notifications to multiple users** ```javascript async function sendNotificationsToAll(userIds, message) { @@ -991,7 +991,7 @@ Promise.race([slow, fast]) .then(winner => console.log(winner)) // "fast" ``` -**Real example — adding a timeout:** +**Real example: adding a timeout** ```javascript function fetchWithTimeout(url, timeout = 5000) { @@ -1053,7 +1053,7 @@ Promise.any([ }) ``` -**Real example — trying multiple CDN mirrors:** +**Real example: trying multiple CDN mirrors** ```javascript async function fetchFromFastestMirror(mirrors) { @@ -1127,7 +1127,7 @@ const promise = new Promise((res, rej) => { ### Sequential Execution -When you need to run things one at a time (not in parallel). Use this when each step depends on the previous result—like database transactions, or when processing order matters (uploading files in a specific sequence). +When you need to run things one at a time (not in parallel). Use this when each step depends on the previous result, like database transactions or when processing order matters (uploading files in a specific sequence). ```javascript // Process items one at a time @@ -1156,7 +1156,7 @@ function processSequentiallyWithReduce(items) { ### Parallel Execution -When operations don't depend on each other. Great for independent fetches like loading a dashboard where you need user data, notifications, and settings all at once—much faster than doing them one by one. +When operations don't depend on each other. Great for independent fetches like loading a dashboard where you need user data, notifications, and settings all at once. Much faster than doing them one by one. ```javascript // Process all items in parallel @@ -1269,7 +1269,7 @@ Node.js has this built-in: `const { promisify } = require('util')` ### Mistake 1: Forgetting to Return -The #1 Promise mistake — forgetting to return from `.then()`: +The #1 Promise mistake is forgetting to return from `.then()`: ```javascript // ❌ WRONG - Promise not returned, chain breaks @@ -1407,7 +1407,7 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s ## Key Takeaways <Info> -**Remember these essential points about Promises:** +**The key things to remember:** 1. **A Promise is a placeholder** — It represents a value that will show up later (or an error if something goes wrong). @@ -1467,7 +1467,7 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s | `Promise.all()` | `Promise.allSettled()` | |-----------------|------------------------| - | Rejects immediately if ANY Promise rejects | Never rejects — waits for ALL to settle | + | Rejects immediately if ANY Promise rejects | Never rejects, waits for ALL to settle | | Returns array of values on success | Returns array of `{status, value/reason}` objects | | Use when all must succeed | Use when you want results regardless of failures | @@ -1501,7 +1501,7 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s outer.then(value => console.log(value)) // "inner value" (after 1 second) ``` - This happens automatically — you can't have a Promise that fulfills with another Promise as its value. + This happens automatically. You can't have a Promise that fulfills with another Promise as its value. </Accordion> <Accordion title="Question 5: What's wrong with this code?"> @@ -1607,22 +1607,22 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s <CardGroup cols={2}> <Card title="JavaScript Promises: An Introduction" icon="newspaper" href="https://web.dev/promises/"> - Google's comprehensive guide to Promises with interactive examples and best practices. + Google's web.dev tutorial with inline runnable code examples you can edit. Covers the full Promise API from basics to advanced patterns like promisification. </Card> <Card title="JavaScript Visualized: Promises & Async/Await" icon="newspaper" href="https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke"> Lydia Hallie's visual explanation with animated GIFs showing exactly how Promises work. </Card> <Card title="Promise Basics — JavaScript.info" icon="newspaper" href="https://javascript.info/promise-basics"> - Clear tutorial covering Promise fundamentals with practical examples. + The go-to reference for Promise fundamentals with the "loadScript" example that makes async patterns click. Includes exercises at the end to test your understanding. </Card> <Card title="Promise Chaining — JavaScript.info" icon="newspaper" href="https://javascript.info/promise-chaining"> - In-depth guide to Promise chaining with diagrams and gotchas. + Excellent diagrams showing how values flow through Promise chains. The "returning promises" section clarifies the trickiest part of chaining. </Card> <Card title="We Have a Problem with Promises" icon="newspaper" href="https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html"> Nolan Lawson's classic article on common Promise mistakes developers make. </Card> <Card title="The Complete JavaScript Promise Guide" icon="newspaper" href="https://blog.webdevsimplified.com/2021-09/javascript-promises"> - Web Dev Simplified's comprehensive tutorial with clear explanations. + Kyle Cook's written companion to his popular YouTube videos. Great if you prefer reading to watching, with the same clear teaching style. </Card> </CardGroup> @@ -1630,7 +1630,7 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s <CardGroup cols={2}> <Card title="JavaScript Promises In 10 Minutes" icon="video" href="https://www.youtube.com/watch?v=DHvZLI7Db8E"> - Web Dev Simplified's quick and practical introduction to Promises. + Perfect if you're short on time. Kyle covers creating, consuming, and chaining Promises with real code examples in just 10 minutes. </Card> <Card title="Promises — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=2d7s3spWAzo"> MPJ's entertaining and thorough explanation of Promises with great analogies. @@ -1639,6 +1639,6 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s Fireship's ultra-concise overview of Promise fundamentals. </Card> <Card title="Promises | Namaste JavaScript" icon="video" href="https://youtu.be/ap-6PPAuK1Y"> - Akshay Saini's deep dive into how Promises work under the hood. + Akshay walks through Promise internals with browser DevTools, showing exactly what happens at each step. Great for understanding the "why" behind Promises. </Card> </CardGroup> From 1949d5c11f188b6ad07377fbe8beb686c94d2ae0 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:15:21 -0300 Subject: [PATCH 071/128] docs: improve callbacks page copywriting and resource descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change 'Master' to 'Understand' in meta description - Remove 'really' filler word from body text - Change heading 'Callbacks Are Still Fundamental' to 'The Foundation' - Change 'essential points' to 'key things to remember' - Replace 17 body text em dashes with periods/commas/colons (75 → 58) - Rewrite all 9 resource descriptions to specific 2-sentence format --- docs/concepts/callbacks.mdx | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/concepts/callbacks.mdx b/docs/concepts/callbacks.mdx index 62e2c21b..4e338743 100644 --- a/docs/concepts/callbacks.mdx +++ b/docs/concepts/callbacks.mdx @@ -1,10 +1,10 @@ --- title: "Callbacks: The Foundation of Async JavaScript" sidebarTitle: "Callbacks: The Foundation of Async" -description: "Learn JavaScript callbacks — functions passed to other functions to be called later. Master sync vs async callbacks, error-first patterns, callback hell, and why Promises were invented." +description: "Learn JavaScript callbacks, functions passed to other functions to be called later. Understand sync vs async callbacks, error-first patterns, callback hell, and why Promises were invented." --- -Why doesn't JavaScript wait? When you set a timer, make a network request, or listen for a click — how does your code keep running instead of freezing until that operation completes? +Why doesn't JavaScript wait? When you set a timer, make a network request, or listen for a click, how does your code keep running instead of freezing until that operation completes? ```javascript console.log('Before timer') @@ -21,7 +21,7 @@ console.log('After timer') // Timer fired! (1 second later) ``` -The answer is **callbacks** — functions you pass to other functions, saying "call me back when you're done." Callbacks power everything async in JavaScript. Every event handler, every timer, every network request — they all rely on them. +The answer is **callbacks**: functions you pass to other functions, saying "call me back when you're done." Callbacks power everything async in JavaScript. Every event handler, every timer, every network request. They all rely on them. <Info> **What you'll learn in this guide:** @@ -36,7 +36,7 @@ The answer is **callbacks** — functions you pass to other functions, saying "c </Info> <Warning> -**Prerequisites:** This guide assumes familiarity with [the Event Loop](/concepts/event-loop) — it's the mechanism that makes async callbacks work! You should also understand [higher-order functions](/concepts/higher-order-functions), since callbacks are higher-order functions in action. +**Prerequisites:** This guide assumes familiarity with [the Event Loop](/concepts/event-loop). It's the mechanism that makes async callbacks work! You should also understand [higher-order functions](/concepts/higher-order-functions), since callbacks are higher-order functions in action. </Warning> --- @@ -60,10 +60,10 @@ function processUserInput(callback) { processUserInput(greet) // "Hello, Alice!" ``` -The term "callback" comes from the idea of being **called back** — like getting a buzzer at a restaurant: "We'll buzz you when your table is ready." +The term "callback" comes from the idea of being **called back**. Think of it like getting a buzzer at a restaurant: "We'll buzz you when your table is ready." <Tip> -**Here's the thing:** A callback is just a regular function. Nothing magical about it. What makes it a "callback" is *how it's used* — passed to another function to be executed later. +**Here's the thing:** A callback is just a regular function. Nothing magical about it. What makes it a "callback" is *how it's used*: passed to another function to be executed later. </Tip> ### Callbacks Can Be Anonymous @@ -134,7 +134,7 @@ Callbacks work like the buzzer you get at a busy restaurant: └─────────────────────────────────────────────────────────────────────────┘ ``` -The key insight: **you don't wait at the counter**. You give them a way to reach you (the callback), and you go do other things. That's how JavaScript stays fast — it never sits around waiting. +The key insight: **you don't wait at the counter**. You give them a way to reach you (the callback), and you go do other things. That's how JavaScript stays fast. It never sits around waiting. ```javascript // You place your order (start async operation) @@ -312,7 +312,7 @@ Why? The `try/catch` runs immediately. By the time the async callback executes, ## How Callbacks Work with the Event Loop -To really get async callbacks, you need to see how they work with the [event loop](/concepts/event-loop). +To understand async callbacks, you need to see how they work with the [event loop](/concepts/event-loop). ``` ┌─────────────────────────────────────────────────────────────────────────┐ @@ -753,7 +753,7 @@ getUser(userId, function(error, user) { 6. **Testing** — Nearly impossible to unit test individual steps <Note> -Want to dive deeper into callback hell? Check out [callbackhell.com](http://callbackhell.com/) — a dedicated guide to understanding and escaping the pyramid of doom. +Want to dive deeper into callback hell? Check out [callbackhell.com](http://callbackhell.com/), a dedicated guide to understanding and escaping the pyramid of doom. </Note> --- @@ -966,7 +966,7 @@ async function initDashboard(userId, password) { ``` <Tip> -**Promises and async/await are built on callbacks.** They don't replace callbacks — they provide a cleaner abstraction over them. Under the hood, Promise `.then()` handlers are still callbacks! +**Promises and async/await are built on callbacks.** They don't replace callbacks. They provide a cleaner abstraction over them. Under the hood, Promise `.then()` handlers are still callbacks! </Tip> --- @@ -975,7 +975,7 @@ async function initDashboard(userId, password) { ### Mistake 1: Calling a Callback Multiple Times -A callback should typically be called exactly once — either with an error or with a result: +A callback should typically be called exactly once, either with an error or with a result: ```javascript // ❌ WRONG - callback called multiple times! @@ -988,7 +988,7 @@ function fetchData(url, callback) { callback(error) // Called on error }) .finally(() => { - callback(null, 'done') // Called ALWAYS — even after success or error! + callback(null, 'done') // Called ALWAYS, even after success or error! }) } @@ -1002,7 +1002,7 @@ function fetchData(url, callback) { ### Mistake 2: Synchronous and Asynchronous Mixing (Zalgo) -A function should be consistently sync or async, never both. This inconsistency is nicknamed "releasing Zalgo" — a reference to an internet meme about unleashing chaos. And chaos is exactly what you get when code behaves unpredictably: +A function should be consistently sync or async, never both. This inconsistency is nicknamed "releasing Zalgo," a reference to an internet meme about unleashing chaos. And chaos is exactly what you get when code behaves unpredictably: ```javascript // ❌ WRONG - sometimes sync, sometimes async (Zalgo!) @@ -1093,7 +1093,7 @@ user.greetLater() // "Hello, Alice!" ### Mistake 4: Not Handling Errors -Always handle errors in async callbacks — unhandled errors can crash your application: +Always handle errors in async callbacks. Unhandled errors can crash your application: ```javascript // ❌ WRONG - error ignored @@ -1128,11 +1128,11 @@ Understanding *why* JavaScript uses callbacks helps everything click into place. ### The Birth of JavaScript (1995) -JavaScript was created by Brendan Eich at Netscape in just 10 days. Its primary purpose was to make web pages interactive — responding to user clicks, form submissions, and other events. +JavaScript was created by Brendan Eich at Netscape in just 10 days. Its primary purpose was to make web pages interactive, responding to user clicks, form submissions, and other events. ### The Single-Threaded Design -JavaScript was designed to be **single-threaded** — one thing at a time. Why? +JavaScript was designed to be **single-threaded**: one thing at a time. Why? 1. **Simplicity** — No race conditions, deadlocks, or complex synchronization 2. **DOM Safety** — Multiple threads modifying the DOM would cause chaos @@ -1172,7 +1172,7 @@ element.onclick = function() { | 2015 | ES6 Promises — official solution to callback hell | | 2017 | ES8 async/await — syntactic sugar for Promises | -### Callbacks Are Still Fundamental +### Callbacks Are Still The Foundation Even with Promises and async/await, callbacks are everywhere: @@ -1181,14 +1181,14 @@ Even with Promises and async/await, callbacks are everywhere: - **Promises** use callbacks internally (`.then(callback)`) - **async/await** is syntactic sugar over Promise callbacks -Callbacks aren't obsolete — they're the foundation that everything else builds upon. +Callbacks aren't obsolete. They're the foundation that everything else builds upon. --- ## Key Takeaways <Info> -**Remember these essential points about callbacks:** +**The key things to remember:** 1. **A callback is a function passed to another function** to be executed later — nothing magical @@ -1318,7 +1318,7 @@ Callbacks aren't obsolete — they're the foundation that everything else builds <Accordion title="Question 5: Why can't you use try/catch with async callbacks?"> **Answer:** - The `try/catch` block executes **synchronously**. By the time an async callback runs, the try/catch is long gone — it's on a different "turn" of the event loop. + The `try/catch` block executes **synchronously**. By the time an async callback runs, the try/catch is long gone. It's on a different "turn" of the event loop. ```javascript try { @@ -1412,16 +1412,16 @@ Callbacks aren't obsolete — they're the foundation that everything else builds <CardGroup cols={2}> <Card title="JavaScript Callbacks Explained" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-callback-functions-what-are-callbacks-in-js-and-how-to-use-them/"> - freeCodeCamp's beginner-friendly guide to understanding callback functions with practical examples. + Starts with the "I'll call you back" phone analogy that makes callbacks click. Builds up from simple examples to async patterns step by step. </Card> <Card title="Callback Functions in JavaScript" icon="newspaper" href="https://javascript.info/callbacks"> - JavaScript.info's in-depth guide with interactive examples and explanations. + Uses a script-loading example to show why callbacks exist and how they solve real problems. The "pyramid of doom" section shows exactly how callback hell develops. </Card> <Card title="Callback Hell" icon="newspaper" href="http://callbackhell.com/"> - The definitive guide to understanding and avoiding callback hell, with practical solutions. + A single-page site dedicated entirely to callback hell. Shows the problem visually, then walks through three concrete solutions with before/after code. </Card> <Card title="Understand Callback Functions in JavaScript" icon="newspaper" href="https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced"> - Brandon Morelli's clear explanation of what callbacks are and how they work. + Breaks down callbacks into two simple rules you can remember. Short read that cuts through the jargon and gets to the point fast. </Card> </CardGroup> @@ -1429,18 +1429,18 @@ Callbacks aren't obsolete — they're the foundation that everything else builds <CardGroup cols={2}> <Card title="Callbacks in JavaScript Explained!" icon="video" href="https://www.youtube.com/watch?v=cNjIUSDnb9k"> - Mosh Hamedani's clear explanation of callbacks with visual examples. + Mosh uses a movie database example that shows callbacks in a realistic context. Great production quality with on-screen code highlighting. </Card> <Card title="JavaScript Callbacks" icon="video" href="https://www.youtube.com/watch?v=QRq2zMHlBz4"> - Web Dev Simplified's beginner-friendly callback tutorial. + Kyle explains callbacks in under 8 minutes with zero fluff. Perfect if you want a quick refresher without sitting through a long tutorial. </Card> <Card title="Callback Functions" icon="video" href="https://www.youtube.com/watch?v=Nau-iEEgEoM"> - Fun Fun Function's entertaining deep-dive into callbacks. + MPJ's signature style makes this feel like a conversation, not a lecture. Explains the "why" behind callbacks, not just the "how." </Card> <Card title="Asynchronous JavaScript Tutorial" icon="video" href="https://www.youtube.com/watch?v=PoRJizFvM7s"> - Traversy Media's comprehensive guide covering callbacks, Promises, and async/await. + Covers the full async story: callbacks, then Promises, then async/await. Watch this one if you want to see how each pattern improves on the last. </Card> <Card title="JavaScript Callback Functions" icon="video" href="https://www.youtube.com/watch?v=pTbSfCT42_M"> - techsith's practical tutorial on callback functions with examples. + Walks through callbacks with a setTimeout example, then shows how to create your own callback-accepting functions. Good for hands-on learners. </Card> </CardGroup> From a6e520d7e89b44b7c37750733bd521df3a0100a1 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:25:30 -0300 Subject: [PATCH 072/128] docs: improve DOM page copywriting and resource descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove 'crucial' (3x) and 'incredibly' from body text - Change 'essential points' to 'key things to remember' - Replace 13 body text em dashes with periods/commas/colons (49 → 36) - Rewrite 13 resource descriptions to specific 2-sentence format - 10 articles: Eloquent JS, DigitalOcean, freeCodeCamp, CSS-Tricks, Zell Liew, JavaScript.info (2), Medium, Google, bitsofco.de - 3 videos: freeCodeCamp, Net Ninja, Traversy Media --- docs/concepts/dom.mdx | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 5673b518..88dd5c2f 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -13,7 +13,7 @@ document.body.style.backgroundColor = 'lightblue' document.getElementById('btn').addEventListener('click', handleClick) ``` -The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is the bridge between your HTML and JavaScript — it lets you read, modify, and respond to changes in web page content. With the DOM, you can use methods like **[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** to find elements, **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** to grab specific nodes, and **[`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)** to respond to user interactions. +The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is the bridge between your HTML and JavaScript. It lets you read, modify, and respond to changes in web page content. With the DOM, you can use methods like **[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** to find elements, **[`getElementById()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)** to grab specific nodes, and **[`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)** to respond to user interactions. <Info> **What you'll learn in this guide:** @@ -34,7 +34,7 @@ The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web ## What is the DOM in JavaScript? -The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface that represents HTML documents as a tree of objects. When a browser loads a webpage, it parses the HTML and creates the DOM — a live, structured representation that JavaScript can read and modify. Every element, attribute, and piece of text becomes a node in this tree. **In short: the DOM is how JavaScript "sees" and changes a webpage.** +The **[Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)** is a programming interface that represents HTML documents as a tree of objects. When a browser loads a webpage, it parses the HTML and creates the DOM, a live, structured representation that JavaScript can read and modify. Every element, attribute, and piece of text becomes a node in this tree. **In short: the DOM is how JavaScript "sees" and changes a webpage.** --- @@ -84,7 +84,7 @@ Just like navigating a family reunion, the DOM lets you: | Search the whole family | "Where's cousin Bob?" | `document.querySelector('#bob')` | <Note> -**Key insight:** Every element, text, and comment in your HTML becomes a "node" in this tree. JavaScript lets you navigate this tree and modify it — changing content, adding elements, or removing them entirely. +**Key insight:** Every element, text, and comment in your HTML becomes a "node" in this tree. JavaScript lets you navigate this tree and modify it: changing content, adding elements, or removing them entirely. </Note> --- @@ -93,7 +93,7 @@ Just like navigating a family reunion, the DOM lets you: ### The DOM is NOT Your HTML Source Code -This is a crucial distinction! Your HTML file and the DOM are **different things**: +Here's the key thing: your HTML file and the DOM are **different things**: <Tabs> <Tab title="HTML Source"> @@ -298,7 +298,7 @@ console.log(ghost) // null ``` <Tip> -IDs must be unique in a document. If you have duplicate IDs, `getElementById` returns the first one. But don't do this—it's invalid HTML! +IDs must be unique in a document. If you have duplicate IDs, `getElementById` returns the first one. But don't do this. It's invalid HTML! </Tip> ### getElementsByClassName() and getElementsByTagName() @@ -322,7 +322,7 @@ console.log(allParagraphs.length) // 3 ### The Modern Way: querySelector() and querySelectorAll() -**[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** and **[`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)** use CSS selectors to find elements—much more powerful! +**[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)** and **[`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)** use CSS selectors to find elements. Much more powerful! ```javascript // querySelector returns the FIRST match (or null) @@ -367,7 +367,7 @@ document.querySelector('p:first-of-type') ### Live vs Static Collections -This is a crucial difference that trips up many developers. **[`getElementsByClassName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName)** returns a live **[HTMLCollection](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)**, while **[`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)** returns a static **[NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)**: +This difference trips up many developers. **[`getElementsByClassName()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName)** returns a live **[HTMLCollection](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)**, while **[`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)** returns a static **[NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)**: ```javascript const liveList = document.getElementsByClassName('item') // LIVE HTMLCollection @@ -489,7 +489,7 @@ const card = li.closest('.card') // Finds nearest ancestor with class "card" const self = li.closest('li') // Returns li itself if it matches! ``` -The **[`closest()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)** method is incredibly useful for event delegation (see [Event Loop](/concepts/event-loop) for how events are processed): +The **[`closest()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)** method is useful for event delegation (see [Event Loop](/concepts/event-loop) for how events are processed): ```javascript // Handle clicks on any button inside a card @@ -675,7 +675,7 @@ Visual representation: <Tabs> <Tab title="remove()"> - Modern and simple—element removes itself: + Modern and simple. Element removes itself: ```javascript const element = document.querySelector('.to-remove') @@ -683,7 +683,7 @@ Visual representation: ``` </Tab> <Tab title="removeChild()"> - Classic method—remove via parent: + Classic method. Remove via parent: ```javascript const parent = document.querySelector('ul') @@ -1407,7 +1407,7 @@ div.appendChild(welcomeText) ## Event Propagation: Bubbling and Capturing -When an event occurs on a DOM element, it doesn't just trigger on that element — it travels through the DOM tree in a process called **event propagation**. Understanding this is crucial for event handling. +When an event occurs on a DOM element, it doesn't just trigger on that element. It travels through the DOM tree in a process called **event propagation**. Understanding this helps with event handling. ### The Three Phases @@ -1539,7 +1539,7 @@ form.addEventListener('focusin', (e) => { ### Event Delegation -Instead of adding listeners to many elements, add one to a parent. This pattern relies on **event bubbling** — when you click a child element, the event bubbles up to the parent where your listener catches it: +Instead of adding listeners to many elements, add one to a parent. This pattern relies on **event bubbling**. When you click a child element, the event bubbles up to the parent where your listener catches it: ```javascript // Bad: Many listeners @@ -1788,9 +1788,9 @@ el.textContent // "Hidden Visible" el.innerText // " Visible" (hidden text excluded) ``` -**Security warning:** Never use `innerHTML` with user input — it can execute malicious scripts (XSS attacks). Use `textContent` instead. +**Security warning:** Never use `innerHTML` with user input. It can execute malicious scripts (XSS attacks). Use `textContent` instead. -**Best answer:** Mention the XSS security risk with innerHTML — this shows you understand real-world implications. +**Best answer:** Mention the XSS security risk with innerHTML. This shows you understand real-world implications. </Accordion> ### Question 5: How do you efficiently add 1000 elements to the DOM? @@ -1850,7 +1850,7 @@ input.value // "hello" | Type | Always string | Can be any type | | Updates | Manual only | Automatically with interaction | -**Best answer:** Use the `<input value="">` example — it's the clearest demonstration of the difference. +**Best answer:** Use the `<input value="">` example. It's the clearest demonstration of the difference. </Accordion> --- @@ -1858,7 +1858,7 @@ input.value // "hello" ## Key Takeaways <Info> -**Remember these essential points:** +**The key things to remember:** 1. **The DOM is a tree** — Elements are nodes with parent, child, and sibling relationships @@ -2034,7 +2034,7 @@ input.value // "hello" ul.appendChild(fragment) // Single reflow! ``` - A DocumentFragment is a virtual container. When appended, only its children are inserted—the fragment disappears. + A DocumentFragment is a virtual container. When appended, only its children are inserted. The fragment disappears. Alternative: Build HTML string and use `innerHTML` once (but sanitize if user input!). </Accordion> @@ -2088,40 +2088,40 @@ input.value // "hello" <CardGroup cols={2}> <Card title="Eloquent JavaScript: The Document Object Model" icon="book" href="https://eloquentjavascript.net/14_dom.html"> - Marijn Haverbeke's excellent free book chapter covering DOM fundamentals with clear explanations and exercises. + A free book chapter with runnable code examples you can edit right in the browser. Includes exercises at the end to test your understanding. </Card> <Card title="How To Understand and Modify the DOM in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/introduction-to-the-dom"> - Tania Rascia's beginner-friendly guide covering DOM basics, selection, traversal, and manipulation. + Tania Rascia walks through each concept with side-by-side HTML and JavaScript examples. Great for visual learners who want to see code and results together. </Card> <Card title="What's the Document Object Model, and why you should know how to use it" icon="newspaper" href="https://medium.freecodecamp.org/whats-the-document-object-model-and-why-you-should-know-how-to-use-it-1a2d0bc5429d"> - A practical introduction to the DOM with real-world examples. + Builds a simple project while explaining DOM concepts. Good if you learn better by building something rather than reading theory. </Card> <Card title="What is the DOM?" icon="newspaper" href="https://css-tricks.com/dom/"> - Chris Coyier's clear explanation of what the DOM is and common misconceptions. + Short read that clears up the "DOM vs HTML source" confusion with visual examples. Explains why DevTools shows something different from View Source. </Card> <Card title="Traversing the DOM with JavaScript" icon="newspaper" href="https://zellwk.com/blog/dom-traversals/"> - Zell Liew's detailed guide to navigating parent, child, and sibling elements efficiently. + Zell explains the difference between Node and Element traversal methods with clear diagrams. Includes the "whitespace text node" gotcha that trips up beginners. </Card> <Card title="DOM Tree" icon="newspaper" href="https://javascript.info/dom-nodes"> - JavaScript.info's comprehensive tutorial on DOM structure, node types, and tree navigation. + Interactive examples you can edit and run in the browser. Part of a larger DOM tutorial series if you want to keep going deeper. </Card> <Card title="How to traverse the DOM in JavaScript" icon="newspaper" href="https://medium.com/javascript-in-plain-english/how-to-traverse-the-dom-in-javascript-d6555c335b4e"> - Vojislav Grujic's in-depth guide to DOM traversal with practical examples. + Covers every traversal method with console output screenshots. Useful reference when you forget which property to use for siblings vs children. </Card> <Card title="Render Tree Construction" icon="newspaper" href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction"> - Google's authoritative guide to how browsers construct the render tree from DOM and CSSOM. + Google's official explanation of the Critical Rendering Path. Essential reading if you want to understand why some DOM operations are slow. </Card> <Card title="What, exactly, is the DOM?" icon="newspaper" href="https://bitsofco.de/what-exactly-is-the-dom/"> - Ire Aderinokun's excellent breakdown of what the DOM is and isn't, including how it differs from the render tree. + Compares DOM vs HTML source vs Render Tree side by side with diagrams. Clears up the confusion about what DevTools actually shows you. </Card> <Card title="JavaScript DOM Tutorial" icon="newspaper" href="https://www.javascripttutorial.net/javascript-dom/"> - A comprehensive multi-part tutorial covering selection, traversal, manipulation, and events. + A multi-part tutorial organized by topic, so you can jump to exactly what you need. Each page is self-contained with try-it-yourself examples. </Card> <Card title="Event Propagation — MDN" icon="newspaper" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events"> MDN's guide to event handling including bubbling, capturing, and delegation patterns. </Card> <Card title="Bubbling and Capturing" icon="newspaper" href="https://javascript.info/bubbling-and-capturing"> - JavaScript.info's detailed explanation of how events travel through the DOM tree. + Animated diagrams showing events traveling up and down the DOM tree. Makes the three-phase model (capture, target, bubble) easy to visualize. </Card> </CardGroup> @@ -2129,13 +2129,13 @@ input.value // "hello" <CardGroup cols={2}> <Card title="JavaScript DOM Manipulation – Full Course for Beginners" icon="graduation-cap" href="https://www.youtube.com/watch?v=5fb2aPlgoys"> - A comprehensive freeCodeCamp course covering DOM selection, traversal, manipulation, and events from the ground up. + A 2-hour freeCodeCamp course that builds multiple projects while teaching DOM concepts. Good if you want structured learning from zero to comfortable. </Card> <Card title="JavaScript DOM Tutorial" icon="video" href="https://www.youtube.com/watch?v=FIORjGvT0kk"> - The Net Ninja's popular playlist covering DOM fundamentals step by step. + A playlist of short, focused videos (5-10 min each). Pick the topic you need instead of watching everything in order. </Card> <Card title="JavaScript DOM Crash Course" icon="video" href="https://www.youtube.com/watch?v=0ik6X4DJKCc"> - Traversy Media's comprehensive crash course covering DOM manipulation in one video. + Brad Traversy's 4-part series (this is part 1). Builds a task list project by the end, so you see DOM skills applied to something real. </Card> <Card title="JavaScript DOM Manipulation Methods" icon="video" href="https://www.youtube.com/watch?v=y17RuWkWdn8"> Web Dev Simplified explains createElement, appendChild, and other manipulation methods. From e76b8a4273eb44b77a9a49ee94321dd6ee34ba64 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:32:55 -0300 Subject: [PATCH 073/128] docs: improve http-fetch page copywriting and resource descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove 'really' filler word and 'dramatically' superlative - Replace 'essential' (5x) with varied alternatives - Change 'essential points' to 'key things to remember' - Replace 14 body text em dashes with periods/commas/colons (57 → 43) - Rewrite all 8 resource descriptions to specific 2-sentence format - 4 articles: Dmitri Pavlutin, Web Dev Simplified, TJ VanToll, OpenReplay - 4 videos: Traversy Media (2), Web Dev Simplified, Steve Griffith --- docs/concepts/http-fetch.mdx | 58 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index c8e89979..19fbcb48 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -4,7 +4,7 @@ sidebarTitle: "Fetch API: Making HTTP Requests the Modern Way" description: "Learn how to make HTTP requests with the JavaScript Fetch API. Understand GET, POST, response handling, JSON parsing, error patterns, and AbortController for cancellation." --- -How does JavaScript get data from a server? How do you load user profiles, submit forms, or fetch the latest posts from an API? The answer is the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** — JavaScript's modern way to make network requests. +How does JavaScript get data from a server? How do you load user profiles, submit forms, or fetch the latest posts from an API? The answer is the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)**, JavaScript's modern way to make network requests. ```javascript // This is how you fetch data in JavaScript @@ -13,7 +13,7 @@ const user = await response.json() console.log(user.name) // "Alice" ``` -But to really understand Fetch, you need to understand what's happening underneath: **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)**. +But to understand Fetch, you need to understand what's happening underneath: **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)**. <Info> **What you'll learn in this guide:** @@ -27,7 +27,7 @@ But to really understand Fetch, you need to understand what's happening undernea </Info> <Warning> -**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so those concepts are essential. If you're not comfortable with Promises yet, read that guide first! +**Prerequisite:** This guide assumes you understand [Promises](/concepts/promises) and [async/await](/concepts/async-await). Fetch is Promise-based, so you'll need those concepts. If you're not comfortable with Promises yet, read that guide first! </Warning> ## What is HTTP? @@ -35,7 +35,7 @@ But to really understand Fetch, you need to understand what's happening undernea **[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)** (Hypertext Transfer Protocol) is the foundation of data communication on the web. It defines how messages are formatted and transmitted between clients (like web browsers) and servers. Every time you load a webpage, submit a form, or fetch data with JavaScript, HTTP is the protocol making that exchange possible. <Note> -**HTTP is not JavaScript.** HTTP is a language-agnostic protocol — Python, Ruby, Go, Java, and every other language uses it too. We cover HTTP basics in this guide because understanding the protocol is essential to using the Fetch API effectively. If you want to dive deeper into HTTP itself, check out the MDN resources below. +**HTTP is not JavaScript.** HTTP is a language-agnostic protocol. Python, Ruby, Go, Java, and every other language uses it too. We cover HTTP basics in this guide because understanding the protocol helps with using the Fetch API effectively. If you want to dive deeper into HTTP itself, check out the MDN resources below. </Note> <CardGroup cols={2}> @@ -43,7 +43,7 @@ But to really understand Fetch, you need to understand what's happening undernea Comprehensive guide to the HTTP protocol </Card> <Card title="An Overview of HTTP — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview"> - Understanding the fundamentals of HTTP + How HTTP works under the hood </Card> </CardGroup> @@ -108,7 +108,7 @@ Every HTTP interaction follows a simple pattern: </Step> <Step title="Client Handles Response"> - Your JavaScript code receives the response and does something with it — display data, show an error, redirect the user, etc. + Your JavaScript code receives the response and does something with it: display data, show an error, redirect the user, etc. </Step> </Steps> @@ -238,7 +238,7 @@ console.log(data) ### Before Fetch: The XMLHttpRequest Days -Before Fetch existed, developers used XMLHttpRequest (XHR) — a verbose, callback-based API that powered "AJAX" requests. Libraries like **[jQuery](https://jquery.com/)** became popular partly because they simplified this painful process. jQuery was revolutionary for JavaScript — for many years it was the go-to library that made DOM manipulation, animations, and AJAX requests dramatically easier. It changed how developers wrote JavaScript and shaped the modern web. +Before Fetch existed, developers used XMLHttpRequest (XHR), a verbose, callback-based API that powered "AJAX" requests. Libraries like **[jQuery](https://jquery.com/)** became popular partly because they simplified this painful process. jQuery was revolutionary for JavaScript. For many years it was the go-to library that made DOM manipulation, animations, and AJAX requests much easier. It changed how developers wrote JavaScript and shaped the modern web. ```javascript // The old way: XMLHttpRequest (verbose and callback-based) @@ -284,7 +284,7 @@ Now that you understand what Fetch is and how it compares to older approaches, l The `fetch()` function takes a URL and returns a Promise that resolves to a Response object. By default, it makes a GET request. </Step> <Step title="Check if the response was successful"> - Always verify `response.ok` before processing — fetch doesn't throw errors for HTTP status codes like 404 or 500. + Always verify `response.ok` before processing. Fetch doesn't throw errors for HTTP status codes like 404 or 500. </Step> <Step title="Parse the response body"> Use `response.json()` for JSON data or `response.text()` for plain text. These methods return another Promise. @@ -369,7 +369,7 @@ Here's what this looks like in code. By default, `fetch()` uses the **GET** meth ### Understanding the Response Object -When `fetch()` resolves, you get a **[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)** object. This object contains everything about the server's reply — status codes, headers, and methods to read the body: +When `fetch()` resolves, you get a **[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)** object. This object contains everything about the server's reply: status codes, headers, and methods to read the body: ```javascript const response = await fetch('https://api.example.com/users/1') @@ -415,7 +415,7 @@ async function getUser(id) { ### Sending Data with POST -So far we've only *retrieved* data. But what about *sending* data — like creating a user account or submitting a form? +So far we've only *retrieved* data. But what about *sending* data, like creating a user account or submitting a form? That's where **[POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)** comes in. It's the HTTP method that tells the server "I'm sending you data to create something new." To make a POST request, you need to specify the method, set a `Content-Type` header, and include your data in the body: @@ -443,7 +443,7 @@ console.log(user.id) // New user's ID from server ### Setting Headers -**[HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)** are metadata you send with your request — things like authentication tokens, content types, and caching instructions. You pass them as an object in the `headers` option: +**[HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)** are metadata you send with your request: things like authentication tokens, content types, and caching instructions. You pass them as an object in the `headers` option: ```javascript const response = await fetch('https://api.example.com/data', { @@ -501,7 +501,7 @@ const response = await fetch(`/api/search?${params}`) ``` <Tip> -**Why use URL/URLSearchParams instead of string concatenation?** These APIs automatically handle URL encoding for special characters. If a user searches for "C++ tutorial", it becomes `q=C%2B%2B+tutorial` — something you'd have to handle manually with string concatenation. +**Why use URL/URLSearchParams instead of string concatenation?** These APIs automatically handle URL encoding for special characters. If a user searches for "C++ tutorial", it becomes `q=C%2B%2B+tutorial`. Something you'd have to handle manually with string concatenation. </Tip> --- @@ -512,7 +512,7 @@ Here's a mistake almost every developer makes when learning fetch: > "I wrapped my fetch in try/catch, so I'm handling all errors... right?" -**Wrong.** The problem? `fetch()` only throws an error when the *network* fails — not when the server returns a 404 or 500. A "Page Not Found" response is still a successful network request from fetch's perspective! +**Wrong.** The problem? `fetch()` only throws an error when the *network* fails, not when the server returns a 404 or 500. A "Page Not Found" response is still a successful network request from fetch's perspective! ### Two Types of "Errors" @@ -539,12 +539,12 @@ When working with `fetch()`, there are two completely different types of failure ``` <Warning> -**The Trap:** `fetch()` only rejects its Promise for network errors. An HTTP 404 or 500 response is still a "successful" fetch — the network request completed! You must check `response.ok` to detect HTTP errors. +**The Trap:** `fetch()` only rejects its Promise for network errors. An HTTP 404 or 500 response is still a "successful" fetch. The network request completed! You must check `response.ok` to detect HTTP errors. </Warning> ### The Mistake: Only Catching Network Errors -This code looks fine, but it has a subtle bug — HTTP errors like 404 or 500 slip right through the catch block: +This code looks fine, but it has a subtle bug. HTTP errors like 404 or 500 slip right through the catch block: ```javascript // ❌ WRONG - This misses HTTP errors! @@ -588,7 +588,7 @@ async function fetchUser(id) { ### Building a Reusable Fetch Helper -Here's a pattern you can use in real projects — a wrapper function that handles the `response.ok` check for you: +Here's a pattern you can use in real projects: a wrapper function that handles the `response.ok` check for you: ```javascript async function fetchJSON(url, options = {}) { @@ -635,7 +635,7 @@ try { ## How to Use async/await with Fetch -The examples above use `.then()` chains, but modern JavaScript has a cleaner syntax: `async/await`. If you're not familiar with it, check out our [async/await concept](/concepts/async-await) first — it'll make your fetch code much easier to read. +The examples above use `.then()` chains, but modern JavaScript has a cleaner syntax: `async/await`. If you're not familiar with it, check out our [async/await concept](/concepts/async-await) first. It'll make your fetch code much easier to read. ### Basic async/await Pattern @@ -735,7 +735,7 @@ if (result.loading) { ## How to Cancel Requests -The **[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)** API lets you cancel in-flight fetch requests. This is essential for: +The **[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)** API lets you cancel in-flight fetch requests. This is useful for: - **Timeouts** — Cancel requests that take too long - **User navigation** — Cancel pending requests when user leaves a page @@ -888,7 +888,7 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. ## Key Takeaways <Info> -**Remember these essential points about HTTP & Fetch:** +**The key things to remember:** 1. **HTTP is request-response** — Client sends a request, server sends a response @@ -908,7 +908,7 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. 9. **Use Promise.all for parallel requests** — Don't await sequentially when you don't have to -10. **AbortController cancels requests** — Essential for search inputs and cleanup +10. **AbortController cancels requests** — Useful for search inputs and cleanup </Info> --- @@ -1041,7 +1041,7 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. <CardGroup cols={2}> <Card title="Promises" icon="handshake" href="/concepts/promises"> - Fetch is Promise-based — understanding Promises is essential + Fetch is Promise-based — you need to understand Promises first </Card> <Card title="async/await" icon="hourglass" href="/concepts/async-await"> Modern syntax for working with Promises and fetch @@ -1077,16 +1077,16 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. <CardGroup cols={2}> <Card title="How to Use Fetch with async/await" icon="newspaper" href="https://dmitripavlutin.com/javascript-fetch-async-await/"> - Dmitri Pavlutin's clear guide to modern fetch patterns with practical examples. + Breaks down fetch into 5 simple recipes you can copy-paste. Great reference when you forget the exact syntax for POST requests or headers. </Card> <Card title="JavaScript Fetch API Ultimate Guide" icon="newspaper" href="https://blog.webdevsimplified.com/2022-01/js-fetch-api/"> - Web Dev Simplified's comprehensive tutorial covering all fetch fundamentals. + Kyle Cook's written version of his popular YouTube tutorials. Covers GET, POST, error handling, and AbortController with the same clear teaching style. </Card> <Card title="Fetch API Error Handling" icon="newspaper" href="https://www.tjvantoll.com/2015/09/13/fetch-and-errors/"> - TJ VanToll's essential article on the fetch error handling gotcha that trips up everyone. + The article that explains why fetch doesn't reject on 404/500. Short read that saves you hours of debugging the "#1 fetch mistake." </Card> <Card title="Abort Controller: Why and How" icon="newspaper" href="https://blog.openreplay.com/abort-controller--why-and-how-to-use-it/"> - OpenReplay's guide to cancelling fetch requests with AbortController. + Shows the timeout and search-input patterns with working code examples. Read this when you need to cancel requests in a real project. </Card> </CardGroup> @@ -1094,15 +1094,15 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. <CardGroup cols={2}> <Card title="JavaScript Fetch API" icon="video" href="https://www.youtube.com/watch?v=cuEtnrL9-H0"> - Traversy Media's beginner-friendly introduction to the Fetch API. + Brad builds a simple app while explaining fetch concepts. Good if you learn better by watching someone code than reading docs. </Card> <Card title="Learn Fetch API in 6 Minutes" icon="video" href="https://www.youtube.com/watch?v=37vxWr0WgQk"> - Web Dev Simplified's quick and clear fetch tutorial. + The fastest way to learn fetch if you're short on time. Kyle covers the essentials without any filler. </Card> <Card title="Async JS Crash Course - Callbacks, Promises, Async/Await" icon="video" href="https://www.youtube.com/watch?v=PoRJizFvM7s"> - Traversy Media's comprehensive async JavaScript guide including fetch. + Covers callbacks, Promises, and async/await before getting to fetch. Watch this if you want the full async picture, not just fetch. </Card> <Card title="JavaScript Fetch with Request and Headers Objects" icon="video" href="https://www.youtube.com/watch?v=CVZhFBjn8Rw"> - Steve Griffith's detailed walkthrough of fetch's Request and Headers APIs. + Goes deeper into the Request and Headers objects most tutorials skip. Watch after you know the basics and want to level up. </Card> </CardGroup> From 918918def926b3dbdd98c155e7a653624428a608 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:39:51 -0300 Subject: [PATCH 074/128] docs: improve factories-classes page copywriting and resource descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove 'one of the most powerful' and 'actually' filler words - Change 'Key Points to Remember' to 'The key things to remember' - Replace 8 body text em dashes with periods/commas (47 → 39) - Rewrite 8 resource descriptions to specific 2-sentence format - 5 articles: Tania Rascia, Under The Hood, Factory Functions, Composition vs Inheritance, Understanding super - 3 videos: Mosh, Fun Fun Function, Traversy --- docs/concepts/factories-classes.mdx | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx index dfe03d88..c52cf593 100644 --- a/docs/concepts/factories-classes.mdx +++ b/docs/concepts/factories-classes.mdx @@ -38,7 +38,7 @@ console.log(player.attack()) // "Alice attacks!" console.log(enemy.attack()) // "Goblin attacks!" ``` -**Factories** and **[Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)** are two patterns for creating objects efficiently. A factory function is a regular function that returns a new object. A class is a blueprint that uses the [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class) keyword and the [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) operator. Both achieve the same goal — but they work differently and have different strengths. +**Factories** and **[Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)** are two patterns for creating objects efficiently. A factory function is a regular function that returns a new object. A class is a blueprint that uses the [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class) keyword and the [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) operator. Both achieve the same goal, but they work differently and have different strengths. <Info> **What you'll learn in this guide:** @@ -167,17 +167,17 @@ JavaScript gives us the same options: └─────────────────────────────────────────────────────────────────────────┘ ``` -Both factories and classes solve the same problem — they just do it differently. Let's explore each approach. +Both factories and classes solve the same problem. They just do it differently. Let's explore each approach. --- ## What is a Factory Function in JavaScript? -A **factory function** is a regular JavaScript function that creates and returns a new object each time it's called. Unlike constructors or classes, factory functions don't require the `new` keyword. They can use `this` in returned methods (like simple objects do), or use [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to avoid `this` entirely — giving you flexibility that classes don't offer. +A **factory function** is a regular JavaScript function that creates and returns a new object each time it's called. Unlike constructors or classes, factory functions don't require the `new` keyword. They can use `this` in returned methods (like simple objects do), or use [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to avoid `this` entirely, giving you flexibility that classes don't offer. ### Basic Factory Function -Think of it like an assembly line — you put in the specifications, and it produces the product: +Think of it like an assembly line. You put in the specifications, and it produces the product: ```javascript // A simple factory function @@ -312,7 +312,7 @@ const villager = createCharacter({ name: "Villager" }); ### Factory with Private Variables (Closures) -One of the most powerful features of factory functions is creating **truly private** variables using [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures): +A powerful feature of factory functions is creating **truly private** variables using [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures): ```javascript function createBankAccount(ownerName, initialBalance = 0) { @@ -444,7 +444,7 @@ console.log(staff.attack()); // "Cast a spell for 35 damage! (Costs 10 mana)" <AccordionGroup> <Accordion title="You need truly private data"> - Factory functions with closures provide **real** privacy. Variables inside the factory can't be accessed or modified from outside — not even through hacks or reflection. + Factory functions with closures provide **real** privacy. Variables inside the factory can't be accessed or modified from outside, not even through hacks or reflection. </Accordion> <Accordion title="You don't need instanceof checks"> @@ -647,7 +647,7 @@ Always use `new` with constructor functions! Without it, `this` refers to the gl ## What Are ES6 Classes in JavaScript? -An **[ES6 class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)** is JavaScript's modern syntax for creating constructor functions and prototypes. Introduced in ECMAScript 2015, classes provide a cleaner, more familiar syntax for object-oriented programming while working exactly the same as constructor functions under the hood — they're often called "syntactic sugar." Classes use the `class` keyword and require the `new` operator to create instances. +An **[ES6 class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)** is JavaScript's modern syntax for creating constructor functions and prototypes. Introduced in ECMAScript 2015, classes provide a cleaner, more familiar syntax for object-oriented programming while working exactly the same as constructor functions under the hood. They're often called "syntactic sugar." Classes use the `class` keyword and require the `new` operator to create instances. ### Basic Class Syntax @@ -679,7 +679,7 @@ console.log(alice instanceof Player); // true ### Classes Are "Syntactic Sugar" -Classes don't add new functionality — they're just a nicer way to write constructor functions. Under the hood, they work exactly the same: +Classes don't add new functionality. They're just a nicer way to write constructor functions. Under the hood, they work exactly the same: <Tabs> <Tab title="ES6 Class"> @@ -1242,7 +1242,7 @@ console.log(safeCounter.getCount()); // 1 // account.balance → undefined ``` - **Not truly private:** The `_underscore` convention is just a naming hint — those properties are fully accessible. + **Not truly private:** The `_underscore` convention is just a naming hint. Those properties are fully accessible. **Best answer:** Distinguish between the `_underscore` convention (not private) and the two truly private approaches. </Accordion> @@ -1347,7 +1347,7 @@ console.log(safeCounter.getCount()); // 1 </Accordion> <Accordion title="Misconception: 'You should always use classes because they're the modern way'"> - **Reality:** Classes were added in ES6 (2015), but that doesn't mean they're always better. The JavaScript community has actually moved **toward** functions in many cases: + **Reality:** Classes were added in ES6 (2015), but that doesn't mean they're always better. The JavaScript community has moved **toward** functions in many cases: - **React:** Moved from class components to function components with hooks - **Functional programming:** Favors factory functions and composition @@ -1762,7 +1762,7 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); ## Key Takeaways <Info> -**Key Points to Remember:** +**The key things to remember:** 1. **Factory functions** are regular functions that return objects — simple and flexible @@ -1972,22 +1972,22 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); <CardGroup cols={2}> <Card title="How To Use Classes in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-classes-in-javascript"> - Tania Rascia's comprehensive tutorial covering class syntax, constructors, inheritance, and static methods with clear examples. + Tania builds a Character class step by step, adding features one at a time. Great if you want to follow along and type the code yourself. </Card> <Card title="JavaScript Classes — Under The Hood" icon="newspaper" href="https://medium.com/tech-tajawal/javascript-classes-under-the-hood-6b26d2667677"> - Deep dive into how classes are just syntactic sugar over prototypes and constructor functions. + Shows the ES5 equivalent of every ES6 class feature side by side. Read this to understand what JavaScript is really doing when you write a class. </Card> <Card title="Factory Functions in JavaScript" icon="newspaper" href="https://atendesigngroup.com/blog/factory-functions-javascript"> - Practical guide to factory function patterns including closures for privacy and composition. + Focuses on the closure-based privacy pattern with a bank account example. Short and focused if you just want to learn factories. </Card> <Card title="Class vs Factory function" icon="newspaper" href="https://medium.freecodecamp.org/class-vs-factory-function-exploring-the-way-forward-73258b6a8d15"> Cristi Salcescu's comparison of both approaches with pros, cons, and when to use each. </Card> <Card title="Composition vs Inheritance" icon="newspaper" href="https://ui.dev/javascript-inheritance-vs-composition/"> - UI.dev's guide explaining why "favor composition over inheritance" is important in JavaScript. + Uses a game character example to show how composition avoids the problems of deep inheritance. Includes the mixin pattern for adding behaviors. </Card> <Card title="Understanding super in JavaScript" icon="newspaper" href="https://jordankasper.com/understanding-super-in-javascript"> - Jordan Kasper's deep dive into the super keyword, constructor calls, and method overriding. + Explains when and why you need super() with clear error examples. Covers the "must call super before this" rule that trips up beginners. </Card> </CardGroup> @@ -1997,15 +1997,15 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); <CardGroup cols={2}> <Card title="JavaScript Factory Functions" icon="video" href="https://www.youtube.com/watch?v=jpegXpQpb3o"> - Programming with Mosh explains factory functions, when to use them, and how they compare to constructors. + Mosh builds a circle factory from scratch in under 10 minutes. Good starting point if you've never seen factories before. </Card> <Card title="Factory Functions in JavaScript" icon="video" href="https://www.youtube.com/watch?v=ImwrezYhw4w"> - Fun Fun Function's entertaining take on factories with practical examples and real-world use cases. + MPJ's signature conversational style makes factories feel approachable. Includes the "why not just use classes?" discussion. </Card> <Card title="Composition over Inheritance" icon="video" href="https://www.youtube.com/watch?v=wfMtDGfHWpA"> Fun Fun Function explains why composition is often better than inheritance with the "Gorilla-Banana" problem. </Card> <Card title="JavaScript Classes Tutorial" icon="video" href="https://www.youtube.com/watch?v=2ZphE5HcQPQ"> - Comprehensive class tutorial covering syntax, inheritance, static methods, and private fields. + Traversy covers classes from basic syntax to private fields in one video. Watch at 1.5x speed for a quick refresher. </Card> </CardGroup> From d51b729bf413441f5c74bf82449f02fa53d880fa Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:45:30 -0300 Subject: [PATCH 075/128] docs: improve this-call-apply-bind page copywriting and resource descriptions --- docs/concepts/this-call-apply-bind.mdx | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx index a302d746..56e018da 100644 --- a/docs/concepts/this-call-apply-bind.mdx +++ b/docs/concepts/this-call-apply-bind.mdx @@ -19,7 +19,7 @@ const greet = user.greet; greet(); // "Hi, I'm undefined" - broken! ``` -The **[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)** keyword is one of JavaScript's most confusing features — but it follows specific rules. Once you understand them, you'll never be confused again. +The **[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)** keyword is one of JavaScript's most confusing features, but it follows specific rules. Once you understand them, you'll never be confused again. <Info> **What you'll learn in this guide:** @@ -59,7 +59,7 @@ Think about the word "I" in everyday conversation. It's a simple word, but its m └─────────────────────────────────────────────────────────────────┘ ``` -This is exactly how `this` works in JavaScript! The keyword `this` is like the pronoun "I" — it refers to different objects depending on **who is "speaking"** (which object is calling the function). +This is exactly how `this` works in JavaScript! The keyword `this` is like the pronoun "I". It refers to different objects depending on **who is "speaking"** (which object is calling the function). ```javascript const alice = { @@ -87,13 +87,13 @@ But here's where JavaScript gets interesting. What if Alice could make Bob say h bob.introduce.call(alice); // "I am Alice" (Bob's function, Alice's this) ``` -That's what `call`, `apply`, and `bind` do — they let you control **who "I" refers to**, regardless of which function is speaking. +That's what `call`, `apply`, and `bind` do. They let you control **who "I" refers to**, regardless of which function is speaking. --- ## What is `this` in JavaScript? -The **`this`** keyword is a special identifier that JavaScript automatically creates in every function execution context. It refers to the object that is currently executing the code — typically the object that "owns" the method being called. Unlike most languages where `this` is fixed at definition time, JavaScript determines `this` dynamically at **call time**, based on how a function is invoked. +The **`this`** keyword is a special identifier that JavaScript automatically creates in every function execution context. It refers to the object that is currently executing the code, typically the object that "owns" the method being called. Unlike most languages where `this` is fixed at definition time, JavaScript determines `this` dynamically at **call time**, based on how a function is invoked. ### The Key Insight: Call-Time Binding @@ -116,7 +116,7 @@ obj.showThis(); // obj (the object before the dot) showThis.call({}); // {} (explicitly specified) ``` -In non-strict mode, plain function calls return **[`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)** (the global object — `window` in browsers, `global` in Node.js). +In non-strict mode, plain function calls return **[`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)** (the global object: `window` in browsers, `global` in Node.js). ### Why Does JavaScript Work This Way? @@ -167,7 +167,7 @@ BINDING RULES (Highest to Lowest Priority) ``` <Note> -Arrow functions are listed last not because they're lowest priority, but because they work differently — they don't have their own `this` at all. We'll cover them in detail. +Arrow functions are listed last not because they're lowest priority, but because they work differently. They don't have their own `this` at all. We'll cover them in detail. </Note> --- @@ -356,7 +356,7 @@ company.department.getName(); // "Engineering" (this = department) #### The Implicit Binding Gotcha: Lost Context -This is one of the most common sources of bugs. When you **extract a method** from an object, it loses its implicit binding: +This trips up many developers. When you **extract a method** from an object, it loses its implicit binding: ```javascript const user = { @@ -374,7 +374,7 @@ const greetFn = user.greet; greetFn(); // "Hello, I'm undefined" (strict mode: this = undefined) ``` -Why? Because `greetFn()` is a plain function call — there's no dot, so implicit binding doesn't apply. We fall through to default binding. +Why? Because `greetFn()` is a plain function call. There's no dot, so implicit binding doesn't apply. We fall through to default binding. ``` IMPLICIT BINDING LOST @@ -429,7 +429,7 @@ showThis(); // window (in browser) or global (in Node.js) ``` <Warning> -**Always use strict mode!** Non-strict mode's default binding to `globalThis` is dangerous — it can accidentally create or modify global variables, leading to hard-to-find bugs. +**Always use strict mode!** Non-strict mode's default binding to `globalThis` is dangerous. It can accidentally create or modify global variables, leading to hard-to-find bugs. ES6 modules and classes are automatically in strict mode. </Warning> @@ -486,7 +486,7 @@ user.regularGreet(); // "Hi, I'm Alice" (this = user) user.arrowGreet(); // "Hi, I'm undefined" (this = enclosing scope, not user!) ``` -Wait, why is `arrowGreet` showing `undefined`? Because the arrow function was defined in the object literal, and the enclosing scope at that point is the module/global scope — not the `user` object. +Wait, why is `arrowGreet` showing `undefined`? Because the arrow function was defined in the object literal, and the enclosing scope at that point is the module/global scope, not the `user` object. #### Where Arrow Functions Shine @@ -791,7 +791,7 @@ greetAlice(); // "Hello, I'm Alice" (still works!) #### Key Characteristic: Permanent Binding -Once bound, the `this` value cannot be changed — not even with `call()` or `apply()`: +Once bound, the `this` value cannot be changed, not even with `call()` or `apply()`: ```javascript function showThis() { @@ -811,7 +811,7 @@ boundToAlice.bind(bob)(); // "Alice" (bind ignored!) #### Use Case: Event Handlers -This is one of the most common uses of `bind()`: +This is a common use of `bind()`: ```javascript class Toggle { @@ -873,7 +873,7 @@ triple(5); // 15 (3 * 5) double(7); // 14 (2 * 7) ``` -This technique is called **partial application** — you're partially applying arguments to create a more specific function. +This technique is called **partial application**. You're partially applying arguments to create a more specific function. ```javascript function greet(greeting, name) { @@ -1249,7 +1249,7 @@ function* generatorFn() { yield 1; } // Works ## Key Takeaways <Info> -**Remember these essential points about `this`, `call`, `apply`, and `bind`:** +**The key things to remember about `this`, `call`, `apply`, and `bind`:** 1. **`this` is determined at call time** — Not when the function is defined, but when it's called. This is called dynamic binding. @@ -1436,17 +1436,17 @@ Try to figure out what `this` refers to in each example before revealing the ans <CardGroup cols={2}> <Card title="Grokking call(), apply() and bind() methods in JavaScript" icon="newspaper" href="https://levelup.gitconnected.com/grokking-call-apply-and-bind-methods-in-javascript-392351a4be8b"> - Aniket Kudale's comprehensive guide to understanding these three methods with clear examples. + Uses a "borrowing a car" analogy that makes method borrowing click instantly. The side-by-side comparisons of call vs apply syntax are especially helpful. </Card> <Card title="Javascript: call(), apply() and bind()" icon="newspaper" href="https://medium.com/@omergoldberg/javascript-call-apply-and-bind-e5c27301f7bb"> - Omer Goldberg's practical walkthrough of call, apply, and bind with real-world scenarios. + Builds understanding progressively from basic examples to implementing your own bind. Great for developers who want to know what's happening under the hood. </Card> <Card title="The Top 7 Tricky this Interview Questions" icon="newspaper" href="https://dmitripavlutin.com/javascript-this-interview-questions/"> Dmitri Pavlutin's collection of challenging this-related questions to test your understanding. </Card> <Card title="How to understand the keyword this and context in JavaScript" icon="newspaper" href="https://www.freecodecamp.org/news/how-to-understand-the-keyword-this-and-context-in-javascript-cd624c6b74b8/"> - Lukas Gisder-Dube's freeCodeCamp article explaining this and execution context. + Covers the relationship between execution context and `this` binding that many tutorials skip. The "3 scenarios" framework makes debugging `this` issues straightforward. </Card> </CardGroup> @@ -1454,13 +1454,13 @@ Try to figure out what `this` refers to in each example before revealing the ans <CardGroup cols={2}> <Card title="JavaScript call, apply and bind" icon="video" href="https://www.youtube.com/watch?v=c0mLRpw-9rI"> - techsith's clear tutorial on the three function methods with practical examples. + Explains each method by solving real problems like borrowing array methods. The visual code walkthroughs make the execution order crystal clear. </Card> <Card title="JS Function Methods call(), apply(), and bind()" icon="video" href="https://www.youtube.com/watch?v=uBdH0iB1VDM"> - Steve Griffith's in-depth explanation of how these methods work under the hood. + Shows exactly when `this` gets assigned during function execution. The step-through debugging demonstrations reveal what's actually happening in memory. </Card> <Card title="bind and this - Object Creation in JavaScript" icon="video" href="https://www.youtube.com/watch?v=GhbhD1HR5vk"> - Fun Fun Function's entertaining take on bind and this in object-oriented JavaScript. + MPJ's signature storytelling style makes `this` binding feel intuitive. Part of a larger series that builds up to understanding JavaScript's object system. </Card> <Card title="Javascript Interview Questions (Call, Bind and Apply)" icon="video" href="https://www.youtube.com/watch?v=VkmUOktYDAU"> Roadside Coder's interview-focused video covering common questions about these methods. From dfcdac7ba5d2625faf9deee9a7113d29ac8d84d9 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:50:27 -0300 Subject: [PATCH 076/128] docs: improve object-creation-prototypes page copywriting and resource descriptions --- docs/concepts/object-creation-prototypes.mdx | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/concepts/object-creation-prototypes.mdx b/docs/concepts/object-creation-prototypes.mdx index 62c56e6b..ee65c9e2 100644 --- a/docs/concepts/object-creation-prototypes.mdx +++ b/docs/concepts/object-creation-prototypes.mdx @@ -18,7 +18,7 @@ console.log(player.hasOwnProperty("name")) // true console.log(Object.getPrototypeOf(player)) // { constructor: Object, toString: f, ... } ``` -The answer is the **[prototype chain](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)** — JavaScript's inheritance mechanism. Every object has a hidden link to another object called its **prototype**. When you access a property, JavaScript looks for it on the object first, then follows this chain of prototypes until it finds the property or reaches the end (`null`). +The answer is the **[prototype chain](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)**. It's JavaScript's inheritance mechanism. Every object has a hidden link to another object called its **prototype**. When you access a property, JavaScript looks for it on the object first, then follows this chain of prototypes until it finds the property or reaches the end (`null`). <Info> **What you'll learn in this guide:** @@ -40,7 +40,7 @@ The answer is the **[prototype chain](https://developer.mozilla.org/en-US/docs/W ## What is the Prototype Chain? -The **prototype chain** is JavaScript's way of implementing inheritance. Every object has an internal link (called `[[Prototype]]`) to another object — its prototype. When you try to access a property on an object, JavaScript: +The **prototype chain** is JavaScript's way of implementing inheritance. Every object has an internal link (called `[[Prototype]]`) to another object, its prototype. When you try to access a property on an object, JavaScript: 1. First looks for the property on the object itself 2. If not found, looks on the object's prototype @@ -112,7 +112,7 @@ console.log(apprentice.castSpell()) // "Harry casts a spell!" ``` <Tip> -**The Chain Always Ends:** Every prototype chain eventually reaches `Object.prototype`, then `null`. This is why all objects have access to methods like `toString()` and `hasOwnProperty()` — they inherit them from `Object.prototype`. +**The Chain Always Ends:** Every prototype chain eventually reaches `Object.prototype`, then `null`. This is why all objects have access to methods like `toString()` and `hasOwnProperty()`. They inherit them from `Object.prototype`. </Tip> --- @@ -304,7 +304,7 @@ JavaScript gives you several ways to create objects, each with different use cas ### 1. Object Literals -The simplest way — great for one-off objects: +The simplest way. Great for one-off objects: ```javascript // Object literal — prototype is automatically Object.prototype @@ -353,7 +353,7 @@ console.log(Object.getPrototypeOf(cat) === animalProto) // true #### Creating Objects with No Prototype -Pass `null` to create an object with **no prototype** — useful for dictionaries: +Pass `null` to create an object with **no prototype**. This is useful for dictionaries: ```javascript // Regular object inherits from Object.prototype @@ -861,7 +861,7 @@ function Player(name) { ## Key Takeaways <Info> -**Essential Points About Prototypes and Object Creation:** +**Key things to remember about prototypes and object creation:** 1. **Every object has a prototype** — a hidden link (`[[Prototype]]`) to another object, forming a chain that ends at `null` @@ -1040,7 +1040,7 @@ function Player(name) { Explore advanced inheritance patterns and polymorphism in JavaScript </Card> <Card title="Value vs Reference Types" icon="copy" href="/concepts/value-reference-types"> - Understand the difference between primitives and objects — essential for prototypes + Understand the difference between primitives and objects, key background for prototypes </Card> </CardGroup> @@ -1073,16 +1073,16 @@ function Player(name) { <CardGroup cols={2}> <Card title="A Beginner's Guide to JavaScript's Prototype" icon="newspaper" href="https://www.freecodecamp.org/news/a-beginners-guide-to-javascripts-prototype/"> - A beginner-friendly introduction to prototypes with clear examples + Uses a "meal recipe" analogy that makes prototype inheritance click for visual learners. The step-by-step diagrams showing object relationships are particularly helpful. </Card> <Card title="Understanding Prototypes in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript"> - DigitalOcean's comprehensive tutorial on prototypes and inheritance + Walks through building a full inheritance hierarchy from scratch with runnable examples. Great for developers who learn by building rather than reading theory. </Card> <Card title="JavaScript Object Creation Patterns" icon="newspaper" href="https://www.patterns.dev/vanilla/object-creation-patterns"> - Different patterns for creating objects in JavaScript + Compares factory functions, constructor patterns, and Object.create() side by side. Helps you choose the right pattern for your specific use case. </Card> <Card title="The Prototype Chain Explained" icon="newspaper" href="https://javascript.info/prototype-inheritance"> - JavaScript.info's detailed explanation of prototype inheritance + Includes interactive code examples you can edit and run in the browser. The "tasks" section at the end tests your understanding with practical challenges. </Card> </CardGroup> @@ -1090,12 +1090,12 @@ function Player(name) { <CardGroup cols={2}> <Card title="JavaScript Prototypes Explained" icon="video" href="https://www.youtube.com/watch?v=riDVvXZ_Kb4"> - Fun Funktion's visual explanation of how prototypes work + MPJ's signature whiteboard diagrams make the prototype chain visible and intuitive. His "delegation, not copying" explanation is how prototypes finally click for many developers. </Card> <Card title="Object.create and Prototypes" icon="video" href="https://www.youtube.com/watch?v=MACDGu96wrA"> - Kyle Simpson explains Object.create and prototype linkage + Kyle Simpson (author of "You Don't Know JS") challenges common misconceptions about prototypes. His "behavior delegation" framing offers a clearer mental model than classical inheritance. </Card> <Card title="The new Keyword Explained" icon="video" href="https://www.youtube.com/watch?v=Y3zzCY62NYc"> - What happens under the hood when you use new + Steps through each of the 4 things `new` does with live code demonstrations. Shows exactly what happens to `this` and prototype links during object construction. </Card> </CardGroup> From 31716c702f8a89f09bec2d1078032493bcfa213f Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 13:54:17 -0300 Subject: [PATCH 077/128] docs: improve event-loop page copywriting --- docs/concepts/event-loop.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/concepts/event-loop.mdx b/docs/concepts/event-loop.mdx index 012c6422..c20470a3 100644 --- a/docs/concepts/event-loop.mdx +++ b/docs/concepts/event-loop.mdx @@ -19,7 +19,7 @@ console.log('End'); // Timeout ``` -Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model)** — JavaScript's mechanism for handling asynchronous operations while remaining single-threaded. +Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model)**. It's JavaScript's mechanism for handling asynchronous operations while remaining single-threaded. <Info> **What you'll learn in this guide:** @@ -39,7 +39,7 @@ Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event lo ## What is the Event Loop? -The **event loop** is JavaScript's mechanism for executing code, handling events, and managing asynchronous operations. It continuously monitors the call stack and callback queues, pushing queued tasks to the stack when it's empty—enabling non-blocking behavior despite JavaScript being single-threaded. +The **event loop** is JavaScript's mechanism for executing code, handling events, and managing asynchronous operations. It continuously monitors the call stack and callback queues, pushing queued tasks to the stack when it's empty. This enables non-blocking behavior despite JavaScript being single-threaded. ### The Restaurant Analogy @@ -89,7 +89,7 @@ Here's how it maps to JavaScript: The chef (JavaScript) can only work on one dish (task) at a time. But kitchen timers (Web APIs) run independently! When a timer goes off, the dish goes to the "Order Up!" window (Task Queue). The kitchen manager (Event Loop) constantly checks: "Is the chef free? Here's the next order!" -**VIP orders (Promises)** always get priority — they jump ahead of regular orders in the queue. +**VIP orders (Promises)** always get priority. They jump ahead of regular orders in the queue. <Note> **TL;DR:** JavaScript is single-threaded but achieves concurrency by delegating work to browser APIs, which run in the background. When they're done, callbacks go into queues. The Event Loop moves callbacks from queues to the call stack when it's empty. @@ -210,7 +210,7 @@ To understand the Event Loop, you need to see the full picture: <AccordionGroup> <Accordion title="Call Stack"> - The **[Call Stack](/concepts/call-stack)** is where JavaScript keeps track of what function is currently running. It's a LIFO (Last In, First Out) structure — like a stack of plates. + The **[Call Stack](/concepts/call-stack)** is where JavaScript keeps track of what function is currently running. It's a LIFO (Last In, First Out) structure, like a stack of plates. ```javascript function multiply(a, b) { @@ -487,7 +487,7 @@ Even though the second promise is created AFTER setTimeout was registered, it st | [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) | `postMessage` callbacks | <Note> -**What about requestAnimationFrame?** rAF is NOT a task — it runs during the rendering phase, after microtasks but before the browser paints. It's covered in detail in the [Timers section](#requestanimationframe-smooth-animations). +**What about requestAnimationFrame?** rAF is NOT a task. It runs during the rendering phase, after microtasks but before the browser paints. It's covered in detail in the [Timers section](#requestanimationframe-smooth-animations). </Note> ### What Creates Microtasks? @@ -1477,7 +1477,7 @@ Watch how: ## Key Takeaways <Info> -**Remember these essential points:** +**The key things to remember:** 1. **JavaScript is single-threaded** — only one thing runs at a time on the call stack From 73bf52513db5ec251b8d939fe1cce16e04cbc320 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 14:00:10 -0300 Subject: [PATCH 078/128] docs: improve iife-modules page copywriting and resource descriptions --- docs/concepts/iife-modules.mdx | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 3f2de0b9..454765a5 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -18,7 +18,7 @@ import { formatDate } from './utils.js' console.log(formatDate(new Date())) // "12/30/2025" ``` -This is **[ES6 modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)** — JavaScript's built-in way to organize code into separate files, each with its own private scope. But before modules existed, developers invented clever patterns like **IIFEs** and **namespaces** to solve the same problems. +This is **[ES6 modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)**. It's JavaScript's built-in way to organize code into separate files, each with its own private scope. But before modules existed, developers invented clever patterns like **IIFEs** and **namespaces** to solve the same problems. <Info> **What you'll learn in this guide:** @@ -38,7 +38,7 @@ This is **[ES6 modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/ ## What is an IIFE? -An **[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it's defined. It creates a private scope to protect variables from polluting the global namespace — a pattern that was essential before ES6 modules existed. +An **[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it's defined. It creates a private scope to protect variables from polluting the global namespace. This pattern was essential before ES6 modules existed. ```javascript // An IIFE — runs immediately, no calling needed @@ -54,7 +54,7 @@ An **[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)** (Immediate The parentheses around the function turn it from a declaration into an expression, and the `()` at the end immediately invokes it. This was the go-to pattern for creating private scope before JavaScript had built-in modules. <Note> -**Historical context:** IIFEs were everywhere in JavaScript codebases from 2010-2015. Today, most projects use ES6 modules (`import`/`export`), so you won't write many IIFEs in modern code. However, understanding them is valuable — you'll encounter IIFEs in older codebases, libraries, and they're still useful for specific cases like async initialization or quick scripts. +**Historical context:** IIFEs were everywhere in JavaScript codebases from 2010-2015. Today, most projects use ES6 modules (`import`/`export`), so you won't write many IIFEs in modern code. However, understanding them is valuable. You'll encounter IIFEs in older codebases, libraries, and they're still useful for specific cases like async initialization or quick scripts. </Note> --- @@ -332,7 +332,7 @@ console.log(counter.count); // undefined (it's private!) counter.log("test"); // TypeError: counter.log is not a function ``` -This pattern is called the **Module Pattern** — it uses [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to keep variables private. It was the standard way to create "modules" before ES6. +This pattern is called the **Module Pattern**. It uses [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to keep variables private. It was the standard way to create "modules" before ES6. ### IIFE with Parameters @@ -523,7 +523,7 @@ MyApp.Views.UserList.render([user]); ### Combining Namespaces with IIFEs -The best of both worlds — organized AND private: +The best of both worlds: organized AND private: ```javascript const MyApp = {}; @@ -642,7 +642,7 @@ const fs = require('fs'); module.exports = { myFunction }; ``` -This is called **CommonJS** — Node.js's original module system. While still widely used, ES modules (`import`/`export`) are the modern standard and work in both browsers and Node.js. New projects should use ES modules. +This is called **CommonJS**, Node.js's original module system. While still widely used, ES modules (`import`/`export`) are the modern standard and work in both browsers and Node.js. New projects should use ES modules. </Note> --- @@ -1244,7 +1244,7 @@ export function createDefaultUser() { ``` <Tip> -**Rule of Thumb:** Draw your import arrows — they should flow in one direction like a tree, not in circles. If module A imports from B, module B should NOT import from A. If you need shared code, create a third module that both can import from. +**Rule of Thumb:** Draw your import arrows. They should flow in one direction like a tree, not in circles. If module A imports from B, module B should NOT import from A. If you need shared code, create a third module that both can import from. </Tip> --- @@ -1252,7 +1252,7 @@ export function createDefaultUser() { ## Key Takeaways <Info> -**Key Points to Remember:** +**The key things to remember:** 1. **IIFEs** create private scope by running immediately — useful for initialization and avoiding globals @@ -1447,28 +1447,28 @@ Try to answer each question before revealing the solution: <CardGroup cols={2}> <Card title="Mastering Immediately-Invoked Function Expressions" icon="newspaper" href="https://medium.com/@vvkchandra/essential-javascript-mastering-immediately-invoked-function-expressions-67791338ddc6"> - Deep dive into IIFE patterns with practical examples and use cases. + Covers the classical and Crockford IIFE variations with clear syntax breakdowns. Great for understanding why the parentheses are placed where they are. </Card> <Card title="JavaScript Modules: A Beginner's Guide" icon="newspaper" href="https://medium.freecodecamp.org/javascript-modules-a-beginner-s-guide-783f7d7a5fcc"> - Comprehensive introduction to modules from the history to modern ES6 syntax. + Traces the evolution from global scripts to CommonJS to ES6 modules with code examples at each stage. Perfect if you're wondering why we have so many module formats. </Card> <Card title="A 10 minute primer to JavaScript modules" icon="newspaper" href="https://www.jvandemo.com/a-10-minute-primer-to-javascript-modules-module-formats-module-loaders-and-module-bundlers/"> - Quick overview of module formats, loaders, and bundlers. + Explains the difference between module formats (AMD, CommonJS, ES6), loaders (RequireJS, SystemJS), and bundlers (Webpack, Rollup). Clears up the confusing terminology quickly. </Card> <Card title="ES6 Modules in Depth" icon="newspaper" href="https://ponyfoo.com/articles/es6-modules-in-depth"> - Advanced article covering all aspects of ES6 module system. + Nicolás Bevacqua's thorough exploration of edge cases like circular dependencies and live bindings. Read this after you understand the basics. </Card> <Card title="Using JavaScript modules on the web" icon="newspaper" href="https://developers.google.com/web/fundamentals/primers/modules"> Google's guide to using modules in browsers, including performance tips. </Card> <Card title="Understanding ES6 Modules" icon="newspaper" href="https://www.sitepoint.com/understanding-es6-modules/"> - Practical guide with examples of organizing real projects with modules. + Walks through setting up modules in both browser and Node.js environments. Includes a complete example project structure you can copy. </Card> <Card title="All you need to know about Expressions, Statements and Expression Statements" icon="newspaper" href="https://dev.to/promhize/javascript-in-depth-all-you-need-to-know-about-expressions-statements-and-expression-statements-5k2"> - Comprehensive guide to understanding the difference between expressions and statements. + Explains why `function(){}()` fails but `(function(){})()` works. The expression vs statement distinction finally makes sense after reading this. </Card> <Card title="Function Expressions vs Function Declarations" icon="newspaper" href="https://www.sitepoint.com/function-expressions-vs-declarations/"> - Clear explanation of when to use function expressions vs declarations. + Covers hoisting behavior, named vs anonymous expressions, and arrow functions. Helps you choose the right syntax for each situation. </Card> </CardGroup> @@ -1476,21 +1476,21 @@ Try to answer each question before revealing the solution: <CardGroup cols={2}> <Card title="Immediately Invoked Function Expression — Beau teaches JavaScript" icon="video" href="https://www.youtube.com/watch?v=3cbiZV4H22c"> - Clear explanation of IIFEs from freeCodeCamp's JavaScript series. + Short and focused 4-minute explanation perfect for quick learning. Part of freeCodeCamp's beginner-friendly JavaScript series. </Card> <Card title="JavaScript Modules: ES6 Import and Export" icon="video" href="https://www.youtube.com/watch?v=_3oSWwapPKQ"> - Comprehensive tutorial on ES6 modules with practical examples. + Kyle from Web Dev Simplified builds a project step-by-step showing named exports, default exports, and barrel files. Great for seeing modules in action. </Card> <Card title="JavaScript IIFE — Steve Griffith" icon="video" href="https://www.youtube.com/watch?v=Xd7zgPFwVX8"> - In-depth look at IIFEs and their role in JavaScript development. + Demonstrates the Module Pattern with private variables and public methods. Shows exactly how closures make IIFEs powerful. </Card> <Card title="ES6 Modules in the Real World" icon="video" href="https://www.youtube.com/watch?v=fIP4pjAqCtQ"> Conference talk on practical module usage in production applications. </Card> <Card title="Expressions vs. Statements in JavaScript" icon="video" href="https://www.youtube.com/watch?v=WVyCrI1cHi8"> - Clear explanation of the fundamental difference between expressions and statements. + Uses simple examples to show why expressions produce values and statements perform actions. Essential for understanding IIFE syntax. </Card> <Card title="JavaScript - Expression vs. Statement" icon="video" href="https://www.youtube.com/watch?v=3jDpNGJkupA"> - Visual walkthrough of expressions and statements with examples. + Answers "can I use this in an if condition?" and similar practical questions. Helps you recognize expressions vs statements in real code. </Card> </CardGroup> From 262f2bad2b7ec510d0d4aa6dfe7cf0a5e557465b Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 14:28:14 -0300 Subject: [PATCH 079/128] docs: improve call-stack page copywriting and resource descriptions --- docs/concepts/call-stack.mdx | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 6f7e02ed..9a195849 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -6,7 +6,7 @@ description: "Learn how the JavaScript call stack tracks function execution. Und How does JavaScript keep track of which function is running? When a function calls another function, how does JavaScript know where to return when that function finishes? -The answer is the **[call stack](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)** — JavaScript's mechanism for tracking function execution. +The answer is the **[call stack](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)**. It's JavaScript's mechanism for tracking function execution. ```javascript function greet(name) { @@ -40,7 +40,7 @@ When `greet` calls `createMessage`, JavaScript remembers where it was in `greet` ## The Stack of Plates: A Real-World Analogy -Imagine you're working in a restaurant kitchen, washing dishes. As clean plates come out, you stack them one on top of another. When a server needs a plate, they always take the one from the **top** of the stack — not from the middle or bottom. +Imagine you're working in a restaurant kitchen, washing dishes. As clean plates come out, you stack them one on top of another. When a server needs a plate, they always take the one from the **top** of the stack, not from the middle or bottom. ``` ┌───────────┐ @@ -54,13 +54,13 @@ Imagine you're working in a restaurant kitchen, washing dishes. As clean plates This is exactly how JavaScript keeps track of your functions! When you call a function, JavaScript puts it on top of a "stack." When that function finishes, JavaScript removes it from the top and goes back to whatever was underneath. -This simple concept — **adding to the top and removing from the top** — is the foundation of how JavaScript executes your code. +This simple concept, **adding to the top and removing from the top**, is the foundation of how JavaScript executes your code. --- ## What is the Call Stack? -The **[call stack](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)** is a mechanism that JavaScript uses to keep track of where it is in your code. Think of it as JavaScript's "to-do list" for function calls — but one where it can only work on the item at the top. +The **[call stack](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)** is a mechanism that JavaScript uses to keep track of where it is in your code. Think of it as JavaScript's "to-do list" for function calls, but one where it can only work on the item at the top. ```javascript function first() { second(); } @@ -74,7 +74,7 @@ first(); ### The LIFO Principle -The call stack follows a principle called **LIFO** — **Last In, First Out**. +The call stack follows a principle called **LIFO**: **Last In, First Out**. - **Last In**: The most recent function call goes on top - **First Out**: The function on top must finish before we can get to the ones below @@ -412,7 +412,7 @@ The call stack has a **limited size**. If you keep adding functions without remo ``` <Warning> -**The Trap:** Recursive functions without a proper stopping condition will crash your program. The most common cause is **infinite recursion** — a function that calls itself forever without a base case. +**The Trap:** Recursive functions without a proper stopping condition will crash your program. The most common cause is **infinite recursion**, a function that calls itself forever without a base case. </Warning> ### The Classic Mistake: Missing Base Case @@ -537,7 +537,7 @@ Stack: [ empty ] 1. Always define a clear **base case** for recursive functions 2. Make sure each recursive call moves **toward** the base case 3. Consider using **iteration** (loops) instead of recursion for simple cases -4. Be careful with property setters — use different internal property names +4. Be careful with property setters, use different internal property names </Tip> --- @@ -548,7 +548,7 @@ When something goes wrong, the call stack is your best friend for figuring out w ### Reading a Stack Trace -When an error occurs, JavaScript gives you a **[stack trace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack)** — a snapshot of the call stack at the moment of the error. +When an error occurs, JavaScript gives you a **[stack trace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack)**, a snapshot of the call stack at the moment of the error. ```javascript function a() { b(); } @@ -641,7 +641,7 @@ console.log('Third'); // Second ← Even with 0ms delay, this runs last! ``` -The `setTimeout` callback doesn't go directly on the call stack — it waits in a queue until the stack is empty. This is why "Third" prints before "Second" even though the timeout is 0 milliseconds. +The `setTimeout` callback doesn't go directly on the call stack. It waits in a queue until the stack is empty. This is why "Third" prints before "Second" even though the timeout is 0 milliseconds. <CardGroup cols={2}> <Card title="Event Loop" icon="rotate" href="/concepts/event-loop"> @@ -704,7 +704,7 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in </Accordion> <Accordion title="JavaScript can run multiple functions at once"> - **Wrong!** JavaScript is **single-threaded** — it has ONE call stack and can only execute ONE thing at a time. + **Wrong!** JavaScript is **single-threaded**. It has ONE call stack and can only execute ONE thing at a time. ```javascript function a() { @@ -725,7 +725,7 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in </Accordion> <Accordion title="Promises are completely asynchronous"> - **Wrong!** The Promise *constructor* runs **synchronously** — only the `.then()` callbacks are asynchronous: + **Wrong!** The Promise *constructor* runs **synchronously**. Only the `.then()` callbacks are asynchronous: ```javascript console.log('1'); @@ -752,7 +752,7 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in ## Key Takeaways <Info> -**Remember these essential points about the Call Stack:** +**The key things to remember about the Call Stack:** 1. **JavaScript is single-threaded** — It has ONE call stack and can only do one thing at a time @@ -971,24 +971,24 @@ The `setTimeout` callback doesn't go directly on the call stack — it waits in Part of the popular "Namaste JavaScript" series. Akshay Saini explains execution with great visuals and examples. </Card> <Card title="Understanding JavaScript Execution" icon="video" href="https://www.youtube.com/watch?v=Z6a1cLyq7Ac"> - Codesmith's in-depth walkthrough of JavaScript execution. Part of a larger playlist on JS fundamentals. + Shows how JavaScript creates execution contexts and manages memory during function calls. Part of Codesmith's excellent "JavaScript: The Hard Parts" series. </Card> <Card title="Javascript: the Call Stack explained" icon="video" href="https://www.youtube.com/watch?v=w6QGEiQceOM"> - Coding Blocks India provides a clear, methodical explanation with step-by-step code walkthroughs. + Traces through nested function calls line by line, showing exactly when frames are pushed and popped. Good for visual learners who want to see each step. </Card> <Card title="What is the Call Stack?" icon="video" href="https://www.youtube.com/watch?v=w7QWQlkLY_s"> - Eric Traub gives a concise explanation of call stack fundamentals in an easy-to-follow format. + Uses a simple factorial example to demonstrate recursion on the call stack. Under 10 minutes, perfect for a quick refresher. </Card> <Card title="The Call Stack" icon="video" href="https://www.youtube.com/watch?v=Q2sFmqvpBe0"> - Kevin Drumm explains the call stack concept with clear diagrams and simple examples. + Draws out the stack visually as code executes, making the LIFO concept easy to grasp. Includes a stack overflow example that shows what happens when things go wrong. </Card> <Card title="Call Stacks - CS50" icon="video" href="https://www.youtube.com/watch?v=aCPkszeKRa4"> Harvard's CS50 explains call stacks from a computer science perspective — great for understanding the theory. </Card> <Card title="Learn the JavaScript Call Stack" icon="video" href="https://www.youtube.com/watch?v=HXqXPGS96rw"> - codecupdev walks through the call stack with beginner-friendly explanations and live coding. + Live codes examples while explaining each concept, so you see exactly how to trace execution yourself. Great for following along in your own editor. </Card> <Card title="JavaScript Functions and the Call Stack" icon="video" href="https://www.youtube.com/watch?v=P6H-T4cUDR4"> - Chidre's Tech Tutorials covers how functions interact with the call stack in detail. + Focuses on the relationship between function invocation and stack frames. Explains why understanding the call stack helps you debug errors faster. </Card> </CardGroup> From 0e8b93c76523cc7fcdba5ea2141cc3a04fd8f9a2 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 14:41:51 -0300 Subject: [PATCH 080/128] docs: improve scope-and-closures page copywriting and resource descriptions --- docs/concepts/scope-and-closures.mdx | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/concepts/scope-and-closures.mdx b/docs/concepts/scope-and-closures.mdx index 0ff14a78..ca46dfaf 100644 --- a/docs/concepts/scope-and-closures.mdx +++ b/docs/concepts/scope-and-closures.mdx @@ -1,7 +1,7 @@ --- title: "Scope and Closures: How Variables Really Work in JavaScript" sidebarTitle: "Scope and Closures: How Variables Really Work" -description: "Master JavaScript scope and closures. Learn the three types of scope, var vs let vs const, lexical scoping, the scope chain, and closure patterns for data privacy." +description: "Learn JavaScript scope and closures. Understand the three types of scope, var vs let vs const, lexical scoping, the scope chain, and closure patterns for data privacy." --- Why can some variables be accessed from anywhere in your code, while others seem to disappear? How do functions "remember" variables from their parent functions, even after those functions have finished running? @@ -21,7 +21,7 @@ console.log(counter()) // 1 console.log(counter()) // 2 — it remembers! ``` -The answers lie in understanding **scope** and **closures** — two fundamental concepts that govern how variables work in JavaScript. Scope determines *where* variables are visible, while closures allow functions to *remember* their original environment. +The answers lie in understanding **scope** and **closures**. These two fundamental concepts govern how variables work in JavaScript. Scope determines *where* variables are visible, while closures allow functions to *remember* their original environment. <Info> **What you'll learn in this guide:** @@ -47,11 +47,11 @@ The answers lie in understanding **scope** and **closures** — two fundamental ## The Office Building Analogy -Imagine it's after hours and you're wandering through your office building (legally — you work there, promise). You notice something interesting about what you can and can't see: +Imagine it's after hours and you're wandering through your office building (legally, you work there, promise). You notice something interesting about what you can and can't see: - **Inside your private office**, you can see everything on your desk, peek into the hallway through your door, and even see the lobby through the glass walls -- **In the hallway**, you can see the lobby clearly, but those private offices? Their blinds are shut — no peeking allowed -- **In the lobby**, you're limited to just what's there — the reception desk, some chairs, maybe a sad-looking plant +- **In the hallway**, you can see the lobby clearly, but those private offices? Their blinds are shut. No peeking allowed +- **In the lobby**, you're limited to just what's there: the reception desk, some chairs, maybe a sad-looking plant ``` ┌─────────────────────────────────────────────────────────────┐ @@ -80,7 +80,7 @@ Imagine it's after hours and you're wandering through your office building (lega This is exactly how **scope** works in JavaScript! Code in inner scopes can "look out" and access variables from outer scopes, but outer scopes can never "look in" to inner scopes. -And here's where it gets really interesting: imagine someone who worked in that private office quits and leaves the building. But they took a mental snapshot of everything in there — the passwords on sticky notes, the secret project plans, the snack drawer location. Even though they've left, they still *remember* everything. That's essentially what a **closure** is: a function that "remembers" the scope where it was created, even after that scope is gone. +And here's where it gets really interesting: imagine someone who worked in that private office quits and leaves the building. But they took a mental snapshot of everything in there: the passwords on sticky notes, the secret project plans, the snack drawer location. Even though they've left, they still *remember* everything. That's essentially what a **closure** is: a function that "remembers" the scope where it was created, even after that scope is gone. ### Why Does Scope Exist? @@ -190,7 +190,7 @@ const MyApp = { ### 2. Function Scope -Variables declared with [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) inside a function are **function-scoped** — they're only accessible within that function. +Variables declared with [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) inside a function are **function-scoped**. They're only accessible within that function. ```javascript function calculateTotal() { @@ -443,7 +443,7 @@ outerFunction(); ### The Scope Chain -When JavaScript needs to find a variable, it walks up the **scope chain** — starting from the current scope and moving outward until it finds the variable or reaches the global scope. +When JavaScript needs to find a variable, it walks up the **scope chain**. It starts from the current scope and moves outward until it finds the variable or reaches the global scope. <Steps> <Step title="Look in Current Scope"> @@ -525,7 +525,7 @@ Shadowing can be confusing. While sometimes intentional, accidental shadowing is A **[closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)** is a function bundled together with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to variables from an outer (enclosing) scope, even after that outer function has finished executing and returned. Every function in JavaScript creates a closure at creation time. -Remember our office building analogy? A closure is like someone who worked in the private office, left the building, but still remembers exactly where everything was — and can still use that knowledge! +Remember our office building analogy? A closure is like someone who worked in the private office, left the building, but still remembers exactly where everything was, and can still use that knowledge! ### Every Function Creates a Closure @@ -599,7 +599,7 @@ After createGreeter("Hello") returns: ## Closures in the Wild -Closures aren't just a theoretical concept — you'll use them every day. Here are the patterns that make closures so powerful. +Closures aren't just a theoretical concept. You'll use them every day. Here are the patterns that make closures so powerful. ### 1. Data Privacy & Encapsulation @@ -752,7 +752,7 @@ Understanding scope and closures means understanding where things go wrong. Thes ### The #1 Closure Interview Question -This is the classic closure trap — almost everyone gets it wrong the first time: +This is the classic closure trap. Almost everyone gets it wrong the first time: ### The Problem @@ -803,7 +803,7 @@ What actually happens: <Tabs> <Tab title="Solution 1: Use let"> - The simplest modern solution — `let` creates a new binding for each iteration: + The simplest modern solution. `let` creates a new binding for each iteration: ```javascript for (let i = 0; i < 3; i++) { @@ -904,7 +904,7 @@ cleanup(); // Removes listener, allows memory to be freed ## Key Takeaways <Info> -**Remember these essential points about Scope & Closures:** +**The key things to remember about Scope & Closures:** 1. **Scope = Variable Visibility** — It determines where variables can be accessed @@ -953,7 +953,7 @@ cleanup(); // Removes listener, allows memory to be freed </Accordion> <Accordion title="Question 2: What is the Temporal Dead Zone?"> - **Answer:** The Temporal Dead Zone (TDZ) is the period between entering a scope and the actual declaration of a `let` or `const` variable. During this time, the variable exists but cannot be accessed — doing so throws a `ReferenceError`. + **Answer:** The Temporal Dead Zone (TDZ) is the period between entering a scope and the actual declaration of a `let` or `const` variable. During this time, the variable exists but cannot be accessed. Doing so throws a `ReferenceError`. ```javascript function example() { @@ -1164,7 +1164,7 @@ cleanup(); // Removes listener, allows memory to be freed <CardGroup cols={2}> <Card title="JavaScript The Hard Parts: Closure, Scope & Execution Context" icon="video" href="https://www.youtube.com/watch?v=XTAzsODSCsM"> - Codesmith's in-depth exploration of how scope and closures really work under the hood. + Will Sentance draws out execution contexts and the scope chain on a whiteboard as code runs. This visual approach makes the "how" of closures click. </Card> <Card title="Closures in JavaScript" icon="video" href="https://youtu.be/qikxEIxsXco"> Akshay Saini's popular Namaste JavaScript episode with clear visual explanations. From 2df9b4db76db1168a2407db73d8bbeccd257c6e9 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 14:51:37 -0300 Subject: [PATCH 081/128] docs: improve equality-operators page copywriting and resource descriptions --- docs/concepts/equality-operators.mdx | 38 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/concepts/equality-operators.mdx b/docs/concepts/equality-operators.mdx index fabafffc..96f0ed02 100644 --- a/docs/concepts/equality-operators.mdx +++ b/docs/concepts/equality-operators.mdx @@ -16,7 +16,7 @@ console.log(typeof null); // "object" — a 28-year-old bug! console.log(NaN === NaN); // false — NaN never equals anything ``` -Understanding JavaScript's **[equality operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality)** is crucial because comparison bugs are among the most common in JavaScript code. This guide will teach you exactly how `==`, **[`===`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality)**, and **[`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)** work — and when to use each one. +Understanding JavaScript's **[equality operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality)** is crucial because comparison bugs are among the most common in JavaScript code. This guide will teach you exactly how `==`, **[`===`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality)**, and **[`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)** work, and when to use each one. <Info> **What you'll learn in this guide:** @@ -29,7 +29,7 @@ Understanding JavaScript's **[equality operators](https://developer.mozilla.org/ </Info> <Warning> -**Prerequisites:** This guide assumes you understand [Primitive Types](/concepts/primitive-types) and [Type Coercion](/concepts/type-coercion). Equality operators rely heavily on how JavaScript converts types — if those concepts are new to you, read those guides first! +**Prerequisites:** This guide assumes you understand [Primitive Types](/concepts/primitive-types) and [Type Coercion](/concepts/type-coercion). Equality operators rely heavily on how JavaScript converts types. If those concepts are new to you, read those guides first! </Warning> --- @@ -55,7 +55,7 @@ console.log(Object.is(num, str)); // false (different types) ``` <Note> -**The simple rule:** Always use `===` for comparisons. The only exception: use `== null` to check if a value is empty (null or undefined). You'll rarely need `Object.is()` — it's for special cases we'll cover later. +**The simple rule:** Always use `===` for comparisons. The only exception: use `== null` to check if a value is empty (null or undefined). You'll rarely need `Object.is()`. It's for special cases we'll cover later. </Note> --- @@ -373,7 +373,7 @@ true // 0 equals 0! ``` <Warning> -This example shows why `==` can produce unexpected results. An empty array appears to equal its own negation! This isn't a bug — it's how JavaScript's conversion rules work. This is why most developers prefer `===`. +This example shows why `==` can produce unexpected results. An empty array appears to equal its own negation! This isn't a bug. It's how JavaScript's conversion rules work. This is why most developers prefer `===`. </Warning> ### When `==` Might Be Useful @@ -427,7 +427,7 @@ When you write `x === y`, JavaScript asks: 1. Are `x` and `y` the same type? No → return `false` 2. Same type? → Compare their values -That's it. No conversions, no surprises (well, *almost* — there's one special case with **[`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN)**). +That's it. No conversions, no surprises (well, *almost*. There's one special case with **[`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN)**). ### The Strict Equality Comparison Algorithm @@ -575,7 +575,7 @@ Even `===` has two edge cases that might surprise you: // NaN is the only value that is not equal to itself NaN === NaN // false! - // NaN doesn't equal anything — not even itself! + // NaN doesn't equal anything, not even itself! // This is part of how numbers work in all programming languages // This is by design (IEEE 754 specification) @@ -821,7 +821,7 @@ typeof(operand) // Both forms are valid Object.prototype.toString.call([]) // "[object Array]" ``` - Use **[`Array.isArray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)** — it's the most reliable method. + Use **[`Array.isArray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)**. It's the most reliable method. </Accordion> <Accordion title="Functions Return 'function'"> @@ -1114,7 +1114,7 @@ These common mistakes trip up many JavaScript developers. Learning about them no if (" " == false) { } // false (but " " is truthy) ``` - **Why it's confusing:** The `==` operator doesn't check truthiness — it performs type coercion according to specific rules. + **Why it's confusing:** The `==` operator doesn't check truthiness. It performs type coercion according to specific rules. **The fix:** ```javascript @@ -1267,7 +1267,7 @@ These common mistakes trip up many JavaScript developers. Learning about them no </Accordion> <Accordion title="Misconception 2: '=== checks if types are the same'"> - **Partially wrong!** `===` doesn't *just* check types — it checks if two values are the **same type AND same value**. + **Partially wrong!** `===` doesn't *just* check types. It checks if two values are the **same type AND same value**. ```javascript // Same type, different values → false @@ -1353,7 +1353,7 @@ These common mistakes trip up many JavaScript developers. Learning about them no ## Key Takeaways <Info> -**Remember these essential points about Equality Operators:** +**The key things to remember about Equality Operators:** 1. **Use `===` by default** — It's predictable and doesn't convert types @@ -1512,7 +1512,7 @@ Try to answer each question before revealing the solution: Array.isArray(null) // false ``` - **Why not `typeof`?** Because `typeof [] === "object"` — arrays are objects in JavaScript. + **Why not `typeof`?** Because `typeof [] === "object"`. Arrays are objects in JavaScript. **Why not `instanceof Array`?** It works in most cases, but can fail across different JavaScript realms (like iframes) where each realm has its own `Array` constructor. </Accordion> @@ -1556,19 +1556,19 @@ Try to answer each question before revealing the solution: <CardGroup cols={2}> <Card title="JavaScript Double Equals vs. Triple Equals — Brandon Morelli" icon="newspaper" href="https://codeburst.io/javascript-double-equals-vs-triple-equals-61d4ce5a121a"> - Beginner-friendly explanation of the differences between == and === with clear examples and use cases. + Uses side-by-side code comparisons to show exactly when == and === produce different results. Great starting point if you're new to JavaScript equality. </Card> <Card title="What is the difference between == and === in JavaScript? — Craig Buckler" icon="newspaper" href="https://www.oreilly.com/learning/what-is-the-difference-between-and-in-javascript"> - Authoritative O'Reilly article explaining equality operators with practical guidance on when to use each. + O'Reilly's take on the equality debate with a clear recommendation on which operator to default to. Includes the edge cases that trip up even experienced developers. </Card> <Card title="=== vs == Comparison in JavaScript — FreeCodeCamp" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-triple-equals-sign-vs-double-equals-sign-comparison-operators-explained-with-examples/"> - Comprehensive guide with numerous examples showing how both operators behave in different scenarios. + Walks through the type coercion algorithm step-by-step with dozens of examples. The boolean comparison section explains why `true == "true"` returns false. </Card> <Card title="Checking Types in Javascript — Toby Ho" icon="newspaper" href="http://tobyho.com/2011/01/28/checking-types-in-javascript/"> - In-depth exploration of typeof, instanceof, and alternative type-checking strategies. + Covers the limitations of typeof and when to use instanceof, Object.prototype.toString, or duck typing instead. Includes a reusable type-checking utility function. </Card> <Card title="How to better check data types in JavaScript — Webbjocke" icon="newspaper" href="https://webbjocke.com/javascript-check-data-types/"> - Modern approaches to type checking with practical utility functions you can use in your projects. + Provides copy-paste utility functions for checking arrays, objects, nulls, and primitives. Explains why each approach works and when to use which method. </Card> <Card title="JavaScript Equality Table — dorey" icon="newspaper" href="https://dorey.github.io/JavaScript-Equality-Table/"> Visual comparison table showing the results of == and === for all type combinations. Essential reference! @@ -1581,13 +1581,13 @@ Try to answer each question before revealing the solution: <CardGroup cols={2}> <Card title='JavaScript "==" VS "===" — Web Dev Simplified' icon="video" href="https://www.youtube.com/watch?v=C5ZVC4HHgIg"> - Clear, visual explanation of equality operators with practical examples. Perfect for visual learners. + 8-minute breakdown with on-screen code examples showing type coercion in action. Kyle's explanation of the null/undefined special case is particularly helpful. </Card> <Card title="JavaScript - The typeof operator — Java Brains" icon="video" href="https://www.youtube.com/watch?v=ol_su88I3kw"> - Comprehensive coverage of the typeof operator, its quirks, and when to use alternatives. + Demonstrates the typeof null bug and explains why it exists. Shows how to build a reliable type-checking function that handles all edge cases. </Card> <Card title="=== vs == in JavaScript — Hitesh Choudhary" icon="video" href="https://www.youtube.com/watch?v=a0S1iG3TgP0"> - Popular explanation of equality operators with real-world coding examples. + Live coding session showing surprising equality results and debugging them in the console. Great for seeing how these operators behave in real development. </Card> <Card title="== ? === ??? ...#@^% — Shirmung Bielefeld" icon="video" href="https://www.youtube.com/watch?v=qGyqzN0bjhc&t"> Conference talk diving deep into JavaScript's equality quirks and type coercion weirdness. From a9dbddc574294b48b2af42945a4dec07d0417d9c Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 14:51:53 -0300 Subject: [PATCH 082/128] docs: improve generators-iterators page copywriting and em dash usage --- docs/concepts/generators-iterators.mdx | 47 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/concepts/generators-iterators.mdx b/docs/concepts/generators-iterators.mdx index 747791f7..1d20279b 100644 --- a/docs/concepts/generators-iterators.mdx +++ b/docs/concepts/generators-iterators.mdx @@ -1,7 +1,7 @@ --- title: "Generators & Iterators: Pausable Functions in JavaScript" sidebarTitle: "Generators & Iterators: Pausable Functions" -description: "Learn JavaScript generators and iterators — functions that can pause and resume execution. Master the yield keyword, iteration protocols, lazy evaluation, and async generators." +description: "Learn JavaScript generators and iterators. Understand yield, the iteration protocol, lazy evaluation, infinite sequences, and async generators with for await...of." --- What if a function could pause mid-execution, return a value, and then resume right where it left off? What if you could create a sequence of values that are computed only when you ask for them — not all at once? @@ -21,7 +21,7 @@ console.log(counter.next().value) // 2 console.log(counter.next().value) // 3 ``` -This is the power of **[generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)** — functions that can pause with `yield` and pick up where they left off. Combined with **[iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)** (objects that define how to step through a sequence), they open up patterns like lazy evaluation, infinite sequences, and clean data pipelines. +This is the power of **[generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)**. These are functions that can pause with `yield` and pick up where they left off. Combined with **[iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)** (objects that define how to step through a sequence), they open up patterns like lazy evaluation, infinite sequences, and clean data pipelines. <Info> **What you'll learn in this guide:** @@ -42,7 +42,7 @@ This is the power of **[generators](https://developer.mozilla.org/en-US/docs/Web ## What is an Iterator? -Before getting into generators, we need to cover **iterators** — the foundation that makes generators work. +Before getting into generators, we need to cover **iterators**, the foundation that makes generators work. An **[iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol)** is an object that defines a sequence and provides a way to access values one at a time. It must have a `.next()` method that returns an object with two properties: @@ -80,7 +80,7 @@ Why not just use an array? Two reasons: 1. **Lazy evaluation** — Values are computed only when you ask for them, not upfront 2. **Memory efficiency** — You don't need to hold the entire sequence in memory -Say you need to process a million records. With an array, you'd load all million into memory. With an iterator, you process one at a time — memory stays flat. +Say you need to process a million records. With an array, you'd load all million into memory. With an iterator, you process one at a time. Memory stays flat. ### Built-in Iterables @@ -156,7 +156,7 @@ Here's how this maps to generator concepts: | Machine remembers position | Generator remembers its state | | Machine is empty | `done: true` | -A generator works the same way — one value at a time, pausing between each. +A generator works the same way: one value at a time, pausing between each. --- @@ -178,7 +178,7 @@ function* myGenerator() { } ``` -When you call a generator function, the code inside doesn't run yet — you just get back a **generator object** (which is an iterator): +When you call a generator function, the code inside doesn't run yet. You just get back a **generator object** (which is an iterator): ```javascript const gen = myGenerator() // Nothing logs yet! @@ -404,7 +404,7 @@ console.log(chat.next('Blue').value) ## The Iteration Protocol (`Symbol.iterator`) -Now for the fun part — making your own objects work with `for...of`. An object is **iterable** if it has a `[Symbol.iterator]` method that returns an iterator. +Now for the fun part: making your own objects work with `for...of`. An object is **iterable** if it has a `[Symbol.iterator]` method that returns an iterator. ### Making a Custom Object Iterable @@ -497,6 +497,23 @@ for (const n of new Range(1, 3)) { When you write a `for...of` loop, JavaScript does this behind the scenes: +<Steps> + <Step title="Get the iterator"> + JavaScript calls `iterable[Symbol.iterator]()` to get an iterator object. + </Step> + <Step title="Call .next()"> + The loop calls `iterator.next()` to get the first `{ value, done }` result. + </Step> + <Step title="Check if done"> + If `done` is `false`, the `value` goes into your loop variable. + </Step> + <Step title="Repeat until done"> + Steps 2-3 repeat until `done` is `true`, then the loop exits. + </Step> +</Steps> + +Here's what that looks like in code: + ```javascript // This: for (const item of iterable) { @@ -522,7 +539,7 @@ while (!result.done) { ## Lazy Evaluation & Infinite Sequences -The killer feature of generators is **lazy evaluation** — values are computed only when you ask for them, not ahead of time. +The killer feature of generators is **lazy evaluation**. Values are computed only when you ask for them, not ahead of time. ### Memory Efficiency @@ -556,7 +573,7 @@ console.log([...rangeGenerator(1, 5)]) // [1, 2, 3, 4, 5] ### Infinite Sequences -Because generators are lazy, you can create **infinite sequences** — something impossible with arrays: +Because generators are lazy, you can create **infinite sequences**, something impossible with arrays: ```javascript // Infinite sequence of natural numbers @@ -578,7 +595,7 @@ console.log(numbers.next().value) // 3 ### Fibonacci Sequence -A classic example — the infinite Fibonacci sequence: +A classic example: the infinite Fibonacci sequence: ```javascript function* fibonacci() { @@ -628,7 +645,7 @@ console.log(firstFive) // [1, 2, 3, 4, 5] ``` <Warning> -**Be careful with infinite generators!** Never use `[...infiniteGenerator()]` or `for...of` on an infinite generator without a break condition — your program will hang trying to iterate forever. +**Be careful with infinite generators!** Never use `[...infiniteGenerator()]` or `for...of` on an infinite generator without a break condition. Your program will hang trying to iterate forever. ```javascript // ❌ DANGER — This will hang/crash! @@ -791,7 +808,7 @@ function changeLight() { ### Pattern 5: Tree Traversal -Generators are excellent for traversing tree structures: +Generators work great for traversing trees: ```javascript function* traverseTree(node) { @@ -831,7 +848,7 @@ console.log([...traverseTree(tree)]) ## Async Generators & `for await...of` -What about yielding values from async operations — API calls, file reads, that kind of thing? That's what **async generators** are for. +What about yielding values from async operations like API calls, file reads, that kind of thing? That's what **async generators** are for. ### The Problem with Regular Generators @@ -1267,7 +1284,7 @@ async function processLogFile(url) { console.log(gen.next(5).value) // "Sum: 15" (b = 5) ``` - Note: The first `.next()` starts the generator — any value passed to it is ignored because there's no `yield` waiting to receive it yet. + Note: The first `.next()` starts the generator. Any value passed to it is ignored because there's no `yield` waiting to receive it yet. </Accordion> <Accordion title="Question 5: When would you use an async generator?"> @@ -1304,7 +1321,7 @@ async function processLogFile(url) { <Accordion title="Question 6: Why can't you use [...infiniteGenerator()]?"> **Answer:** - Spread syntax (`...`) tries to collect ALL values into an array. With an infinite generator, this means infinite iteration — your program will hang trying to collect infinite values. + Spread syntax (`...`) tries to collect ALL values into an array. With an infinite generator, this means infinite iteration. Your program will hang trying to collect infinite values. ```javascript function* forever() { From f45141ceef912a35610093654f3ce5e7aa3d0835 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 15:00:37 -0300 Subject: [PATCH 083/128] docs: improve type-coercion page copywriting and resource descriptions --- docs/concepts/type-coercion.mdx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index edcaf7cc..a2a8b238 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -13,7 +13,7 @@ console.log("5" - 3); // 2 (numeric subtraction) console.log([] == ![]); // true (wait, what?!) ``` -This surprising behavior is **[type coercion](https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion)** — JavaScript automatically converting values from one type to another. Understanding these rules helps you avoid bugs and write more predictable code. +This surprising behavior is **[type coercion](https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion)**. JavaScript automatically converts values from one type to another. Understanding these rules helps you avoid bugs and write more predictable code. <Info> **What you'll learn in this guide:** @@ -33,11 +33,11 @@ This surprising behavior is **[type coercion](https://developer.mozilla.org/en-U ## What Is Type Coercion? -**Type coercion** is the automatic or implicit conversion of values from one data type to another in JavaScript. When you use operators or functions that expect a certain type, JavaScript will convert (coerce) values to make the operation work — sometimes helpfully, sometimes surprisingly. Understanding these conversion rules is essential for writing predictable, bug-free code. +**Type coercion** is the automatic or implicit conversion of values from one data type to another in JavaScript. When you use operators or functions that expect a certain type, JavaScript will convert (coerce) values to make the operation work, sometimes helpfully, sometimes surprisingly. Understanding these conversion rules helps you write predictable, bug-free code. ### The Shapeshifter Analogy -Imagine JavaScript as an overly helpful translator. When you give it values of different types, it tries to "help" by converting them — sometimes correctly, sometimes... creatively. +Imagine JavaScript as an overly helpful translator. When you give it values of different types, it tries to "help" by converting them, sometimes correctly, sometimes... creatively. ``` ┌─────────────────────────────────────────────────────────────────────────┐ @@ -57,7 +57,7 @@ Imagine JavaScript as an overly helpful translator. When you give it values of d └─────────────────────────────────────────────────────────────────────────┘ ``` -This "helpful" behavior is called **type coercion** — JavaScript automatically converting values from one type to another. Sometimes it's useful, sometimes it creates bugs that will haunt your dreams. +This "helpful" behavior is called **type coercion**. JavaScript automatically converts values from one type to another. Sometimes it's useful, sometimes it creates bugs that will haunt your dreams. ``` ┌─────────────────────────────────────────────────────────────────────────┐ @@ -99,7 +99,7 @@ There are two ways coercion happens: These functions — [`Number()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), [`String()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), [`Boolean()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean), [`parseInt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt), and [`parseFloat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat) — give you full control. - This is the **safe** way — you know exactly what's happening. + This is the **safe** way. You know exactly what's happening. </Tab> <Tab title="Implicit Coercion"> **JavaScript** automatically converts types when operators or functions expect a different type. @@ -120,7 +120,7 @@ There are two ways coercion happens: ### Why Does JavaScript Do This? -JavaScript is a **dynamically typed** language — variables don't have fixed types. This flexibility means JavaScript needs to figure out what to do when types don't match. +JavaScript is a **dynamically typed** language. Variables don't have fixed types. This flexibility means JavaScript needs to figure out what to do when types don't match. ```javascript // In JavaScript, variables can hold any type @@ -175,7 +175,7 @@ That's it. No matter how complex the coercion seems, the end result is always a ## String Conversion -String conversion is the most straightforward — almost anything can become a string. +String conversion is the most straightforward. Almost anything can become a string. ### When Does It Happen? @@ -210,7 +210,7 @@ The [`toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer ### The + Operator's Split Personality -The `+` operator is special — it does **both** addition and concatenation: +The `+` operator is special: it does **both** addition and concatenation: ```javascript // With two numbers: addition @@ -295,7 +295,7 @@ undefined + 5 // NaN ### Math Operators Always Convert to Numbers -Unlike `+`, the other math operators (`-`, `*`, `/`, `%`) **only** do math — they always convert to numbers: +Unlike `+`, the other math operators (`-`, `*`, `/`, `%`) **only** do math. They always convert to numbers: ```javascript "6" - "2" // 4 (both become numbers) @@ -326,7 +326,7 @@ The unary `+` (plus sign before a value) is a quick way to convert to a number: ## Boolean Conversion -Boolean conversion is actually the simplest — every value is either **truthy** or **falsy**. +Boolean conversion is actually the simplest. Every value is either **truthy** or **falsy**. ### When Does It Happen? @@ -399,7 +399,7 @@ if (arr.length === 0) { } // checks if array is empty ### Logical Operators Don't Return Booleans! -A common misconception: `&&` and `||` don't necessarily return `true` or `false` — they return one of the **original values**: +A common misconception: `&&` and `||` don't necessarily return `true` or `false`. They return one of the **original values**: ```javascript // || returns the FIRST truthy value (or the last value) @@ -854,7 +854,7 @@ function process(count) { ## Key Takeaways <Info> -**Remember these essentials about Type Coercion:** +**The key things to remember about Type Coercion:** 1. **Three conversions only** — JavaScript converts to String, Number, or Boolean — nothing else @@ -1009,9 +1009,9 @@ function process(count) { Entertaining JSConf talk by Shirmung Bielefeld exploring the chaos of JavaScript equality operators with live examples and audience participation. </Card> <Card title="Coercion in Javascript — Hitesh Choudhary" icon="video" href="https://www.youtube.com/watch?v=b04Q_vyqEG8"> - Clear explanation of type coercion basics from popular JavaScript educator Hitesh Choudhary. Great for visual learners. + Hitesh walks through coercion step-by-step in the browser console, showing exactly what JavaScript does at each conversion. Good pace for beginners. </Card> <Card title="What is Coercion? — Steven Hancock" icon="video" href="https://www.youtube.com/watch?v=z4-8wMSPJyI"> - Beginner-friendly introduction to coercion concepts with practical examples and clear explanations. + Steven breaks down the three conversion types (string, number, boolean) with simple examples. Short video that covers the fundamentals quickly. </Card> </CardGroup> From e0cf7a2bb3bc8add4e55cfab86f043e02ce5d1b0 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 15:29:24 -0300 Subject: [PATCH 084/128] docs: improve value-reference-types page copywriting and resource descriptions --- docs/concepts/value-reference-types.mdx | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/concepts/value-reference-types.mdx b/docs/concepts/value-reference-types.mdx index b07d705f..181bbc0f 100644 --- a/docs/concepts/value-reference-types.mdx +++ b/docs/concepts/value-reference-types.mdx @@ -14,7 +14,7 @@ copy.name = "Bob"; console.log(original.name); // "Bob" — Wait, what?! ``` -The answer lies in how JavaScript stores data in memory. **Primitives** (like numbers and strings) store actual values, while **objects** store *references* (pointers) to data. This difference is one of the most important concepts in JavaScript — and the source of countless bugs. +The answer lies in how JavaScript stores data in memory. **Primitives** (like numbers and strings) store actual values, while **objects** store *references* (pointers) to data. This difference causes countless bugs in JavaScript code. <Info> **What you'll learn in this guide:** @@ -84,7 +84,7 @@ Imagine you have two ways to share information with a friend: **Sticky Note (Value Types):** You write "42" on a sticky note and hand it to your friend. They now have their own note with "42" on it. If they change their note to "100", your note still says "42". You each have independent copies. -**Map to Treasure (Reference Types):** Instead of giving your friend the treasure itself, you give them a map to where the treasure is buried. Now you BOTH have maps pointing to the SAME treasure. If they dig it up and add more gold, you'll see the extra gold too — because you're both looking at the same treasure! +**Map to Treasure (Reference Types):** Instead of giving your friend the treasure itself, you give them a map to where the treasure is buried. Now you BOTH have maps pointing to the SAME treasure. If they dig it up and add more gold, you'll see the extra gold too, because you're both looking at the same treasure! ``` ┌─────────────────────────────────────────────────────────────────────────┐ @@ -110,7 +110,7 @@ Imagine you have two ways to share information with a friend: This difference between "storing the value itself" vs "storing a map to the value" is fundamental to understanding JavaScript. <Tip> -**Quick Rule:** Primitives store the actual value — copying creates an independent copy. Objects and arrays store a *reference* (pointer) — copying creates another pointer to the SAME data. +**Quick Rule:** Primitives store the actual value. Copying creates an independent copy. Objects and arrays store a *reference* (pointer). Copying creates another pointer to the SAME data. </Tip> --- @@ -214,7 +214,7 @@ let scores = [95, 87, 92]; // Reference on stack, array on heap ## Copying Behavior: The Critical Difference -This is where things get interesting — and where bugs love to hide. +This is where things get interesting, and where bugs love to hide. ### Copying Primitives: Independent Copies @@ -247,7 +247,7 @@ STEP 1: let a = 10 STEP 2: let b = a STEP 3: b = 20 ### Copying Objects: Shared References -When you copy an object, you copy the *reference* — both variables now point to the SAME object: +When you copy an object, you copy the *reference*. Both variables now point to the SAME object: ```javascript let obj1 = { name: "Alice" }; @@ -388,7 +388,7 @@ _.isEqual(obj1, obj2) Here's a topic that confuses even experienced developers: -**JavaScript is ALWAYS "pass by value"** — but when passing objects, the *value being passed is a reference*. +**JavaScript is ALWAYS "pass by value"**, but when passing objects, the *value being passed is a reference*. ### Passing Primitives @@ -425,7 +425,7 @@ BEFORE double(x): INSIDE double(x): AFTER double(x): ### Passing Objects: Mutation WORKS -When you pass an object, the function receives a copy of the reference — both point to the same object: +When you pass an object, the function receives a copy of the reference. Both point to the same object: ```javascript function rename(person) { @@ -555,7 +555,7 @@ obj = { name: "Eve" }; // TypeError: Assignment to constant variable ## True Immutability with [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) -We learned that `const` doesn't make objects immutable — it only prevents reassignment. But what if you NEED a truly immutable object? +We learned that `const` doesn't make objects immutable. It only prevents reassignment. But what if you NEED a truly immutable object? ### Object.freeze(): Shallow Immutability @@ -954,7 +954,7 @@ const deep = JSON.parse(JSON.stringify(original)); </Accordion> <Accordion title="7. Memory Leaks from Forgotten References (Advanced)"> - When you store objects in Maps or arrays, those references prevent garbage collection — even if you don't need the object anymore. + When you store objects in Maps or arrays, those references prevent garbage collection, even if you don't need the object anymore. ```javascript // Potential memory leak: cache holds references forever @@ -1037,7 +1037,7 @@ const deep = JSON.parse(JSON.stringify(original)); ## Key Takeaways <Info> -**Remember these essential points:** +**The key things to remember:** 1. **Value types** (primitives) store values directly; **reference types** store pointers @@ -1244,10 +1244,10 @@ const deep = JSON.parse(JSON.stringify(original)); Clear explanation by Arnav Aggarwal with visual diagrams showing how primitives and objects behave differently in memory. </Card> <Card title="JavaScript Primitive vs. Reference Values" icon="newspaper" href="https://www.javascripttutorial.net/javascript-primitive-vs-reference-values/"> - Comprehensive tutorial with excellent stack/heap diagrams and practical examples. + JavaScript Tutorial's guide includes animated diagrams showing stack vs heap memory allocation. Covers copying behavior and function parameter passing with runnable code examples. </Card> <Card title="Back to roots: JavaScript Value vs Reference" icon="newspaper" href="https://medium.com/dailyjs/back-to-roots-javascript-value-vs-reference-8fb69d587a18"> - Deep dive by Miro Koczka into how JavaScript handles values and references. + Miro Koczka explains why `const` doesn't make objects immutable and how to avoid accidental mutations. Includes common bug patterns and fixes. </Card> <Card title="You Don't Know JS: Types & Grammar" icon="book" href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/types-grammar/README.md"> Kyle Simpson's definitive guide to JavaScript's type system. Free to read online. @@ -1258,10 +1258,10 @@ const deep = JSON.parse(JSON.stringify(original)); <CardGroup cols={2}> <Card title="JavaScript Value vs Reference Types — Programming with Mosh" icon="video" href="https://www.youtube.com/watch?v=fD0t_DKREbE"> - Clear, beginner-friendly explanation from one of the most popular JavaScript educators. Great visual demonstrations of how memory works. + Mosh uses whiteboard diagrams to show exactly how primitives live on the stack while objects live on the heap. 10-minute video that makes memory concepts click. </Card> <Card title="Reference vs Primitive Values — Academind" icon="video" href="https://www.youtube.com/watch?v=9ooYYRLdg_g"> - Thorough walkthrough of how primitives and reference types differ, with live coding examples showing the implications for real code. + Academind's Max demonstrates mutation bugs in real-time, then shows how to fix them with spread operators and structuredClone. Good for seeing the problems before learning solutions. </Card> <Card title="Javascript Pass by Value vs Pass by Reference — techsith" icon="video" href="https://www.youtube.com/watch?v=E-dAnFdq8k8"> Focused explanation of how function parameters work with primitives vs objects. Great for understanding the "pass by value of the reference" concept. From b41c92b98eefd1ed79a01770ba5938239d544774 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 15:30:20 -0300 Subject: [PATCH 085/128] docs: add comprehensive Web Workers concept page - Complete rewrite from skeleton to ~1600 line comprehensive guide - Cover async vs parallel distinction with ASCII diagrams - Add restaurant analogy (multiple chefs) extending event-loop concept - Document postMessage, structured cloning, and transferable objects - Cover Dedicated, Shared, and Service Workers (brief overview) - Add OffscreenCanvas section for graphics in workers - Include Common Mistakes, Real-World Patterns, Worker Pools - Add inline workers (Blob URL trick) pattern - 10 Key Takeaways, 6 Test Your Knowledge Q&As - SEO optimized with question-format H2s for featured snippets - 4 MDN references, 4 articles, 3 videos in resources --- docs/concepts/web-workers.mdx | 1622 ++++++++++++++++++++++++++++++++- 1 file changed, 1587 insertions(+), 35 deletions(-) diff --git a/docs/concepts/web-workers.mdx b/docs/concepts/web-workers.mdx index e1ff02a7..15f1bac2 100644 --- a/docs/concepts/web-workers.mdx +++ b/docs/concepts/web-workers.mdx @@ -1,69 +1,1621 @@ --- -title: "Web Workers: Parallel JavaScript Execution" -sidebarTitle: "Web Workers: Parallel JavaScript" -description: "Learn Web Workers in JavaScript — run scripts in background threads for parallel execution. Master dedicated workers, shared workers, and off-main-thread processing for better performance." +title: "Web Workers: True Parallelism in JavaScript" +sidebarTitle: "Web Workers: True Parallelism" +description: "Learn Web Workers in JavaScript for running code in background threads. Understand postMessage, Dedicated and Shared Workers, and transferable objects." --- -## Overview +Ever clicked a button and watched your entire page freeze? Tried to scroll while a script was running and nothing happened? -**Web Workers** allow JavaScript to run scripts in background threads, separate from the main execution thread. This enables parallel processing without blocking the UI, making them essential for CPU-intensive tasks like data processing, image manipulation, and complex calculations. +```javascript +// This will freeze your entire page for ~5 seconds +function heavyCalculation() { + const start = Date.now() + while (Date.now() - start < 5000) { + // Simulating heavy work + } + return 'Done!' +} + +document.getElementById('btn').addEventListener('click', () => { + console.log('Starting...') + const result = heavyCalculation() // Page freezes here + console.log(result) +}) + +// During those 5 seconds: +// - Can't click anything +// - Can't scroll +// - Animations stop +// - The page looks broken +``` + +That's JavaScript's single thread at work. But there's a way out: **[Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)**. They let you run JavaScript in background threads, keeping your UI smooth while crunching numbers, parsing data, or processing images. <Info> **What you'll learn in this guide:** -- Why JavaScript needs Web Workers (single-threaded limitations) -- Creating and communicating with workers (`postMessage`, `onmessage`) -- Dedicated Workers vs Shared Workers vs Service Workers -- Transferable objects for efficient data transfer -- Worker limitations (no DOM access, different global scope) -- Use cases: heavy computations, data parsing, image processing -- Worker pools and performance patterns -- Comlink and other worker libraries +- Why JavaScript's single thread causes UI freezes (and why async doesn't help) +- How Web Workers provide true parallelism (not just concurrency) +- Creating workers and communicating with `postMessage` +- The difference between Dedicated, Shared, and Service Workers +- Transferable objects for moving large data without copying +- OffscreenCanvas for graphics processing in workers +- Real-world patterns: worker pools, inline workers, heavy computations </Info> <Warning> -**Prerequisites:** This guide builds on your understanding of [the Event Loop](/concepts/event-loop) and [async JavaScript](/concepts/async-await). Web Workers provide true parallelism, unlike async code which is still single-threaded. +**Prerequisites:** This guide builds on [the Event Loop](/concepts/event-loop) and [async/await](/concepts/async-await). Understanding those concepts will help you see why Web Workers solve problems that async code can't. +</Warning> + +--- + +## The Problem: Why Async Isn't Enough + +You might think: "I already know async JavaScript. Doesn't that solve the freezing problem?" + +Not quite. Here's the thing everyone gets wrong about async: **async JavaScript is still single-threaded**. It's concurrent, not parallel. + +```javascript +// Async code is NOT running at the same time +async function fetchData() { + console.log('1: Starting fetch') + const response = await fetch('/api/data') // Waits, but doesn't block + console.log('3: Got response') + return response.json() +} + +console.log('0: Before fetch') +fetchData() +console.log('2: After fetch call') + +// Output: +// 0: Before fetch +// 1: Starting fetch +// 2: After fetch call +// 3: Got response (later) +``` + +The `await` lets other code run while waiting for the network. But here's the catch: **the actual JavaScript execution is still one thing at a time**. + +### The CPU-Bound Problem + +Async works great for I/O operations (network requests, file reads) because you're waiting for something external. But what about CPU-bound tasks? + +```javascript +// This async function STILL freezes the page +async function processLargeArray(data) { + const results = [] + + // This loop is synchronous JavaScript + // The "async" keyword doesn't help here! + for (let i = 0; i < data.length; i++) { + results.push(expensiveCalculation(data[i])) + } + + return results +} + +// The page freezes during the loop +// async/await only helps with WAITING, not COMPUTING +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ASYNC VS PARALLEL: THE DIFFERENCE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ASYNC (Concurrency) PARALLEL (Web Workers) │ +│ ──────────────────── ───────────────────── │ +│ │ +│ Main Thread Main Thread Worker Thread │ +│ ┌─────────────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Task A │ │ Task A │ │ Task B │ │ +│ │ (work) │ │ (work) │ │ (work) │ │ +│ ├─────────────────┤ │ │ │ │ │ +│ │ Wait for I/O... │ ← yields │ │ │ │ │ +│ ├─────────────────┤ │ │ │ │ │ +│ │ Task B │ │ │ │ │ │ +│ │ (work) │ │ │ │ │ │ +│ ├─────────────────┤ └──────────┘ └──────────┘ │ +│ │ Task A resumed │ │ +│ └─────────────────┘ Both run at the SAME TIME │ +│ on different CPU cores │ +│ One thread, tasks take turns │ +│ │ +│ GOOD FOR: Network requests, GOOD FOR: Heavy calculations, │ +│ file reads, timers image processing, data parsing │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Tip> +**The Rule:** Use async/await when you're **waiting** for something. Use Web Workers when you're **computing** something heavy. +</Tip> + +--- + +## The Restaurant Analogy: Multiple Chefs + +If you've read our [Event Loop guide](/concepts/event-loop), you know JavaScript is like a restaurant with a **single chef**. The chef can only cook one dish at a time, but clever scheduling (the event loop) keeps things moving. + +Web Workers are like **hiring more chefs**. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE MULTI-CHEF KITCHEN (WEB WORKERS) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MAIN KITCHEN (Main Thread) PREP KITCHEN (Worker Thread) │ +│ ┌─────────────────────────┐ ┌─────────────────────────┐ │ +│ │ │ │ │ │ +│ │ HEAD CHEF │ │ PREP CHEF │ │ +│ │ ┌─────────┐ │ │ ┌─────────┐ │ │ +│ │ │ ^_^ │ │ │ │ ^_^ │ │ │ +│ │ └─────────┘ │ │ └─────────┘ │ │ +│ │ │ │ │ │ +│ │ • Takes customer │ │ • Chops vegetables │ │ +│ │ orders (events) │ │ • Preps ingredients │ │ +│ │ • Plates dishes (UI) │ │ • Heavy work │ │ +│ │ • Talks to customers │ │ • No customer contact │ │ +│ │ (DOM access) │ │ (no DOM!) │ │ +│ │ │ │ │ │ +│ └───────────┬─────────────┘ └───────────┬─────────────┘ │ +│ │ │ │ +│ │ ┌──────────────────┐ │ │ +│ │ │ SERVICE WINDOW │ │ │ +│ └─────►│ (postMessage) │◄─────────┘ │ +│ │ │ │ +│ │ "Need 50 onions │ │ +│ │ chopped!" │ │ +│ │ │ │ +│ │ "Here they are!"│ │ +│ └──────────────────┘ │ +│ │ +│ KEY RULES: │ +│ • Chefs can't share cutting boards (no shared memory by default) │ +│ • They communicate through the service window (postMessage) │ +│ • Prep chef can't talk to customers (workers can't touch the DOM) │ +│ • Prep chef has their own tools (workers have their own global scope) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +| Kitchen | JavaScript | +|---------|------------| +| **Head Chef** | Main thread (handles UI, events, DOM) | +| **Prep Chef** | Web Worker (handles heavy computation) | +| **Service Window** | `postMessage()` / `onmessage` (communication) | +| **Cutting Board** | Memory (each chef has their own) | +| **Customers** | Users interacting with the page | +| **Kitchen Rules** | Worker limitations (no DOM access) | + +The prep chef works independently in their own kitchen. They can't talk to customers (no DOM access), but they can do heavy prep work without slowing down the head chef. When they're done, they pass the result through the service window. + +--- + +## What is a Web Worker? + +A **[Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker)** is a JavaScript script that runs in a background thread, separate from the main thread. It has its own global scope, its own event loop, and executes truly in parallel with your main code. Workers communicate with the main thread through message passing using `postMessage()` and `onmessage`. This lets you run expensive computations without freezing the UI. + +Here's a basic example: + +```javascript +// main.js - runs on the main thread +const worker = new Worker('worker.js') + +// Send data to the worker +worker.postMessage({ numbers: [1, 2, 3, 4, 5] }) + +// Receive results from the worker +worker.onmessage = (event) => { + console.log('Result from worker:', event.data) +} +``` + +```javascript +// worker.js - runs in a separate thread +self.onmessage = (event) => { + const { numbers } = event.data + + // Do heavy computation (won't freeze the UI!) + const sum = numbers.reduce((a, b) => a + b, 0) + + // Send result back to main thread + self.postMessage({ sum }) +} +``` + +<Note> +Inside a worker, `self` refers to the worker's global scope (a [`DedicatedWorkerGlobalScope`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope)). You can also use `this` at the top level, but `self` is clearer. +</Note> + +### The Communication Model + +Workers and the main thread communicate through **messages**. They can't directly access each other's variables. This is intentional: it prevents the race conditions and bugs that plague traditional multi-threaded programming. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ WORKER COMMUNICATION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MAIN THREAD WORKER THREAD │ +│ ┌───────────────────────┐ ┌───────────────────────┐ │ +│ │ │ postMessage │ │ │ +│ │ const worker = │ ─────────────► │ self.onmessage = │ │ +│ │ new Worker(...) │ │ (event) => {...} │ │ +│ │ │ │ │ │ +│ │ worker.postMessage() │ │ // Do heavy work │ │ +│ │ │ │ │ │ +│ │ worker.onmessage = │ ◄───────────── │ self.postMessage() │ │ +│ │ (event) => {...} │ postMessage │ │ │ +│ │ │ │ │ │ +│ └───────────────────────┘ └───────────────────────┘ │ +│ │ +│ DATA IS COPIED (by default), not shared │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## How Do You Create a Web Worker? + +There are two ways to create workers: the **classic way** (original syntax) and the **module way** (modern, recommended). + +### Classic Workers + +The original way to create workers uses `importScripts()` for loading dependencies: + +```javascript +// main.js +const worker = new Worker('worker.js') + +worker.postMessage('Hello from main!') + +worker.onmessage = (event) => { + console.log('Worker said:', event.data) +} + +worker.onerror = (error) => { + console.error('Worker error:', error.message) +} +``` + +```javascript +// worker.js (classic style) +importScripts('https://example.com/some-library.js') // Load dependencies + +self.onmessage = (event) => { + console.log('Main said:', event.data) + + // Do some work... + + self.postMessage('Hello from worker!') +} +``` + +### Module Workers (Recommended) + +Modern browsers support module workers with `import`/`export`. This is cleaner and matches how you write other JavaScript: + +```javascript +// main.js +const worker = new Worker('worker.js', { type: 'module' }) + +worker.postMessage({ task: 'process', data: [1, 2, 3] }) + +worker.onmessage = (event) => { + console.log('Result:', event.data) +} +``` + +```javascript +// worker.js (module style) +import { processData } from './utils.js' // Standard ES modules! + +self.onmessage = (event) => { + const { task, data } = event.data + + if (task === 'process') { + const result = processData(data) + self.postMessage(result) + } +} +``` + +<Tip> +**Use module workers** whenever possible. They support `import`/`export`, have strict mode by default, and work better with modern tooling. Check [browser support](https://caniuse.com/mdn-api_worker_worker_options_type_parameter) before using in production. +</Tip> + +### Comparison: Classic vs Module Workers + +| Feature | Classic Worker | Module Worker | +|---------|---------------|---------------| +| **Syntax** | `new Worker('file.js')` | `new Worker('file.js', { type: 'module' })` | +| **Dependencies** | `importScripts()` | `import` / `export` | +| **Strict mode** | Optional | Always on | +| **Top-level await** | No | Yes | +| **Browser support** | All browsers | Modern browsers | +| **Tooling** | Limited | Works with bundlers | + +--- + +## How Does postMessage Work? + +Communication between workers and the main thread happens through [`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage). Understanding how data is transferred is important for performance. + +### The Structured Clone Algorithm + +When you send data via `postMessage`, it's **copied** using the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This is deeper than `JSON.stringify`: it handles more types and preserves object references within the data. + +```javascript +// main.js +const data = { + name: 'Alice', + scores: [95, 87, 92], + metadata: { + date: new Date(), + pattern: /test/gi + } +} + +worker.postMessage(data) +// The worker receives a COPY of this object +// Modifying it in the worker won't affect the original +``` + +### What Can Be Cloned? + +| Can Clone | Cannot Clone | +|-----------|--------------| +| Primitives (string, number, boolean, null, undefined) | Functions | +| Plain objects and arrays | DOM nodes | +| Date objects | Error objects (partially) | +| RegExp objects | Symbols | +| Blob, File, FileList | WeakMap, WeakSet | +| ArrayBuffer, TypedArrays | Objects with prototype chains | +| Map, Set | Getters/setters | +| ImageBitmap, ImageData | Proxies | + +```javascript +// ✓ These work +worker.postMessage({ + text: 'hello', + numbers: [1, 2, 3], + date: new Date(), + regex: /pattern/g, + binary: new Uint8Array([1, 2, 3]), + map: new Map([['a', 1], ['b', 2]]) +}) + +// ❌ These will throw errors +worker.postMessage({ + fn: () => console.log('hi'), // Functions can't be cloned + element: document.body, // DOM nodes can't be cloned + sym: Symbol('test') // Symbols can't be cloned +}) +``` + +<Warning> +**Performance trap:** Structured cloning can be slow for large objects. If you're passing megabytes of data, consider using Transferable objects instead (covered below). +</Warning> + +### Handling Errors + +Always set up error handlers for workers: + +```javascript +// main.js +const worker = new Worker('worker.js', { type: 'module' }) + +// Handle messages +worker.onmessage = (event) => { + console.log('Result:', event.data) +} + +// Handle errors thrown in the worker +worker.onerror = (event) => { + console.error('Worker error:', event.message) + console.error('File:', event.filename) + console.error('Line:', event.lineno) +} + +// Handle message errors (e.g., data can't be cloned) +worker.onmessageerror = (event) => { + console.error('Message error:', event) +} +``` + +### Using addEventListener (Alternative Syntax) + +You can also use `addEventListener` instead of `onmessage`: + +```javascript +// main.js +const worker = new Worker('worker.js', { type: 'module' }) + +worker.addEventListener('message', (event) => { + console.log('Result:', event.data) +}) + +worker.addEventListener('error', (event) => { + console.error('Error:', event.message) +}) +``` + +```javascript +// worker.js +self.addEventListener('message', (event) => { + const result = processData(event.data) + self.postMessage(result) +}) +``` + +--- + +## Transferable Objects: Zero-Copy Data Transfer + +Copying large amounts of data between threads is slow. For big ArrayBuffers, images, or binary data, use **transferable objects** to move data instead of copying it. + +### The Problem with Copying + +```javascript +// main.js +// Creating a 100MB buffer +const hugeBuffer = new ArrayBuffer(100 * 1024 * 1024) +const array = new Uint8Array(hugeBuffer) + +// Fill it with data +for (let i = 0; i < array.length; i++) { + array[i] = i % 256 +} + +console.time('copy') +worker.postMessage(hugeBuffer) // This COPIES 100MB - slow! +console.timeEnd('copy') // Could take hundreds of milliseconds +``` + +### The Solution: Transfer Ownership + +Instead of copying, you can **transfer** the buffer to the worker. The transfer is nearly instant, but the original becomes unusable: + +```javascript +// main.js +const hugeBuffer = new ArrayBuffer(100 * 1024 * 1024) +const array = new Uint8Array(hugeBuffer) + +// Fill with data... + +console.time('transfer') +// Second argument is an array of objects to transfer +worker.postMessage(hugeBuffer, [hugeBuffer]) +console.timeEnd('transfer') // Nearly instant! + +// WARNING: hugeBuffer is now "neutered" (empty) +console.log(hugeBuffer.byteLength) // 0 +console.log(array.length) // 0 +``` + +```javascript +// worker.js +self.onmessage = (event) => { + const buffer = event.data + console.log(buffer.byteLength) // 104857600 (100MB) + + // Process the data... + const array = new Uint8Array(buffer) + + // Transfer it back when done + self.postMessage(buffer, [buffer]) +} +``` + +### What Can Be Transferred? + +| Transferable Object | Use Case | +|--------------------|-----------| +| [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) | Raw binary data | +| [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) | Communication channels | +| [`ImageBitmap`](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap) | Image data for canvas | +| [`OffscreenCanvas`](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) | Canvas for off-main-thread rendering | +| [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | Streaming data | +| [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) | Streaming data | +| [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) | Streaming transforms | + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ COPY VS TRANSFER │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ COPY (Default) TRANSFER │ +│ ───────────── ──────── │ +│ │ +│ Main Thread Worker Thread Main Thread Worker Thread │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ [data] │ │ │ │ [data] │ │ │ │ +│ │ 100MB │ │ │ │ 100MB │ │ │ │ +│ └────┬────┘ └─────────┘ └────┬────┘ └─────────┘ │ +│ │ │ │ +│ │ copy │ move │ +│ ▼ ▼ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ [data] │ │ [data] │ │ [empty] │ │ [data] │ │ +│ │ 100MB │ │ 100MB │ │ 0MB │ │ 100MB │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +│ │ +│ • Slow (copies bytes) • Fast (moves pointer) │ +│ • Both have the data • Only one has the data │ +│ • Memory doubled • Memory unchanged │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Tip> +**Rule of thumb:** Transfer when the data is large (> 1MB) and you don't need to keep it in the sending context. Copy when the data is small or you need it in both places. +</Tip> + +--- + +## Types of Workers + +There are three types of workers in the browser, each with different purposes. + +### Dedicated Workers + +**[Dedicated Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker)** are the most common type. They're owned by a single script and can only communicate with that script. + +```javascript +// Only this script can talk to this worker +const worker = new Worker('worker.js', { type: 'module' }) +``` + +Use dedicated workers for: +- Heavy calculations +- Data processing +- Image manipulation +- Any task you want off the main thread + +### Shared Workers + +**[Shared Workers](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker)** can be accessed by multiple scripts, even across different browser tabs or iframes (as long as they're from the same origin). + +```javascript +// main.js (Tab 1) +const worker = new SharedWorker('shared-worker.js') + +worker.port.onmessage = (event) => { + console.log('Received:', event.data) +} + +worker.port.postMessage('Hello from Tab 1') +``` + +```javascript +// main.js (Tab 2) - connects to the SAME worker +const worker = new SharedWorker('shared-worker.js') + +worker.port.onmessage = (event) => { + console.log('Received:', event.data) +} + +worker.port.postMessage('Hello from Tab 2') +``` + +```javascript +// shared-worker.js +const connections = [] + +self.onconnect = (event) => { + const port = event.ports[0] + connections.push(port) + + port.onmessage = (e) => { + // Broadcast to all connected tabs + connections.forEach(p => { + p.postMessage(`Someone said: ${e.data}`) + }) + } + + port.start() +} +``` + +Use shared workers for: +- Shared state across tabs +- Single WebSocket connection for multiple tabs +- Shared cache or data layer +- Reducing resource usage for identical workers + +<Warning> +Shared Workers have limited browser support. Check [caniuse.com](https://caniuse.com/sharedworkers) before using. They're not supported in Safari at all. +</Warning> + +### Service Workers (Brief Overview) + +**[Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)** are a special type of worker designed for a different purpose: they act as a proxy between your web app and the network. They enable offline functionality, push notifications, and background sync. + +```javascript +// Registering a service worker (in main.js) +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js') + .then(registration => { + console.log('SW registered:', registration) + }) + .catch(error => { + console.log('SW registration failed:', error) + }) +} +``` + +```javascript +// sw.js - intercepts network requests +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request) + .then(response => response || fetch(event.request)) + ) +}) +``` + +| Feature | Dedicated Worker | Shared Worker | Service Worker | +|---------|-----------------|---------------|----------------| +| **Purpose** | Background computation | Shared computation | Network proxy, offline | +| **Lifetime** | While page is open | While any tab uses it | Independent of pages | +| **Communication** | `postMessage` | `port.postMessage` | `postMessage` + events | +| **DOM access** | No | No | No | +| **Network intercept** | No | No | Yes | +| **Scope** | Single script | Same-origin scripts | Controlled pages | + +<Note> +Service Workers are a deep topic with their own complexities around lifecycle, caching strategies, and updates. They deserve their own dedicated guide. For now, just know they exist and are different from Web Workers. +</Note> + +--- + +## OffscreenCanvas: Graphics in Workers + +Normally, canvas operations happen on the main thread. With [`OffscreenCanvas`](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas), you can move rendering to a worker, keeping the main thread free for user interactions. + +### Basic OffscreenCanvas Usage + +```javascript +// main.js +const canvas = document.getElementById('myCanvas') + +// Transfer control to an OffscreenCanvas +const offscreen = canvas.transferControlToOffscreen() + +const worker = new Worker('canvas-worker.js', { type: 'module' }) + +// Transfer the canvas to the worker +worker.postMessage({ canvas: offscreen }, [offscreen]) +``` + +```javascript +// canvas-worker.js +let ctx + +self.onmessage = (event) => { + if (event.data.canvas) { + const canvas = event.data.canvas + ctx = canvas.getContext('2d') + + // Start animation loop in the worker + animate() + } +} + +function animate() { + // Clear canvas + ctx.fillStyle = '#000' + ctx.fillRect(0, 0, 800, 600) + + // Draw something + ctx.fillStyle = '#0f0' + ctx.fillRect( + Math.random() * 700, + Math.random() * 500, + 100, + 100 + ) + + // Request next frame (in worker context) + requestAnimationFrame(animate) +} +``` + +### Real-World Use: Image Processing + +One common use for OffscreenCanvas is image processing: + +```javascript +// main.js +const worker = new Worker('image-worker.js', { type: 'module' }) + +async function processImage(file) { + const bitmap = await createImageBitmap(file) + + worker.postMessage({ + bitmap, + filter: 'grayscale' + }, [bitmap]) // Transfer the bitmap +} + +worker.onmessage = (event) => { + const processedBitmap = event.data.bitmap + + // Draw the result on a visible canvas + const canvas = document.getElementById('result') + const ctx = canvas.getContext('2d') + ctx.drawImage(processedBitmap, 0, 0) +} +``` + +```javascript +// image-worker.js +self.onmessage = async (event) => { + const { bitmap, filter } = event.data + + // Create an OffscreenCanvas matching the image size + const canvas = new OffscreenCanvas(bitmap.width, bitmap.height) + const ctx = canvas.getContext('2d') + + // Draw the image + ctx.drawImage(bitmap, 0, 0) + + // Get pixel data + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) + const data = imageData.data + + // Apply grayscale filter + if (filter === 'grayscale') { + for (let i = 0; i < data.length; i += 4) { + const avg = (data[i] + data[i + 1] + data[i + 2]) / 3 + data[i] = avg // R + data[i + 1] = avg // G + data[i + 2] = avg // B + // Alpha unchanged + } + } + + // Put processed data back + ctx.putImageData(imageData, 0, 0) + + // Convert to bitmap and send back + const resultBitmap = await createImageBitmap(canvas) + self.postMessage({ bitmap: resultBitmap }, [resultBitmap]) +} +``` + +<Tip> +OffscreenCanvas is great for games, data visualizations, and image/video processing. Anything that involves heavy canvas work can benefit from being moved to a worker. +</Tip> + +--- + +## What Can't Web Workers Do? + +Workers run in a restricted environment. Understanding what they **can't** do is just as important as knowing what they can. + +### No DOM Access + +Workers cannot access the DOM. They can't read or modify HTML elements: + +```javascript +// worker.js +// ❌ All of these will fail +document.getElementById('app') // document is undefined +window.location // window is undefined +document.createElement('div') // Can't create elements +element.addEventListener('click', fn) // Can't add event listeners +``` + +If you need to update the DOM based on worker results, send the data back to the main thread: + +```javascript +// worker.js +const result = heavyCalculation() +self.postMessage({ result }) // Send data to main thread +``` + +```javascript +// main.js +worker.onmessage = (event) => { + // Update DOM on the main thread + document.getElementById('result').textContent = event.data.result +} +``` + +### Different Global Scope + +Workers have their own global object: [`DedicatedWorkerGlobalScope`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope). Many familiar globals are missing or different: + +```javascript +// worker.js +console.log(self) // DedicatedWorkerGlobalScope +console.log(window) // undefined +console.log(document) // undefined +console.log(localStorage) // undefined +console.log(sessionStorage) // undefined +console.log(alert) // undefined +``` + +### What Workers CAN Access + +Workers aren't completely isolated. They have access to: + +| Available | Example | +|-----------|---------| +| [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) | `fetch('/api/data')` | +| [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) | Network requests | +| [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) / [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) | Timers | +| [`IndexedDB`](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) | Database storage | +| [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) | Real-time connections | +| [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) | Cryptographic operations | +| [`navigator`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) (partial) | `navigator.userAgent`, etc. | +| [`location`](https://developer.mozilla.org/en-US/docs/Web/API/WorkerLocation) (read-only) | URL information | +| [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) | Logging (appears in DevTools) | +| `importScripts()` | Load scripts (classic workers) | +| `import` / `export` | ES modules (module workers) | + +```javascript +// worker.js - These all work! +console.log('Worker started') + +setTimeout(() => { + console.log('Timer fired in worker') +}, 1000) + +fetch('/api/data') + .then(r => r.json()) + .then(data => { + self.postMessage(data) + }) +``` + +--- + +## Common Mistakes + +### Mistake #1: Trying to Access the DOM + +The most common mistake. It fails silently or throws cryptic errors: + +```javascript +// worker.js +// ❌ WRONG - This won't work +self.onmessage = (event) => { + const result = calculate(event.data) + document.getElementById('output').textContent = result // ERROR! +} + +// ✓ CORRECT - Send data back to main thread +self.onmessage = (event) => { + const result = calculate(event.data) + self.postMessage(result) // Main thread updates the DOM +} +``` + +### Mistake #2: Not Terminating Workers + +Workers consume resources. If you don't terminate them, they keep running: + +```javascript +// main.js +// ❌ WRONG - Creates a new worker for each click, never cleans up +button.addEventListener('click', () => { + const worker = new Worker('worker.js') + worker.postMessage(data) + worker.onmessage = (e) => showResult(e.data) + // Worker keeps running even after we're done! +}) + +// ✓ CORRECT - Terminate when done +button.addEventListener('click', () => { + const worker = new Worker('worker.js') + worker.postMessage(data) + worker.onmessage = (e) => { + showResult(e.data) + worker.terminate() // Clean up! + } +}) + +// ✓ BETTER - Reuse the same worker +const worker = new Worker('worker.js') +worker.onmessage = (e) => showResult(e.data) + +button.addEventListener('click', () => { + worker.postMessage(data) // Reuse existing worker +}) +``` + +### Mistake #3: Overusing Workers for Small Tasks + +Workers have overhead. Creating them, posting messages, and cloning data all take time: + +```javascript +// ❌ WRONG - Worker overhead exceeds computation time +const worker = new Worker('worker.js') +worker.postMessage([1, 2, 3]) // Adding 3 numbers doesn't need a worker + +// ✓ CORRECT - Just do it on the main thread +const sum = [1, 2, 3].reduce((a, b) => a + b, 0) +``` + +<Tip> +**Rule of thumb:** Only use workers for tasks that take more than 50-100ms. For quick operations, the overhead isn't worth it. +</Tip> + +### Mistake #4: Sending Functions to Workers + +Functions can't be cloned: + +```javascript +// ❌ WRONG - Functions can't be sent +worker.postMessage({ + data: [1, 2, 3], + callback: (result) => console.log(result) // ERROR! +}) + +// ✓ CORRECT - Send data, handle callback in onmessage +worker.postMessage({ data: [1, 2, 3] }) +worker.onmessage = (e) => console.log(e.data) // "Callback" on main thread +``` + +### Mistake #5: Forgetting Error Handling + +Workers fail silently if you don't handle errors: + +```javascript +// ❌ WRONG - Errors disappear +const worker = new Worker('worker.js') +worker.postMessage(data) +worker.onmessage = (e) => console.log(e.data) + +// ✓ CORRECT - Always handle errors +const worker = new Worker('worker.js') +worker.postMessage(data) +worker.onmessage = (e) => console.log(e.data) +worker.onerror = (e) => { + console.error('Worker error:', e.message) + console.error('In file:', e.filename, 'line:', e.lineno) +} +``` + +--- + +## Real-World Patterns + +### Pattern 1: Heavy Computation + +Moving CPU-intensive work off the main thread: + +```javascript +// main.js +const worker = new Worker('prime-worker.js', { type: 'module' }) + +document.getElementById('findPrimes').addEventListener('click', () => { + const max = parseInt(document.getElementById('max').value) + + document.getElementById('status').textContent = 'Calculating...' + document.getElementById('findPrimes').disabled = true + + worker.postMessage({ findPrimesUpTo: max }) +}) + +worker.onmessage = (event) => { + const { primes, timeTaken } = event.data + + document.getElementById('status').textContent = + `Found ${primes.length} primes in ${timeTaken}ms` + document.getElementById('findPrimes').disabled = false +} +``` + +```javascript +// prime-worker.js +function isPrime(n) { + if (n < 2) return false + for (let i = 2; i <= Math.sqrt(n); i++) { + if (n % i === 0) return false + } + return true +} + +function findPrimes(max) { + const primes = [] + for (let i = 2; i <= max; i++) { + if (isPrime(i)) primes.push(i) + } + return primes +} + +self.onmessage = (event) => { + const { findPrimesUpTo } = event.data + + const start = performance.now() + const primes = findPrimes(findPrimesUpTo) + const timeTaken = performance.now() - start + + self.postMessage({ primes, timeTaken }) +} +``` + +### Pattern 2: Data Parsing + +Parsing large JSON or CSV files: + +```javascript +// main.js +const worker = new Worker('parser-worker.js', { type: 'module' }) + +async function parseFile(file) { + const text = await file.text() + worker.postMessage({ csv: text }) +} + +worker.onmessage = (event) => { + const { rows, headers, errors } = event.data + console.log(`Parsed ${rows.length} rows`) + displayData(rows) +} + +document.getElementById('fileInput').addEventListener('change', (e) => { + parseFile(e.target.files[0]) +}) +``` + +```javascript +// parser-worker.js +function parseCSV(text) { + const lines = text.split('\n') + const headers = lines[0].split(',').map(h => h.trim()) + const rows = [] + const errors = [] + + for (let i = 1; i < lines.length; i++) { + const line = lines[i].trim() + if (!line) continue + + try { + const values = line.split(',') + const row = {} + headers.forEach((header, index) => { + row[header] = values[index]?.trim() + }) + rows.push(row) + } catch (e) { + errors.push({ line: i, error: e.message }) + } + } + + return { headers, rows, errors } +} + +self.onmessage = (event) => { + const { csv } = event.data + const result = parseCSV(csv) + self.postMessage(result) +} +``` + +### Pattern 3: Real-Time Data Processing + +Processing streaming data (like from WebSocket or sensors): + +```javascript +// main.js +const processingWorker = new Worker('stream-worker.js', { type: 'module' }) +const ws = new WebSocket('wss://data-feed.example.com') + +ws.onmessage = (event) => { + // Don't process on main thread - send to worker + processingWorker.postMessage(JSON.parse(event.data)) +} + +processingWorker.onmessage = (event) => { + // Only update UI with processed results + updateChart(event.data) +} +``` + +```javascript +// stream-worker.js +let buffer = [] +const BATCH_SIZE = 100 + +function processBuffer() { + if (buffer.length < BATCH_SIZE) return + + // Calculate statistics + const values = buffer.map(d => d.value) + const avg = values.reduce((a, b) => a + b, 0) / values.length + const max = Math.max(...values) + const min = Math.min(...values) + + self.postMessage({ avg, max, min, count: buffer.length }) + buffer = [] +} + +self.onmessage = (event) => { + buffer.push(event.data) + processBuffer() +} + +// Process remaining data periodically +setInterval(processBuffer, 1000) +``` + +--- + +## Worker Pools: Reusing Workers + +Creating workers has overhead. For repeated tasks, use a **worker pool** to reuse workers instead of creating new ones: + +```javascript +// WorkerPool.js +export class WorkerPool { + constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) { + this.workers = [] + this.queue = [] + this.poolSize = poolSize + this.workerScript = workerScript + + // Create workers + for (let i = 0; i < poolSize; i++) { + this.workers.push({ + worker: new Worker(workerScript, { type: 'module' }), + busy: false + }) + } + } + + runTask(data) { + return new Promise((resolve, reject) => { + const task = { data, resolve, reject } + + // Find available worker + const available = this.workers.find(w => !w.busy) + + if (available) { + this.#runOnWorker(available, task) + } else { + // Queue the task + this.queue.push(task) + } + }) + } + + #runOnWorker(workerInfo, task) { + workerInfo.busy = true + + const handleMessage = (event) => { + workerInfo.worker.removeEventListener('message', handleMessage) + workerInfo.busy = false + + task.resolve(event.data) + + // Process queued tasks + if (this.queue.length > 0) { + const nextTask = this.queue.shift() + this.#runOnWorker(workerInfo, nextTask) + } + } + + const handleError = (error) => { + workerInfo.worker.removeEventListener('error', handleError) + workerInfo.busy = false + task.reject(error) + } + + workerInfo.worker.addEventListener('message', handleMessage) + workerInfo.worker.addEventListener('error', handleError) + workerInfo.worker.postMessage(task.data) + } + + terminate() { + this.workers.forEach(w => w.worker.terminate()) + this.workers = [] + this.queue = [] + } +} +``` + +```javascript +// main.js - Using the pool +import { WorkerPool } from './WorkerPool.js' + +const pool = new WorkerPool('compute-worker.js', 4) + +// Process many items in parallel +async function processItems(items) { + const results = await Promise.all( + items.map(item => pool.runTask(item)) + ) + return results +} + +// Example: process 100 items using 4 workers +const items = Array.from({ length: 100 }, (_, i) => ({ id: i, data: Math.random() })) +const results = await processItems(items) +console.log(results) + +// Clean up when done +pool.terminate() +``` + +```javascript +// compute-worker.js +self.onmessage = (event) => { + const { id, data } = event.data + + // Simulate heavy computation + let result = data + for (let i = 0; i < 1000000; i++) { + result = Math.sin(result) * Math.cos(result) + } + + self.postMessage({ id, result }) +} +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ WORKER POOL │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MAIN THREAD │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ WorkerPool │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ TASK QUEUE │ │ │ +│ │ │ [Task 5] [Task 6] [Task 7] [Task 8] ... │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ └──────────────┬───────────────┬───────────────┬───────────────┬───┘ │ +│ │ │ │ │ │ +│ ▼ ▼ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Worker 1 │ │ Worker 2 │ │ Worker 3 │ │ Worker 4 │ │ +│ │ [Task 1] │ │ [Task 2] │ │ [Task 3] │ │ [Task 4] │ │ +│ │ (busy) │ │ (busy) │ │ (busy) │ │ (busy) │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ • Reuses existing workers (no creation overhead) │ +│ • Tasks queue when all workers are busy │ +│ • Automatically assigns tasks as workers become free │ +│ • Pool size often matches CPU core count │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Tip> +`navigator.hardwareConcurrency` returns the number of logical CPU cores. Using this as your pool size lets you maximize parallelism without oversubscribing. +</Tip> + +--- + +## Inline Workers: The Blob URL Trick + +Sometimes you want a worker without a separate file. You can create workers from strings using [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) URLs: + +```javascript +// Create a worker from a string (no separate file needed!) +function createWorkerFromString(code) { + const blob = new Blob([code], { type: 'application/javascript' }) + const url = URL.createObjectURL(blob) + const worker = new Worker(url) + + // Clean up the URL when done + worker.addEventListener('message', () => {}, { once: true }) + URL.revokeObjectURL(url) // Revoke after worker starts + + return worker +} + +// Usage +const workerCode = ` + self.onmessage = (event) => { + const numbers = event.data + const sum = numbers.reduce((a, b) => a + b, 0) + self.postMessage(sum) + } +` + +const worker = createWorkerFromString(workerCode) +worker.postMessage([1, 2, 3, 4, 5]) +worker.onmessage = (e) => console.log('Sum:', e.data) // Sum: 15 +``` + +### A Cleaner Pattern: Function-Based Workers + +You can even define the worker logic as a function: + +```javascript +function createWorkerFromFunction(fn) { + // Convert function to string and wrap in self.onmessage + const code = ` + const workerFn = ${fn.toString()} + self.onmessage = (event) => { + const result = workerFn(event.data) + self.postMessage(result) + } + ` + + const blob = new Blob([code], { type: 'application/javascript' }) + const url = URL.createObjectURL(blob) + return new Worker(url) +} + +// Usage - define worker logic as a normal function! +const worker = createWorkerFromFunction((data) => { + // This runs in the worker + return data.map(n => n * 2) +}) + +worker.postMessage([1, 2, 3]) +worker.onmessage = (e) => console.log(e.data) // [2, 4, 6] +``` + +<Warning> +**Limitations of inline workers:** +- The function can't use closures (no access to outer scope) +- Can't import modules (the code is a string) +- Harder to debug (no source maps) +- Best used for simple, self-contained tasks </Warning> +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Web Workers provide true parallelism** — Unlike async/await (which is concurrent but single-threaded), workers run on separate CPU threads simultaneously. + +2. **Use workers for CPU-bound tasks** — Async is for waiting (network, timers). Workers are for computing (heavy calculations, data processing). + +3. **Workers communicate via `postMessage`** — Data is copied by default using the structured clone algorithm. Workers can't directly access main thread variables. + +4. **Workers can't touch the DOM** — No `document`, no `window`, no `localStorage`. If you need to update the UI, send data back to the main thread. + +5. **Transfer large data instead of copying** — For big ArrayBuffers, use `postMessage(data, [data])` to transfer ownership. The transfer is nearly instant. + +6. **Module workers are the modern approach** — Use `new Worker('file.js', { type: 'module' })` to enable `import`/`export` syntax and modern features. + +7. **Three types of workers exist** — Dedicated (one owner), Shared (multiple tabs), and Service Workers (network proxy). Use Dedicated for most cases. + +8. **Always terminate workers when done** — Call `worker.terminate()` or they'll keep running and consuming resources. + +9. **Don't overuse workers for small tasks** — Worker creation and message passing have overhead. Only use them for tasks taking 50ms+. + +10. **Worker pools improve performance** — Reuse workers instead of creating new ones for repeated tasks. Match pool size to CPU cores. +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between async/await and Web Workers?"> + **Answer:** + + Async/await provides **concurrency** on a single thread. When you `await`, JavaScript pauses that function and runs other code, but everything still runs on one thread, taking turns. + + Web Workers provide **parallelism** on multiple threads. A worker runs on a completely separate thread, executing simultaneously with the main thread. + + ```javascript + // Async: Takes turns on one thread + async function fetchData() { + await fetch('/api') // Pauses here, other code can run + } + + // Workers: Actually runs at the same time + const worker = new Worker('heavy-task.js') + worker.postMessage(data) // Worker computes in parallel + // Main thread continues immediately + ``` + + Use async for I/O-bound tasks (network, files). Use workers for CPU-bound tasks (calculations, processing). + </Accordion> + + <Accordion title="Question 2: Why can't workers access the DOM?"> + **Answer:** + + The DOM is not thread-safe. If multiple threads could modify the DOM simultaneously, you'd get race conditions and corrupted state. Browsers would need complex locking mechanisms. + + Instead, browsers made a design choice: only the main thread can touch the DOM. Workers do computation and send results back: + + ```javascript + // worker.js + // ❌ Can't do this + document.getElementById('result').textContent = 'Done' + + // ✓ Send data back instead + self.postMessage({ result: 'Done' }) + + // main.js + worker.onmessage = (e) => { + document.getElementById('result').textContent = e.data.result + } + ``` + + This constraint keeps things simple and bug-free. + </Accordion> + + <Accordion title="Question 3: When should you use transferable objects?"> + **Answer:** + + Use transferable objects when: + 1. You're sending large data (> 1MB) + 2. You don't need to keep the data in the sending context + + ```javascript + // Large buffer (100MB) + const buffer = new ArrayBuffer(100 * 1024 * 1024) + + // ❌ SLOW: Copies 100MB + worker.postMessage(buffer) + + // ✓ FAST: Transfers ownership instantly + worker.postMessage(buffer, [buffer]) + // buffer is now empty (byteLength = 0) + ``` + + Transferable objects include: ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas, and various streams. + </Accordion> + + <Accordion title="Question 4: What's the difference between Dedicated and Shared Workers?"> + **Answer:** + + **Dedicated Workers** belong to a single script. Only that script can communicate with them. + + ```javascript + const worker = new Worker('worker.js') // Only this script uses it + ``` + + **Shared Workers** can be accessed by multiple scripts, even across different tabs of the same origin. + + ```javascript + // Tab 1 and Tab 2 both connect to the same worker + const worker = new SharedWorker('shared.js') + worker.port.postMessage('hello') + ``` + + Use Shared Workers for: + - Shared state across tabs + - Single WebSocket connection for multiple tabs + - Reducing memory by sharing one worker instance + + Note: Shared Workers have limited browser support (not in Safari). + </Accordion> + + <Accordion title="Question 5: How do you create a worker without a separate file?"> + **Answer:** + + Use a Blob URL to create a worker from a string: + + ```javascript + const code = ` + self.onmessage = (event) => { + const result = event.data * 2 + self.postMessage(result) + } + ` + + const blob = new Blob([code], { type: 'application/javascript' }) + const url = URL.createObjectURL(blob) + const worker = new Worker(url) + + worker.postMessage(5) + worker.onmessage = (e) => console.log(e.data) // 10 + + // Clean up + URL.revokeObjectURL(url) + ``` + + This is useful for simple tasks or demos, but has limitations: no imports, no closures, harder to debug. + </Accordion> + + <Accordion title="Question 6: What happens if you forget to terminate a worker?"> + **Answer:** + + The worker keeps running and consuming resources (memory, CPU time). If you create workers in a loop or on repeated events without terminating them, you'll leak resources: + + ```javascript + // ❌ Memory leak: creates new worker every click + button.onclick = () => { + const worker = new Worker('task.js') + worker.postMessage(data) + worker.onmessage = (e) => showResult(e.data) + // Worker never terminated! + } + + // ✓ Fixed: terminate after use + button.onclick = () => { + const worker = new Worker('task.js') + worker.postMessage(data) + worker.onmessage = (e) => { + showResult(e.data) + worker.terminate() // Clean up + } + } + + // ✓ Better: reuse one worker + const worker = new Worker('task.js') + worker.onmessage = (e) => showResult(e.data) + + button.onclick = () => { + worker.postMessage(data) // Reuse + } + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + How JavaScript handles async on a single thread. Workers bypass this limitation. + </Card> + <Card title="Promises" icon="handshake" href="/concepts/promises"> + The foundation of async JavaScript. Workers use postMessage, not Promises, for communication. + </Card> + <Card title="async/await" icon="hourglass" href="/concepts/async-await"> + Modern async syntax. Great for I/O, but workers handle CPU-bound tasks better. + </Card> + <Card title="Callbacks" icon="phone" href="/concepts/callbacks"> + The original async pattern. Workers use message callbacks for communication. + </Card> +</CardGroup> + +--- + ## Reference <CardGroup cols={2}> <Card title="Web Workers API — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API"> - MDN documentation for Web Workers API + Official MDN reference for the Web Workers API. </Card> <Card title="Using Web Workers — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"> - MDN guide to using Web Workers + Comprehensive guide to creating and using Web Workers. + </Card> + <Card title="Worker — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Worker"> + API reference for the Worker constructor and methods. + </Card> + <Card title="Structured Clone Algorithm — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm"> + How data is copied when using postMessage. </Card> </CardGroup> ## Articles <CardGroup cols={2}> - <Card title="The Basics of Web Workers" icon="newspaper" href="https://www.html5rocks.com/en/tutorials/workers/basics/"> - By HTML5 Rocks — comprehensive introduction + <Card title="How Fast Are Web Workers? — Mozilla Hacks" icon="newspaper" href="https://hacks.mozilla.org/2015/07/how-fast-are-web-workers/"> + Performance analysis from Mozilla engineers. Answers "is the overhead worth it?" with real benchmarks. + </Card> + <Card title="Threading the Web with Module Workers — web.dev" icon="newspaper" href="https://web.dev/articles/module-workers"> + Deep dive into module workers with `type: 'module'`. Essential reading for modern worker patterns. </Card> - <Card title="Using Web Workers for Safe, Concurrent JavaScript" icon="newspaper" href="https://www.smashingmagazine.com/2021/06/web-workers-2021/"> - By Smashing Magazine + <Card title="Using Web Workers for Safe Concurrent JavaScript — Smashing Magazine" icon="newspaper" href="https://www.smashingmagazine.com/2021/06/web-workers-2021/"> + Real-world examples including image processing and physics simulations. Great for seeing workers in action. + </Card> + <Card title="Comlink — Google Chrome Labs" icon="newspaper" href="https://github.com/GoogleChromeLabs/comlink"> + Library that makes workers feel like async functions. Eliminates postMessage boilerplate entirely. </Card> </CardGroup> -- [JavaScript Web Workers: A Beginner's Guide — SitePoint](https://www.sitepoint.com/javascript-web-workers/) -- [Web Workers — JavaScript.info](https://javascript.info/web-workers) -- [Comlink: Web Workers Made Easy — Google Chrome Labs](https://github.com/GoogleChromeLabs/comlink) -- [Parallel Programming in JavaScript Using Web Workers — LogRocket](https://blog.logrocket.com/parallel-programming-in-javascript-using-web-workers/) -- [A Simple Introduction to Web Workers — David Walsh](https://davidwalsh.name/web-workers) -- [Threading the Web with Module Workers — web.dev](https://web.dev/module-workers/) - ## Videos <CardGroup cols={2}> - <Card title="Web Workers Explained" icon="video" href="https://www.youtube.com/watch?v=Gcp7triXFjg"> - By Web Dev Simplified + <Card title="Web Workers Explained — Web Dev Simplified" icon="video" href="https://www.youtube.com/watch?v=Gcp7triXFjg"> + Beginner-friendly 12-minute introduction. Perfect starting point if you've never used workers. </Card> - <Card title="JavaScript Web Workers" icon="video" href="https://www.youtube.com/watch?v=EiPytIxrZtU"> - By Fireship + <Card title="JavaScript Web Workers — Fireship" icon="video" href="https://www.youtube.com/watch?v=EiPytIxrZtU"> + Fast-paced 100-second overview. Great for quick refresher or deciding if you need workers. + </Card> + <Card title="Web Workers Crash Course — Traversy Media" icon="video" href="https://www.youtube.com/watch?v=tPwkKF8WAXs"> + Practical 30-minute deep dive with a real example. Shows the full workflow from problem to solution. </Card> </CardGroup> - -- [Web Workers Crash Course — Traversy Media](https://www.youtube.com/watch?v=tPwkKF8WAXs) -- [Multi-Threading in JavaScript with Web Workers — Hussein Nasser](https://www.youtube.com/watch?v=kUXRxEwOL3A) -- [Service Workers vs Web Workers — Google Chrome Developers](https://www.youtube.com/watch?v=JYXXGNFJjwc) From 2f8337433904a2a57d3db4f5b0015900daed614d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Wed, 31 Dec 2025 15:49:39 -0300 Subject: [PATCH 086/128] docs: improve primitive-types page copywriting and resource descriptions --- docs/concepts/primitive-types.mdx | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/concepts/primitive-types.mdx b/docs/concepts/primitive-types.mdx index 3803ca5e..b9ccd933 100644 --- a/docs/concepts/primitive-types.mdx +++ b/docs/concepts/primitive-types.mdx @@ -21,7 +21,7 @@ console.log(typeof num); // "number" console.log(typeof nul); // "object" — Wait, what?! ``` -These seven **[primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)** types are the foundation of all data in JavaScript. Unlike objects, primitives are **immutable** (unchangeable) and **compared by value**. Understanding them deeply is essential because every complex structure you'll ever build — arrays, objects, classes — ultimately relies on these simple building blocks. +These seven **[primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)** types are the foundation of all data in JavaScript. Unlike objects, primitives are **immutable** (unchangeable) and **compared by value**. Every complex structure you build (arrays, objects, classes) ultimately relies on these simple building blocks. <Info> **What you'll learn in this guide:** @@ -35,7 +35,7 @@ These seven **[primitive](https://developer.mozilla.org/en-US/docs/Glossary/Prim </Info> <Note> -**New to JavaScript?** This guide is beginner-friendly! No prior knowledge required — we'll explain everything from the ground up. +**New to JavaScript?** This guide is beginner-friendly! No prior knowledge required. We'll explain everything from the ground up. </Note> --- @@ -98,7 +98,7 @@ All primitives share these fundamental traits: ## The Atoms vs Molecules Analogy -Think of data in JavaScript like chemistry class (but way more fun, and no lab goggles required). **Primitives** are like atoms — the fundamental, indivisible building blocks that cannot be broken down further. **Objects** are like molecules — complex structures made up of multiple atoms combined together. +Think of data in JavaScript like chemistry class (but way more fun, and no lab goggles required). **Primitives** are like atoms: the fundamental, indivisible building blocks that cannot be broken down further. **Objects** are like molecules: complex structures made up of multiple atoms combined together. ``` ┌─────────────────────────────────────────────────────────────────────────┐ @@ -131,7 +131,7 @@ Let's explore each primitive type in detail. ### String -A **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** represents text data — a sequence of characters. +A **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** represents text data: a sequence of characters. ```javascript // Three ways to create strings @@ -142,7 +142,7 @@ let backtick = `Hello World`; // Template literal (ES6) #### Template Literals (Still Just Strings!) -[Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) (backticks) are **not a separate type** — they're just a more powerful syntax for creating strings. The result is still a regular string primitive: +[Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) (backticks) are **not a separate type**. They're just a more powerful syntax for creating strings. The result is still a regular string primitive: ```javascript let name = "Alice"; @@ -176,7 +176,7 @@ console.log(str); // "Hello" ``` <Tip> -String methods like `toUpperCase()`, `slice()`, `replace()` always return **new strings** — they never modify the original. +String methods like `toUpperCase()`, `slice()`, `replace()` always return **new strings**. They never modify the original. </Tip> --- @@ -209,7 +209,7 @@ console.log(0.1 + 0.2); // 0.30000000000000004 console.log(0.1 + 0.2 === 0.3); // false! Welcome to JavaScript! ``` -This isn't a JavaScript bug — it's how floating-point math works in all programming languages. The decimal `0.1` cannot be perfectly represented in binary. +This isn't a JavaScript bug. It's how floating-point math works in all programming languages. The decimal `0.1` cannot be perfectly represented in binary. <Warning> **Working with money?** Never use floating-point for calculations! Store amounts in cents as integers, then use JavaScript's built-in [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) for display. @@ -238,7 +238,7 @@ console.log(euroFormatter.format(1234.56)); // "1.234,56 €" </Warning> <Tip> -`Intl.NumberFormat` is built into JavaScript — no external libraries needed! It handles currency symbols, decimal separators, and locale-specific formatting automatically. +`Intl.NumberFormat` is built into JavaScript. No external libraries needed! It handles currency symbols, decimal separators, and locale-specific formatting automatically. </Tip> #### Safe Integer Range @@ -374,7 +374,7 @@ Don't explicitly assign `undefined` to variables. Use `null` instead to indicate ### null -**[`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null)** means "intentionally empty" — you're explicitly saying "this has no value." +**[`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null)** means "intentionally empty". You're explicitly saying "this has no value." ```javascript // Intentionally clearing a variable @@ -509,7 +509,7 @@ Object.prototype.toString.call(new Date()); // "[object Date]" ## Immutability Explained -**Immutable** means "cannot be changed." Primitive values are immutable — you cannot alter the value itself. +**Immutable** means "cannot be changed." Primitive values are immutable. You cannot alter the value itself. ### Seeing Immutability in Action @@ -804,7 +804,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most NaN is so confused about its identity that it doesn't even equal itself! - **Why?** By the IEEE 754 specification, NaN represents "Not a Number" — an undefined or unrepresentable result. Since it's not a specific number, it can't equal anything, including itself. + **Why?** By the IEEE 754 specification, NaN represents "Not a Number", an undefined or unrepresentable result. Since it's not a specific number, it can't equal anything, including itself. **How to check for NaN:** ```javascript @@ -904,7 +904,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most ## Key Takeaways <Info> -**Remember these essential points about Primitive Types:** +**The key things to remember about Primitive Types:** 1. **7 primitives**: string, number, bigint, boolean, undefined, null, symbol @@ -994,7 +994,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most console.log(NaN === NaN); // false! ``` - This is per the IEEE 754 floating-point specification. `NaN` represents an undefined or unrepresentable mathematical result, so it can't equal anything — including itself. + This is per the IEEE 754 floating-point specification. `NaN` represents an undefined or unrepresentable mathematical result, so it can't equal anything, including itself. **How to check for NaN:** ```javascript @@ -1055,10 +1055,10 @@ JavaScript has some famous "weird parts" that every developer should know. Most Expert deep-dive by Dr. Axel Rauschmayer into IEEE 754 floating-point representation, explaining why 0.1 + 0.2 !== 0.3 and how JavaScript stores numbers internally. </Card> <Card title="(Not) Everything in JavaScript is an Object" icon="newspaper" href="https://dev.to/d4nyll/not-everything-in-javascript-is-an-object"> - Debunks the common myth that "everything in JS is an object." Excellent explanation of primitives, autoboxing, and wrapper objects with clear examples and a helpful summary. + Debunks the common myth that "everything in JS is an object." Shows how autoboxing creates the illusion that primitives have methods, with diagrams explaining what happens behind the scenes. </Card> <Card title="Methods of Primitives" icon="newspaper" href="https://javascript.info/primitives-methods"> - Official-quality explanation from javascript.info on how autoboxing works and why primitives can use methods. Clear, up-to-date, and includes practice exercises. + The javascript.info guide walks through each wrapper type (String, Number, Boolean) and includes interactive tasks to test your understanding. One of the best resources for learning autoboxing. </Card> <Card title="The Differences Between Object.freeze() vs Const" icon="newspaper" href="https://medium.com/@bolajiayodeji/the-differences-between-object-freeze-vs-const-in-javascript-4eacea534d7c"> Clears up the common confusion between const (prevents reassignment) and immutability (prevents mutation). Short and beginner-friendly. @@ -1078,7 +1078,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most Free preview of one of the most acclaimed JavaScript courses ever made. Covers types, coercion, and the "weird parts" that confuse developers. Perfect starting point before buying the full course. </Card> <Card title="JavaScript: Understanding the Weird Parts (Full Course) — Anthony Alicea" icon="graduation-cap" href="https://www.udemy.com/course/understand-javascript/"> - The complete 12-hour course that has helped mass developers truly understand JavaScript. Deep coverage of types, operators, objects, and the engine internals. Worth every penny. + The complete 12-hour course covering types, operators, objects, and engine internals. Anthony's explanations of scope, closures, and prototypes are particularly helpful for intermediate developers. </Card> <Card title="Introduction to Primitives — Piccalilli" icon="graduation-cap" href="https://piccalil.li/javascript-for-everyone/lessons/9"> Part of the "JavaScript for Everyone" course by Mat Marquis. This module covers all 7 primitive types with dedicated lessons for Numbers, Strings, Booleans, null/undefined, BigInt, and Symbol. Beautifully written with a fun narrative style. @@ -1089,7 +1089,7 @@ JavaScript has some famous "weird parts" that every developer should know. Most <CardGroup cols={2}> <Card title="JavaScript Reference vs Primitive Types — Academind" icon="video" href="https://www.youtube.com/watch?v=9ooYYRLdg_g"> - Clear comparison of how primitives and reference types behave differently when assigned and passed to functions. Great visual explanations. + Academind's Max shows what happens in memory when you copy primitives vs objects. The side-by-side code examples make the difference immediately obvious. </Card> <Card title="Value Types and Reference Types — Programming with Mosh" icon="video" href="https://www.youtube.com/watch?v=e-_mDyqm2oU"> Mosh Hamedani's clear teaching style makes this complex topic easy to understand. Includes practical examples showing memory behavior. From af36a1a722158ceca0dee388d50e4648b95cee7d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Thu, 1 Jan 2026 14:04:55 -0300 Subject: [PATCH 087/128] docs: add comprehensive Higher-Order Functions concept page - Rewrite concept page with complete structure following write-concept skill - Add opening hook with engaging questions and immediate code example - Include Pea Soup analogy explaining abstraction levels with ASCII diagram - Cover first-class functions as the foundation for HOFs - Explain HOFs that accept functions (repeat, calculate, unless) - Explain HOFs that return functions (greaterThan, multiplier, noisy) - Add function factories section (validators, formatters, partial application) - Document closure connection with createCounter and createBankAccount examples - Include built-in HOFs overview table with link to map-reduce-filter page - Add common mistakes section (arrow function returns, this context, parseInt gotcha) - Include 9 key takeaways and 6 test-your-knowledge Q&As - Add related concepts, MDN references, articles, and videos sections - Create comprehensive test file with 31 tests covering all code examples --- docs/concepts/higher-order-functions.mdx | 957 +++++++++++++++++- .../higher-order-functions.test.js | 516 ++++++++++ 2 files changed, 1442 insertions(+), 31 deletions(-) create mode 100644 tests/functional-programming/higher-order-functions/higher-order-functions.test.js diff --git a/docs/concepts/higher-order-functions.mdx b/docs/concepts/higher-order-functions.mdx index dc6bd995..5a8db115 100644 --- a/docs/concepts/higher-order-functions.mdx +++ b/docs/concepts/higher-order-functions.mdx @@ -1,54 +1,949 @@ --- -title: "Higher-Order Functions: Functions as Data in JavaScript" -sidebarTitle: "Higher-Order Functions: Functions as Data" -description: "Learn higher-order functions in JavaScript — functions that take functions as arguments or return them. Master callbacks, closures, and functional patterns like map, filter, and reduce." +title: "Higher-Order Functions: Functions That Use Functions in JavaScript" +sidebarTitle: "Higher-Order Functions: Functions That Use Functions" +description: "Learn higher-order functions in JavaScript. Understand functions that accept or return other functions, create reusable abstractions, and write cleaner code." --- -## Overview +What if you could tell a function *how* to do something, not just *what* data to work with? What if you could pass behavior itself as an argument, just like you pass numbers or strings? -A **higher-order function** is a function that either takes one or more functions as arguments, returns a function as its result, or both. Higher-order functions are a key concept in functional programming and enable powerful patterns like callbacks, currying, and function composition. +```javascript +// Without higher-order functions: repetitive code +for (let i = 0; i < 3; i++) { + console.log(i) +} + +// With higher-order functions: reusable abstraction +function repeat(times, action) { + for (let i = 0; i < times; i++) { + action(i) + } +} + +repeat(3, console.log) // 0, 1, 2 +repeat(3, i => console.log(i * 2)) // 0, 2, 4 +``` + +This is the power of **higher-order functions**. They let you write functions that are flexible, reusable, and abstract. Instead of writing the same loop over and over with slightly different logic, you write one function and pass in the logic that changes. <Info> -Common higher-order functions in JavaScript include `map`, `filter`, `reduce`, `forEach`, `sort`, and `find`. Understanding these functions is essential for writing clean, functional JavaScript code. +**What you'll learn in this guide:** +- What makes a function "higher-order" +- The connection between first-class functions and HOFs +- How to create functions that accept other functions +- How to create functions that return other functions (function factories) +- How closures enable higher-order functions +- Common mistakes and how to avoid them +- When and why to use higher-order functions </Info> -## Books +<Warning> +**Prerequisites:** This guide assumes you understand [scope and closures](/concepts/scope-and-closures). Closures are created when higher-order functions return other functions. You should also be familiar with [callbacks](/concepts/callbacks), since callbacks are the functions being passed to higher-order functions. +</Warning> -<Card title="Eloquent JavaScript, 3rd Edition: Ch. 5 - Higher-order Functions" icon="book" href="https://eloquentjavascript.net/05_higher_order.html"> - By Marijn Haverbeke -</Card> +--- -## Articles +## What is a Higher-Order Function? + +A **higher-order function** is a function that does at least one of these two things: + +1. **Accepts one or more functions as arguments** +2. **Returns a function as its result** + +That's it. If a function takes a function or returns a function, it's higher-order. + +```javascript +// 1. Accepts a function as an argument +function doTwice(action) { + action() + action() +} + +doTwice(() => console.log('Hello!')) +// Hello! +// Hello! + +// 2. Returns a function as its result +function createGreeter(greeting) { + return function(name) { + return `${greeting}, ${name}!` + } +} + +const sayHello = createGreeter('Hello') +console.log(sayHello('Alice')) // Hello, Alice! +console.log(sayHello('Bob')) // Hello, Bob! +``` + +<Tip> +**The name "higher-order"** comes from mathematics, where functions that operate on other functions are considered to be at a "higher level" of abstraction. In JavaScript, we just call them higher-order functions, or HOFs for short. +</Tip> + +### Why Does This Matter? + +Higher-order functions let you: + +- **Avoid repetition**: Write the structure once, vary the behavior +- **Create abstractions**: Hide complexity behind simple interfaces +- **Build reusable utilities**: Functions that work with any logic you pass them +- **Compose functionality**: Combine simple functions into complex ones + +Without higher-order functions, you'd repeat the same patterns over and over. With them, you write flexible code that adapts to different needs. + +--- + +## The Pea Soup Analogy + +To understand why higher-order functions matter, let's look at an analogy from *Eloquent JavaScript*. + +Compare these two recipes for pea soup: + +**Recipe 1 (Low-level instructions):** + +> Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas... + +**Recipe 2 (Higher-level instructions):** + +> Per person: 1 cup dried split peas, 4 cups of water, half a chopped onion, a stalk of celery, and a carrot. +> +> Soak peas for 12 hours. Simmer for 2 hours. Chop and add vegetables. Cook for 10 more minutes. + +The second recipe is shorter and easier to understand. But it requires you to know what "soak", "simmer", and "chop" mean. These are **abstractions**. They hide the step-by-step details behind meaningful names. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ LEVELS OF ABSTRACTION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ HIGH LEVEL (What you want) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ "Calculate the area for each radius" │ │ +│ │ │ │ +│ │ radii.map(calculateArea) │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ MEDIUM LEVEL (How to iterate) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ function map(array, transform) { │ │ +│ │ const result = [] │ │ +│ │ for (const item of array) { │ │ +│ │ result.push(transform(item)) │ │ +│ │ } │ │ +│ │ return result │ │ +│ │ } │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ LOW LEVEL (Step by step) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ const result = [] │ │ +│ │ for (let i = 0; i < radii.length; i++) { │ │ +│ │ const radius = radii[i] │ │ +│ │ const area = Math.PI * radius * radius │ │ +│ │ result.push(area) │ │ +│ │ } │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ Higher-order functions let you work at the level that makes sense │ +│ for your problem, hiding the mechanical details below. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Higher-order functions are how we create these abstractions in JavaScript. We package up common patterns (like "do something to each item") into reusable functions, then pass in the specific behavior we need. + +--- + +## First-Class Functions: The Foundation + +Higher-order functions are possible because JavaScript has **[first-class functions](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function)**. This means functions are treated like any other value. You can: + +### 1. Assign Functions to Variables + +```javascript +// Functions are values, just like numbers or strings +const greet = function(name) { + return `Hello, ${name}!` +} + +// Arrow functions work the same way +const add = (a, b) => a + b + +console.log(greet('Alice')) // Hello, Alice! +console.log(add(2, 3)) // 5 +``` + +### 2. Pass Functions as Arguments + +```javascript +function callTwice(fn) { + fn() + fn() +} + +callTwice(function() { + console.log('This runs twice!') +}) +// This runs twice! +// This runs twice! +``` + +### 3. Return Functions from Functions + +```javascript +function createMultiplier(multiplier) { + // This returned function "remembers" the multiplier + return function(number) { + return number * multiplier + } +} + +const double = createMultiplier(2) +const triple = createMultiplier(3) + +console.log(double(5)) // 10 +console.log(triple(5)) // 15 +``` + +<Note> +**Not all languages have first-class functions.** In older languages like C or Java (before version 8), you couldn't easily pass functions around. JavaScript's first-class functions make functional programming patterns natural and powerful. +</Note> + +--- + +## Higher-Order Functions That Accept Functions + +The most common type of HOF accepts a function as an argument. You pass in *what* should happen, and the HOF handles *when* and *how* it happens. + +### Example: A Reusable `repeat` Function + +Instead of writing loops everywhere, create a function that handles the looping: + +```javascript +function repeat(times, action) { + for (let i = 0; i < times; i++) { + action(i) + } +} + +// Now you can reuse this for any repeated action +repeat(3, i => console.log(`Iteration ${i}`)) +// Iteration 0 +// Iteration 1 +// Iteration 2 + +repeat(5, i => console.log('*'.repeat(i + 1))) +// * +// ** +// *** +// **** +// ***** +``` + +The `repeat` function doesn't know or care what action you want to perform. It just knows how to repeat something. You provide the "something." + +### Example: A Flexible `calculate` Function + +Suppose you need to calculate different properties of circles: + +```javascript +// Without HOF: repetitive code +function calculateAreas(radii) { + const result = [] + for (let i = 0; i < radii.length; i++) { + result.push(Math.PI * radii[i] * radii[i]) + } + return result +} + +function calculateCircumferences(radii) { + const result = [] + for (let i = 0; i < radii.length; i++) { + result.push(2 * Math.PI * radii[i]) + } + return result +} + +function calculateDiameters(radii) { + const result = [] + for (let i = 0; i < radii.length; i++) { + result.push(2 * radii[i]) + } + return result +} +``` + +That's a lot of repetition! The only thing that changes is the formula. Let's use a higher-order function: + +```javascript +// With HOF: write the loop once, pass in the logic +function calculate(radii, formula) { + const result = [] + for (const radius of radii) { + result.push(formula(radius)) + } + return result +} + +// Define the specific logic separately +const area = r => Math.PI * r * r +const circumference = r => 2 * Math.PI * r +const diameter = r => 2 * r + +const radii = [1, 2, 3] + +console.log(calculate(radii, area)) +// [3.14159..., 12.56637..., 28.27433...] + +console.log(calculate(radii, circumference)) +// [6.28318..., 12.56637..., 18.84955...] + +console.log(calculate(radii, diameter)) +// [2, 4, 6] +``` + +Now adding a new calculation is easy. Just write a new formula function: + +```javascript +const volume = r => (4/3) * Math.PI * r * r * r +console.log(calculate(radii, volume)) +``` + +### Example: An `unless` Function + +You can create new control flow abstractions: + +```javascript +function unless(condition, action) { + if (!condition) { + action() + } +} + +// Use it to express "do this unless that" +repeat(5, n => { + unless(n % 2 === 1, () => { + console.log(n, 'is even') + }) +}) +// 0 is even +// 2 is even +// 4 is even +``` + +This reads almost like English: "Unless n is odd, log that it's even." + +--- + +## Higher-Order Functions That Return Functions + +The second type of HOF returns a function. This is powerful because the returned function can "remember" values from when it was created. + +### Example: The `greaterThan` Factory + +```javascript +function greaterThan(n) { + return function(m) { + return m > n + } +} + +const greaterThan10 = greaterThan(10) +const greaterThan100 = greaterThan(100) + +console.log(greaterThan10(11)) // true +console.log(greaterThan10(5)) // false +console.log(greaterThan100(50)) // false +console.log(greaterThan100(150)) // true +``` + +`greaterThan` is a **function factory**. You give it a number, and it manufactures a new function that tests if other numbers are greater than that number. + +### Example: The `multiplier` Factory + +```javascript +function multiplier(factor) { + return number => number * factor +} + +const double = multiplier(2) +const triple = multiplier(3) +const tenX = multiplier(10) + +console.log(double(5)) // 10 +console.log(triple(5)) // 15 +console.log(tenX(5)) // 50 + +// You can use the factory directly too +console.log(multiplier(7)(3)) // 21 +``` + +### Example: A `noisy` Wrapper + +Higher-order functions can wrap other functions to add behavior: + +```javascript +function noisy(fn) { + return function(...args) { + console.log('Calling with arguments:', args) + const result = fn(...args) + console.log('Returned:', result) + return result + } +} + +const noisyMax = noisy(Math.max) + +noisyMax(3, 1, 4, 1, 5) +// Calling with arguments: [3, 1, 4, 1, 5] +// Returned: 5 + +const noisyFloor = noisy(Math.floor) + +noisyFloor(4.7) +// Calling with arguments: [4.7] +// Returned: 4 +``` + +The original functions (`Math.max`, `Math.floor`) are unchanged. We've created new functions that log their inputs and outputs, wrapping the original behavior. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE WRAPPER PATTERN │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Original Function Wrapped Function │ +│ ┌─────────────────┐ ┌─────────────────────────────────┐ │ +│ │ │ │ 1. Log the arguments │ │ +│ │ Math.max │ noisy() │ 2. Call Math.max │ │ +│ │ │ ────────► │ 3. Log the result │ │ +│ │ (3,1,4) → 4 │ │ 4. Return the result │ │ +│ │ │ │ │ │ +│ └─────────────────┘ └─────────────────────────────────┘ │ +│ │ +│ The wrapper adds behavior before and after, without changing │ +│ the original function. This is the "decorator" pattern. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Function Factories in Practice + +Function factories are functions that create and return other functions. They're useful when you need many similar functions that differ only in some configuration. + +### Example: Creating Validators + +```javascript +function createValidator(min, max) { + return function(value) { + return value >= min && value <= max + } +} + +const isValidAge = createValidator(0, 120) +const isValidPercentage = createValidator(0, 100) +const isValidTemperature = createValidator(-273.15, 1000000) + +console.log(isValidAge(25)) // true +console.log(isValidAge(150)) // false +console.log(isValidPercentage(50)) // true +console.log(isValidPercentage(101)) // false +``` + +### Example: Creating Formatters + +```javascript +function createFormatter(prefix, suffix) { + return function(value) { + return `${prefix}${value}${suffix}` + } +} + +const formatDollars = createFormatter('$', '') +const formatPercent = createFormatter('', '%') +const formatParens = createFormatter('(', ')') + +console.log(formatDollars(99.99)) // $99.99 +console.log(formatPercent(75)) // 75% +console.log(formatParens('aside')) // (aside) +``` + +### Example: Pre-filling Arguments (Partial Application) + +```javascript +function partial(fn, ...presetArgs) { + return function(...laterArgs) { + return fn(...presetArgs, ...laterArgs) + } +} + +function greet(greeting, punctuation, name) { + return `${greeting}, ${name}${punctuation}` +} + +const sayHello = partial(greet, 'Hello', '!') +const askHowAreYou = partial(greet, 'How are you', '?') + +console.log(sayHello('Alice')) // Hello, Alice! +console.log(sayHello('Bob')) // Hello, Bob! +console.log(askHowAreYou('Charlie')) // How are you, Charlie? +``` + +--- + +## The Closure Connection + +Higher-order functions that return functions rely on **[closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)**. When a function is created inside another function, it "closes over" the variables in its surrounding scope, remembering them even after the outer function has finished. + +```javascript +function createCounter(start = 0) { + let count = start // This variable is "enclosed" + + return function() { + count++ // The inner function can access and modify it + return count + } +} + +const counter1 = createCounter() +const counter2 = createCounter(100) + +console.log(counter1()) // 1 +console.log(counter1()) // 2 +console.log(counter1()) // 3 + +console.log(counter2()) // 101 +console.log(counter2()) // 102 + +// Each counter has its own private count variable +console.log(counter1()) // 4 (not affected by counter2) +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ HOW CLOSURES WORK WITH HOFs │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ createCounter(0) createCounter(100) │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ count = 0 │ │ count = 100 │ │ +│ │ │ │ │ │ +│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │ +│ │ │ function() { │ │ │ │ function() { │ │ │ +│ │ │ count++ │◄─┼───────┐ │ │ count++ │◄─┼───────┐ │ +│ │ │ return count│ │ │ │ │ return count│ │ │ │ +│ │ │ } │ │ │ │ │ } │ │ │ │ +│ │ └───────────────┘ │ │ │ └───────────────┘ │ │ │ +│ └─────────────────────┘ │ └─────────────────────┘ │ │ +│ │ │ │ │ │ +│ ▼ │ ▼ │ │ +│ counter1 ───────────────┘ counter2 ───────────────┘ │ +│ │ +│ Each returned function has its own "backpack" containing the │ +│ variables from when it was created. This is a closure. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Private Variables Through Closures + +This pattern creates truly private variables. Nothing outside can access `count` directly: + +```javascript +function createBankAccount(initialBalance) { + let balance = initialBalance // Private variable + + return { + deposit(amount) { + if (amount > 0) { + balance += amount + return balance + } + }, + withdraw(amount) { + if (amount > 0 && amount <= balance) { + balance -= amount + return balance + } + return 'Insufficient funds' + }, + getBalance() { + return balance + } + } +} + +const account = createBankAccount(100) +console.log(account.getBalance()) // 100 +console.log(account.deposit(50)) // 150 +console.log(account.withdraw(30)) // 120 + +// Can't access balance directly +console.log(account.balance) // undefined +``` + +--- + +## Built-in Higher-Order Functions + +JavaScript provides many built-in higher-order functions, especially for working with arrays. These are covered in depth in the [Map, Reduce, and Filter](/concepts/map-reduce-filter) guide, but here's a quick overview: + +| Method | What it does | Returns | +|--------|--------------|---------| +| `forEach(fn)` | Calls `fn` on each element | `undefined` | +| `map(fn)` | Transforms each element with `fn` | New array | +| `filter(fn)` | Keeps elements where `fn` returns `true` | New array | +| `reduce(fn, init)` | Accumulates elements into single value | Single value | +| `find(fn)` | Returns first element where `fn` returns `true` | Element or `undefined` | +| `some(fn)` | Tests if any element passes `fn` | `boolean` | +| `every(fn)` | Tests if all elements pass `fn` | `boolean` | +| `sort(fn)` | Sorts elements using comparator `fn` | Sorted array (mutates!) | + +```javascript +const numbers = [1, 2, 3, 4, 5] + +// All of these accept a function as an argument +numbers.forEach(n => console.log(n)) // Logs each number +numbers.map(n => n * 2) // [2, 4, 6, 8, 10] +numbers.filter(n => n > 2) // [3, 4, 5] +numbers.reduce((sum, n) => sum + n, 0) // 15 +numbers.find(n => n > 3) // 4 +numbers.some(n => n > 4) // true +numbers.every(n => n > 0) // true +``` + +<Note> +For a deep dive into these methods with practical examples, see [Map, Reduce, and Filter](/concepts/map-reduce-filter). +</Note> + +--- + +## Common Mistakes + +### 1. Forgetting to Return in Arrow Functions + +When using curly braces in arrow functions, you must explicitly `return`: + +```javascript +// ❌ WRONG - implicit return only works without braces +const double = numbers.map(n => { + n * 2 // This doesn't return anything! +}) +console.log(double) // [undefined, undefined, undefined, ...] + +// ✓ CORRECT - explicit return with braces +const double = numbers.map(n => { + return n * 2 +}) + +// ✓ CORRECT - implicit return without braces +const double = numbers.map(n => n * 2) +``` + +### 2. Losing `this` Context + +When passing methods as callbacks, `this` may not be what you expect: + +```javascript +const user = { + name: 'Alice', + greet() { + console.log(`Hello, I'm ${this.name}`) + } +} + +// ❌ WRONG - 'this' is lost +setTimeout(user.greet, 1000) // "Hello, I'm undefined" + +// ✓ CORRECT - bind the context +setTimeout(user.greet.bind(user), 1000) // "Hello, I'm Alice" + +// ✓ CORRECT - use an arrow function wrapper +setTimeout(() => user.greet(), 1000) // "Hello, I'm Alice" +``` + +### 3. The `parseInt` Gotcha with `map` + +`map` passes three arguments to its callback: `(element, index, array)`. Some functions don't expect this: + +```javascript +// ❌ WRONG - parseInt receives (string, index) and uses index as radix +['1', '2', '3'].map(parseInt) // [1, NaN, NaN] + +// Why? map calls: +// parseInt('1', 0) → 1 (radix 0 is treated as 10) +// parseInt('2', 1) → NaN (radix 1 is invalid) +// parseInt('3', 2) → NaN (3 is not valid in binary) + +// ✓ CORRECT - wrap parseInt to only pass the string +['1', '2', '3'].map(str => parseInt(str, 10)) // [1, 2, 3] + +// ✓ CORRECT - use Number instead +['1', '2', '3'].map(Number) // [1, 2, 3] +``` + +### 4. Using Higher-Order Functions When a Simple Loop is Clearer + +Don't force HOFs when a simple loop would be clearer: + +```javascript +// Sometimes this is clearer... +let sum = 0 +for (const n of numbers) { + sum += n +} + +// ...than this (for simple cases) +const sum = numbers.reduce((acc, n) => acc + n, 0) +``` + +Use HOFs when they make the code more readable, not just to seem clever. + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **A higher-order function** accepts functions as arguments OR returns a function. If it does either, it's higher-order. + +2. **First-class functions** make HOFs possible. In JavaScript, functions are values you can assign, pass, and return. + +3. **HOFs that accept functions** let you parameterize behavior. Write the structure once, pass in what varies. + +4. **HOFs that return functions** create function factories. They "manufacture" specialized functions from a template. + +5. **Closures are the key** to functions returning functions. The returned function remembers variables from when it was created. + +6. **Built-in array methods** like `map`, `filter`, `reduce`, `forEach`, `find`, `some`, and `every` are all higher-order functions. + +7. **The abstraction benefit** is huge. HOFs let you work at the right level of abstraction, hiding mechanical details. + +8. **Watch out for common gotchas** like losing `this`, forgetting to return, and unexpected arguments like with `parseInt`. + +9. **Don't overuse HOFs**. Sometimes a simple loop is clearer. Use HOFs when they make code more readable, not less. +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="What makes a function 'higher-order'?"> + **Answer:** + + A function is higher-order if it does at least one of these two things: + + 1. Accepts one or more functions as arguments + 2. Returns a function as its result + + ```javascript + // Accepts a function + function doTwice(fn) { + fn() + fn() + } + + // Returns a function + function multiplier(factor) { + return n => n * factor + } + + // Does both! + function compose(f, g) { + return x => f(g(x)) + } + ``` + </Accordion> + + <Accordion title="What's the relationship between callbacks and higher-order functions?"> + **Answer:** + + They're two sides of the same coin: + + - A **callback** is a function passed to another function to be executed later + - A **higher-order function** is a function that accepts (or returns) other functions + + When you pass a callback to a higher-order function, the HOF decides when to call it. + + ```javascript + // setTimeout is a higher-order function + // The arrow function is the callback + setTimeout(() => console.log('Done!'), 1000) + + // map is a higher-order function + // n => n * 2 is the callback + [1, 2, 3].map(n => n * 2) + ``` + </Accordion> + + <Accordion title="Why does ['1','2','3'].map(parseInt) return [1, NaN, NaN]?"> + **Answer:** + + `map` passes three arguments to its callback: `(element, index, array)`. + + `parseInt` accepts two arguments: `(string, radix)`. So `map` accidentally passes the index as the radix: + + ```javascript + // What map actually calls: + parseInt('1', 0) // 1 (radix 0 → default base 10) + parseInt('2', 1) // NaN (radix 1 is invalid) + parseInt('3', 2) // NaN (3 is not valid binary) + ``` + + The fix is to wrap `parseInt`: + + ```javascript + ['1', '2', '3'].map(str => parseInt(str, 10)) // [1, 2, 3] + // or + ['1', '2', '3'].map(Number) // [1, 2, 3] + ``` + </Accordion> + + <Accordion title="How do closures enable function factories?"> + **Answer:** + + When a function returns another function, the inner function "closes over" variables from the outer function's scope. It remembers them even after the outer function has finished. + + ```javascript + function createMultiplier(factor) { + // 'factor' is captured by the returned function + return function(number) { + return number * factor + } + } + + const double = createMultiplier(2) // factor = 2 is remembered + const triple = createMultiplier(3) // factor = 3 is remembered + + console.log(double(5)) // 10 (uses factor = 2) + console.log(triple(5)) // 15 (uses factor = 3) + ``` + + Each returned function has its own closure with its own `factor` value. + </Accordion> + + <Accordion title="When should you NOT use higher-order functions?"> + **Answer:** + + Avoid HOFs when: + + 1. **A simple loop is clearer** for your specific case + 2. **Performance is critical** (loops can be faster for simple operations) + 3. **The abstraction adds more complexity** than it removes + 4. **You're chaining too many operations** making debugging hard + + ```javascript + // Sometimes this is perfectly fine: + let sum = 0 + for (const n of numbers) { + sum += n + } + + // Don't force this just to use HOFs: + const sum = numbers.reduce((acc, n) => acc + n, 0) + ``` + + The goal is readable, maintainable code. Use whatever achieves that. + </Accordion> + + <Accordion title="What's the difference between map() and forEach()?"> + **Answer:** + + | Aspect | `map()` | `forEach()` | + |--------|---------|-------------| + | Returns | New array with transformed elements | `undefined` | + | Purpose | Transform data | Perform side effects | + | Chainable | Yes | No | + | Use when | You need the result | You just want to do something | + + ```javascript + const numbers = [1, 2, 3] + + // map: transforms and returns new array + const doubled = numbers.map(n => n * 2) + console.log(doubled) // [2, 4, 6] + + // forEach: just executes, returns undefined + const result = numbers.forEach(n => console.log(n)) + console.log(result) // undefined + ``` + + Use `map` when you need the transformed array. Use `forEach` when you just want to do something with each element (like logging or updating external state). + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="Higher-Order Functions in JavaScript" icon="newspaper" href="https://www.sitepoint.com/higher-order-functions-javascript/"> - By M. David Green + <Card title="Callbacks" icon="phone" href="/concepts/callbacks"> + Callbacks are functions passed to higher-order functions + </Card> + <Card title="Map, Reduce, Filter" icon="filter" href="/concepts/map-reduce-filter"> + The most common built-in higher-order functions + </Card> + <Card title="Pure Functions" icon="sparkles" href="/concepts/pure-functions"> + HOFs work best when combined with pure functions </Card> - <Card title="Higher Order Functions: Using Filter, Map and Reduce" icon="newspaper" href="https://medium.freecodecamp.org/higher-order-functions-in-javascript-d9101f9cf528"> - By Guido Schmitz + <Card title="Currying & Composition" icon="layer-group" href="/concepts/currying-composition"> + Advanced patterns built on top of higher-order functions + </Card> + <Card title="Scope and Closures" icon="lock" href="/concepts/scope-and-closures"> + Closures are what make functions returning functions work </Card> </CardGroup> -- [First-class and Higher Order Functions: Effective Functional JavaScript — Hugo Di Francesco](https://hackernoon.com/effective-functional-javascript-first-class-and-higher-order-functions-713fde8df50a) -- [Higher Order Functions in JavaScript — John Hannah](https://www.lullabot.com/articles/higher-order-functions-in-javascript) -- [Just a reminder on how to use high order functions — Pedro Filho](https://github.com/pedroapfilho/high-order-functions) -- [Understanding Higher-Order Functions in JavaScript — Sukhjinder Arora](https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad) -- [Higher Order Functions - A pragmatic approach — emmanuel ikwuoma](https://dev.to/nuel_ikwuoma/higher-order-functions-a-pragmatic-approach-51fb) +--- -## Videos +## Reference <CardGroup cols={2}> - <Card title="JavaScript Higher Order Functions & Arrays" icon="video" href="https://www.youtube.com/watch?v=rRgD1yVwIvE"> - By Traversy Media + <Card title="First-class Function — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function"> + The foundation that makes higher-order functions possible </Card> - <Card title="Higher Order Functions" icon="video" href="https://www.youtube.com/watch?v=BMUiFMZr7vk"> - By Fun Fun Function + <Card title="Closures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures"> + Essential for understanding functions that return functions + </Card> + <Card title="Array Methods — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array"> + Reference for built-in higher-order array methods + </Card> + <Card title="Functions — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions"> + Complete guide to JavaScript functions </Card> </CardGroup> -- [Higher Order Functions in Javascript — Raja Yogan](https://www.youtube.com/watch?v=dTlpYnmBW9I) -- [Higher Order Iterators in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=GYRMNp1SKXA) -- [Higher Order Functions in JavaScript — The Coding Train](https://www.youtube.com/watch?v=H4awPsyugS0) -- [Part 1: An Introduction to Callbacks and Higher Order Functions - Codesmith](https://www.youtube.com/watch?v=7E8ctomPQJw) -- [Part 2: Understanding Why We Need Higher Order Functions - Codesmith](https://www.youtube.com/watch?v=28MXziDZkE4) -- [Higher-Order Functions ft. Functional Programming - Akshay Saini](https://www.youtube.com/watch?v=HkWxvB1RJq0) +## Articles + +<CardGroup cols={2}> + <Card title="Eloquent JavaScript, Chapter 5" icon="book" href="https://eloquentjavascript.net/05_higher_order.html"> + The pea soup analogy and abstraction concepts come from this excellent free book. Includes exercises to practice HOF concepts. + </Card> + <Card title="Higher Order Functions in JavaScript — freeCodeCamp" icon="newspaper" href="https://www.freecodecamp.org/news/higher-order-functions-in-javascript-explained/"> + Practical examples with shopping carts and user data. Great step-by-step explanations of map, filter, and reduce. + </Card> + <Card title="JavaScript Array Methods — javascript.info" icon="newspaper" href="https://javascript.info/array-methods"> + Comprehensive coverage of all array HOF methods with interactive examples and exercises. + </Card> + <Card title="Understanding Higher-Order Functions — Sukhjinder Arora" icon="newspaper" href="https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad"> + Clear explanations with practical examples showing how to create custom higher-order functions. + </Card> +</CardGroup> + +## Videos + +<CardGroup cols={2}> + <Card title="Higher Order Functions — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=BMUiFMZr7vk"> + Part of the legendary "Functional Programming in JavaScript" series. MPJ's engaging teaching style makes HOFs click. + </Card> + <Card title="Higher-Order Functions ft. Functional Programming — Akshay Saini" icon="video" href="https://www.youtube.com/watch?v=HkWxvB1RJq0"> + Deep dive into HOFs with the calculate function example. Popular in the JavaScript community for its clear explanations. + </Card> + <Card title="JavaScript Higher Order Functions & Arrays — Traversy Media" icon="video" href="https://www.youtube.com/watch?v=rRgD1yVwIvE"> + Practical, project-based approach to understanding map, filter, reduce, and other array HOFs. + </Card> +</CardGroup> diff --git a/tests/functional-programming/higher-order-functions/higher-order-functions.test.js b/tests/functional-programming/higher-order-functions/higher-order-functions.test.js new file mode 100644 index 00000000..197c7ccf --- /dev/null +++ b/tests/functional-programming/higher-order-functions/higher-order-functions.test.js @@ -0,0 +1,516 @@ +import { describe, it, expect, vi } from 'vitest' + +describe('Higher-Order Functions', () => { + describe('Functions that accept functions as arguments', () => { + it('should execute the passed function', () => { + const mockFn = vi.fn() + + function doTwice(action) { + action() + action() + } + + doTwice(mockFn) + + expect(mockFn).toHaveBeenCalledTimes(2) + }) + + it('should repeat an action n times', () => { + const results = [] + + function repeat(times, action) { + for (let i = 0; i < times; i++) { + action(i) + } + } + + repeat(5, i => results.push(i)) + + expect(results).toEqual([0, 1, 2, 3, 4]) + }) + + it('should apply different logic with the same structure', () => { + function calculate(numbers, operation) { + const result = [] + for (const num of numbers) { + result.push(operation(num)) + } + return result + } + + const numbers = [1, 2, 3, 4, 5] + + const doubled = calculate(numbers, n => n * 2) + const squared = calculate(numbers, n => n * n) + const incremented = calculate(numbers, n => n + 1) + + expect(doubled).toEqual([2, 4, 6, 8, 10]) + expect(squared).toEqual([1, 4, 9, 16, 25]) + expect(incremented).toEqual([2, 3, 4, 5, 6]) + }) + + it('should implement unless as a control flow abstraction', () => { + const results = [] + + function unless(condition, action) { + if (!condition) { + action() + } + } + + for (let i = 0; i < 5; i++) { + unless(i % 2 === 1, () => results.push(i)) + } + + expect(results).toEqual([0, 2, 4]) + }) + + it('should calculate circle properties using formulas', () => { + function calculate(radii, formula) { + const result = [] + for (const radius of radii) { + result.push(formula(radius)) + } + return result + } + + const area = r => Math.PI * r * r + const circumference = r => 2 * Math.PI * r + const diameter = r => 2 * r + const volume = r => (4/3) * Math.PI * r * r * r + + const radii = [1, 2, 3] + + // Test area: π * r² + const areas = calculate(radii, area) + expect(areas[0]).toBeCloseTo(Math.PI, 5) // π * 1² = π + expect(areas[1]).toBeCloseTo(4 * Math.PI, 5) // π * 2² = 4π + expect(areas[2]).toBeCloseTo(9 * Math.PI, 5) // π * 3² = 9π + + // Test circumference: 2πr + const circumferences = calculate(radii, circumference) + expect(circumferences[0]).toBeCloseTo(2 * Math.PI, 5) // 2π * 1 + expect(circumferences[1]).toBeCloseTo(4 * Math.PI, 5) // 2π * 2 + expect(circumferences[2]).toBeCloseTo(6 * Math.PI, 5) // 2π * 3 + + // Test diameter: 2r + const diameters = calculate(radii, diameter) + expect(diameters).toEqual([2, 4, 6]) + + // Test volume: (4/3)πr³ + const volumes = calculate(radii, volume) + expect(volumes[0]).toBeCloseTo((4/3) * Math.PI, 5) // (4/3)π * 1³ + expect(volumes[1]).toBeCloseTo((4/3) * Math.PI * 8, 5) // (4/3)π * 2³ + expect(volumes[2]).toBeCloseTo((4/3) * Math.PI * 27, 5) // (4/3)π * 3³ + }) + }) + + describe('Functions that return functions', () => { + it('should create a greaterThan comparator', () => { + function greaterThan(n) { + return function(m) { + return m > n + } + } + + const greaterThan10 = greaterThan(10) + const greaterThan100 = greaterThan(100) + + expect(greaterThan10(11)).toBe(true) + expect(greaterThan10(5)).toBe(false) + expect(greaterThan10(10)).toBe(false) + expect(greaterThan100(150)).toBe(true) + expect(greaterThan100(50)).toBe(false) + }) + + it('should create multiplier functions', () => { + function multiplier(factor) { + return number => number * factor + } + + const double = multiplier(2) + const triple = multiplier(3) + const tenX = multiplier(10) + + expect(double(5)).toBe(10) + expect(triple(5)).toBe(15) + expect(tenX(5)).toBe(50) + expect(double(0)).toBe(0) + expect(triple(-3)).toBe(-9) + }) + + it('should wrap functions with logging behavior', () => { + const logs = [] + + function noisy(fn) { + return function(...args) { + logs.push({ type: 'call', args }) + const result = fn(...args) + logs.push({ type: 'return', result }) + return result + } + } + + const noisyMax = noisy(Math.max) + const result = noisyMax(3, 1, 4, 1, 5) + + expect(result).toBe(5) + expect(logs).toEqual([ + { type: 'call', args: [3, 1, 4, 1, 5] }, + { type: 'return', result: 5 } + ]) + }) + + it('should wrap Math.floor with noisy', () => { + const logs = [] + + function noisy(fn) { + return function(...args) { + logs.push({ type: 'call', args }) + const result = fn(...args) + logs.push({ type: 'return', result }) + return result + } + } + + const noisyFloor = noisy(Math.floor) + const result = noisyFloor(4.7) + + expect(result).toBe(4) + expect(logs).toEqual([ + { type: 'call', args: [4.7] }, + { type: 'return', result: 4 } + ]) + }) + + it('should create greeting functions with createGreeter', () => { + function createGreeter(greeting) { + return function(name) { + return `${greeting}, ${name}!` + } + } + + const sayHello = createGreeter('Hello') + const sayGoodbye = createGreeter('Goodbye') + + expect(sayHello('Alice')).toBe('Hello, Alice!') + expect(sayHello('Bob')).toBe('Hello, Bob!') + expect(sayGoodbye('Alice')).toBe('Goodbye, Alice!') + }) + + it('should allow direct factory invocation', () => { + function multiplier(factor) { + return number => number * factor + } + + // Direct invocation without storing intermediate function + expect(multiplier(7)(3)).toBe(21) + expect(multiplier(2)(10)).toBe(20) + expect(multiplier(0.5)(100)).toBe(50) + }) + }) + + describe('Function factories', () => { + it('should create validator functions', () => { + function createValidator(min, max) { + return function(value) { + return value >= min && value <= max + } + } + + const isValidAge = createValidator(0, 120) + const isValidPercentage = createValidator(0, 100) + + expect(isValidAge(25)).toBe(true) + expect(isValidAge(150)).toBe(false) + expect(isValidAge(-5)).toBe(false) + expect(isValidPercentage(50)).toBe(true) + expect(isValidPercentage(101)).toBe(false) + }) + + it('should create formatter functions', () => { + function createFormatter(prefix, suffix) { + return function(value) { + return `${prefix}${value}${suffix}` + } + } + + const formatDollars = createFormatter('$', '') + const formatPercent = createFormatter('', '%') + const formatParens = createFormatter('(', ')') + + expect(formatDollars(99.99)).toBe('$99.99') + expect(formatPercent(75)).toBe('75%') + expect(formatParens('aside')).toBe('(aside)') + }) + + it('should implement partial application', () => { + function partial(fn, ...presetArgs) { + return function(...laterArgs) { + return fn(...presetArgs, ...laterArgs) + } + } + + function greet(greeting, punctuation, name) { + return `${greeting}, ${name}${punctuation}` + } + + const sayHello = partial(greet, 'Hello', '!') + const askHowAreYou = partial(greet, 'How are you', '?') + + expect(sayHello('Alice')).toBe('Hello, Alice!') + expect(sayHello('Bob')).toBe('Hello, Bob!') + expect(askHowAreYou('Charlie')).toBe('How are you, Charlie?') + }) + + it('should create temperature validator', () => { + function createValidator(min, max) { + return function(value) { + return value >= min && value <= max + } + } + + // Absolute zero in Celsius is -273.15 + const isValidTemperature = createValidator(-273.15, 1000000) + + expect(isValidTemperature(25)).toBe(true) + expect(isValidTemperature(-273.15)).toBe(true) // Exactly at absolute zero + expect(isValidTemperature(-300)).toBe(false) // Below absolute zero + expect(isValidTemperature(1000000)).toBe(true) // At max + expect(isValidTemperature(1000001)).toBe(false) // Above max + }) + }) + + describe('Closures with higher-order functions', () => { + it('should create independent counters', () => { + function createCounter(start = 0) { + let count = start + return function() { + count++ + return count + } + } + + const counter1 = createCounter() + const counter2 = createCounter(100) + + expect(counter1()).toBe(1) + expect(counter1()).toBe(2) + expect(counter1()).toBe(3) + + expect(counter2()).toBe(101) + expect(counter2()).toBe(102) + + // counter1 should not be affected by counter2 + expect(counter1()).toBe(4) + }) + + it('should create private state with closures', () => { + function createBankAccount(initialBalance) { + let balance = initialBalance + + return { + deposit(amount) { + if (amount > 0) { + balance += amount + return balance + } + return balance + }, + withdraw(amount) { + if (amount > 0 && amount <= balance) { + balance -= amount + return balance + } + return 'Insufficient funds' + }, + getBalance() { + return balance + } + } + } + + const account = createBankAccount(100) + + expect(account.getBalance()).toBe(100) + expect(account.deposit(50)).toBe(150) + expect(account.withdraw(30)).toBe(120) + expect(account.withdraw(200)).toBe('Insufficient funds') + expect(account.getBalance()).toBe(120) + + // balance is not directly accessible + expect(account.balance).toBeUndefined() + }) + }) + + describe('Common mistakes', () => { + it('should demonstrate the parseInt gotcha with map', () => { + // This is the WRONG way - demonstrates the bug + const buggyResult = ['1', '2', '3'].map(parseInt) + + // parseInt receives (string, index) from map + // parseInt('1', 0) → 1 (radix 0 is treated as 10) + // parseInt('2', 1) → NaN (radix 1 is invalid) + // parseInt('3', 2) → NaN (3 is not valid in binary) + expect(buggyResult).toEqual([1, NaN, NaN]) + + // The CORRECT way + const correctResult = ['1', '2', '3'].map(str => parseInt(str, 10)) + expect(correctResult).toEqual([1, 2, 3]) + + // Alternative correct way using Number + const alternativeResult = ['1', '2', '3'].map(Number) + expect(alternativeResult).toEqual([1, 2, 3]) + }) + + it('should demonstrate losing this context', () => { + const user = { + name: 'Alice', + greet() { + // Using optional chaining to handle undefined 'this' safely + return `Hello, I'm ${this?.name ?? 'undefined'}` + } + } + + // Direct call works + expect(user.greet()).toBe("Hello, I'm Alice") + + // Passing as callback loses 'this' + function callLater(fn) { + return fn() + } + + // This fails because 'this' is lost (undefined in strict mode) + const lostThis = callLater(user.greet) + expect(lostThis).toBe("Hello, I'm undefined") + + // Fix with bind + const boundGreet = callLater(user.greet.bind(user)) + expect(boundGreet).toBe("Hello, I'm Alice") + + // Fix with arrow function wrapper + const wrappedGreet = callLater(() => user.greet()) + expect(wrappedGreet).toBe("Hello, I'm Alice") + }) + + it('should show difference between map and forEach return values', () => { + const numbers = [1, 2, 3] + + // map returns a new array + const mapResult = numbers.map(n => n * 2) + expect(mapResult).toEqual([2, 4, 6]) + + // forEach returns undefined + const forEachResult = numbers.forEach(n => n * 2) + expect(forEachResult).toBeUndefined() + }) + }) + + describe('First-class functions', () => { + it('should allow assigning functions to variables', () => { + const greet = function(name) { + return `Hello, ${name}!` + } + + const add = (a, b) => a + b + + expect(greet('Alice')).toBe('Hello, Alice!') + expect(add(2, 3)).toBe(5) + }) + + it('should allow passing functions as arguments', () => { + function callWith5(fn) { + return fn(5) + } + + expect(callWith5(n => n * 2)).toBe(10) + expect(callWith5(n => n + 3)).toBe(8) + expect(callWith5(Math.sqrt)).toBeCloseTo(2.236, 2) + }) + + it('should allow returning functions from functions', () => { + function createAdder(x) { + return function(y) { + return x + y + } + } + + const add5 = createAdder(5) + const add10 = createAdder(10) + + expect(add5(3)).toBe(8) + expect(add10(3)).toBe(13) + }) + }) + + describe('Built-in higher-order functions (overview)', () => { + const numbers = [1, 2, 3, 4, 5] + + it('should use forEach for side effects', () => { + const results = [] + const returnValue = numbers.forEach(n => results.push(n * 2)) + + expect(results).toEqual([2, 4, 6, 8, 10]) + expect(returnValue).toBeUndefined() + }) + + it('should use map for transformations', () => { + const doubled = numbers.map(n => n * 2) + + expect(doubled).toEqual([2, 4, 6, 8, 10]) + expect(numbers).toEqual([1, 2, 3, 4, 5]) // Original unchanged + }) + + it('should use filter for selection', () => { + const evens = numbers.filter(n => n % 2 === 0) + const greaterThan3 = numbers.filter(n => n > 3) + + expect(evens).toEqual([2, 4]) + expect(greaterThan3).toEqual([4, 5]) + }) + + it('should use reduce for accumulation', () => { + const sum = numbers.reduce((acc, n) => acc + n, 0) + const product = numbers.reduce((acc, n) => acc * n, 1) + + expect(sum).toBe(15) + expect(product).toBe(120) + }) + + it('should use find to get first matching element', () => { + const firstEven = numbers.find(n => n % 2 === 0) + const firstGreaterThan10 = numbers.find(n => n > 10) + + expect(firstEven).toBe(2) + expect(firstGreaterThan10).toBeUndefined() + }) + + it('should use some to test if any element matches', () => { + const hasEven = numbers.some(n => n % 2 === 0) + const hasNegative = numbers.some(n => n < 0) + + expect(hasEven).toBe(true) + expect(hasNegative).toBe(false) + }) + + it('should use every to test if all elements match', () => { + const allPositive = numbers.every(n => n > 0) + const allEven = numbers.every(n => n % 2 === 0) + + expect(allPositive).toBe(true) + expect(allEven).toBe(false) + }) + + it('should use sort with a comparator function', () => { + const unsorted = [3, 1, 4, 1, 5, 9, 2, 6] + + // Ascending order + const ascending = [...unsorted].sort((a, b) => a - b) + expect(ascending).toEqual([1, 1, 2, 3, 4, 5, 6, 9]) + + // Descending order + const descending = [...unsorted].sort((a, b) => b - a) + expect(descending).toEqual([9, 6, 5, 4, 3, 2, 1, 1]) + }) + }) +}) From 74e8cb8c84229c6dbfa2dcd898161676a3d8fe30 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Thu, 1 Jan 2026 14:18:52 -0300 Subject: [PATCH 088/128] docs: add comprehensive Pure Functions concept page - Complete rewrite with SEO-optimized title and description - Add engaging opening with questions and immediate code example - Include Kitchen Recipe analogy with ASCII diagrams - Cover the two rules of purity with practical examples - Add Common Side Effects table and mutation patterns - Include immutable patterns (objects, arrays, sorting) - Add structuredClone and shallow copy trap sections - Create Why Pure Functions Matter accordion section - Add When Pure Functions Aren't Possible with edge-pushing strategy - Include 10 Key Takeaways and 6 Test Your Knowledge Q&As - Add curated articles (freeCodeCamp, Eric Elliott, A List Apart, James Sinclair) - Add curated videos (Fun Fun Function, and others) - Create comprehensive test suite with 42 tests covering all examples - Add inline MDN links for Math.random, Math.max, Date, Intl.NumberFormat --- docs/concepts/pure-functions.mdx | 814 +++++++++++++++++- .../pure-functions/pure-functions.test.js | 809 +++++++++++++++++ 2 files changed, 1595 insertions(+), 28 deletions(-) create mode 100644 tests/functional-programming/pure-functions/pure-functions.test.js diff --git a/docs/concepts/pure-functions.mdx b/docs/concepts/pure-functions.mdx index 8e57c0f5..7ec361b3 100644 --- a/docs/concepts/pure-functions.mdx +++ b/docs/concepts/pure-functions.mdx @@ -1,47 +1,805 @@ --- -title: "Pure Functions: Side Effects & Immutability in JavaScript" -sidebarTitle: "Pure Functions: Side Effects & Immutability" -description: "Learn pure functions in JavaScript — functions with no side effects that always return the same output for the same input. Master immutability, state mutation, and predictable code." +title: "Pure Functions: Writing Predictable Code in JavaScript" +sidebarTitle: "Pure Functions: Writing Predictable Code" +description: "Learn pure functions in JavaScript. Understand the two rules of purity, avoid side effects, and write testable, predictable code with immutable patterns." --- -## Overview +Why does the same function sometimes give you different results? Why is some code easy to test while other code requires elaborate setup and mocking? Why do bugs seem to appear "randomly" when your logic looks correct? -**Pure functions** are functions that always return the same output for the same input and have no side effects. They don't modify external state or depend on external mutable state. Understanding pure functions, side effects, and state mutation is fundamental to writing predictable and testable JavaScript code. +The answer often comes down to **pure functions**. They're at the heart of functional programming, and understanding them will change how you write JavaScript. + +```javascript +// A pure function: same input always gives same output +function add(a, b) { + return a + b +} + +add(2, 3) // 5 +add(2, 3) // 5, always 5, no matter when or where you call it +``` + +A pure function is simple, predictable, and trustworthy. Once you understand why, you'll start seeing opportunities to write cleaner code everywhere. + +<Info> +**What you'll learn in this guide:** +- The two rules that make a function "pure" +- What side effects are and how they create bugs +- How to identify pure vs impure functions +- Practical patterns for avoiding mutations +- When pure functions aren't possible (and what to do instead) +- Why purity makes testing and debugging much easier +</Info> + +<Warning> +**Helpful background:** This guide references object and array mutations frequently. If you're not comfortable with how JavaScript handles [value vs reference types](/concepts/value-reference-types), read that guide first. It explains why `const arr = [1,2,3]; arr.push(4)` works but shouldn't surprise you. +</Warning> + +--- + +## What is a Pure Function? + +A **pure function** is a function that follows two simple rules: + +1. **Same input → Same output**: Given the same arguments, it always returns the same result +2. **No side effects**: It doesn't change anything outside itself + +That's it. If a function follows both rules, it's pure. If it breaks either rule, it's impure. + +```javascript +// ✓ PURE: Follows both rules +function double(x) { + return x * 2 +} + +double(5) // 10 +double(5) // 10, always 10 +``` + +Using [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) breaks purity because it introduces randomness: + +```javascript +// ❌ IMPURE: Breaks rule 1 (different output for same input) +function randomDouble(x) { + return x * Math.random() +} + +randomDouble(5) // 2.3456... +randomDouble(5) // 4.1234... different every time! +``` + +```javascript +// ❌ IMPURE: Breaks rule 2 (has a side effect) +let total = 0 + +function addToTotal(x) { + total += x // Modifies external variable! + return total +} + +addToTotal(5) // 5 +addToTotal(5) // 10. Different result because total changed! +``` + +<CardGroup cols={2}> + <Card title="Functions — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions"> + MDN guide covering JavaScript function fundamentals + </Card> + <Card title="Functional Programming — Wikipedia" icon="book" href="https://en.wikipedia.org/wiki/Pure_function"> + Formal definition of pure functions in computer science + </Card> +</CardGroup> + +--- + +## The Kitchen Recipe Analogy + +Think of a pure function like a recipe. If you give a recipe the same ingredients, you get the same dish every time. The recipe doesn't care what time it is, what else is in your kitchen, or what you cooked yesterday. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PURE VS IMPURE FUNCTIONS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ PURE FUNCTION (Like a Recipe) │ +│ ───────────────────────────── │ +│ │ +│ Ingredients Recipe Dish │ +│ ┌───────────┐ ┌─────────┐ ┌───────┐ │ +│ │ 2 eggs │ │ │ │ │ │ +│ │ flour │ ────► │ mix & │ ────► │ cake │ │ +│ │ sugar │ │ bake │ │ │ │ +│ └───────────┘ └─────────┘ └───────┘ │ +│ │ +│ ✓ Same ingredients = Same cake, every time │ +│ ✓ Doesn't rearrange your kitchen │ +│ ✓ Doesn't depend on the weather │ +│ │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ IMPURE FUNCTION (Unpredictable Chef) │ +│ ──────────────────────────────────── │ +│ │ +│ ┌───────────┐ ┌─────────┐ ┌───────┐ │ +│ │ 2 eggs │ │ checks │ │ ??? │ │ +│ │ flour │ ────► │ clock, │ ────► │ │ │ +│ │ sugar │ │ mood... │ │ │ │ +│ └───────────┘ └─────────┘ └───────┘ │ +│ │ +│ ✗ Same ingredients might give different results │ +│ ✗ Might rearrange your whole kitchen while cooking │ +│ ✗ Depends on external factors you can't control │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +A pure function is like a recipe: predictable, self-contained, and trustworthy. An impure function is like a chef who checks the weather, changes the recipe based on mood, and rearranges your kitchen while cooking. + +--- + +## Rule 1: Same Input → Same Output + +This rule is also called **referential transparency**. It means you could replace a function call with its result and the program would work exactly the same. + +[`Math.max()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) is a great example of a pure function: + +```javascript +// ✓ PURE: Math.max always returns the same result for the same inputs +Math.max(2, 8, 5) // 8 +Math.max(2, 8, 5) // 8, always 8 + +// You could replace Math.max(2, 8, 5) with 8 anywhere in your code +// and nothing would change. That's referential transparency. +``` + +### What Breaks This Rule? + +Anything that makes the output depend on something other than the inputs: + +<Tabs> + <Tab title="Random Values"> + ```javascript + // ❌ IMPURE: Output depends on randomness + function getRandomDiscount(price) { + return price * Math.random() + } + +getRandomDiscount(100) // 47.23... +getRandomDiscount(100) // 82.91... different! + ``` + </Tab> + <Tab title="Current Time"> + Using [`new Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) makes functions impure because the output depends on when you call them: + + ```javascript + // ❌ IMPURE: Output depends on when you call it + function getGreeting(name) { + const hour = new Date().getHours() + if (hour < 12) return `Good morning, ${name}` + return `Good afternoon, ${name}` + } + + // Same input, different output depending on time of day + ``` + </Tab> + <Tab title="External State"> + ```javascript + // ❌ IMPURE: Output depends on external variable + let taxRate = 0.08 + + function calculateTotal(price) { + return price + (price * taxRate) + } + + calculateTotal(100) // 108 + taxRate = 0.10 + calculateTotal(100) // 110. Different! + ``` + </Tab> +</Tabs> + +### How to Fix It + +Pass everything the function needs as arguments: + +```javascript +// ✓ PURE: Tax rate is now an input, not external state +function calculateTotal(price, taxRate) { + return price + (price * taxRate) +} + +calculateTotal(100, 0.08) // 108 +calculateTotal(100, 0.08) // 108, always the same +calculateTotal(100, 0.10) // 110 — different input, different output (that's fine!) +``` + +<Tip> +**Quick test for Rule 1:** Can you predict the output just by looking at the inputs? If you need to know the current time, check a global variable, or run it to find out, it's probably not pure. +</Tip> + +--- + +## Rule 2: No Side Effects + +A **side effect** is anything a function does besides computing and returning a value. Side effects are actions that affect the world outside the function. + +### Common Side Effects + +| Side Effect | Example | Why It's a Problem | +|-------------|---------|-------------------| +| **Mutating input** | `array.push(item)` | Changes data the caller might still be using | +| **Modifying external variables** | `counter++` | Creates hidden dependencies | +| **Console output** | `console.log()` | Does something besides returning a value | +| **DOM manipulation** | `element.innerHTML = '...'` | Changes the page state | +| **HTTP requests** | `fetch('/api/data')` | Communicates with external systems | +| **Writing to storage** | `localStorage.setItem()` | Persists data outside the function | +| **Throwing exceptions** | `throw new Error()` | Exits function in an unexpected way | + +```javascript +// ❌ IMPURE: Multiple side effects +function processUser(user) { + user.lastLogin = new Date() // Side effect: mutates input + console.log(`User ${user.name}`) // Side effect: console output + userCount++ // Side effect: modifies external variable + return user +} + +// ✓ PURE: Returns new data, no side effects +function processUser(user, loginTime) { + return { + ...user, + lastLogin: loginTime + } +} +``` + +<Note> +**Is `console.log()` really that bad?** Technically, yes. It's a side effect. But practically? It's fine for debugging. The key is understanding that it makes your function impure. Don't let `console.log` statements slip into production code that should be pure. +</Note> + +--- + +## The #1 Pure Functions Mistake: Mutations + +The most common way developers accidentally create impure functions is by **mutating objects or arrays** that were passed in. + +```javascript +// ❌ IMPURE: Mutates the input array +function addItem(cart, item) { + cart.push(item) // This changes the original cart! + return cart +} + +const myCart = ['apple', 'banana'] +const newCart = addItem(myCart, 'orange') + +console.log(myCart) // ['apple', 'banana', 'orange'] — Original changed! +console.log(newCart) // ['apple', 'banana', 'orange'] +console.log(myCart === newCart) // true — They're the same array! +``` + +This creates bugs because any other code using `myCart` now sees unexpected changes. The fix is simple: return a **new** array instead of modifying the original. + +```javascript +// ✓ PURE: Returns a new array, original unchanged +function addItem(cart, item) { + return [...cart, item] // Spread into new array +} + +const myCart = ['apple', 'banana'] +const newCart = addItem(myCart, 'orange') + +console.log(myCart) // ['apple', 'banana'] — Original unchanged! +console.log(newCart) // ['apple', 'banana', 'orange'] +console.log(myCart === newCart) // false — Different arrays +``` + +### Shallow Copy Trap + +Watch out: the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) only creates a **shallow copy**. Nested objects are still shared: + +```javascript +// ⚠️ DANGER: Shallow copy with nested objects +const user = { + name: 'Alice', + address: { city: 'NYC', zip: '10001' } +} + +const updatedUser = { ...user, name: 'Bob' } + +// Top level is a new object... +console.log(user === updatedUser) // false ✓ + +// But nested object is SHARED +updatedUser.address.city = 'LA' +console.log(user.address.city) // 'LA'. Original changed! +``` + +For nested objects, use [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) for a deep copy: + +```javascript +// ✓ SAFE: Deep clone for nested objects +const user = { + name: 'Alice', + address: { city: 'NYC', zip: '10001' } +} + +const updatedUser = { + ...structuredClone(user), + name: 'Bob' +} + +updatedUser.address.city = 'LA' +console.log(user.address.city) // 'NYC' — Original safe! +``` + +<Warning> +**The Trap:** Spread operator (`...`) only copies one level deep. If you have nested objects or arrays, mutations to the nested data will affect the original. Use `structuredClone()` for deep copies, or see our [Value vs Reference Types](/concepts/value-reference-types) guide for more patterns. +</Warning> + +--- + +## Immutable Patterns for Pure Functions + +Here are the most common patterns for writing pure functions that handle objects and arrays: + +### Updating Objects + +```javascript +// ❌ IMPURE: Mutates the object +function updateEmail(user, email) { + user.email = email + return user +} + +// ✓ PURE: Returns new object with updated property +function updateEmail(user, email) { + return { ...user, email } +} +``` + +### Adding to Arrays + +```javascript +// ❌ IMPURE: Mutates the array +function addTodo(todos, newTodo) { + todos.push(newTodo) + return todos +} + +// ✓ PURE: Returns new array with item added +function addTodo(todos, newTodo) { + return [...todos, newTodo] +} +``` + +### Removing from Arrays + +```javascript +// ❌ IMPURE: Mutates the array +function removeTodo(todos, index) { + todos.splice(index, 1) + return todos +} + +// ✓ PURE: Returns new array without the item +function removeTodo(todos, index) { + return todos.filter((_, i) => i !== index) +} +``` + +### Updating Array Items + +```javascript +// ❌ IMPURE: Mutates item in array +function completeTodo(todos, index) { + todos[index].completed = true + return todos +} + +// ✓ PURE: Returns new array with updated item +function completeTodo(todos, index) { + return todos.map((todo, i) => + i === index ? { ...todo, completed: true } : todo + ) +} +``` + +### Sorting Arrays + +```javascript +// ❌ IMPURE: sort() mutates the original array! +function getSorted(numbers) { + return numbers.sort((a, b) => a - b) +} + +// ✓ PURE: Copy first, then sort +function getSorted(numbers) { + return [...numbers].sort((a, b) => a - b) +} + +// ✓ PURE (ES2023+): Use toSorted() which returns a new array +function getSorted(numbers) { + return numbers.toSorted((a, b) => a - b) +} +``` + +<Tip> +**ES2023 added non-mutating versions** of several array methods: `toSorted()`, `toReversed()`, `toSpliced()`, and `with()`. These are perfect for pure functions. Check browser support before using in production. +</Tip> + +--- + +## Why Pure Functions Matter + +Writing pure functions isn't just about following rules. It brings real, practical benefits: + +<AccordionGroup> + <Accordion title="1. Easier to Test"> + Pure functions are a testing dream. No mocking, no setup, no cleanup. Just call the function and check the result. + + ```javascript + // Testing a pure function is trivial + function add(a, b) { + return a + b + } + + // Test + expect(add(2, 3)).toBe(5) + expect(add(-1, 1)).toBe(0) + expect(add(0, 0)).toBe(0) + // Done! No mocks, no setup, no teardown + ``` + + Compare this to testing a function that reads from the DOM, makes API calls, or depends on global state. You'd need elaborate setup just to run one test. + </Accordion> + + <Accordion title="2. Easier to Debug"> + When something goes wrong, pure functions narrow down the problem fast. If a pure function returns the wrong value, the bug is in *that function*. It can't be caused by some other code changing global state. + + ```javascript + // If calculateTax(100, 0.08) returns the wrong value, + // the bug MUST be inside calculateTax. + // No need to check what other code ran before it. + function calculateTax(amount, rate) { + return amount * rate + } + ``` + </Accordion> + + <Accordion title="3. Safe to Cache (Memoization)"> + Since pure functions always return the same output for the same input, you can safely cache their results. This is called [memoization](https://developer.mozilla.org/en-US/docs/Glossary/Memoization). + + ```javascript + // Expensive calculation - safe to cache because it's pure + function fibonacci(n) { + if (n <= 1) return n + return fibonacci(n - 1) + fibonacci(n - 2) + } + + // With memoization, fibonacci(40) computes once, then returns cached result + ``` + + You can't safely cache impure functions because they might need to return different values even with the same inputs. + </Accordion> + + <Accordion title="4. Safe to Parallelize"> + Pure functions don't depend on shared state, so they can safely run in parallel. This is how libraries like TensorFlow process massive datasets across multiple CPU cores or GPU threads. + + ```javascript + // These can all run at the same time - no conflicts! + const results = await Promise.all([ + processChunk(data.slice(0, 1000)), + processChunk(data.slice(1000, 2000)), + processChunk(data.slice(2000, 3000)) + ]) + ``` + </Accordion> + + <Accordion title="5. Easier to Understand"> + When you see a pure function, you know everything it can do is right there in the code. No hidden dependencies, no spooky action at a distance. + + ```javascript + // You can understand this function completely by reading it + function formatPrice(cents, currency = 'USD') { + const dollars = cents / 100 + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency + }).format(dollars) + } + ``` + + This function uses [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) but remains pure because the same inputs always produce the same formatted output. + </Accordion> +</AccordionGroup> + +--- + +## When Pure Functions Aren't Possible + +Let's be realistic: you can't build useful applications with *only* pure functions. At some point you need to: + +- Read from and write to the DOM +- Make HTTP requests +- Log errors +- Save to localStorage +- Respond to user events + +The strategy is to **push impure code to the edges** of your application. Keep the core logic pure, and isolate the impure parts. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ STRUCTURE OF A WELL-DESIGNED APP │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ EDGES (Impure) CORE (Pure) EDGES (Impure) │ +│ ────────────── ────────── ────────────── │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Read from │ │ Transform │ │ Write to │ │ +│ │ DOM, API, │ ──────► │ Calculate │ ──────► │ DOM, API, │ │ +│ │ user input │ │ Process │ │ console │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ INPUT LOGIC OUTPUT │ +│ (impure) (pure) (impure) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Example: Separating Pure from Impure + +```javascript +// IMPURE: Reads from DOM +function getUserInput() { + return document.querySelector('#username').value +} + +// PURE: Transforms data (no DOM access) +function formatUsername(name) { + return name.trim().toLowerCase() +} + +// PURE: Validates data (no side effects) +function isValidUsername(name) { + return name.length >= 3 && name.length <= 20 +} + +// IMPURE: Writes to DOM +function displayError(message) { + document.querySelector('#error').textContent = message +} + +// Orchestration: impure code at the edges +function handleSubmit() { + const raw = getUserInput() // Impure: read + const formatted = formatUsername(raw) // Pure: transform + const isValid = isValidUsername(formatted) // Pure: validate + + if (!isValid) { + displayError('Username must be 3-20 characters') // Impure: write + } +} +``` + +The pure functions (`formatUsername`, `isValidUsername`) are easy to test and reuse. The impure functions are isolated at the edges where they're easy to find and manage. + +--- + +## Key Takeaways + +<Info> +**The key things to remember about pure functions:** + +1. **Two rules define purity**: same input → same output, and no side effects + +2. **Side effects** include mutations, console.log, DOM access, HTTP requests, randomness, and current time + +3. **Mutations are the #1 trap**. Use spread operator or `structuredClone()` to return new data instead + +4. **Shallow copies aren't enough** for nested objects. The spread operator only copies one level deep + +5. **Pure functions are easier to test**. No mocking, no setup. Just input and expected output + +6. **Pure functions are easier to debug**. If the output is wrong, the bug is in that function + +7. **Pure functions can be cached**. Same input always means same output, so memoization is safe + +8. **You can't avoid impurity entirely**. The goal is to isolate it at the edges of your application + +9. **console.log is technically impure** but acceptable for debugging. Just don't let it slip into logic that should be pure + +10. **ES2023 added `toSorted()`, `toReversed()`** and other non-mutating array methods. Use them when you can! +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What two rules define a pure function?"> + **Answer:** + + A pure function must follow both rules: + + 1. **Same input → Same output**: Given the same arguments, it always returns the same result (referential transparency) + 2. **No side effects**: It doesn't modify anything outside itself (no mutations, no I/O, no external state changes) + + ```javascript + // Pure: follows both rules + function multiply(a, b) { + return a * b + } + ``` + </Accordion> + + <Accordion title="Question 2: Is this function pure? Why or why not?"> + ```javascript + function greet(name) { + return `Hello, ${name}! The time is ${new Date().toLocaleTimeString()}` + } + ``` + + **Answer:** + + No, this function is **impure**. It breaks Rule 1 (same input → same output) because it uses `new Date()`. Calling `greet('Alice')` at 10:00 AM gives a different result than calling it at 3:00 PM, even though the input is the same. + + To make it pure, pass the time as a parameter: + + ```javascript + function greet(name, time) { + return `Hello, ${name}! The time is ${time}` + } + ``` + </Accordion> + + <Accordion title="Question 3: What's wrong with this function?"> + ```javascript + function addToCart(cart, item) { + cart.push(item) + return cart + } + ``` + + **Answer:** + + This function **mutates its input**. The `push()` method modifies the original `cart` array, which is a side effect. Any other code using that cart array will see unexpected changes. + + Fix it by returning a new array: + + ```javascript + function addToCart(cart, item) { + return [...cart, item] + } + ``` + </Accordion> + + <Accordion title="Question 4: How do you safely update a nested object in a pure function?"> + **Answer:** + + Use `structuredClone()` for a deep copy, or carefully spread at each level: + + ```javascript + // Option 1: structuredClone (simplest) + function updateCity(user, newCity) { + const copy = structuredClone(user) + copy.address.city = newCity + return copy + } + + // Option 2: Spread at each level + function updateCity(user, newCity) { + return { + ...user, + address: { + ...user.address, + city: newCity + } + } + } + ``` + + Note: A simple `{ ...user }` shallow copy would still share the nested `address` object with the original. + </Accordion> + + <Accordion title="Question 5: Why are pure functions easier to test?"> + **Answer:** + + Pure functions only depend on their inputs and only produce their return value. This means: + + - **No setup needed**: You don't need to configure global state, mock APIs, or set up DOM elements + - **No cleanup needed**: The function doesn't change anything, so there's nothing to reset + - **Predictable**: Same input always gives same output, so tests are deterministic + - **Isolated**: If a test fails, the bug must be in that function + + ```javascript + // Testing a pure function - simple and straightforward + expect(add(2, 3)).toBe(5) + expect(formatName(' ALICE ')).toBe('alice') + expect(isValidEmail('test@example.com')).toBe(true) + ``` + </Accordion> + + <Accordion title="Question 6: When is it okay to have impure functions?"> + **Answer:** + + Impure functions are necessary for any real application. You need them to: + + - Read user input from the DOM + - Make HTTP requests to APIs + - Write output to the screen + - Save data to localStorage or databases + - Log errors and debugging info + + The strategy is to **isolate impurity at the edges** of your application. Keep your core business logic in pure functions, and use impure functions only for I/O operations. This gives you the best of both worlds: testable, predictable logic with the ability to interact with the outside world. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Value vs Reference Types" icon="diagram-project" href="/concepts/value-reference-types"> + Understanding mutations, shallow vs deep copies, and why objects behave differently than primitives + </Card> + <Card title="Higher-Order Functions" icon="arrows-repeat" href="/concepts/higher-order-functions"> + Functions that take or return functions, perfect for composing pure functions + </Card> + <Card title="map, reduce & filter" icon="filter" href="/concepts/map-reduce-filter"> + Non-mutating array methods that return new arrays, ideal for pure functions + </Card> + <Card title="Currying & Composition" icon="wand-magic-sparkles" href="/concepts/currying-composition"> + Advanced patterns for building complex pure functions from simple ones + </Card> +</CardGroup> + +--- + +## Reference + +<CardGroup cols={2}> + <Card title="Array Methods — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array"> + Complete reference for all array methods, including which ones mutate + </Card> + <Card title="Spread Syntax — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"> + How to use the spread operator for shallow copies of arrays and objects + </Card> + <Card title="structuredClone() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/structuredClone"> + Modern API for deep cloning objects, including nested structures + </Card> + <Card title="Object.freeze() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze"> + How to make objects immutable (though only shallowly) + </Card> +</CardGroup> ## Articles <CardGroup cols={2}> - <Card title="Javascript and Functional Programming — Pure Functions" icon="newspaper" href="https://hackernoon.com/javascript-and-functional-programming-pt-3-pure-functions-d572bb52e21c"> - By Omer Goldberg + <Card title="What Is a Pure Function in JavaScript?" icon="newspaper" href="https://www.freecodecamp.org/news/what-is-a-pure-function-in-javascript-acb887375dfe/"> + Yazeed Bzadough's checklist approach with clear examples. Perfect starting point for understanding the two rules of pure functions and common side effects. </Card> <Card title="Master the JavaScript Interview: What is a Pure Function?" icon="newspaper" href="https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976"> - By Eric Elliott + Eric Elliott's deep dive into referential transparency and shared state. Includes real-world examples of race conditions caused by impure functions. + </Card> + <Card title="Making your JavaScript Pure" icon="newspaper" href="https://alistapart.com/article/making-your-javascript-pure/"> + Jack Franklin's practical guide focusing on testability. Excellent "before and after" refactoring examples that show how to transform impure code. + </Card> + <Card title="How to Deal with Dirty Side Effects in Pure Functional JavaScript" icon="newspaper" href="https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/"> + James Sinclair's advanced guide to dependency injection and the Effect pattern. For when you're ready to take functional programming to the next level. </Card> </CardGroup> -- [JavaScript: What Are Pure Functions And Why Use Them? — James Jeffery](https://medium.com/@jamesjefferyuk/javascript-what-are-pure-functions-4d4d5392d49c) -- [Pure functions in JavaScript — @nicoespeon](http://www.nicoespeon.com/en/2015/01/pure-functions-javascript/) -- [Functional Programming: Pure Functions — Arne Brasseur](https://www.sitepoint.com/functional-programming-pure-functions/) -- [Making your JavaScript Pure — Jack Franklin](https://alistapart.com/article/making-your-javascript-pure) -- [Arrays, Objects and Mutations — Federico Knüssel](https://medium.com/@fknussel/arrays-objects-and-mutations-6b23348b54aa) -- [The State of Immutability — Maciej Sikora](https://medium.com/dailyjs/the-state-of-immutability-169d2cd11310) -- [How to deal with dirty side effects in your pure functional JavaScript — James Sinclair](https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/) -- [Preventing Side Effects in JavaScript — David Walsh](https://davidwalsh.name/preventing-sideeffects-javascript) -- [JavaScript: Pure Functions — William S. Vincent](https://wsvincent.com/javascript-pure-functions/) -- [Functional programming paradigms in modern JavaScript: Pure functions — Alexander Kondov](https://hackernoon.com/functional-programming-paradigms-in-modern-javascript-pure-functions-797d9abbee1) -- [Understanding Javascript Mutation and Pure Functions — Chidume Nnamdi](https://blog.bitsrc.io/understanding-javascript-mutation-and-pure-functions-7231cc2180d3) - ## Videos <CardGroup cols={2}> - <Card title="Pure Functions" icon="video" href="https://www.youtube.com/watch?v=dZ41D6LDSBg"> - By Hexlet + <Card title="Pure Functions — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=BMUiFMZr7vk"> + Mattias Petter Johansson's entertaining introduction to pure functions. Part of his excellent functional programming series that makes complex topics approachable. </Card> - <Card title="Pure Functions - Functional Programming in JavaScript" icon="video" href="https://www.youtube.com/watch?v=Jh_Uzqzz_wM"> - By Paul McBride + <Card title="Pure vs Impure Functions" icon="video" href="https://www.youtube.com/watch?v=AHbRVJzpB54"> + Theodore Anderson's clear comparison of pure and impure functions with practical JavaScript examples and visual explanations. + </Card> + <Card title="JavaScript Pure Functions" icon="video" href="https://www.youtube.com/watch?v=frT3H-eBmPc"> + Seth Alexander's focused tutorial covering the fundamentals of pure functions and their benefits for writing maintainable code. </Card> </CardGroup> - -- [JavaScript Pure Functions — Seth Alexander](https://www.youtube.com/watch?v=frT3H-eBmPc) -- [JavaScript Pure vs Impure Functions Explained — Theodore Anderson](https://www.youtube.com/watch?v=AHbRVJzpB54) -- [Pure Functions - Programação Funcional: Parte 1 - Fun Fun Function](https://www.youtube.com/watch?v=BMUiFMZr7vk) diff --git a/tests/functional-programming/pure-functions/pure-functions.test.js b/tests/functional-programming/pure-functions/pure-functions.test.js new file mode 100644 index 00000000..d73b88cc --- /dev/null +++ b/tests/functional-programming/pure-functions/pure-functions.test.js @@ -0,0 +1,809 @@ +import { describe, it, expect } from 'vitest' + +describe('Pure Functions', () => { + describe('Rule 1: Same Input → Same Output', () => { + it('should always return the same result for the same inputs', () => { + // Pure function: deterministic + function add(a, b) { + return a + b + } + + expect(add(2, 3)).toBe(5) + expect(add(2, 3)).toBe(5) + expect(add(2, 3)).toBe(5) + // Always 5, no matter how many times we call it + }) + + it('should demonstrate Math.max as a pure function', () => { + // Math.max is pure: same inputs always give same output + expect(Math.max(2, 8, 5)).toBe(8) + expect(Math.max(2, 8, 5)).toBe(8) + expect(Math.max(-1, -5, -2)).toBe(-1) + }) + + it('should show how external state breaks purity', () => { + // Impure: depends on external state + let taxRate = 0.08 + + function calculateTotalImpure(price) { + return price + price * taxRate + } + + expect(calculateTotalImpure(100)).toBe(108) + + // Changing external state changes the result + taxRate = 0.10 + expect(calculateTotalImpure(100)).toBe(110) // Different! + + // Pure version: all dependencies are parameters + function calculateTotalPure(price, rate) { + return price + price * rate + } + + expect(calculateTotalPure(100, 0.08)).toBe(108) + expect(calculateTotalPure(100, 0.08)).toBe(108) // Always the same + expect(calculateTotalPure(100, 0.10)).toBe(110) // Different input = different output (that's fine) + }) + + it('should demonstrate that Math.random makes functions impure', () => { + // ❌ IMPURE: Output depends on randomness + function randomDouble(x) { + return x * Math.random() + } + + // Same input but (almost certainly) different outputs + const results = new Set() + for (let i = 0; i < 10; i++) { + results.add(randomDouble(5)) + } + + // With random, we get multiple different results for the same input + expect(results.size).toBeGreaterThan(1) + }) + + it('should demonstrate that Date makes functions impure', () => { + // ❌ IMPURE: Output depends on when you call it + function getGreeting(name) { + const hour = new Date().getHours() + if (hour < 12) return `Good morning, ${name}` + return `Good afternoon, ${name}` + } + + // The function works, but its output depends on external state (time) + const result = getGreeting('Alice') + expect(result).toMatch(/Good (morning|afternoon), Alice/) + + // To make it pure, pass the hour as a parameter + function getGreetingPure(name, hour) { + if (hour < 12) return `Good morning, ${name}` + return `Good afternoon, ${name}` + } + + // Now it's deterministic + expect(getGreetingPure('Alice', 9)).toBe('Good morning, Alice') + expect(getGreetingPure('Alice', 9)).toBe('Good morning, Alice') // Always same + expect(getGreetingPure('Alice', 14)).toBe('Good afternoon, Alice') + }) + }) + + describe('Rule 2: No Side Effects', () => { + it('should demonstrate addToTotal impure pattern from docs', () => { + // ❌ IMPURE: Breaks rule 2 (has a side effect) + let total = 0 + + function addToTotal(x) { + total += x // Modifies external variable! + return total + } + + expect(addToTotal(5)).toBe(5) + expect(addToTotal(5)).toBe(10) // Different result because total changed + expect(addToTotal(5)).toBe(15) // Keeps changing! + + // The function modifies external state, making it impure + expect(total).toBe(15) + }) + + it('should demonstrate mutation as a side effect', () => { + // Impure: mutates the input + function addItemImpure(cart, item) { + cart.push(item) + return cart + } + + const myCart = ['apple', 'banana'] + const result = addItemImpure(myCart, 'orange') + + expect(myCart).toEqual(['apple', 'banana', 'orange']) // Original mutated! + expect(result).toBe(myCart) // Same reference + }) + + it('should show pure alternative that returns new array', () => { + // Pure: returns new array, original unchanged + function addItemPure(cart, item) { + return [...cart, item] + } + + const myCart = ['apple', 'banana'] + const newCart = addItemPure(myCart, 'orange') + + expect(myCart).toEqual(['apple', 'banana']) // Original unchanged! + expect(newCart).toEqual(['apple', 'banana', 'orange']) + expect(myCart).not.toBe(newCart) // Different references + }) + + it('should demonstrate external variable modification as a side effect', () => { + let counter = 0 + + // Impure: modifies external variable + function incrementImpure() { + counter++ + return counter + } + + expect(incrementImpure()).toBe(1) + expect(incrementImpure()).toBe(2) // Different result for same (no) input! + expect(incrementImpure()).toBe(3) + + // Pure alternative + function incrementPure(value) { + return value + 1 + } + + expect(incrementPure(0)).toBe(1) + expect(incrementPure(0)).toBe(1) // Always the same + expect(incrementPure(5)).toBe(6) + }) + + it('should demonstrate processUser impure vs pure from docs', () => { + // ❌ IMPURE: Multiple side effects + let userCount = 0 + const loginTime = new Date('2025-01-01T10:00:00') + + function processUserImpure(user) { + user.lastLogin = loginTime // Side effect: mutates input + userCount++ // Side effect: modifies external variable + return user + } + + const user1 = { name: 'Alice' } + const result1 = processUserImpure(user1) + + expect(user1.lastLogin).toEqual(loginTime) // Original mutated! + expect(userCount).toBe(1) // External state changed! + expect(result1).toBe(user1) // Same reference + + // ✓ PURE: Returns new data, no side effects + function processUserPure(user, loginTime) { + return { + ...user, + lastLogin: loginTime + } + } + + const user2 = { name: 'Bob' } + const result2 = processUserPure(user2, loginTime) + + expect(user2.lastLogin).toBe(undefined) // Original unchanged! + expect(result2.lastLogin).toEqual(loginTime) + expect(result2).not.toBe(user2) // Different reference + expect(result2.name).toBe('Bob') + }) + }) + + describe('Identifying Pure vs Impure Functions', () => { + it('should identify pure mathematical functions', () => { + function double(x) { + return x * 2 + } + + function square(x) { + return x * x + } + + function hypotenuse(a, b) { + return Math.sqrt(a * a + b * b) + } + + // All pure: same inputs always give same outputs + expect(double(5)).toBe(10) + expect(square(4)).toBe(16) + expect(hypotenuse(3, 4)).toBe(5) + }) + + it('should identify pure string functions', () => { + function formatName(name) { + return name.trim().toLowerCase() + } + + function greet(name, greeting) { + return `${greeting}, ${name}!` + } + + expect(formatName(' ALICE ')).toBe('alice') + expect(formatName(' ALICE ')).toBe('alice') // Same result + expect(greet('Bob', 'Hello')).toBe('Hello, Bob!') + }) + + it('should identify pure validation functions', () => { + function isValidEmail(email) { + return email.includes('@') && email.includes('.') + } + + function isPositive(num) { + return num > 0 + } + + expect(isValidEmail('test@example.com')).toBe(true) + expect(isValidEmail('invalid')).toBe(false) + expect(isPositive(5)).toBe(true) + expect(isPositive(-3)).toBe(false) + }) + }) + + describe('Immutable Object Patterns', () => { + it('should update object properties without mutation', () => { + const user = { name: 'Alice', age: 25 } + + // Pure: returns new object + function updateAge(user, newAge) { + return { ...user, age: newAge } + } + + const updatedUser = updateAge(user, 26) + + expect(user.age).toBe(25) // Original unchanged + expect(updatedUser.age).toBe(26) + expect(user).not.toBe(updatedUser) + }) + + it('should add properties without mutation', () => { + const product = { name: 'Widget', price: 10 } + + function addDiscount(product, discount) { + return { ...product, discount } + } + + const discountedProduct = addDiscount(product, 0.1) + + expect(product.discount).toBe(undefined) // Original unchanged + expect(discountedProduct.discount).toBe(0.1) + }) + + it('should remove properties without mutation', () => { + const user = { name: 'Alice', age: 25, password: 'secret' } + + function removePassword(user) { + const { password, ...rest } = user + return rest + } + + const safeUser = removePassword(user) + + expect(user.password).toBe('secret') // Original unchanged + expect(safeUser.password).toBe(undefined) + expect(safeUser).toEqual({ name: 'Alice', age: 25 }) + }) + }) + + describe('Immutable Array Patterns', () => { + it('should add items without mutation', () => { + const todos = ['Learn JS', 'Build app'] + + // Pure: returns new array + function addTodo(todos, newTodo) { + return [...todos, newTodo] + } + + const newTodos = addTodo(todos, 'Deploy') + + expect(todos).toEqual(['Learn JS', 'Build app']) // Original unchanged + expect(newTodos).toEqual(['Learn JS', 'Build app', 'Deploy']) + }) + + it('should remove items without mutation', () => { + const numbers = [1, 2, 3, 4, 5] + + // Pure: filter creates new array + function removeItem(arr, index) { + return arr.filter((_, i) => i !== index) + } + + const result = removeItem(numbers, 2) // Remove item at index 2 + + expect(numbers).toEqual([1, 2, 3, 4, 5]) // Original unchanged + expect(result).toEqual([1, 2, 4, 5]) + }) + + it('should update items without mutation', () => { + const todos = [ + { id: 1, text: 'Learn JS', done: false }, + { id: 2, text: 'Build app', done: false } + ] + + function completeTodo(todos, id) { + return todos.map((todo) => (todo.id === id ? { ...todo, done: true } : todo)) + } + + const updated = completeTodo(todos, 1) + + expect(todos[0].done).toBe(false) // Original unchanged + expect(updated[0].done).toBe(true) + expect(updated[1].done).toBe(false) + }) + + it('should sort without mutation using spread', () => { + const numbers = [3, 1, 4, 1, 5, 9, 2, 6] + + // Impure: sort mutates the original + function sortImpure(arr) { + return arr.sort((a, b) => a - b) + } + + // Pure: copy first + function sortPure(arr) { + return [...arr].sort((a, b) => a - b) + } + + const sorted = sortPure(numbers) + + expect(numbers).toEqual([3, 1, 4, 1, 5, 9, 2, 6]) // Original unchanged + expect(sorted).toEqual([1, 1, 2, 3, 4, 5, 6, 9]) + }) + + it('should use toSorted for non-mutating sort (ES2023)', () => { + const numbers = [3, 1, 4, 1, 5] + + const sorted = numbers.toSorted((a, b) => a - b) + + expect(numbers).toEqual([3, 1, 4, 1, 5]) // Original unchanged + expect(sorted).toEqual([1, 1, 3, 4, 5]) + }) + + it('should use toReversed for non-mutating reverse (ES2023)', () => { + const letters = ['a', 'b', 'c', 'd'] + + const reversed = letters.toReversed() + + expect(letters).toEqual(['a', 'b', 'c', 'd']) // Original unchanged + expect(reversed).toEqual(['d', 'c', 'b', 'a']) + }) + }) + + describe('Deep Copy for Nested Objects', () => { + it('should demonstrate shallow copy problem with nested objects', () => { + const user = { + name: 'Alice', + address: { city: 'NYC', zip: '10001' } + } + + // Shallow copy - nested object is shared! + const shallowCopy = { ...user } + + shallowCopy.address.city = 'LA' + + expect(user.address.city).toBe('LA') // Original changed! + }) + + it('should use structuredClone for deep copy', () => { + const user = { + name: 'Alice', + address: { city: 'NYC', zip: '10001' } + } + + const deepCopy = structuredClone(user) + + deepCopy.address.city = 'LA' + + expect(user.address.city).toBe('NYC') // Original unchanged! + expect(deepCopy.address.city).toBe('LA') + }) + + it('should safely update nested properties in pure function', () => { + const user = { + name: 'Alice', + address: { city: 'NYC', zip: '10001' } + } + + // Pure function using structuredClone + function updateCity(user, newCity) { + const copy = structuredClone(user) + copy.address.city = newCity + return copy + } + + // Alternative: spread at each level + function updateCitySpread(user, newCity) { + return { + ...user, + address: { + ...user.address, + city: newCity + } + } + } + + const updated1 = updateCity(user, 'LA') + const updated2 = updateCitySpread(user, 'Boston') + + expect(user.address.city).toBe('NYC') // Original unchanged + expect(updated1.address.city).toBe('LA') + expect(updated2.address.city).toBe('Boston') + }) + }) + + describe('Common Mistakes', () => { + it('should avoid mutating function parameters', () => { + // Bad: mutates the parameter + function processUserBad(user) { + user.processed = true + user.name = user.name.toUpperCase() + return user + } + + // Good: returns new object + function processUserGood(user) { + return { + ...user, + processed: true, + name: user.name.toUpperCase() + } + } + + const user = { name: 'alice', age: 25 } + + const result = processUserGood(user) + + expect(user.processed).toBe(undefined) // Original unchanged + expect(user.name).toBe('alice') + expect(result.processed).toBe(true) + expect(result.name).toBe('ALICE') + }) + + it('should avoid relying on external mutable state', () => { + // Bad: relies on external config + const config = { multiplier: 2 } + + function calculateBad(value) { + return value * config.multiplier + } + + // Good: config passed as parameter + function calculateGood(value, multiplier) { + return value * multiplier + } + + expect(calculateGood(5, 2)).toBe(10) + expect(calculateGood(5, 2)).toBe(10) // Always predictable + }) + + it('should be careful with array methods that mutate', () => { + const numbers = [3, 1, 2] + + // These methods MUTATE the original array: + // sort(), reverse(), splice(), push(), pop(), shift(), unshift(), fill() + + // Safe alternatives: + const sorted = [...numbers].sort((a, b) => a - b) // Copy first + const reversed = [...numbers].reverse() // Copy first + const withNew = [...numbers, 4] // Spread instead of push + + expect(numbers).toEqual([3, 1, 2]) // Original unchanged + expect(sorted).toEqual([1, 2, 3]) + expect(reversed).toEqual([2, 1, 3]) + expect(withNew).toEqual([3, 1, 2, 4]) + }) + }) + + describe('Practical Pure Function Examples', () => { + it('should calculate shopping cart total purely', () => { + function calculateTotal(items, taxRate) { + const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0) + const tax = subtotal * taxRate + return { + subtotal, + tax, + total: subtotal + tax + } + } + + const items = [ + { name: 'Widget', price: 10, quantity: 2 }, + { name: 'Gadget', price: 25, quantity: 1 } + ] + + const result = calculateTotal(items, 0.08) + + expect(result.subtotal).toBe(45) + expect(result.tax).toBeCloseTo(3.6) + expect(result.total).toBeCloseTo(48.6) + + // Original items unchanged + expect(items[0].name).toBe('Widget') + }) + + it('should filter and transform data purely', () => { + function getActiveUserNames(users) { + return users.filter((user) => user.active).map((user) => user.name.toLowerCase()) + } + + const users = [ + { name: 'ALICE', active: true }, + { name: 'BOB', active: false }, + { name: 'CHARLIE', active: true } + ] + + const result = getActiveUserNames(users) + + expect(result).toEqual(['alice', 'charlie']) + expect(users[0].name).toBe('ALICE') // Original unchanged + }) + + it('should compose pure functions', () => { + const trim = (str) => str.trim() + const toLowerCase = (str) => str.toLowerCase() + const removeSpaces = (str) => str.replace(/\s+/g, '-') + + function slugify(title) { + return removeSpaces(toLowerCase(trim(title))) + } + + expect(slugify(' Hello World ')).toBe('hello-world') + expect(slugify(' JavaScript Is Fun ')).toBe('javascript-is-fun') + }) + + it('should validate data purely', () => { + function validateUser(user) { + const errors = [] + + if (!user.name || user.name.length < 2) { + errors.push('Name must be at least 2 characters') + } + + if (!user.email || !user.email.includes('@')) { + errors.push('Valid email is required') + } + + if (!user.age || user.age < 0) { + errors.push('Age must be a positive number') + } + + return { + isValid: errors.length === 0, + errors + } + } + + const validUser = { name: 'Alice', email: 'alice@example.com', age: 25 } + const invalidUser = { name: 'A', email: 'invalid', age: -5 } + + expect(validateUser(validUser).isValid).toBe(true) + expect(validateUser(validUser).errors).toEqual([]) + + expect(validateUser(invalidUser).isValid).toBe(false) + expect(validateUser(invalidUser).errors).toHaveLength(3) + }) + }) + + describe('Benefits of Pure Functions', () => { + it('should be easy to test (no setup needed)', () => { + // Pure functions are trivial to test + function add(a, b) { + return a + b + } + + // No mocking, no setup, no cleanup + expect(add(1, 2)).toBe(3) + expect(add(-1, 1)).toBe(0) + expect(add(0.1, 0.2)).toBeCloseTo(0.3) + }) + + it('should be safe to memoize', () => { + let callCount = 0 + + // Pure function - safe to cache + function expensiveCalculation(n) { + callCount++ + let result = 0 + for (let i = 0; i < n; i++) { + result += i + } + return result + } + + // Simple memoization + function memoize(fn) { + const cache = new Map() + return function (arg) { + if (cache.has(arg)) { + return cache.get(arg) + } + const result = fn(arg) + cache.set(arg, result) + return result + } + } + + const memoizedCalc = memoize(expensiveCalculation) + + // First call computes + expect(memoizedCalc(1000)).toBe(499500) + expect(callCount).toBe(1) + + // Second call returns cached result + expect(memoizedCalc(1000)).toBe(499500) + expect(callCount).toBe(1) // Not called again! + + // Different input computes again + expect(memoizedCalc(500)).toBe(124750) + expect(callCount).toBe(2) + }) + + it('should demonstrate fibonacci as a pure function safe for memoization', () => { + // Expensive calculation - safe to cache because it's pure + function fibonacci(n) { + if (n <= 1) return n + return fibonacci(n - 1) + fibonacci(n - 2) + } + + // Pure: same input always gives same output + expect(fibonacci(0)).toBe(0) + expect(fibonacci(1)).toBe(1) + expect(fibonacci(2)).toBe(1) + expect(fibonacci(3)).toBe(2) + expect(fibonacci(4)).toBe(3) + expect(fibonacci(5)).toBe(5) + expect(fibonacci(10)).toBe(55) + + // Call multiple times - always same result + expect(fibonacci(10)).toBe(55) + expect(fibonacci(10)).toBe(55) + }) + }) + + describe('Examples from Q&A Section', () => { + it('should demonstrate multiply as a pure function', () => { + // Pure: follows both rules + function multiply(a, b) { + return a * b + } + + expect(multiply(3, 4)).toBe(12) + expect(multiply(3, 4)).toBe(12) // Always the same + expect(multiply(-2, 5)).toBe(-10) + expect(multiply(0, 100)).toBe(0) + }) + + it('should demonstrate greet impure vs pure', () => { + // ❌ IMPURE: Uses new Date() - output varies with time + function greetImpure(name) { + return `Hello, ${name}! The time is ${new Date().toLocaleTimeString()}` + } + + // The impure version includes time, making results unpredictable + const result1 = greetImpure('Alice') + expect(result1).toContain('Hello, Alice!') + expect(result1).toContain('The time is') + + // ✓ PURE: Pass time as a parameter + function greetPure(name, time) { + return `Hello, ${name}! The time is ${time}` + } + + expect(greetPure('Alice', '10:00:00 AM')).toBe('Hello, Alice! The time is 10:00:00 AM') + expect(greetPure('Alice', '10:00:00 AM')).toBe('Hello, Alice! The time is 10:00:00 AM') // Always same + expect(greetPure('Bob', '3:00:00 PM')).toBe('Hello, Bob! The time is 3:00:00 PM') + }) + + it('should demonstrate calculateTax as a pure function', () => { + // If calculateTax(100, 0.08) returns the wrong value, + // the bug MUST be inside calculateTax. + // No need to check what other code ran before it. + function calculateTax(amount, rate) { + return amount * rate + } + + expect(calculateTax(100, 0.08)).toBe(8) + expect(calculateTax(100, 0.08)).toBe(8) // Always the same + expect(calculateTax(250, 0.1)).toBe(25) + expect(calculateTax(0, 0.08)).toBe(0) + }) + + it('should demonstrate formatPrice as a pure function', () => { + // You can understand this function completely by reading it + function formatPrice(cents, currency = 'USD') { + const dollars = cents / 100 + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency + }).format(dollars) + } + + expect(formatPrice(1999)).toBe('$19.99') + expect(formatPrice(1999)).toBe('$19.99') // Always the same + expect(formatPrice(500)).toBe('$5.00') + expect(formatPrice(9999, 'USD')).toBe('$99.99') + expect(formatPrice(1000, 'EUR')).toBe('€10.00') + }) + + it('should demonstrate addToCart fix from Q&A', () => { + // ❌ WRONG: This function mutates its input + function addToCartBad(cart, item) { + cart.push(item) + return cart + } + + const cart1 = ['apple'] + const result1 = addToCartBad(cart1, 'banana') + expect(cart1).toEqual(['apple', 'banana']) // Original mutated! + expect(result1).toBe(cart1) // Same reference + + // ✓ CORRECT: Fix it by returning a new array + function addToCartGood(cart, item) { + return [...cart, item] + } + + const cart2 = ['apple'] + const result2 = addToCartGood(cart2, 'banana') + expect(cart2).toEqual(['apple']) // Original unchanged! + expect(result2).toEqual(['apple', 'banana']) + expect(result2).not.toBe(cart2) // Different reference + }) + + it('should demonstrate updateCity with structuredClone from Q&A', () => { + const user = { + name: 'Alice', + address: { city: 'NYC', zip: '10001' } + } + + // Option 1: structuredClone (simplest) + function updateCityClone(user, newCity) { + const copy = structuredClone(user) + copy.address.city = newCity + return copy + } + + const updated1 = updateCityClone(user, 'LA') + expect(user.address.city).toBe('NYC') // Original unchanged + expect(updated1.address.city).toBe('LA') + + // Option 2: Spread at each level + function updateCitySpread(user, newCity) { + return { + ...user, + address: { + ...user.address, + city: newCity + } + } + } + + const updated2 = updateCitySpread(user, 'Boston') + expect(user.address.city).toBe('NYC') // Original still unchanged + expect(updated2.address.city).toBe('Boston') + }) + }) + + describe('Examples from Accordion Sections', () => { + it('should demonstrate testing pure functions is trivial', () => { + // Testing a pure function - simple and straightforward + function add(a, b) { + return a + b + } + + function formatName(name) { + return name.trim().toLowerCase() + } + + function isValidEmail(email) { + return email.includes('@') && email.includes('.') + } + + // No mocking, no setup - just input and expected output + expect(add(2, 3)).toBe(5) + expect(formatName(' ALICE ')).toBe('alice') + expect(isValidEmail('test@example.com')).toBe(true) + expect(isValidEmail('invalid')).toBe(false) + }) + }) +}) From e458acc6cc0abedfc6ebed9dfb4f6470d9e328d3 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Thu, 1 Jan 2026 14:46:12 -0300 Subject: [PATCH 089/128] docs: add comprehensive map, reduce, filter concept page - Expand concept page from 67 lines to 1,259 lines with full explanations - Add factory assembly line analogy with ASCII diagrams - Include SEO-optimized 'What is X?' sections for map, filter, reduce - Cover common mistakes: missing return, reduce initial value, parseInt pitfall - Add method chaining section with real-world e-commerce/analytics examples - Include 55 comprehensive tests covering all code examples - Shorten title/description to meet SEO character limits - Add 6 test questions with accordion answers - Link to MDN docs, javascript.info, and curated video resources --- docs/concepts/map-reduce-filter.mdx | 1278 ++++++++++++++++- .../map-reduce-filter.test.js | 689 +++++++++ 2 files changed, 1924 insertions(+), 43 deletions(-) create mode 100644 tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js diff --git a/docs/concepts/map-reduce-filter.mdx b/docs/concepts/map-reduce-filter.mdx index d1e9e6d7..9d6dfc3a 100644 --- a/docs/concepts/map-reduce-filter.mdx +++ b/docs/concepts/map-reduce-filter.mdx @@ -1,67 +1,1259 @@ --- -title: "map, reduce & filter: Transforming Arrays in JavaScript" -sidebarTitle: "map, reduce & filter: Transforming Arrays" -description: "Learn JavaScript's map, reduce, and filter array methods. Master functional array transformations, method chaining, and data processing without mutating original arrays." +title: "map, reduce, filter: Transform Arrays in JavaScript" +sidebarTitle: "map, reduce, filter" +description: "Learn map, reduce, and filter in JavaScript. Transform, filter, and combine arrays without mutation. Includes method chaining and common pitfalls." --- -## Overview +How do you transform every item in an array? How do you filter out the ones you don't need? How do you combine them all into a single result? These are the three most common operations you'll perform on arrays, and JavaScript gives you three powerful methods to handle them. -`map`, `reduce`, and `filter` are powerful array methods in JavaScript that enable functional programming patterns. They allow you to transform, aggregate, and filter data in arrays without mutating the original array. +```javascript +// The power of map, filter, and reduce in action +const products = [ + { name: 'Laptop', price: 1000, inStock: true }, + { name: 'Phone', price: 500, inStock: false }, + { name: 'Tablet', price: 300, inStock: true }, + { name: 'Watch', price: 200, inStock: true } +] + +const totalInStock = products + .filter(product => product.inStock) // Keep only in-stock items + .map(product => product.price) // Extract just the prices + .reduce((sum, price) => sum + price, 0) // Sum them up + +console.log(totalInStock) // 1500 +``` + +That's **[map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)**, **[filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)**, and **[reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)** working together. Three methods that transform how you work with arrays. + +<Info> +**What you'll learn in this guide:** +- What map(), filter(), and reduce() do and when to use each +- The factory assembly line mental model for array transformations +- How to chain methods together for powerful data pipelines +- The critical mistake with reduce() that crashes your code +- Real-world patterns for extracting, filtering, and aggregating data +- Other useful array methods like find(), some(), and every() +- How to implement map and filter using reduce (advanced) +</Info> + +<Warning> +**Prerequisite:** This guide assumes you understand [higher-order functions](/concepts/higher-order-functions). map, filter, and reduce are all higher-order functions that take callbacks. If that concept is new to you, read that guide first! +</Warning> + +--- + +## The Factory Assembly Line + +Think of these three methods as stations on a factory assembly line. Raw materials (your input array) flow through different stations, each performing a specific job: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE FACTORY ASSEMBLY LINE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Raw Materials PAINTING QUALITY PACKAGING │ +│ (Input Array) STATION CONTROL STATION │ +│ map() filter() reduce() │ +│ │ +│ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┐ ┌─────────┐ │ +│ │ 1 │ 2 │ 3 │ 4 │ → │ 2 │ 4 │ 6 │ 8 │ → │ 6 │ 8 │ → │ 14 │ │ +│ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┘ └─────────┘ │ +│ │ +│ Transform Keep items Combine into │ +│ each item where n > 4 single value │ +│ (n × 2) (sum) │ +│ │ +│ ──────────────────────────────────────────────────────────────────── │ +│ │ +│ INPUT COUNT SAME COUNT FEWER OR SINGLE │ +│ = 4 items = 4 items SAME = 2 OUTPUT │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**Painting Station (map):** Every item gets transformed. You put in 4 items, you get 4 items out. Each one is changed in the same way. + +**Quality Control (filter):** Items are inspected and only those that pass the test continue. You might get fewer items out than you put in. + +**Packaging Station (reduce):** Everything gets combined into a single package. Many items go in, one result comes out. + +The beauty of this assembly line? You can connect the stations in any order. The output of one becomes the input of the next. + +--- + +## What Are These Methods? + +These three methods are the workhorses of functional programming in JavaScript. They let you transform, filter, and aggregate data without writing explicit loops and without mutating your original data. + +| Method | What It Does | Returns | Original Array | +|--------|-------------|---------|----------------| +| `map()` | Transforms every element | New array (same length) | Unchanged | +| `filter()` | Keeps elements that pass a test | New array (0 to same length) | Unchanged | +| `reduce()` | Combines all elements into one value | Any type (number, object, array, etc.) | Unchanged | + +<Tip> +**The Immutability Principle:** None of these methods change the original array. They always return something new. This makes your code predictable and easier to debug. +</Tip> + +--- + +## map() — Transform Every Element + +The **[map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)** method creates a new array by calling a function on every element of the original array. Think of it as a transformation machine: every item goes in, every item comes out changed. + +### What is map() in JavaScript? + +The `map()` method is an array method that creates a new array by applying a callback function to each element of the original array. It returns an array of the same length with each element transformed according to the callback. The original array is never modified, making map a pure, non-mutating operation ideal for functional programming. + +```javascript +const numbers = [1, 2, 3, 4] +const doubled = numbers.map(num => num * 2) + +console.log(doubled) // [2, 4, 6, 8] +console.log(numbers) // [1, 2, 3, 4] — original unchanged! +``` + +### Syntax and Parameters + +```javascript +array.map(callback(element, index, array), thisArg) +``` + +| Parameter | Description | +|-----------|-------------| +| `element` | The current element being processed | +| `index` | The index of the current element (optional) | +| `array` | The array map() was called on (optional) | +| `thisArg` | Value to use as `this` in callback (optional, rarely used) | + +### Basic Transformations + +```javascript +// Double every number +const numbers = [1, 2, 3, 4, 5] +const doubled = numbers.map(n => n * 2) +console.log(doubled) // [2, 4, 6, 8, 10] + +// Convert to uppercase +const words = ['hello', 'world'] +const shouting = words.map(word => word.toUpperCase()) +console.log(shouting) // ['HELLO', 'WORLD'] + +// Square each number +const squares = numbers.map(n => n * n) +console.log(squares) // [1, 4, 9, 16, 25] +``` + +### Extracting Properties from Objects + +One of the most common uses of map is pulling out specific properties from an array of objects: + +```javascript +const users = [ + { id: 1, name: 'Alice', email: 'alice@example.com' }, + { id: 2, name: 'Bob', email: 'bob@example.com' }, + { id: 3, name: 'Charlie', email: 'charlie@example.com' } +] + +// Get just the names +const names = users.map(user => user.name) +console.log(names) // ['Alice', 'Bob', 'Charlie'] + +// Get just the emails +const emails = users.map(user => user.email) +console.log(emails) // ['alice@example.com', 'bob@example.com', 'charlie@example.com'] + +// Get IDs as strings +const ids = users.map(user => `user-${user.id}`) +console.log(ids) // ['user-1', 'user-2', 'user-3'] +``` + +### Transforming Object Shapes + +You can also reshape objects completely: + +```javascript +const users = [ + { firstName: 'Alice', lastName: 'Smith', age: 25 }, + { firstName: 'Bob', lastName: 'Jones', age: 30 } +] + +const displayUsers = users.map(user => ({ + fullName: `${user.firstName} ${user.lastName}`, + isAdult: user.age >= 18 +})) + +console.log(displayUsers) +// [ +// { fullName: 'Alice Smith', isAdult: true }, +// { fullName: 'Bob Jones', isAdult: true } +// ] +``` + +### Using the Index Parameter + +Sometimes you need to know the position of each element: + +```javascript +const letters = ['a', 'b', 'c', 'd'] + +// Add index to each item +const indexed = letters.map((letter, index) => `${index}: ${letter}`) +console.log(indexed) // ['0: a', '1: b', '2: c', '3: d'] + +// Create objects with IDs +const items = ['apple', 'banana', 'cherry'] +const products = items.map((name, index) => ({ + id: index + 1, + name +})) +console.log(products) +// [{ id: 1, name: 'apple' }, { id: 2, name: 'banana' }, { id: 3, name: 'cherry' }] +``` + +### The parseInt Pitfall + +This is a classic JavaScript gotcha. Can you spot the problem? + +```javascript +const strings = ['1', '2', '3'] +const numbers = strings.map(parseInt) + +console.log(numbers) // [1, NaN, NaN] — Wait, what?! +``` + +**Why does this happen?** Because `parseInt` takes two arguments: the string to parse and the radix (base). When you pass `parseInt` directly to map, it receives three arguments: `(element, index, array)`. So the index becomes the radix! + +```javascript +// What's actually happening: +parseInt('1', 0) // 1 (radix 0 defaults to 10) +parseInt('2', 1) // NaN (radix 1 is invalid) +parseInt('3', 2) // NaN (3 is not a valid digit in binary) +``` + +**The fix:** Wrap parseInt in an arrow function or use `Number`: + +```javascript +// Option 1: Wrap in arrow function +const numbers1 = strings.map(str => parseInt(str, 10)) +console.log(numbers1) // [1, 2, 3] + +// Option 2: Use Number (simpler) +const numbers2 = strings.map(Number) +console.log(numbers2) // [1, 2, 3] +``` + +### map() vs forEach() + +Both iterate over arrays, but they're for different purposes: + +| Aspect | map() | forEach() | +|--------|-------|-----------| +| **Returns** | New array | undefined | +| **Purpose** | Transform data | Side effects (logging, etc.) | +| **Chainable** | Yes | No | +| **Use when** | You need the results | You just want to do something | + +```javascript +const numbers = [1, 2, 3] + +// map: When you need a new array +const doubled = numbers.map(n => n * 2) +console.log(doubled) // [2, 4, 6] + +// forEach: When you just want to do something with each item +numbers.forEach(n => console.log(n)) // Logs 1, 2, 3 + +// ❌ WRONG: Using map for side effects (wasteful) +numbers.map(n => console.log(n)) // Creates unused array [undefined, undefined, undefined] + +// ✓ CORRECT: Use forEach for side effects +numbers.forEach(n => console.log(n)) +``` + +<Warning> +**Don't use map() when you don't need the returned array.** If you're just logging or making API calls, use `forEach()`. Using map for side effects creates an unused array and signals the wrong intent to other developers. +</Warning> + +--- + +## filter() — Keep Matching Elements + +The **[filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)** method creates a new array with only the elements that pass a test. Your callback function returns `true` to keep an element or `false` to exclude it. + +### What is filter() in JavaScript? + +The `filter()` method is an array method that creates a new array containing only the elements that pass a test implemented by a callback function. Elements where the callback returns `true` (or a truthy value) are included; elements where it returns `false` are excluded. Like map, filter never modifies the original array. + +```javascript +const numbers = [1, 2, 3, 4, 5, 6] +const evens = numbers.filter(num => num % 2 === 0) + +console.log(evens) // [2, 4, 6] +console.log(numbers) // [1, 2, 3, 4, 5, 6] — original unchanged! +``` + +### Syntax and Parameters + +```javascript +array.filter(callback(element, index, array), thisArg) +``` + +The callback receives the same parameters as `map()`: `element`, `index`, `array`, plus an optional `thisArg`. + +### Basic Filtering + +```javascript +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +// Keep only even numbers +const evens = numbers.filter(n => n % 2 === 0) +console.log(evens) // [2, 4, 6, 8, 10] + +// Keep only odds +const odds = numbers.filter(n => n % 2 !== 0) +console.log(odds) // [1, 3, 5, 7, 9] + +// Keep numbers greater than 5 +const big = numbers.filter(n => n > 5) +console.log(big) // [6, 7, 8, 9, 10] + +// Keep numbers between 3 and 7 +const middle = numbers.filter(n => n >= 3 && n <= 7) +console.log(middle) // [3, 4, 5, 6, 7] +``` + +### Filtering Objects by Property + +```javascript +const users = [ + { name: 'Alice', age: 25, active: true }, + { name: 'Bob', age: 17, active: true }, + { name: 'Charlie', age: 30, active: false }, + { name: 'Diana', age: 22, active: true } +] + +// Keep only active users +const activeUsers = users.filter(user => user.active) +console.log(activeUsers) +// [{ name: 'Alice', ... }, { name: 'Bob', ... }, { name: 'Diana', ... }] + +// Keep only adults (18+) +const adults = users.filter(user => user.age >= 18) +console.log(adults) +// [{ name: 'Alice', ... }, { name: 'Charlie', ... }, { name: 'Diana', ... }] + +// Keep only active adults +const activeAdults = users.filter(user => user.active && user.age >= 18) +console.log(activeAdults) +// [{ name: 'Alice', ... }, { name: 'Diana', ... }] +``` + +### Truthy/Falsy Evaluation + +The filter callback's return value is evaluated for [truthiness](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). This means you can use filter to remove falsy values: + +```javascript +const mixed = [0, 1, '', 'hello', null, undefined, false, true, NaN, 42] + +// Remove all falsy values +const truthy = mixed.filter(Boolean) +console.log(truthy) // [1, 'hello', true, 42] + +// This works because Boolean(value) returns true for truthy values +// Boolean(0) → false +// Boolean(1) → true +// Boolean('') → false +// Boolean('hello') → true +// etc. +``` + +<Note> +**Falsy values in JavaScript:** `false`, `0`, `-0`, `0n` (BigInt), `""` (empty string), `null`, `undefined`, `NaN`. Everything else is truthy. +</Note> + +### Search and Query Filtering + +```javascript +const products = [ + { name: 'MacBook Pro', category: 'laptops', price: 2000 }, + { name: 'iPhone', category: 'phones', price: 1000 }, + { name: 'iPad', category: 'tablets', price: 800 }, + { name: 'Dell XPS', category: 'laptops', price: 1500 } +] + +// Search by name (case-insensitive) +const searchTerm = 'mac' +const results = products.filter(p => + p.name.toLowerCase().includes(searchTerm.toLowerCase()) +) +console.log(results) // [{ name: 'MacBook Pro', ... }] + +// Filter by category +const laptops = products.filter(p => p.category === 'laptops') +console.log(laptops) // [{ name: 'MacBook Pro', ... }, { name: 'Dell XPS', ... }] + +// Filter by price range +const affordable = products.filter(p => p.price <= 1000) +console.log(affordable) // [{ name: 'iPhone', ... }, { name: 'iPad', ... }] +``` + +### filter() vs find() vs some() vs every() + +These methods are related but return different things: + +| Method | Returns | Stops Early? | Use Case | +|--------|---------|--------------|----------| +| `filter()` | Array of all matches | No | Get all matching items | +| `find()` | First match (or undefined) | Yes | Get one item by condition | +| `some()` | true/false | Yes | Check if any match | +| `every()` | true/false | Yes | Check if all match | + +```javascript +const numbers = [1, 2, 3, 4, 5] + +// filter: Get ALL even numbers +numbers.filter(n => n % 2 === 0) // [2, 4] + +// find: Get the FIRST even number +numbers.find(n => n % 2 === 0) // 2 + +// some: Is there ANY even number? +numbers.some(n => n % 2 === 0) // true + +// every: Are ALL numbers even? +numbers.every(n => n % 2 === 0) // false +``` + +```javascript +const users = [ + { id: 1, name: 'Alice', admin: true }, + { id: 2, name: 'Bob', admin: false }, + { id: 3, name: 'Charlie', admin: false } +] + +// ❌ INEFFICIENT: Using filter when you only need one +const result = users.filter(u => u.id === 2)[0] // Checks all elements + +// ✓ EFFICIENT: Use find for single item lookup +const user = users.find(u => u.id === 2) // Stops at first match + +// ❌ WASTEFUL: Using filter just to check existence +const hasAdmin = users.filter(u => u.admin).length > 0 + +// ✓ BETTER: Use some for existence check +const hasAdmin2 = users.some(u => u.admin) // true, stops at first admin +``` + +--- + +## reduce() — Combine Into One Value + +The **[reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)** method executes a "reducer" function on each element, resulting in a single output value. It's the most powerful (and most confusing) of the three. + +### What is reduce() in JavaScript? + +The `reduce()` method is an array method that executes a reducer callback function on each element, accumulating the results into a single value. This value can be any type: a number, string, object, or even another array. The callback receives an accumulator (the running total) and the current element, returning the new accumulator value. Always provide an initial value to avoid crashes on empty arrays. + +```javascript +const numbers = [1, 2, 3, 4, 5] +const sum = numbers.reduce((accumulator, current) => accumulator + current, 0) + +console.log(sum) // 15 +``` + +Think of reduce like a snowball rolling down a hill. It starts small (the initial value) and grows as it picks up each element. + +### The Anatomy of reduce() + +```javascript +array.reduce(callback(accumulator, currentValue, index, array), initialValue) +``` + +| Parameter | Description | +|-----------|-------------| +| `accumulator` | The accumulated value from previous iterations | +| `currentValue` | The current element being processed | +| `index` | The index of the current element (optional) | +| `array` | The array reduce() was called on (optional) | +| `initialValue` | The starting value for the accumulator (ALWAYS provide this!) | + +### Step-by-Step Visualization + +Let's trace through how reduce works: + +```javascript +const numbers = [1, 2, 3, 4] +const sum = numbers.reduce((acc, curr) => acc + curr, 0) +``` + +| Iteration | accumulator | currentValue | acc + curr | New accumulator | +|-----------|-------------|--------------|------------|-----------------| +| 1st | 0 (initial) | 1 | 0 + 1 | 1 | +| 2nd | 1 | 2 | 1 + 2 | 3 | +| 3rd | 3 | 3 | 3 + 3 | 6 | +| 4th | 6 | 4 | 6 + 4 | **10** | + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ reduce() STEP BY STEP │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Initial value: 0 │ +│ │ +│ [1, 2, 3, 4].reduce((acc, curr) => acc + curr, 0) │ +│ │ +│ Step 1: acc=0, curr=1 → 0 + 1 = 1 (accumulator becomes 1) │ +│ Step 2: acc=1, curr=2 → 1 + 2 = 3 (accumulator becomes 3) │ +│ Step 3: acc=3, curr=3 → 3 + 3 = 6 (accumulator becomes 6) │ +│ Step 4: acc=6, curr=4 → 6 + 4 = 10 (final result!) │ +│ │ +│ Result: 10 │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Common Use Cases + +#### Sum and Average + +```javascript +const numbers = [10, 20, 30, 40, 50] + +// Sum +const sum = numbers.reduce((acc, n) => acc + n, 0) +console.log(sum) // 150 + +// Average +const average = numbers.reduce((acc, n) => acc + n, 0) / numbers.length +console.log(average) // 30 +``` + +#### Finding Max/Min + +```javascript +const numbers = [5, 2, 9, 1, 7] + +const max = numbers.reduce((acc, n) => n > acc ? n : acc, numbers[0]) +console.log(max) // 9 + +const min = numbers.reduce((acc, n) => n < acc ? n : acc, numbers[0]) +console.log(min) // 1 + +// Or use Math.max/min with spread (simpler for this case) +console.log(Math.max(...numbers)) // 9 +console.log(Math.min(...numbers)) // 1 +``` + +#### Counting Occurrences + +```javascript +const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'] + +const count = fruits.reduce((acc, fruit) => { + acc[fruit] = (acc[fruit] || 0) + 1 + return acc +}, {}) + +console.log(count) // { apple: 3, banana: 2, orange: 1 } +``` + +#### Grouping by Property + +```javascript +const people = [ + { name: 'Alice', department: 'Engineering' }, + { name: 'Bob', department: 'Marketing' }, + { name: 'Charlie', department: 'Engineering' }, + { name: 'Diana', department: 'Marketing' } +] + +const byDepartment = people.reduce((acc, person) => { + const dept = person.department + if (!acc[dept]) { + acc[dept] = [] + } + acc[dept].push(person) + return acc +}, {}) + +console.log(byDepartment) +// { +// Engineering: [{ name: 'Alice', ... }, { name: 'Charlie', ... }], +// Marketing: [{ name: 'Bob', ... }, { name: 'Diana', ... }] +// } +``` + +#### Building Objects from Arrays + +```javascript +const pairs = [['name', 'Alice'], ['age', 25], ['city', 'NYC']] + +const obj = pairs.reduce((acc, [key, value]) => { + acc[key] = value + return acc +}, {}) + +console.log(obj) // { name: 'Alice', age: 25, city: 'NYC' } +``` + +#### Flattening Nested Arrays + +```javascript +const nested = [[1, 2], [3, 4], [5, 6]] + +const flat = nested.reduce((acc, arr) => acc.concat(arr), []) +console.log(flat) // [1, 2, 3, 4, 5, 6] + +// Note: For simple flattening, use .flat() instead +console.log(nested.flat()) // [1, 2, 3, 4, 5, 6] +``` + +### Implementing map() and filter() with reduce() + +This shows just how powerful reduce is. You can implement both map and filter using reduce: + +```javascript +// map() implemented with reduce +function myMap(array, callback) { + return array.reduce((acc, element, index) => { + acc.push(callback(element, index, array)) + return acc + }, []) +} + +const doubled = myMap([1, 2, 3], n => n * 2) +console.log(doubled) // [2, 4, 6] + + +// filter() implemented with reduce +function myFilter(array, callback) { + return array.reduce((acc, element, index) => { + if (callback(element, index, array)) { + acc.push(element) + } + return acc + }, []) +} + +const evens = myFilter([1, 2, 3, 4, 5], n => n % 2 === 0) +console.log(evens) // [2, 4] +``` + +<Tip> +**When to use reduce:** Use reduce when you need to transform an array into a different type (array to object, array to number, etc.) or when you need complex accumulation logic. For simple transformations, map and filter are usually clearer. +</Tip> + +--- + +## Method Chaining — The Real Power + +The real magic happens when you chain these methods together. Each method returns a new array (or value), which you can immediately call another method on. + +```javascript +const transactions = [ + { type: 'sale', amount: 100 }, + { type: 'refund', amount: 30 }, + { type: 'sale', amount: 200 }, + { type: 'sale', amount: 150 }, + { type: 'refund', amount: 50 } +] + +const totalSales = transactions + .filter(t => t.type === 'sale') // Keep only sales + .map(t => t.amount) // Extract amounts + .reduce((sum, amount) => sum + amount, 0) // Sum them up + +console.log(totalSales) // 450 +``` + +### Reading Chained Methods + +When you see a chain, read it like a data pipeline. Data flows from top to bottom, transformed at each step: + +```javascript +const result = data + .filter(...) // Step 1: Remove unwanted items + .map(...) // Step 2: Transform remaining items + .filter(...) // Step 3: Filter again if needed + .reduce(...) // Step 4: Combine into final result +``` + +### Real-World Examples + +#### E-commerce: Calculate discounted total + +```javascript +const cart = [ + { name: 'Laptop', price: 1000, quantity: 1, discountPercent: 10 }, + { name: 'Mouse', price: 50, quantity: 2, discountPercent: 0 }, + { name: 'Keyboard', price: 100, quantity: 1, discountPercent: 20 } +] + +const total = cart + .map(item => { + const subtotal = item.price * item.quantity + const discount = subtotal * (item.discountPercent / 100) + return subtotal - discount + }) + .reduce((sum, price) => sum + price, 0) + +console.log(total) // 900 + 100 + 80 = 1080 +``` + +#### User dashboard: Get active premium users' emails + +```javascript +const users = [ + { email: 'alice@example.com', active: true, plan: 'premium' }, + { email: 'bob@example.com', active: false, plan: 'premium' }, + { email: 'charlie@example.com', active: true, plan: 'free' }, + { email: 'diana@example.com', active: true, plan: 'premium' } +] + +const premiumEmails = users + .filter(u => u.active) + .filter(u => u.plan === 'premium') + .map(u => u.email) + +console.log(premiumEmails) // ['alice@example.com', 'diana@example.com'] +``` + +#### Analytics: Top 3 performers + +```javascript +const salespeople = [ + { name: 'Alice', sales: 50000 }, + { name: 'Bob', sales: 75000 }, + { name: 'Charlie', sales: 45000 }, + { name: 'Diana', sales: 90000 }, + { name: 'Eve', sales: 60000 } +] + +const top3 = salespeople + .filter(p => p.sales >= 50000) // Minimum threshold + .sort((a, b) => b.sales - a.sales) // Sort descending + .slice(0, 3) // Take top 3 + .map(p => p.name) // Get just names + +console.log(top3) // ['Diana', 'Bob', 'Eve'] +``` + +### Performance Considerations + +Each method in a chain iterates over the array. For small arrays, this doesn't matter. For large arrays, consider combining operations: + +```javascript +const hugeArray = Array.from({ length: 100000 }, (_, i) => i) + +// ❌ SLOW: Three separate iterations +const result1 = hugeArray + .filter(n => n % 2 === 0) // Iteration 1 + .map(n => n * 2) // Iteration 2 + .filter(n => n > 1000) // Iteration 3 + +// ✓ FASTER: Single iteration with reduce +const result2 = hugeArray.reduce((acc, n) => { + if (n % 2 === 0) { + const doubled = n * 2 + if (doubled > 1000) { + acc.push(doubled) + } + } + return acc +}, []) +``` + +<Tip> +**Performance Rule of Thumb:** For arrays under 10,000 items, prioritize readability. For larger arrays or performance-critical code, consider combining operations into a single reduce. +</Tip> + +--- + +## The #1 Mistake: Forgetting reduce()'s Initial Value + +This is the most common mistake developers make with reduce, and it can crash your application: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE INITIAL VALUE PROBLEM │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ❌ WITHOUT INITIAL VALUE ✓ WITH INITIAL VALUE │ +│ ───────────────────────── ──────────────────── │ +│ │ +│ [1, 2, 3].reduce((a,b) => a+b) [1, 2, 3].reduce((a,b) => a+b, 0) │ +│ → Works: 6 → Works: 6 │ +│ │ +│ [].reduce((a,b) => a+b) [].reduce((a,b) => a+b, 0) │ +│ → TypeError! CRASH → Works: 0 │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Empty Array Without Initial Value = CRASH + +```javascript +// ❌ DANGEROUS: No initial value +const numbers = [] +const sum = numbers.reduce((acc, n) => acc + n) +// TypeError: Reduce of empty array with no initial value + +// ✓ SAFE: Always provide initial value +const safeSum = numbers.reduce((acc, n) => acc + n, 0) +console.log(safeSum) // 0 +``` + +### Type Mismatch Without Initial Value + +```javascript +const products = [ + { name: 'Laptop', price: 1000 }, + { name: 'Phone', price: 500 } +] + +// ❌ WRONG: First accumulator will be the first object, not a number! +const total = products.reduce((acc, p) => acc + p.price) +console.log(total) // "[object Object]500" — Oops! + +// ✓ CORRECT: Initial value sets the accumulator type +const total2 = products.reduce((acc, p) => acc + p.price, 0) +console.log(total2) // 1500 +``` + +<Warning> +**The Rule:** Always provide an initial value to reduce(). It prevents crashes on empty arrays and makes your code's intent clear. +</Warning> + +--- + +## Common Mistakes + +### map() Mistakes + +#### Forgetting to Return + +```javascript +const numbers = [1, 2, 3] + +// ❌ WRONG: No return statement +const doubled = numbers.map(n => { + n * 2 // Missing return! +}) +console.log(doubled) // [undefined, undefined, undefined] + +// ✓ CORRECT: Explicit return +const doubled2 = numbers.map(n => { + return n * 2 +}) +console.log(doubled2) // [2, 4, 6] + +// ✓ CORRECT: Implicit return (no curly braces) +const doubled3 = numbers.map(n => n * 2) +console.log(doubled3) // [2, 4, 6] +``` + +#### Mutating Original Objects + +```javascript +const users = [ + { name: 'Alice', score: 85 }, + { name: 'Bob', score: 92 } +] + +// ❌ WRONG: Mutates the original objects +const curved = users.map(user => { + user.score += 5 // Mutates original! + return user +}) + +console.log(users[0].score) // 90 — Original was changed! + +// ✓ CORRECT: Create new objects +const users2 = [ + { name: 'Alice', score: 85 }, + { name: 'Bob', score: 92 } +] + +const curved2 = users2.map(user => ({ + ...user, + score: user.score + 5 +})) + +console.log(users2[0].score) // 85 — Original unchanged +console.log(curved2[0].score) // 90 +``` + +### filter() Mistakes + +#### Using filter When find is Better + +```javascript +const users = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + { id: 3, name: 'Charlie' } +] + +// ❌ INEFFICIENT: Checks entire array, returns array, needs [0] +const user = users.filter(u => u.id === 2)[0] + +// ✓ EFFICIENT: Stops at first match, returns the item directly +const user2 = users.find(u => u.id === 2) +``` + +### reduce() Mistakes + +#### Not Returning the Accumulator + +```javascript +const numbers = [1, 2, 3, 4] + +// ❌ WRONG: Forgetting to return accumulator +const sum = numbers.reduce((acc, n) => { + acc + n // Missing return! +}, 0) +console.log(sum) // undefined + +// ✓ CORRECT: Always return the accumulator +const sum2 = numbers.reduce((acc, n) => { + return acc + n +}, 0) +console.log(sum2) // 10 +``` + +#### Making reduce Too Complex + +```javascript +const users = [ + { name: 'Alice', active: true }, + { name: 'Bob', active: false }, + { name: 'Charlie', active: true } +] + +// ❌ HARD TO READ: Everything crammed into reduce +const result = users.reduce((acc, user) => { + if (user.active) { + acc.push(user.name.toUpperCase()) + } + return acc +}, []) + +// ✓ CLEARER: Use filter + map +const result2 = users + .filter(u => u.active) + .map(u => u.name.toUpperCase()) + +console.log(result2) // ['ALICE', 'CHARLIE'] +``` + +--- + +## Other Useful Array Methods + +JavaScript has many more array methods beyond map, filter, and reduce. Here's a quick reference: + +| Method | Returns | Description | +|--------|---------|-------------| +| `find(fn)` | Element or undefined | First element that passes test | +| `findIndex(fn)` | Number | Index of first match (-1 if none) | +| `some(fn)` | Boolean | True if any element passes test | +| `every(fn)` | Boolean | True if all elements pass test | +| `includes(value)` | Boolean | True if value is in array | +| `indexOf(value)` | Number | Index of value (-1 if not found) | +| `flat(depth)` | Array | Flattens nested arrays | +| `flatMap(fn)` | Array | map() then flat(1) | +| `forEach(fn)` | undefined | Executes function for side effects | +| `sort(fn)` | Array | Sorts in place (mutates!) | +| `reverse()` | Array | Reverses in place (mutates!) | +| `slice(start, end)` | Array | Returns portion (no mutation) | +| `splice(start, count)` | Array | Removes/adds elements (mutates!) | + +### Quick Examples + +```javascript +const numbers = [1, 2, 3, 4, 5] + +// find: Get first even number +numbers.find(n => n % 2 === 0) // 2 + +// findIndex: Get index of first even +numbers.findIndex(n => n % 2 === 0) // 1 + +// some: Is there any number > 4? +numbers.some(n => n > 4) // true + +// every: Are all numbers positive? +numbers.every(n => n > 0) // true + +// includes: Is 3 in the array? +numbers.includes(3) // true + +// flat: Flatten nested arrays +[[1, 2], [3, 4]].flat() // [1, 2, 3, 4] + +// flatMap: Map and flatten +[1, 2].flatMap(n => [n, n * 2]) // [1, 2, 2, 4] +``` + +### Which Method Should I Use? + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CHOOSING THE RIGHT METHOD │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ WHAT DO YOU NEED? USE THIS │ +│ ───────────────── ──────── │ +│ │ +│ Transform every element → map() │ +│ Keep some elements → filter() │ +│ Combine into single value → reduce() │ +│ Find first matching element → find() │ +│ Check if any element matches → some() │ +│ Check if all elements match → every() │ +│ Check if value exists → includes() │ +│ Get index of element → findIndex() or indexOf() │ +│ Just do something with each → forEach() │ +│ Flatten nested arrays → flat() or flatMap() │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **map() transforms every element** — Input array length equals output array length. Use it to change each item in the same way. + +2. **filter() keeps matching elements** — Returns 0 to all elements. Use it to remove items that don't pass a test. + +3. **reduce() combines into one value** — Can return any type: number, string, object, array. The "Swiss Army knife" of array methods. + +4. **None of these mutate the original array** — They always return something new. This makes your code predictable. + +5. **Always provide reduce()'s initial value** — Empty arrays without an initial value crash. Don't risk it. + +6. **Chain methods for powerful pipelines** — filter → map → reduce is a common pattern for data processing. + +7. **map() must return something** — Forgetting the return statement gives you an array of undefined. + +8. **Don't use map() for side effects** — Use forEach() if you just want to do something with each element. + +9. **Use find() for single item lookup** — It's more efficient than filter()[0] because it stops at the first match. + +10. **Async callbacks need Promise.all** — map/filter/reduce don't wait for async callbacks. Wrap in Promise.all(). +</Info> + +--- + +## Test Your Knowledge <AccordionGroup> - <Accordion title="map()"> - Creates a new array by calling a function on every element of the original array and storing the results. + <Accordion title="Question 1: What does map() return if the callback doesn't return anything?"> + **Answer:** + + An array of `undefined` values. In JavaScript, functions without an explicit return statement return `undefined`. + + ```javascript + const numbers = [1, 2, 3] + + // Missing return + const result = numbers.map(n => { + n * 2 // No return! + }) + + console.log(result) // [undefined, undefined, undefined] + ``` + + Always remember to return a value from your map callback, or use implicit return (arrow function without curly braces). + </Accordion> + + <Accordion title="Question 2: What's the difference between filter() and find()?"> + **Answer:** + + - **filter()** returns an **array** of all matching elements (could be empty) + - **find()** returns the **first matching element** (or undefined if none) + + ```javascript + const numbers = [1, 2, 3, 4, 5, 6] + + numbers.filter(n => n % 2 === 0) // [2, 4, 6] — All matches + numbers.find(n => n % 2 === 0) // 2 — First match only + ``` + + Use `find()` when you only need one result. It's more efficient because it stops searching after the first match. + </Accordion> + + <Accordion title="Question 3: Why does reduce() crash on empty arrays without an initial value?"> + **Answer:** + + Without an initial value, reduce uses the first element as the starting accumulator. If the array is empty, there's no first element, so JavaScript throws a TypeError. + + ```javascript + // No initial value + empty array = crash + [].reduce((acc, n) => acc + n) + // TypeError: Reduce of empty array with no initial value + + // With initial value, empty array returns the initial value + [].reduce((acc, n) => acc + n, 0) // 0 + ``` + + Always provide an initial value to prevent crashes and make your intent clear. </Accordion> - <Accordion title="filter()"> - Creates a new array with all elements that pass a test implemented by the provided function. + + <Accordion title="Question 4: What's wrong with this code?"> + ```javascript + const doubled = numbers.map(n => { n * 2 }) + ``` + + **Answer:** + + The curly braces `{}` create a function body, which requires an explicit `return` statement. Without it, the function returns `undefined`. + + ```javascript + // ❌ Wrong (returns undefined) + const doubled = numbers.map(n => { n * 2 }) + + // ✓ Correct (explicit return) + const doubled = numbers.map(n => { return n * 2 }) + + // ✓ Correct (implicit return, no braces) + const doubled = numbers.map(n => n * 2) + ``` </Accordion> - <Accordion title="reduce()"> - Executes a reducer function on each element of the array, resulting in a single output value. + + <Accordion title="Question 5: How would you get the total price of in-stock items?"> + ```javascript + const products = [ + { name: 'Laptop', price: 1000, inStock: true }, + { name: 'Phone', price: 500, inStock: false }, + { name: 'Tablet', price: 300, inStock: true } + ] + ``` + + **Answer:** + + Chain filter → map → reduce: + + ```javascript + const total = products + .filter(p => p.inStock) // Keep only in-stock + .map(p => p.price) // Extract prices + .reduce((sum, p) => sum + p, 0) // Sum them + + console.log(total) // 1300 + + // Or combine map and reduce: + const total2 = products + .filter(p => p.inStock) + .reduce((sum, p) => sum + p.price, 0) + + console.log(total2) // 1300 + ``` + </Accordion> + + <Accordion title="Question 6: What's the output of this code?"> + ```javascript + const result = [1, 2, 3, 4, 5] + .filter(n => n % 2 === 0) + .map(n => n * 3) + .reduce((sum, n) => sum + n, 0) + + console.log(result) + ``` + + **Answer:** + + **18** + + Let's trace through: + 1. `filter(n => n % 2 === 0)` keeps even numbers: `[2, 4]` + 2. `map(n => n * 3)` triples each: `[6, 12]` + 3. `reduce((sum, n) => sum + n, 0)` sums them: `0 + 6 + 12 = 18` </Accordion> </AccordionGroup> -## Articles +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="JavaScript Functional Programming — map, filter and reduce" icon="newspaper" href="https://medium.com/jsguru/javascript-functional-programming-map-filter-and-reduce-846ff9ba492d"> - By Bojan Gvozderac + <Card title="Higher-Order Functions" icon="layer-group" href="/concepts/higher-order-functions"> + map, filter, and reduce are all higher-order functions that take callbacks + </Card> + <Card title="Pure Functions" icon="flask" href="/concepts/pure-functions"> + Why these methods don't mutate and how immutability makes code predictable </Card> - <Card title="Learn map, filter and reduce in Javascript" icon="newspaper" href="https://medium.com/@joomiguelcunha/learn-map-filter-and-reduce-in-javascript-ea59009593c4"> - By João Miguel Cunha + <Card title="Callbacks" icon="phone" href="/concepts/callbacks"> + The callback pattern that powers these array methods + </Card> + <Card title="Recursion" icon="rotate" href="/concepts/recursion"> + An alternative approach to processing arrays with function calls </Card> </CardGroup> -- [JavaScript's Map, Reduce, and Filter — Dan Martensen](https://danmartensen.svbtle.com/javascripts-map-reduce-and-filter) -- [How to Use Map, Filter, & Reduce in JavaScript — Peleke Sengstacke](https://code.tutsplus.com/tutorials/how-to-use-map-filter-reduce-in-javascript--cms-26209) -- [JavaScript — Learn to Chain Map, Filter, and Reduce — Brandon Morelli](https://codeburst.io/javascript-learn-to-chain-map-filter-and-reduce-acd2d0562cd4) -- [Javascript data structure with map, reduce, filter and ES6 — Deepak Gupta](https://codeburst.io/write-beautiful-javascript-with-%CE%BB-fp-es6-350cd64ab5bf) -- [Understanding map, filter and reduce in Javascript — Luuk Gruijs](https://hackernoon.com/understanding-map-filter-and-reduce-in-javascript-5df1c7eee464) -- [Functional Programming in JS: map, filter, reduce (Pt. 5) — Omer Goldberg](https://hackernoon.com/functional-programming-in-js-map-filter-reduce-pt-5-308a205fdd5f) -- [Arrow Functions: Fat and Concise Syntax in JavaScript — Kyle Pennell](https://www.sitepoint.com/es6-arrow-functions-new-fat-concise-syntax-javascript/) -- [JavaScript: Arrow Functions for Beginners — Brandon Morelli](https://codeburst.io/javascript-arrow-functions-for-beginners-926947fc0cdc) -- [When (and why) you should use ES6 arrow functions — and when you shouldn't — Cynthia Lee](https://medium.freecodecamp.org/when-and-why-you-should-use-es6-arrow-functions-and-when-you-shouldnt-3d851d7f0b26) -- [Simplify your JavaScript – Use .map(), .reduce(), and .filter() — Etienne Talbot](https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d) -- [JavaScript's Reduce Method Explained By Going On a Diet — Kevin Kononenko](https://blog.codeanalogies.com/2018/07/24/javascripts-reduce-method-explained-by-going-on-a-diet/) -- [How to write your own map, filter and reduce functions in JavaScript — Hemand Nair](https://medium.freecodecamp.org/how-to-write-your-own-map-filter-and-reduce-functions-in-javascript-ab1e35679d26) -- [JavaScript Map – How to Use the JS .map() Function — FreeCodeCamp](https://www.freecodecamp.org/news/javascript-map-how-to-use-the-js-map-function-array-method/) +--- -## Videos +## Reference + +<CardGroup cols={2}> + <Card title="Array.prototype.map() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"> + Complete documentation for the map method + </Card> + <Card title="Array.prototype.filter() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"> + Complete documentation for the filter method + </Card> + <Card title="Array.prototype.reduce() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"> + Complete documentation for the reduce method + </Card> + <Card title="Array — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array"> + Overview of all array methods in JavaScript + </Card> +</CardGroup> + +## Articles <CardGroup cols={2}> - <Card title="Map, Filter and Reduce" icon="video" href="https://www.youtube.com/watch?v=UXiYii0Y7Nw"> - By Lydia Hallie + <Card title="Array Methods — javascript.info" icon="newspaper" href="https://javascript.info/array-methods"> + Comprehensive reference covering all array methods with interactive examples. Includes a visual diagram for reduce and 13 practice tasks with solutions. + </Card> + <Card title="An Illustrated Guide to Map, Reduce, and Filter" icon="newspaper" href="https://css-tricks.com/an-illustrated-and-musical-guide-to-map-reduce-and-filter-array-methods/"> + Hand-drawn illustrations make these concepts stick. Filter as a strainer, reduce as cooking sauce. Even includes a song to help you remember! + </Card> + <Card title="Map, Reduce, and Filter Explained with Examples" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-map-reduce-and-filter-explained-with-examples/"> + Concise, beginner-friendly introduction with clean code examples. Gets straight to practical usage without overwhelming theory. + </Card> + <Card title="Differences Between forEach and map" icon="newspaper" href="https://www.freecodecamp.org/news/4-main-differences-between-foreach-and-map/"> + Explains the 4 key differences with side-by-side comparisons. Includes performance testing code you can run yourself. + </Card> + <Card title="Simplify Your JavaScript with map, reduce, filter" icon="newspaper" href="https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d"> + Star Wars themed examples make learning fun. Excellent section on method chaining and building elegant data pipelines. </Card> - <Card title="Map, Filter and Reduce" icon="video" href="https://youtu.be/zdp0zrpKzIE?si=6QusFzD6tmwn-el4"> - By Akshaay Saini + <Card title="How to Write Your Own map, filter, reduce" icon="newspaper" href="https://www.freecodecamp.org/news/how-to-write-your-own-map-filter-and-reduce-functions-in-javascript-ab1e35679d26/"> + Build these methods from scratch to understand how they work internally. Great for interview prep and deepening your knowledge. </Card> </CardGroup> -- [Functional JavaScript: Map, forEach, Reduce, Filter — Theodore Anderson](https://www.youtube.com/watch?v=vytzLlY_wmU) -- [JavaScript Array superpowers: Map, Filter, Reduce (part I) — Michael Rosata](https://www.youtube.com/watch?v=qTeeVd8hOFY) -- [JavaScript Array superpowers: Map, Filter, Reduce (part 2) — Michael Rosata](https://www.youtube.com/watch?v=gIm9xLYudL0) -- [JavaScript Higher Order Functions - Filter, Map, Sort & Reduce — Epicop](https://www.youtube.com/watch?v=zYBeEPxNSbw) -- [Arrow functions in JavaScript - What, Why and How — Fun Fun Function](https://www.youtube.com/watch?v=6sQDTgOqh-I) -- [Learning Functional Programming with JavaScript — Anjana Vakil - JSUnconf](https://www.youtube.com/watch?v=e-5obm1G_FY&t=1521s) -- [Reduce basics - Part 3 of FP in JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=Wl98eZpkp-c) -- [Reduce Advanced - Part 4 of FP in JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=1DMolJ2FrNY&t=621s) -- [Different array methods in 1 minute | Midudev (Spanish)](https://youtu.be/Ah7-PPjQ5Ls) +## Videos + +<CardGroup cols={2}> + <Card title="Higher-order Functions" icon="video" href="https://www.youtube.com/watch?v=BMUiFMZr7vk"> + Fun Fun Function's legendary intro to functional programming. Mattias explains the mental model that makes map, filter, reduce click. + </Card> + <Card title="Reduce Basics" icon="video" href="https://www.youtube.com/watch?v=Wl98eZpkp-c"> + The hardest method gets its own deep-dive. Clear accumulator examples that finally make reduce make sense. + </Card> + <Card title="Higher Order Functions & Arrays" icon="video" href="https://www.youtube.com/watch?v=rRgD1yVwIvE"> + Traversy Media's complete crash course with live coding. Covers forEach, map, filter, reduce, sort, and find in one session. + </Card> + <Card title="8 Must Know JavaScript Array Methods" icon="video" href="https://www.youtube.com/watch?v=R8rmfD9Y5-c"> + Web Dev Simplified covers the most useful methods in 12 focused minutes. Perfect for a quick refresher on when to use what. + </Card> + <Card title="Map, Filter & Reduce — Namaste JavaScript" icon="video" href="https://www.youtube.com/watch?v=zdp0zrpKzIE"> + Akshay Saini's interview-prep deep-dive. Includes polyfill implementations and common interview questions about these methods. + </Card> +</CardGroup> diff --git a/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js b/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js new file mode 100644 index 00000000..6dbce049 --- /dev/null +++ b/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js @@ -0,0 +1,689 @@ +import { describe, it, expect } from 'vitest' + +describe('map, reduce, filter', () => { + + describe('map()', () => { + it('should transform every element in the array', () => { + const numbers = [1, 2, 3, 4] + const doubled = numbers.map(n => n * 2) + + expect(doubled).toEqual([2, 4, 6, 8]) + }) + + it('should not mutate the original array', () => { + const original = [1, 2, 3] + const mapped = original.map(n => n * 10) + + expect(original).toEqual([1, 2, 3]) + expect(mapped).toEqual([10, 20, 30]) + }) + + it('should pass element, index, and array to callback', () => { + const letters = ['a', 'b', 'c'] + const result = letters.map((letter, index, arr) => ({ + letter, + index, + arrayLength: arr.length + })) + + expect(result).toEqual([ + { letter: 'a', index: 0, arrayLength: 3 }, + { letter: 'b', index: 1, arrayLength: 3 }, + { letter: 'c', index: 2, arrayLength: 3 } + ]) + }) + + it('should return undefined for elements when callback has no return', () => { + const numbers = [1, 2, 3] + const result = numbers.map(n => { + n * 2 // No return statement + }) + + expect(result).toEqual([undefined, undefined, undefined]) + }) + + it('demonstrates the parseInt pitfall', () => { + const strings = ['1', '2', '3'] + + // The pitfall: parseInt receives (element, index, array) + // So it becomes parseInt('1', 0), parseInt('2', 1), parseInt('3', 2) + const wrongResult = strings.map(parseInt) + expect(wrongResult).toEqual([1, NaN, NaN]) + + // The fix: wrap in arrow function or use Number + const correctResult1 = strings.map(str => parseInt(str, 10)) + expect(correctResult1).toEqual([1, 2, 3]) + + const correctResult2 = strings.map(Number) + expect(correctResult2).toEqual([1, 2, 3]) + }) + + it('should extract properties from objects', () => { + const users = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' } + ] + + const names = users.map(user => user.name) + expect(names).toEqual(['Alice', 'Bob']) + }) + + it('should transform object shapes', () => { + const users = [ + { firstName: 'Alice', lastName: 'Smith' }, + { firstName: 'Bob', lastName: 'Jones' } + ] + + const fullNames = users.map(user => ({ + fullName: `${user.firstName} ${user.lastName}` + })) + + expect(fullNames).toEqual([ + { fullName: 'Alice Smith' }, + { fullName: 'Bob Jones' } + ]) + }) + + it('should convert strings to uppercase', () => { + const words = ['hello', 'world'] + const shouting = words.map(word => word.toUpperCase()) + + expect(shouting).toEqual(['HELLO', 'WORLD']) + }) + + it('should square each number', () => { + const numbers = [1, 2, 3, 4, 5] + const squares = numbers.map(n => n * n) + + expect(squares).toEqual([1, 4, 9, 16, 25]) + }) + + it('should add index prefix to each letter', () => { + const letters = ['a', 'b', 'c', 'd'] + const indexed = letters.map((letter, index) => `${index}: ${letter}`) + + expect(indexed).toEqual(['0: a', '1: b', '2: c', '3: d']) + }) + + it('should create objects with sequential IDs from items', () => { + const items = ['apple', 'banana', 'cherry'] + const products = items.map((name, index) => ({ + id: index + 1, + name + })) + + expect(products).toEqual([ + { id: 1, name: 'apple' }, + { id: 2, name: 'banana' }, + { id: 3, name: 'cherry' } + ]) + }) + }) + + describe('filter()', () => { + it('should keep elements that pass the test', () => { + const numbers = [1, 2, 3, 4, 5, 6] + const evens = numbers.filter(n => n % 2 === 0) + + expect(evens).toEqual([2, 4, 6]) + }) + + it('should return empty array when no elements match', () => { + const numbers = [1, 3, 5, 7] + const evens = numbers.filter(n => n % 2 === 0) + + expect(evens).toEqual([]) + }) + + it('should not mutate the original array', () => { + const original = [1, 2, 3, 4, 5] + const filtered = original.filter(n => n > 3) + + expect(original).toEqual([1, 2, 3, 4, 5]) + expect(filtered).toEqual([4, 5]) + }) + + it('should evaluate truthy/falsy values correctly', () => { + const mixed = [0, 1, '', 'hello', null, undefined, false, true] + const truthy = mixed.filter(Boolean) + + expect(truthy).toEqual([1, 'hello', true]) + }) + + it('should filter objects by property', () => { + const users = [ + { name: 'Alice', active: true }, + { name: 'Bob', active: false }, + { name: 'Charlie', active: true } + ] + + const activeUsers = users.filter(user => user.active) + + expect(activeUsers).toEqual([ + { name: 'Alice', active: true }, + { name: 'Charlie', active: true } + ]) + }) + + it('demonstrates filter vs find', () => { + const numbers = [1, 2, 3, 4, 5, 6] + + // filter returns ALL matches as an array + const allEvens = numbers.filter(n => n % 2 === 0) + expect(allEvens).toEqual([2, 4, 6]) + + // find returns the FIRST match (not an array) + const firstEven = numbers.find(n => n % 2 === 0) + expect(firstEven).toBe(2) + }) + + it('should support multiple conditions', () => { + const products = [ + { name: 'Laptop', price: 1000, inStock: true }, + { name: 'Phone', price: 500, inStock: false }, + { name: 'Tablet', price: 300, inStock: true } + ] + + const affordableInStock = products.filter( + p => p.inStock && p.price < 500 + ) + + expect(affordableInStock).toEqual([ + { name: 'Tablet', price: 300, inStock: true } + ]) + }) + + it('should keep only odd numbers', () => { + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + const odds = numbers.filter(n => n % 2 !== 0) + + expect(odds).toEqual([1, 3, 5, 7, 9]) + }) + + it('should keep numbers greater than threshold', () => { + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + const big = numbers.filter(n => n > 5) + + expect(big).toEqual([6, 7, 8, 9, 10]) + }) + + it('should keep numbers in a range', () => { + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + const middle = numbers.filter(n => n >= 3 && n <= 7) + + expect(middle).toEqual([3, 4, 5, 6, 7]) + }) + + it('should search products by name case-insensitively', () => { + const products = [ + { name: 'MacBook Pro', category: 'laptops', price: 2000 }, + { name: 'iPhone', category: 'phones', price: 1000 }, + { name: 'iPad', category: 'tablets', price: 800 }, + { name: 'Dell XPS', category: 'laptops', price: 1500 } + ] + + const searchTerm = 'mac' + const results = products.filter(p => + p.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + expect(results).toEqual([ + { name: 'MacBook Pro', category: 'laptops', price: 2000 } + ]) + }) + + it('should filter products by category', () => { + const products = [ + { name: 'MacBook Pro', category: 'laptops', price: 2000 }, + { name: 'iPhone', category: 'phones', price: 1000 }, + { name: 'iPad', category: 'tablets', price: 800 }, + { name: 'Dell XPS', category: 'laptops', price: 1500 } + ] + + const laptops = products.filter(p => p.category === 'laptops') + + expect(laptops).toEqual([ + { name: 'MacBook Pro', category: 'laptops', price: 2000 }, + { name: 'Dell XPS', category: 'laptops', price: 1500 } + ]) + }) + + it('should filter products by price range', () => { + const products = [ + { name: 'MacBook Pro', category: 'laptops', price: 2000 }, + { name: 'iPhone', category: 'phones', price: 1000 }, + { name: 'iPad', category: 'tablets', price: 800 }, + { name: 'Dell XPS', category: 'laptops', price: 1500 } + ] + + const affordable = products.filter(p => p.price <= 1000) + + expect(affordable).toEqual([ + { name: 'iPhone', category: 'phones', price: 1000 }, + { name: 'iPad', category: 'tablets', price: 800 } + ]) + }) + }) + + describe('reduce()', () => { + it('should combine array elements into a single value', () => { + const numbers = [1, 2, 3, 4, 5] + const sum = numbers.reduce((acc, n) => acc + n, 0) + + expect(sum).toBe(15) + }) + + it('should use initial value as starting accumulator', () => { + const numbers = [1, 2, 3] + const sumStartingAt10 = numbers.reduce((acc, n) => acc + n, 10) + + expect(sumStartingAt10).toBe(16) // 10 + 1 + 2 + 3 + }) + + it('should throw on empty array without initial value', () => { + const empty = [] + + expect(() => { + empty.reduce((acc, n) => acc + n) + }).toThrow(TypeError) + }) + + it('should return initial value for empty array', () => { + const empty = [] + const result = empty.reduce((acc, n) => acc + n, 0) + + expect(result).toBe(0) + }) + + it('should count occurrences', () => { + const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'] + + const count = fruits.reduce((acc, fruit) => { + acc[fruit] = (acc[fruit] || 0) + 1 + return acc + }, {}) + + expect(count).toEqual({ + apple: 3, + banana: 2, + orange: 1 + }) + }) + + it('should group by property', () => { + const people = [ + { name: 'Alice', department: 'Engineering' }, + { name: 'Bob', department: 'Marketing' }, + { name: 'Charlie', department: 'Engineering' } + ] + + const byDepartment = people.reduce((acc, person) => { + const dept = person.department + if (!acc[dept]) { + acc[dept] = [] + } + acc[dept].push(person.name) + return acc + }, {}) + + expect(byDepartment).toEqual({ + Engineering: ['Alice', 'Charlie'], + Marketing: ['Bob'] + }) + }) + + it('should build objects from arrays', () => { + const pairs = [['a', 1], ['b', 2], ['c', 3]] + + const obj = pairs.reduce((acc, [key, value]) => { + acc[key] = value + return acc + }, {}) + + expect(obj).toEqual({ a: 1, b: 2, c: 3 }) + }) + + it('can implement map with reduce', () => { + const numbers = [1, 2, 3, 4] + + const doubled = numbers.reduce((acc, n) => { + acc.push(n * 2) + return acc + }, []) + + expect(doubled).toEqual([2, 4, 6, 8]) + }) + + it('can implement filter with reduce', () => { + const numbers = [1, 2, 3, 4, 5, 6] + + const evens = numbers.reduce((acc, n) => { + if (n % 2 === 0) { + acc.push(n) + } + return acc + }, []) + + expect(evens).toEqual([2, 4, 6]) + }) + + it('should calculate average', () => { + const numbers = [10, 20, 30, 40, 50] + + const sum = numbers.reduce((acc, n) => acc + n, 0) + const average = sum / numbers.length + + expect(average).toBe(30) + }) + + it('should find max value', () => { + const numbers = [5, 2, 9, 1, 7] + + const max = numbers.reduce((acc, n) => n > acc ? n : acc, numbers[0]) + + expect(max).toBe(9) + }) + + it('should find minimum value', () => { + const numbers = [5, 2, 9, 1, 7] + + const min = numbers.reduce((acc, n) => n < acc ? n : acc, numbers[0]) + + expect(min).toBe(1) + }) + + it('should flatten nested arrays with reduce', () => { + const nested = [[1, 2], [3, 4], [5, 6]] + + const flat = nested.reduce((acc, arr) => acc.concat(arr), []) + + expect(flat).toEqual([1, 2, 3, 4, 5, 6]) + }) + + it('should implement myMap using reduce inline', () => { + const array = [1, 2, 3] + const callback = n => n * 2 + + // myMap implementation from concept page + const result = array.reduce((acc, element, index) => { + acc.push(callback(element, index, array)) + return acc + }, []) + + expect(result).toEqual([2, 4, 6]) + }) + }) + + describe('Method Chaining', () => { + it('should chain filter → map → reduce', () => { + const products = [ + { name: 'Laptop', price: 1000, inStock: true }, + { name: 'Phone', price: 500, inStock: false }, + { name: 'Tablet', price: 300, inStock: true }, + { name: 'Watch', price: 200, inStock: true } + ] + + const totalInStock = products + .filter(p => p.inStock) + .map(p => p.price) + .reduce((sum, price) => sum + price, 0) + + expect(totalInStock).toBe(1500) + }) + + it('demonstrates real-world data pipeline', () => { + const transactions = [ + { type: 'sale', amount: 100 }, + { type: 'refund', amount: 30 }, + { type: 'sale', amount: 200 }, + { type: 'sale', amount: 150 }, + { type: 'refund', amount: 50 } + ] + + // Calculate total sales (not refunds) + const totalSales = transactions + .filter(t => t.type === 'sale') + .map(t => t.amount) + .reduce((sum, amount) => sum + amount, 0) + + expect(totalSales).toBe(450) + + // Calculate net (sales - refunds) + const net = transactions.reduce((acc, t) => { + return t.type === 'sale' + ? acc + t.amount + : acc - t.amount + }, 0) + + expect(net).toBe(370) // 450 - 80 + }) + + it('should get active premium users emails', () => { + const users = [ + { email: 'alice@example.com', active: true, plan: 'premium' }, + { email: 'bob@example.com', active: false, plan: 'premium' }, + { email: 'charlie@example.com', active: true, plan: 'free' }, + { email: 'diana@example.com', active: true, plan: 'premium' } + ] + + const premiumEmails = users + .filter(u => u.active) + .filter(u => u.plan === 'premium') + .map(u => u.email) + + expect(premiumEmails).toEqual([ + 'alice@example.com', + 'diana@example.com' + ]) + }) + + it('should calculate cart total with discounts', () => { + const cart = [ + { name: 'Laptop', price: 1000, quantity: 1, discountPercent: 10 }, + { name: 'Mouse', price: 50, quantity: 2, discountPercent: 0 }, + { name: 'Keyboard', price: 100, quantity: 1, discountPercent: 20 } + ] + + const total = cart + .map(item => { + const subtotal = item.price * item.quantity + const discount = subtotal * (item.discountPercent / 100) + return subtotal - discount + }) + .reduce((sum, price) => sum + price, 0) + + // Laptop: 1000 * 1 - 10% = 900 + // Mouse: 50 * 2 - 0% = 100 + // Keyboard: 100 * 1 - 20% = 80 + // Total: 900 + 100 + 80 = 1080 + expect(total).toBe(1080) + }) + + it('should get top 3 performers sorted by sales', () => { + const salespeople = [ + { name: 'Alice', sales: 50000 }, + { name: 'Bob', sales: 75000 }, + { name: 'Charlie', sales: 45000 }, + { name: 'Diana', sales: 90000 }, + { name: 'Eve', sales: 60000 } + ] + + const top3 = salespeople + .filter(p => p.sales >= 50000) + .sort((a, b) => b.sales - a.sales) + .slice(0, 3) + .map(p => p.name) + + expect(top3).toEqual(['Diana', 'Bob', 'Eve']) + }) + }) + + describe('Other Array Methods', () => { + it('find() returns first matching element', () => { + const users = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + { id: 3, name: 'Charlie' } + ] + + const bob = users.find(u => u.id === 2) + expect(bob).toEqual({ id: 2, name: 'Bob' }) + + const notFound = users.find(u => u.id === 999) + expect(notFound).toBeUndefined() + }) + + it('some() returns true if any element matches', () => { + const numbers = [1, 2, 3, 4, 5] + + expect(numbers.some(n => n > 4)).toBe(true) + expect(numbers.some(n => n > 10)).toBe(false) + }) + + it('every() returns true if all elements match', () => { + const numbers = [2, 4, 6, 8] + + expect(numbers.every(n => n % 2 === 0)).toBe(true) + expect(numbers.every(n => n > 5)).toBe(false) + }) + + it('includes() checks for value membership', () => { + const fruits = ['apple', 'banana', 'orange'] + + expect(fruits.includes('banana')).toBe(true) + expect(fruits.includes('grape')).toBe(false) + }) + + it('findIndex() returns index of first match', () => { + const users = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' } + ] + + const bobIndex = users.findIndex(u => u.name === 'Bob') + expect(bobIndex).toBe(1) + + const notFoundIndex = users.findIndex(u => u.name === 'Eve') + expect(notFoundIndex).toBe(-1) + }) + + it('flat() flattens nested arrays', () => { + const nested = [[1, 2], [3, 4], [5, 6]] + expect(nested.flat()).toEqual([1, 2, 3, 4, 5, 6]) + + const deepNested = [1, [2, [3, [4]]]] + expect(deepNested.flat(2)).toEqual([1, 2, 3, [4]]) + expect(deepNested.flat(Infinity)).toEqual([1, 2, 3, 4]) + }) + + it('flatMap() maps then flattens', () => { + const sentences = ['hello world', 'foo bar'] + const words = sentences.flatMap(s => s.split(' ')) + + expect(words).toEqual(['hello', 'world', 'foo', 'bar']) + }) + }) + + describe('Common Mistakes', () => { + it('demonstrates mutation issue in map', () => { + const users = [ + { name: 'Alice', score: 85 }, + { name: 'Bob', score: 92 } + ] + + // ❌ WRONG: This mutates the original objects + const mutated = users.map(user => { + user.score += 5 // Mutates original! + return user + }) + + expect(users[0].score).toBe(90) // Original was mutated! + + // Reset for next test + const users2 = [ + { name: 'Alice', score: 85 }, + { name: 'Bob', score: 92 } + ] + + // ✓ CORRECT: Create new objects + const notMutated = users2.map(user => ({ + ...user, + score: user.score + 5 + })) + + expect(users2[0].score).toBe(85) // Original unchanged + expect(notMutated[0].score).toBe(90) + }) + + it('demonstrates reduce without initial value type issues', () => { + const products = [ + { name: 'Laptop', price: 1000 }, + { name: 'Phone', price: 500 } + ] + + // ❌ WRONG: Without initial value, first element becomes accumulator + // This would try to add 500 to an object, resulting in string concatenation + const wrongTotal = products.reduce((acc, p) => acc + p.price) + expect(typeof wrongTotal).toBe('string') // "[object Object]500" + + // ✓ CORRECT: Provide initial value + const correctTotal = products.reduce((acc, p) => acc + p.price, 0) + expect(correctTotal).toBe(1500) + }) + + it('demonstrates forgetting to return accumulator in reduce', () => { + const numbers = [1, 2, 3, 4] + + // ❌ WRONG: No return + const wrong = numbers.reduce((acc, n) => { + acc + n // Missing return! + }, 0) + expect(wrong).toBeUndefined() + + // ✓ CORRECT: Return accumulator + const correct = numbers.reduce((acc, n) => { + return acc + n + }, 0) + expect(correct).toBe(10) + }) + + it('shows filter+map is clearer than complex reduce', () => { + const users = [ + { name: 'Alice', active: true }, + { name: 'Bob', active: false }, + { name: 'Charlie', active: true } + ] + + // Complex reduce approach + const resultReduce = users.reduce((acc, user) => { + if (user.active) { + acc.push(user.name.toUpperCase()) + } + return acc + }, []) + + // Clearer filter + map approach + const resultFilterMap = users + .filter(u => u.active) + .map(u => u.name.toUpperCase()) + + // Both should produce the same result + expect(resultReduce).toEqual(['ALICE', 'CHARLIE']) + expect(resultFilterMap).toEqual(['ALICE', 'CHARLIE']) + }) + }) + + describe('Test Your Knowledge Examples', () => { + it('Q6: filter evens, triple, sum equals 18', () => { + const result = [1, 2, 3, 4, 5] + .filter(n => n % 2 === 0) + .map(n => n * 3) + .reduce((sum, n) => sum + n, 0) + + // filter: [2, 4] + // map: [6, 12] + // reduce: 6 + 12 = 18 + expect(result).toBe(18) + }) + }) +}) From 520bcde37f830aec76cd8bd141f145be4c86b6d7 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Thu, 1 Jan 2026 14:49:11 -0300 Subject: [PATCH 090/128] docs: add comprehensive Currying & Composition concept page - Replace stub with full documentation (1,351 lines) - Add Pizza Restaurant analogy for currying with ASCII diagram - Add Assembly Line analogy for composition with ASCII diagram - Cover currying vs partial application with comparison table - Implement curry(), compose(), pipe() from scratch - Add real-world patterns: logging, API clients, validators, event handlers - Explain data-last design and point-free style - Include Lodash/Ramda vs vanilla JS comparison - Add common mistakes sections for both currying and composition - Include 10 Key Takeaways and 6 Test Your Knowledge Q&As - Add 59 comprehensive tests covering all code examples - SEO optimized with inline MDN links throughout --- docs/concepts/currying-composition.mdx | 1370 ++++++++++++++++- .../currying-composition.test.js | 981 ++++++++++++ 2 files changed, 2312 insertions(+), 39 deletions(-) create mode 100644 tests/functional-programming/currying-composition/currying-composition.test.js diff --git a/docs/concepts/currying-composition.mdx b/docs/concepts/currying-composition.mdx index eeb0acf8..8092a025 100644 --- a/docs/concepts/currying-composition.mdx +++ b/docs/concepts/currying-composition.mdx @@ -1,59 +1,1351 @@ --- -title: "Currying & Composition: Building Functions from Functions in JavaScript" -sidebarTitle: "Currying & Composition: Building Functions" -description: "Learn currying and function composition in JavaScript. Master partial application, compose, pipe, and how to build complex functions from simple ones for cleaner, reusable code." +title: "Currying & Composition: Functional Patterns in JavaScript" +sidebarTitle: "Currying & Composition: Functional Patterns" +description: "Learn currying and function composition in JavaScript. Build reusable functions from simple pieces using curry, compose, and pipe for cleaner, modular code." --- -## Overview +How does `add(1)(2)(3)` even work? Why do libraries like [Lodash](https://lodash.com/) and [Ramda](https://ramdajs.com/) let you call functions in multiple ways? And what if you could build complex data transformations by snapping together tiny, single-purpose functions like LEGO blocks? -**Currying** transforms a function with multiple arguments into a sequence of functions each taking a single argument. **Partial application** fixes a number of arguments to a function, producing another function of smaller arity. **Compose** and **pipe** are techniques for combining simple functions to build more complex ones. +```javascript +// Currying: one argument at a time +const add = a => b => c => a + b + c +add(1)(2)(3) // 6 -## Books +// Composition: chain functions together +const process = pipe( + getName, + trim, + capitalize +) +process({ name: " alice " }) // "Alice" +``` -<Card title="Functional-Light JavaScript: Ch. 3 - Managing Function Inputs" icon="book" href="https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch3.md"> - By Kyle Simpson -</Card> +These two techniques, **currying** and **function composition**, are core to functional programming. They let you write smaller, more reusable functions and combine them into powerful pipelines. Once you understand them, you'll see opportunities to simplify your code everywhere. -## Articles +<Info> +**What you'll learn in this guide:** +- What currying is and how `add(1)(2)(3)` actually works +- The difference between currying and partial application (they're not the same!) +- How to implement your own `curry()` helper function +- What function composition is and why it matters +- How to build `compose()` and `pipe()` from scratch +- Why currying and composition work so well together +- When to use libraries like Lodash vs vanilla JavaScript +- Real-world patterns used in production codebases +</Info> + +<Warning> +**Prerequisites:** This guide assumes you understand [closures](/concepts/scope-and-closures) and [higher-order functions](/concepts/higher-order-functions). Currying depends entirely on closures to work, and both currying and composition involve functions that return functions. +</Warning> + +--- + +## What is Currying? + +**Currying** is a transformation that converts a function with multiple arguments into a sequence of functions, each taking a single argument. It's named after mathematician Haskell Curry. + +Instead of calling `add(1, 2, 3)` with all arguments at once, a curried version lets you call `add(1)(2)(3)`, providing one argument at a time. Each call returns a new function waiting for the next argument. + +```javascript +// Regular function: takes all arguments at once +function add(a, b, c) { + return a + b + c +} +add(1, 2, 3) // 6 + +// Curried function: takes one argument at a time +function curriedAdd(a) { + return function(b) { + return function(c) { + return a + b + c + } + } +} +curriedAdd(1)(2)(3) // 6 +``` + +With arrow functions, curried functions become beautifully concise: + +```javascript +const add = a => b => c => a + b + c +add(1)(2)(3) // 6 +``` + +<Tip> +**Key insight:** Currying doesn't call the function. It transforms it. The original logic only runs when ALL arguments have been provided. +</Tip> + +--- + +## The Pizza Restaurant Analogy + +Imagine you're at a build-your-own pizza restaurant. Instead of shouting your entire order at once ("Large thin-crust pepperoni pizza!"), you go through a series of stations: + +1. **Size station:** "What size?" → "Large" → You get a ticket for a large pizza +2. **Crust station:** "What crust?" → "Thin" → Your ticket now says large thin-crust +3. **Toppings station:** "What toppings?" → "Pepperoni" → Your pizza is made! + +Each station remembers your previous choices and waits for just one more piece of information. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE PIZZA RESTAURANT ANALOGY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ orderPizza(size)(crust)(toppings) │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ +│ │ SIZE STATION │ │ CRUST STATION │ │TOPPING STATION│ │ +│ │ │ │ │ │ │ │ +│ │ "What size?" │ ──► │ "What crust?" │ ──► │ "Toppings?" │ ──► 🍕 │ +│ │ "Large" │ │ "Thin" │ │ "Pepperoni" │ │ +│ │ │ │ │ │ │ │ +│ └───────────────┘ └───────────────┘ └───────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ Returns function Returns function Returns the │ +│ that remembers that remembers final pizza! │ +│ size="Large" size + crust │ +│ │ +│ Each station REMEMBERS your previous choices using closures! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Here's that pizza order in code: + +```javascript +const orderPizza = size => crust => topping => { + return `${size} ${crust}-crust ${topping} pizza` +} + +// Full order at once +orderPizza("Large")("Thin")("Pepperoni") +// "Large Thin-crust Pepperoni pizza" + +// Or step by step +const largeOrder = orderPizza("Large") // Remembers size +const largeThinOrder = largeOrder("Thin") // Remembers size + crust +const myPizza = largeThinOrder("Pepperoni") // Final pizza! +// "Large Thin-crust Pepperoni pizza" + +// Create reusable "order templates" +const orderLarge = orderPizza("Large") +const orderLargeThin = orderLarge("Thin") + +orderLargeThin("Mushroom") // "Large Thin-crust Mushroom pizza" +orderLargeThin("Hawaiian") // "Large Thin-crust Hawaiian pizza" +``` + +The magic is that each intermediate function "remembers" the arguments from previous calls. That's [closures](/concepts/scope-and-closures) at work! + +--- + +## How Currying Works Step by Step + +Let's trace through exactly what happens when you call a curried function: + +```javascript +const add = a => b => c => a + b + c + +// Step 1: Call add(1) +const step1 = add(1) +// Returns: b => c => 1 + b + c +// The value 1 is "closed over" - remembered by the returned function + +// Step 2: Call step1(2) +const step2 = step1(2) +// Returns: c => 1 + 2 + c +// Now both 1 and 2 are remembered + +// Step 3: Call step2(3) +const result = step2(3) +// Returns: 1 + 2 + 3 = 6 +// All arguments collected, computation happens! + +console.log(result) // 6 +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ HOW CURRYING EXECUTES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ add(1)(2)(3) │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ add(1) │ │ +│ │ a = 1 │ │ +│ │ Returns: b => c => 1 + b + c │ │ +│ └──────────────────────────────┬──────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ (2) ← called on returned function │ │ +│ │ b = 2, a = 1 (from closure) │ │ +│ │ Returns: c => 1 + 2 + c │ │ +│ └──────────────────────────────┬──────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ (3) ← called on returned function │ │ +│ │ c = 3, b = 2, a = 1 (all from closures) │ │ +│ │ Returns: 1 + 2 + 3 = 6 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### The Closure Connection + +Currying depends entirely on [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to work. Each nested function "closes over" the arguments from its parent function, keeping them alive even after the parent returns. + +```javascript +const multiply = a => b => a * b + +const double = multiply(2) // 'a' is now 2, locked in by closure +const triple = multiply(3) // Different closure, 'a' is 3 + +double(5) // 10 (2 * 5) +triple(5) // 15 (3 * 5) +double(10) // 20 (2 * 10) + +// 'double' and 'triple' each have their own closure +// with their own remembered value of 'a' +``` + +--- + +## Implementing a Curry Helper + +Writing curried functions manually works, but it's tedious for functions with many parameters. Let's build a `curry()` helper that transforms any function automatically. + +### Basic Curry (Two Arguments) + +```javascript +function curry(fn) { + return function(a) { + return function(b) { + return fn(a, b) + } + } +} + +// Usage +const add = (a, b) => a + b +const curriedAdd = curry(add) + +curriedAdd(1)(2) // 3 +``` + +### Advanced Curry (Any Number of Arguments) + +This version handles functions with any number of arguments and supports calling with multiple arguments at once. It uses [`fn.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length) to know how many arguments the original function expects: + +```javascript +function curry(fn) { + return function curried(...args) { + // If we have enough arguments, call the original function + if (args.length >= fn.length) { + return fn.apply(this, args) + } + + // Otherwise, return a function that collects more arguments + return function(...nextArgs) { + return curried.apply(this, args.concat(nextArgs)) + } + } +} +``` + +Let's break down how this works: + +```javascript +function sum(a, b, c) { + return a + b + c +} + +const curriedSum = curry(sum) + +// All these work: +curriedSum(1, 2, 3) // 6 - called normally +curriedSum(1)(2)(3) // 6 - fully curried +curriedSum(1, 2)(3) // 6 - mixed +curriedSum(1)(2, 3) // 6 - mixed +``` + +<Accordion title="Step-by-step trace of curry(sum)(1)(2)(3)"> +```javascript +// Initial call: curry(sum) +// fn = sum, fn.length = 3 +// Returns the 'curried' function + +// Call: curriedSum(1) +// args = [1], args.length (1) < fn.length (3) +// Returns a new function that remembers [1] + +// Call: (previousResult)(2) +// args = [1, 2], args.length (2) < fn.length (3) +// Returns a new function that remembers [1, 2] + +// Call: (previousResult)(3) +// args = [1, 2, 3], args.length (3) >= fn.length (3) +// Calls sum(1, 2, 3) and returns 6 +``` +</Accordion> + +### ES6 Concise Version + +For those who love one-liners: + +```javascript +const curry = fn => + function curried(...args) { + return args.length >= fn.length + ? fn(...args) + : (...next) => curried(...args, ...next) + } +``` + +<Warning> +**Limitation:** The `fn.length` property doesn't count [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) or parameters with [default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters): + +```javascript +function withRest(...args) {} // length is 0 +function withDefault(a, b = 2) {} // length is 1 + +// Curry won't work correctly with these! +// You'd need to specify arity manually: +// curry(fn, expectedArgCount) +``` +</Warning> + +--- + +## Currying vs Partial Application + +These terms are often confused, but they're different techniques: + +| Aspect | Currying | Partial Application | +|--------|----------|---------------------| +| Arguments per call | Always **one** | Any number | +| What it returns | Chain of unary functions | Single function with some args fixed | +| Transformation | Structural (changes function shape) | Creates specialized version | + +### Currying Example + +Currying always produces functions that take exactly one argument: + +```javascript +// Curried: each call takes ONE argument +const add = a => b => c => a + b + c + +add(1) // Returns: b => c => 1 + b + c +add(1)(2) // Returns: c => 1 + 2 + c +add(1)(2)(3) // Returns: 6 +``` + +### Partial Application Example + +Partial application fixes some arguments upfront, and the resulting function takes all remaining arguments at once: + +```javascript +// Partial application helper +function partial(fn, ...presetArgs) { + return function(...laterArgs) { + return fn(...presetArgs, ...laterArgs) + } +} + +function greet(greeting, punctuation, name) { + return `${greeting}, ${name}${punctuation}` +} + +// Fix the first two arguments +const greetExcitedly = partial(greet, "Hello", "!") + +greetExcitedly("Alice") // "Hello, Alice!" +greetExcitedly("Bob") // "Hello, Bob!" + +// The returned function takes remaining args TOGETHER, not one at a time +``` + +### Visual Comparison + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CURRYING VS PARTIAL APPLICATION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Original: greet(greeting, punctuation, name) │ +│ │ +│ CURRYING: │ +│ ───────── │ +│ curriedGreet("Hello")("!")("Alice") │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ [1 arg] → [1 arg] → [1 arg] → result │ +│ │ +│ PARTIAL APPLICATION: │ +│ ──────────────────── │ +│ partial(greet, "Hello", "!")("Alice") │ +│ │ │ │ +│ ▼ ▼ │ +│ [2 args fixed] → [1 arg] → result │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Tip> +**When to use which?** +- **Currying:** When you want maximum flexibility in how arguments are provided +- **Partial Application:** When you want to create specialized versions of functions with some arguments preset +</Tip> + +--- + +## Real-World Currying Patterns + +Currying isn't just a theoretical concept. Here are patterns you'll see in production code: + +### 1. Configurable Logging + +```javascript +// Curried logger factory +const createLogger = level => timestamp => message => { + const time = timestamp ? new Date().toISOString() : '' + console.log(`[${level}]${time ? ' ' + time : ''} ${message}`) +} + +// Create specialized loggers +const info = createLogger('INFO')(true) +const debug = createLogger('DEBUG')(true) +const error = createLogger('ERROR')(true) + +// Use them +info('Application started') // [INFO] 2024-01-15T10:30:00.000Z Application started +debug('Processing request') // [DEBUG] 2024-01-15T10:30:00.000Z Processing request +error('Connection failed') // [ERROR] 2024-01-15T10:30:00.000Z Connection failed + +// Logger without timestamp for development +const quickLog = createLogger('LOG')(false) +quickLog('Quick debug message') // [LOG] Quick debug message +``` + +### 2. API Client Factory + +```javascript +const createApiClient = baseUrl => endpoint => options => { + return fetch(`${baseUrl}${endpoint}`, options) + .then(res => res.json()) +} + +// Create clients for different APIs +const githubApi = createApiClient('https://api.github.com') +const myApi = createApiClient('https://api.myapp.com') + +// Create endpoint-specific fetchers +const getGithubUser = githubApi('/users') +const getMyAppUsers = myApi('/users') + +// Use them +getGithubUser({ method: 'GET' }) + .then(users => console.log(users)) +``` + +### 3. Event Handler Configuration + +```javascript +const handleEvent = eventType => element => callback => { + element.addEventListener(eventType, callback) + + // Return cleanup function + return () => element.removeEventListener(eventType, callback) +} + +// Create specialized handlers +const onClick = handleEvent('click') +const onHover = handleEvent('mouseenter') + +// Attach to elements +const button = document.querySelector('#myButton') +const removeClick = onClick(button)(() => console.log('Clicked!')) + +// Later: cleanup +removeClick() +``` + +### 4. Validation Functions + +```javascript +const isGreaterThan = min => value => value > min +const isLessThan = max => value => value < max +const hasLength = length => str => str.length === length + +// Create specific validators +const isAdult = isGreaterThan(17) +const isValidAge = isLessThan(120) +const isValidZipCode = hasLength(5) + +// Use with array methods +const ages = [15, 22, 45, 8, 67] +const adults = ages.filter(isAdult) // [22, 45, 67] + +const zipCodes = ['12345', '1234', '123456', '54321'] +const validZips = zipCodes.filter(isValidZipCode) // ['12345', '54321'] +``` + +### 5. Discount Calculator + +```javascript +const applyDiscount = discountPercent => price => { + return price * (1 - discountPercent / 100) +} + +const tenPercentOff = applyDiscount(10) +const twentyPercentOff = applyDiscount(20) +const blackFridayDeal = applyDiscount(50) + +tenPercentOff(100) // 90 +twentyPercentOff(100) // 80 +blackFridayDeal(100) // 50 + +// Apply to multiple items +const prices = [100, 200, 50, 75] +const discountedPrices = prices.map(tenPercentOff) // [90, 180, 45, 67.5] +``` + +--- + +## What is Function Composition? + +**Function composition** is the process of combining two or more functions to produce a new function. The output of one function becomes the input of the next. + +In mathematics, composition is written as `(f ∘ g)(x) = f(g(x))`. In code, we read this as "f after g" or "first apply g, then apply f to the result." + +```javascript +// Individual functions +const add10 = x => x + 10 +const multiply2 = x => x * 2 +const subtract5 = x => x - 5 + +// Manual composition (nested calls) +const result = subtract5(multiply2(add10(5))) +// Step by step: 5 → 15 → 30 → 25 + +// With a compose function +const composed = compose(subtract5, multiply2, add10) +composed(5) // 25 +``` + +Why compose instead of nesting? Because this: + +```javascript +subtract5(multiply2(add10(toString(trim(getName(user)))))) +``` + +Becomes this: + +```javascript +const processUser = compose( + subtract5, + multiply2, + add10, + toNumber, + trim, + getName +) +processUser(user) +``` + +Much easier to read, modify, and test! + +--- + +## The Assembly Line Analogy + +Think of function composition like a factory assembly line. Raw materials enter one end, pass through a series of stations, and a finished product comes out the other end. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE ASSEMBLY LINE ANALOGY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ RAW INPUT ──► [Station A] ──► [Station B] ──► [Station C] ──► OUTPUT │ +│ │ +│ pipe(stationA, stationB, stationC)(rawInput) │ +│ │ +│ ───────────────────────────────────────────────────────────────────── │ +│ │ +│ Example: Transform user data │ +│ │ +│ { name: " ALICE " } │ +│ │ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ getName │ → " ALICE " │ +│ └─────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ trim │ → "ALICE" │ +│ └─────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ toLowerCase │ → "alice" │ +│ └─────────────┘ │ +│ │ │ +│ ▼ │ +│ Final output: "alice" │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Each station: +1. Takes input from the previous station +2. Does ONE specific transformation +3. Passes output to the next station + +This is exactly how composed functions work! + +--- + +## compose() and pipe() + +There are two ways to compose functions, differing only in direction: + +| Function | Direction | Reads like... | +|----------|-----------|---------------| +| `compose(f, g, h)` | Right to left | Math: `f(g(h(x)))` | +| `pipe(f, g, h)` | Left to right | A recipe: "first f, then g, then h" | + +### Implementing pipe() + +`pipe` flows left-to-right, which many developers find more intuitive. It uses [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) to chain functions together: + +```javascript +const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) +``` + +Let's trace through it: + +```javascript +const getName = obj => obj.name +const toUpperCase = str => str.toUpperCase() +const addExclaim = str => str + '!' + +const shout = pipe(getName, toUpperCase, addExclaim) + +shout({ name: 'alice' }) + +// reduce trace: +// Initial: x = { name: 'alice' } +// Step 1: getName({ name: 'alice' }) → 'alice' +// Step 2: toUpperCase('alice') → 'ALICE' +// Step 3: addExclaim('ALICE') → 'ALICE!' +// Result: 'ALICE!' +``` + +### Implementing compose() + +`compose` flows right-to-left, matching mathematical notation. It uses [`reduceRight()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight) instead: + +```javascript +const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x) +``` + +```javascript +// compose processes right-to-left +const shout = compose(addExclaim, toUpperCase, getName) +shout({ name: 'alice' }) // 'ALICE!' + +// This is equivalent to: +addExclaim(toUpperCase(getName({ name: 'alice' }))) +``` + +### Which Should You Use? + +```javascript +// These produce the same result: +pipe(a, b, c)(x) // a first, then b, then c +compose(c, b, a)(x) // Same! c(b(a(x))) +``` + +Most developers prefer `pipe` because: +1. It reads left-to-right like English +2. Functions are listed in execution order +3. It's easier to follow the data flow + +```javascript +// pipe: reads in order of execution +const processUser = pipe( + validateInput, // First + sanitizeData, // Second + saveToDatabase, // Third + sendNotification // Fourth +) + +// compose: reads in reverse order +const processUser = compose( + sendNotification, // Fourth (but listed first) + saveToDatabase, // Third + sanitizeData, // Second + validateInput // First (but listed last) +) +``` + +--- + +## Building Data Pipelines + +Composition really shines when building data transformation pipelines: + +```javascript +const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + +// Individual transformation functions +const removeSpaces = str => str.trim() +const toLowerCase = str => str.toLowerCase() +const splitWords = str => str.split(' ') +const capitalizeFirst = words => words.map(w => w[0].toUpperCase() + w.slice(1)) +const joinWords = words => words.join('') + +// Compose them into a pipeline +const toCamelCase = pipe( + removeSpaces, + toLowerCase, + splitWords, + capitalizeFirst, + joinWords +) + +toCamelCase(' HELLO WORLD ') // 'HelloWorld' +toCamelCase('my variable name') // 'MyVariableName' +``` + +### Real-World Pipeline: Processing API Data + +```javascript +// Transform API response into display format +const processApiResponse = pipe( + // Extract data from response + response => response.data, + + // Filter active users only + users => users.filter(u => u.isActive), + + // Sort by name + users => users.sort((a, b) => a.name.localeCompare(b.name)), + + // Transform to display format + users => users.map(u => ({ + id: u.id, + displayName: `${u.firstName} ${u.lastName}`, + email: u.email + })), + + // Take first 10 + users => users.slice(0, 10) +) + +// Use it +fetch('/api/users') + .then(res => res.json()) + .then(processApiResponse) + .then(users => renderUserList(users)) +``` + +--- + +## Why Currying and Composition Work Together + +Currying and composition are natural partners. Here's why: + +### The Problem: Functions with Multiple Arguments + +Composition works best with functions that take a single argument and return a single value. But many useful functions need multiple arguments: + +```javascript +const add = (a, b) => a + b +const multiply = (a, b) => a * b + +// This doesn't work! +const addThenMultiply = pipe(add, multiply) +addThenMultiply(1, 2) // NaN - multiply receives one value, not two +``` + +### The Solution: Currying + +Currying converts multi-argument functions into chains of single-argument functions, making them perfect for composition: + +```javascript +// Curried versions +const add = a => b => a + b +const multiply = a => b => a * b + +// Now we can compose! +const add5 = add(5) // x => 5 + x +const double = multiply(2) // x => 2 * x + +const add5ThenDouble = pipe(add5, double) +add5ThenDouble(10) // (10 + 5) * 2 = 30 +``` + +### Data-Last Parameter Order + +For composition to work smoothly, the **data** should be the **last** parameter. This is called "data-last" design: + +```javascript +// ❌ Data-first (hard to compose) +const map = (array, fn) => array.map(fn) +const filter = (array, fn) => array.filter(fn) + +// ✓ Data-last (easy to compose) +const map = fn => array => array.map(fn) +const filter = fn => array => array.filter(fn) + +// Now they compose beautifully +const double = x => x * 2 +const isEven = x => x % 2 === 0 + +const doubleEvens = pipe( + filter(isEven), + map(double) +) + +doubleEvens([1, 2, 3, 4, 5, 6]) // [4, 8, 12] +``` + +### Point-Free Style + +When currying and composition combine, you can write code without explicitly mentioning the data being processed. This is called **point-free** style: + +```javascript +// With explicit data parameter (pointed style) +const processNumbers = numbers => { + return numbers + .filter(x => x > 0) + .map(x => x * 2) + .reduce((sum, x) => sum + x, 0) +} + +// Point-free style (no explicit 'numbers' parameter) +const isPositive = x => x > 0 +const double = x => x * 2 +const sum = (a, b) => a + b + +const processNumbers = pipe( + filter(isPositive), + map(double), + reduce(sum, 0) +) + +// Both do the same thing: +processNumbers([1, -2, 3, -4, 5]) // 18 +``` + +Point-free code focuses on the transformations, not the data. It's often more declarative and easier to reason about. + +--- + +## Lodash, Ramda, and Vanilla JavaScript + +Libraries like [Lodash](https://lodash.com/) and [Ramda](https://ramdajs.com/) are popular because they provide battle-tested implementations of currying, composition, and many other utilities. + +### Why Use a Library? + +Libraries offer features our simple implementations lack: + +```javascript +import _ from 'lodash' + +// 1. Placeholder support +const greet = _.curry((greeting, name) => `${greeting}, ${name}!`) +greet(_.__, 'Alice')('Hello') // "Hello, Alice!" +// The __ placeholder lets you skip arguments + +// 2. Works with variadic functions +const sum = _.curry((...nums) => nums.reduce((a, b) => a + b, 0), 3) +sum(1)(2)(3) // 6 + +// 3. Auto-curried utility functions +_.map(x => x * 2)([1, 2, 3]) // [2, 4, 6] +// Lodash/fp provides auto-curried, data-last versions +``` + +### Ramda: Built for Composition + +[Ramda](https://ramdajs.com/) is designed from the ground up for functional programming: + +```javascript +import * as R from 'ramda' + +// All functions are auto-curried +R.add(1)(2) // 3 +R.add(1, 2) // 3 + +// Data-last by default +R.map(x => x * 2, [1, 2, 3]) // [2, 4, 6] +R.map(x => x * 2)([1, 2, 3]) // [2, 4, 6] + +// Built-in compose and pipe +const processUser = R.pipe( + R.prop('name'), + R.trim, + R.toLower +) + +processUser({ name: ' ALICE ' }) // 'alice' +``` + +### Lodash/fp: Functional Lodash + +Lodash provides a functional programming variant: + +```javascript +import fp from 'lodash/fp' + +// Auto-curried, data-last +const getAdultNames = fp.pipe( + fp.filter(user => user.age >= 18), + fp.map(fp.get('name')), + fp.sortBy(fp.identity) +) + +const users = [ + { name: 'Charlie', age: 25 }, + { name: 'Alice', age: 17 }, + { name: 'Bob', age: 30 } +] + +getAdultNames(users) // ['Bob', 'Charlie'] +``` + +### Vanilla JavaScript Alternatives + +You don't always need a library. Here are vanilla implementations for common patterns: + +```javascript +// Curry +const curry = fn => { + return function curried(...args) { + return args.length >= fn.length + ? fn(...args) + : (...next) => curried(...args, ...next) + } +} + +// Pipe and Compose +const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) +const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x) + +// Partial Application +const partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs) + +// Data-last map and filter +const map = fn => arr => arr.map(fn) +const filter = fn => arr => arr.filter(fn) +const reduce = (fn, initial) => arr => arr.reduce(fn, initial) +``` + +### When to Use What? + +| Situation | Recommendation | +|-----------|----------------| +| Learning/small project | Vanilla JS implementations | +| Already using Lodash | Use `lodash/fp` for functional code | +| Heavy functional programming | Consider Ramda | +| Bundle size matters | Vanilla JS or tree-shakeable imports | +| Team familiarity | Match existing codebase patterns | + +--- + +## Common Currying Mistakes + +### Mistake #1: Forgetting Curried Functions Return Functions + +```javascript +const add = a => b => a + b + +// ❌ Wrong: Forgot the second call +const result = add(1) +console.log(result) // [Function] - not a number! + +// ✓ Correct +const result = add(1)(2) +console.log(result) // 3 +``` + +### Mistake #2: Wrong Argument Order + +For composition to work, data should come last: + +```javascript +// ❌ Data-first: hard to compose +const multiply = (value, factor) => value * factor + +// ✓ Data-last: composes well +const multiply = factor => value => value * factor + +const double = multiply(2) +const triple = multiply(3) + +pipe(double, triple)(5) // 30 +``` + +### Mistake #3: Currying Functions with Rest Parameters + +```javascript +function sum(...nums) { + return nums.reduce((a, b) => a + b, 0) +} + +console.log(sum.length) // 0 - rest parameters have length 0! + +// Our curry won't work correctly +const curriedSum = curry(sum) +curriedSum(1)(2)(3) // Calls immediately with just 1! +``` + +**Solution:** Specify arity explicitly: + +```javascript +const curryN = (fn, arity) => { + return function curried(...args) { + return args.length >= arity + ? fn(...args) + : (...next) => curried(...args, ...next) + } +} + +const curriedSum = curryN(sum, 3) +curriedSum(1)(2)(3) // 6 +``` + +--- + +## Common Composition Mistakes + +### Mistake #1: Type Mismatches in Pipeline + +Each function's output must match the next function's expected input: + +```javascript +const getName = obj => obj.name // Returns string +const getLength = arr => arr.length // Expects array! + +// ❌ Broken pipeline +const broken = pipe(getName, getLength) +broken({ name: 'Alice' }) // 5 (works by accident - string has .length) + +// But what if getName returns something without .length? +const getAge = obj => obj.age // Returns number +const getLength = arr => arr.length + +const reallyBroken = pipe(getAge, getLength) +reallyBroken({ age: 25 }) // undefined - numbers don't have .length +``` + +### Mistake #2: Side Effects in Pipelines + +Composed functions should be [pure](/concepts/pure-functions). Side effects make pipelines unpredictable: + +```javascript +// ❌ Side effect in pipeline +let globalCounter = 0 +const addAndCount = x => { + globalCounter++ // Side effect! + return x + 1 +} + +// This is unpredictable - depends on global state +const process = pipe(addAndCount, addAndCount) +``` + +### Mistake #3: Over-Composing + +Sometimes explicit code is clearer than a point-free pipeline: + +```javascript +// ❌ Too clever - hard to understand +const processUser = pipe( + prop('account'), + prop('settings'), + prop('preferences'), + prop('theme'), + defaultTo('light'), + eq('dark'), + ifElse(identity, always('🌙'), always('☀️')) +) + +// ✓ Clearer +function getThemeEmoji(user) { + const theme = user?.account?.settings?.preferences?.theme ?? 'light' + return theme === 'dark' ? '🌙' : '☀️' +} +``` + +<Tip> +**Rule of thumb:** Use composition when it makes code clearer, not just shorter. If a colleague would struggle to understand your pipeline, consider a more explicit approach. +</Tip> + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Currying transforms `f(a, b, c)` into `f(a)(b)(c)`** — each call takes one argument and returns a function waiting for the next + +2. **Currying depends on closures** — each nested function "closes over" arguments from parent functions, remembering them + +3. **Currying ≠ Partial Application** — currying always produces unary functions; partial application fixes some args and takes the rest together + +4. **Function composition combines simple functions into complex ones** — output of one becomes input of the next + +5. **`pipe()` flows left-to-right, `compose()` flows right-to-left** — most developers prefer pipe because it reads in execution order + +6. **Currying enables composition** — curried functions take one input and return one output, perfect for chaining + +7. **"Data-last" ordering is essential** — put the data parameter last so curried functions compose naturally + +8. **Point-free style focuses on transformations** — no explicit data parameters, just a chain of operations + +9. **Libraries like Lodash/Ramda add powerful features** — placeholders, auto-currying, and battle-tested utilities + +10. **Vanilla JS implementations work for most cases** — `curry`, `pipe`, and `compose` are just a few lines each +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between currying and partial application?"> + **Answer:** + + **Currying** transforms a function so that it takes arguments one at a time, returning a new function after each argument until all are received. + + **Partial application** fixes some arguments upfront and returns a function that takes the remaining arguments together. + + ```javascript + // Currying: one argument at a time + const curriedAdd = a => b => c => a + b + c + curriedAdd(1)(2)(3) // 6 + + // Partial application: fix some args, take rest together + const add = (a, b, c) => a + b + c + const partial = (fn, ...preset) => (...rest) => fn(...preset, ...rest) + const add1 = partial(add, 1) + add1(2, 3) // 6 - takes remaining args at once + ``` + </Accordion> + + <Accordion title="Question 2: Implement a simple curry function"> + **Answer:** + + ```javascript + function curry(fn) { + return function curried(...args) { + if (args.length >= fn.length) { + return fn(...args) + } + return (...nextArgs) => curried(...args, ...nextArgs) + } + } + + // Usage + const add = (a, b, c) => a + b + c + const curriedAdd = curry(add) + + curriedAdd(1)(2)(3) // 6 + curriedAdd(1, 2)(3) // 6 + curriedAdd(1)(2, 3) // 6 + curriedAdd(1, 2, 3) // 6 + ``` + </Accordion> + + <Accordion title="Question 3: What's the difference between compose() and pipe()?"> + **Answer:** + + Both combine functions, but in opposite directions: + + - **`pipe(f, g, h)(x)`** — Left to right: `h(g(f(x)))` + - **`compose(f, g, h)(x)`** — Right to left: `f(g(h(x)))` + + ```javascript + const add1 = x => x + 1 + const double = x => x * 2 + const square = x => x * x + + // pipe: add1 first, then double, then square + pipe(add1, double, square)(3) // ((3+1)*2)² = 64 + + // compose: square first, then double, then add1 + compose(add1, double, square)(3) // (3²*2)+1 = 19 + ``` + + Most developers prefer `pipe` because functions are listed in execution order. + </Accordion> + + <Accordion title="Question 4: Why do currying and composition work well together?"> + **Answer:** + + Composition works best with functions that take one input and return one output. Currying transforms multi-argument functions into chains of single-argument functions, making them perfect for composition. + + ```javascript + // Without currying - can't compose + const add = (a, b) => a + b + const multiply = (a, b) => a * b + // How would you pipe these? + + // With currying - composes naturally + const add = a => b => a + b + const multiply = a => b => a * b + + const add5 = add(5) + const double = multiply(2) + + const add5ThenDouble = pipe(add5, double) + add5ThenDouble(10) // 30 + ``` + + The key is "data-last" ordering: configure the function first, pass data last. + </Accordion> + + <Accordion title="Question 5: Implement sum(1)(2)(3)...(n)() that returns the total"> + **Answer:** + + This is a classic interview question. The trick is to return a function that can be called with more arguments OR returns the sum when called with no arguments: + + ```javascript + function sum(a) { + return function next(b) { + if (b === undefined) { + return a // No more arguments, return sum + } + return sum(a + b) // More arguments, keep accumulating + } + } + + sum(1)(2)(3)() // 6 + sum(1)(2)(3)(4)(5)() // 15 + sum(10)() // 10 + ``` + + Alternative using `valueOf` for implicit conversion: + + ```javascript + function sum(a) { + const fn = b => sum(a + b) + fn.valueOf = () => a + return fn + } + + +sum(1)(2)(3) // 6 (unary + triggers valueOf) + ``` + </Accordion> + + <Accordion title="Question 6: What is 'point-free' style?"> + **Answer:** + + Point-free style (also called "tacit programming") is writing functions without explicitly mentioning their arguments. Instead of defining what to do with data, you compose operations. + + ```javascript + // Pointed style (explicit argument) + const getUpperName = user => user.name.toUpperCase() + + // Point-free style (no explicit argument) + const getUpperName = pipe( + prop('name'), + toUpperCase + ) + + // Another example + // Pointed: + const doubleAll = numbers => numbers.map(x => x * 2) + + // Point-free: + const doubleAll = map(x => x * 2) + ``` + + Point-free code focuses on the transformations rather than the data being transformed. It's often more declarative and can be easier to reason about, but can also be harder to read if overused. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="Composition and Currying Elegance in JavaScript" icon="newspaper" href="https://medium.com/@pragyan88/writing-middleware-composition-and-currying-elegance-in-javascript-8b15c98a541b"> - By Pragyan Das + <Card title="Scope and Closures" icon="lock" href="/concepts/scope-and-closures"> + Currying depends on closures to remember arguments between calls + </Card> + <Card title="Higher-Order Functions" icon="layer-group" href="/concepts/higher-order-functions"> + Functions that return functions are the foundation of currying </Card> - <Card title="Functional JavaScript: Function Composition For Every Day Use" icon="newspaper" href="https://hackernoon.com/javascript-functional-composition-for-every-day-use-22421ef65a10"> - By Joel Thoms + <Card title="Pure Functions" icon="sparkles" href="/concepts/pure-functions"> + Composed pipelines work best with pure, side-effect-free functions + </Card> + <Card title="Map, Reduce, Filter" icon="filter" href="/concepts/map-reduce-filter"> + Array methods that compose beautifully when curried </Card> </CardGroup> -- [Functional Composition: compose() and pipe() — Anton Paras](https://medium.com/@acparas/what-i-learned-today-july-2-2017-ab9a46dbf85f) -- [Why The Hipsters Compose Everything: Functional Composing In JavaScript — A. Sharif](http://busypeoples.github.io/post/functional-composing-javascript/) -- [A Gentle Introduction to Functional JavaScript pt III: Functions for making functions — James Sinclair](https://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-functions/) -- [Curry And Compose (why you should be using something like ramda in your code) — jsanchesleao](https://jsleao.wordpress.com/2015/02/22/curry-and-compose-why-you-should-be-using-something-like-ramda-in-your-code/) -- [Function Composition in JavaScript with Pipe — Andy Van Slaars](https://vanslaars.io/post/create-pipe-function/) -- [Practical Functional JavaScript with Ramda — Andrew D'Amelio, Yuri Takhteyev](https://developer.telerik.com/featured/practical-functional-javascript-ramda/) -- [The beauty in Partial Application, Currying, and Function Composition — Joel Thoms](https://hackernoon.com/the-beauty-in-partial-application-currying-and-function-composition-d885bdf0d574) -- [Curry or Partial Application? — Eric Elliott](https://medium.com/javascript-scene/curry-or-partial-application-8150044c78b8) -- [Partial Application in JavaScript — Ben Alman](http://benalman.com/news/2012/09/partial-application-in-javascript/) -- [Partial Application of Functions — Functional Reactive Ninja](https://hackernoon.com/partial-application-of-functions-dbe7d9b80760) -- [So You Want to be a Functional Programmer pt. I — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-1-1f15e387e536) -- [An introduction to the basic principles of Functional Programming — TK](https://medium.freecodecamp.org/an-introduction-to-the-basic-principles-of-functional-programming-a2c2a15c84) -- [Concepts of Functional Programming in javascript — TK](https://medium.com/the-renaissance-developer/concepts-of-functional-programming-in-javascript-6bc84220d2aa) -- [A practical guide to writing more functional JavaScript — Nadeesha Cabral](https://medium.freecodecamp.org/a-practical-guide-to-writing-more-functional-javascript-db49409f71) -- [A simple explanation of functional pipe in JavaScript — Ben Lesh](https://dev.to/benlesh/a-simple-explanation-of-functional-pipe-in-javascript-2hbj) +--- + +## Reference -## Videos +<CardGroup cols={2}> + <Card title="Functions — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions"> + Complete guide to JavaScript functions, the building blocks of currying and composition + </Card> + <Card title="Closures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures"> + Understanding closures is essential for understanding how currying preserves arguments + </Card> + <Card title="Array.prototype.reduce() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"> + The reduce method powers our compose and pipe implementations + </Card> + <Card title="Array.prototype.reduceRight() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight"> + Used in compose to process functions from right to left + </Card> +</CardGroup> + +## Articles <CardGroup cols={2}> - <Card title="Compose vs Pipe: Functional Programming in JavaScript" icon="video" href="https://www.youtube.com/watch?v=Wl2ejJOqHUU"> - By Chyld Studios + <Card title="Currying — javascript.info" icon="newspaper" href="https://javascript.info/currying-partials"> + The definitive tutorial on currying with clear examples and an advanced curry implementation. Includes a practical logging example that shows real-world benefits. + </Card> + <Card title="A Quick Introduction to pipe() and compose()" icon="newspaper" href="https://www.freecodecamp.org/news/pipe-and-compose-in-javascript-5b04004ac937/"> + Step-by-step debugger walkthrough showing exactly how pipe and compose work internally. The visual traces make the concept click. + </Card> + <Card title="Curry and Function Composition — Eric Elliott" icon="newspaper" href="https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983"> + Part of the "Composing Software" series. Comprehensive coverage of how currying enables composition, with trace utilities for debugging pipelines. </Card> - <Card title="JavaScript Functional Programing: Compose" icon="video" href="https://www.youtube.com/watch?v=jigHxo9YR30"> - By Theodore Anderson + <Card title="Functional-Light JavaScript: Chapter 3 — Kyle Simpson" icon="newspaper" href="https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch3.md"> + Free online chapter covering function inputs, currying, and partial application in depth. Kyle Simpson explains the nuances between currying and partial application better than almost anyone. + </Card> + <Card title="Composing Software: The Book — Eric Elliott" icon="newspaper" href="https://medium.com/javascript-scene/composing-software-the-book-f31c77fc3ddc"> + Index to the complete "Composing Software" series covering functional programming, composition, functors, and more in JavaScript. + </Card> + <Card title="A Gentle Introduction to Functional JavaScript — James Sinclair" icon="newspaper" href="https://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-functions/"> + Covers practical functional JavaScript patterns including currying and composition with approachable explanations. </Card> </CardGroup> -- [Function Composition - Functional JavaScript — NWCalvank](https://www.youtube.com/watch?v=mth5WpEc4Qs) -- [JavaScript Function Composition Explained — Theodore Anderson](https://www.youtube.com/watch?v=Uam37AlzPYw) -- [Let's code with function composition — Fun Fun Function](https://www.youtube.com/watch?v=VGB9HbL1GHk) -- [Partial Application vs. Currying — NWCalvank](https://www.youtube.com/watch?v=DzLkRsUN2vE) -- [JavaScript Partial Application — Theodore Anderson](https://www.youtube.com/watch?v=jkebgHEcvac) +## Videos + +<CardGroup cols={2}> + <Card title="Currying — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=iZLP4qOwY8I"> + Mattias Petter Johansson's entertaining explanation of currying as part of his beloved functional programming series. Great for visual learners. + </Card> + <Card title="Compose and Pipe — Web Dev Simplified" icon="video" href="https://www.youtube.com/watch?v=yd2FZ1kP5wE"> + Kyle Cook's clear, beginner-friendly walkthrough of compose and pipe with practical examples you can follow along with. + </Card> + <Card title="JavaScript Functional Programming — Traversy Media" icon="video" href="https://www.youtube.com/watch?v=e-5obm1G_FY"> + Brad Traversy covers functional programming patterns including currying and composition in his practical, project-focused style. + </Card> +</CardGroup> diff --git a/tests/functional-programming/currying-composition/currying-composition.test.js b/tests/functional-programming/currying-composition/currying-composition.test.js new file mode 100644 index 00000000..fc7d9425 --- /dev/null +++ b/tests/functional-programming/currying-composition/currying-composition.test.js @@ -0,0 +1,981 @@ +import { describe, it, expect } from 'vitest' + +describe('Currying & Composition', () => { + describe('Basic Currying', () => { + describe('Manual Currying with Arrow Functions', () => { + it('should create a curried function with arrow syntax', () => { + const add = a => b => c => a + b + c + + expect(add(1)(2)(3)).toBe(6) + }) + + it('should allow partial application at each step', () => { + const add = a => b => c => a + b + c + + const add1 = add(1) // Returns b => c => 1 + b + c + const add1and2 = add1(2) // Returns c => 1 + 2 + c + const result = add1and2(3) // Returns 6 + + expect(typeof add1).toBe('function') + expect(typeof add1and2).toBe('function') + expect(result).toBe(6) + }) + + it('should demonstrate closures preserving arguments', () => { + const multiply = a => b => a * b + + const double = multiply(2) + const triple = multiply(3) + + expect(double(5)).toBe(10) + expect(triple(5)).toBe(15) + expect(double(10)).toBe(20) + expect(triple(10)).toBe(30) + }) + }) + + describe('Traditional Function Currying', () => { + it('should work with traditional function syntax', () => { + function curriedAdd(a) { + return function(b) { + return function(c) { + return a + b + c + } + } + } + + expect(curriedAdd(1)(2)(3)).toBe(6) + }) + }) + + describe('Pizza Restaurant Example', () => { + it('should demonstrate the pizza ordering pattern', () => { + const orderPizza = size => crust => topping => { + return `${size} ${crust}-crust ${topping} pizza` + } + + expect(orderPizza("Large")("Thin")("Pepperoni")) + .toBe("Large Thin-crust Pepperoni pizza") + }) + + it('should allow creating reusable order templates', () => { + const orderPizza = size => crust => topping => { + return `${size} ${crust}-crust ${topping} pizza` + } + + const orderLarge = orderPizza("Large") + const orderLargeThin = orderLarge("Thin") + + expect(orderLargeThin("Mushroom")).toBe("Large Thin-crust Mushroom pizza") + expect(orderLargeThin("Hawaiian")).toBe("Large Thin-crust Hawaiian pizza") + }) + }) + }) + + describe('Curry Helper Implementation', () => { + describe('Basic Two-Argument Curry', () => { + it('should curry a two-argument function', () => { + function curry(fn) { + return function(a) { + return function(b) { + return fn(a, b) + } + } + } + + const add = (a, b) => a + b + const curriedAdd = curry(add) + + expect(curriedAdd(1)(2)).toBe(3) + }) + }) + + describe('Advanced Curry (Any Number of Arguments)', () => { + const curry = fn => { + return function curried(...args) { + if (args.length >= fn.length) { + return fn.apply(this, args) + } + return (...nextArgs) => curried.apply(this, args.concat(nextArgs)) + } + } + + it('should support full currying', () => { + const sum = (a, b, c) => a + b + c + const curriedSum = curry(sum) + + expect(curriedSum(1)(2)(3)).toBe(6) + }) + + it('should support normal function calls', () => { + const sum = (a, b, c) => a + b + c + const curriedSum = curry(sum) + + expect(curriedSum(1, 2, 3)).toBe(6) + }) + + it('should support mixed calling styles', () => { + const sum = (a, b, c) => a + b + c + const curriedSum = curry(sum) + + expect(curriedSum(1, 2)(3)).toBe(6) + expect(curriedSum(1)(2, 3)).toBe(6) + }) + + it('should work with functions of different arities', () => { + const add2 = (a, b) => a + b + const add4 = (a, b, c, d) => a + b + c + d + + const curriedAdd2 = curry(add2) + const curriedAdd4 = curry(add4) + + expect(curriedAdd2(1)(2)).toBe(3) + expect(curriedAdd4(1)(2)(3)(4)).toBe(10) + expect(curriedAdd4(1, 2)(3, 4)).toBe(10) + }) + }) + + describe('curryN (Explicit Arity)', () => { + const curryN = (fn, arity) => { + return function curried(...args) { + if (args.length >= arity) { + return fn(...args) + } + return (...nextArgs) => curried(...args, ...nextArgs) + } + } + + it('should curry variadic functions with explicit arity', () => { + const sum = (...nums) => nums.reduce((a, b) => a + b, 0) + + const curriedSum3 = curryN(sum, 3) + const curriedSum5 = curryN(sum, 5) + + expect(curriedSum3(1)(2)(3)).toBe(6) + expect(curriedSum5(1)(2)(3)(4)(5)).toBe(15) + }) + }) + }) + + describe('Currying vs Partial Application', () => { + describe('Currying (One Argument at a Time)', () => { + it('should demonstrate currying with unary functions', () => { + const curriedAdd = a => b => c => a + b + c + + // Each call takes exactly ONE argument + const step1 = curriedAdd(1) // Returns function + const step2 = step1(2) // Returns function + const step3 = step2(3) // Returns 6 + + expect(typeof step1).toBe('function') + expect(typeof step2).toBe('function') + expect(step3).toBe(6) + }) + }) + + describe('Partial Application (Fix Some Args)', () => { + const partial = (fn, ...presetArgs) => { + return (...laterArgs) => fn(...presetArgs, ...laterArgs) + } + + it('should fix some arguments upfront', () => { + const greet = (greeting, punctuation, name) => { + return `${greeting}, ${name}${punctuation}` + } + + const greetExcitedly = partial(greet, "Hello", "!") + + expect(greetExcitedly("Alice")).toBe("Hello, Alice!") + expect(greetExcitedly("Bob")).toBe("Hello, Bob!") + }) + + it('should take remaining arguments together, not one at a time', () => { + const add = (a, b, c, d) => a + b + c + d + + const add10 = partial(add, 10) + + // Takes remaining 3 args at once + expect(add10(1, 2, 3)).toBe(16) + }) + + it('should differ from currying in how arguments are collected', () => { + const add = (a, b, c) => a + b + c + + // Curried: takes args one at a time + const curriedAdd = a => b => c => a + b + c + + // Partial: fixes some args, takes rest together + const add1 = partial(add, 1) + + // Curried needs 3 calls + expect(curriedAdd(1)(2)(3)).toBe(6) + + // Partial takes remaining in one call + expect(add1(2, 3)).toBe(6) + }) + }) + }) + + describe('Real-World Currying Patterns', () => { + describe('Configurable Logger', () => { + it('should create specialized loggers', () => { + const logs = [] + + const createLogger = level => prefix => message => { + const logEntry = `[${level}] ${prefix}: ${message}` + logs.push(logEntry) + return logEntry + } + + const infoLogger = createLogger('INFO')('App') + const errorLogger = createLogger('ERROR')('App') + + expect(infoLogger('Started')).toBe('[INFO] App: Started') + expect(errorLogger('Failed')).toBe('[ERROR] App: Failed') + }) + }) + + describe('API Client Factory', () => { + it('should create specialized API clients', () => { + const createApiUrl = baseUrl => endpoint => params => { + const queryString = new URLSearchParams(params).toString() + return `${baseUrl}${endpoint}${queryString ? '?' + queryString : ''}` + } + + const githubApi = createApiUrl('https://api.github.com') + const getUsers = githubApi('/users') + + expect(getUsers({})).toBe('https://api.github.com/users') + expect(getUsers({ per_page: 10 })).toBe('https://api.github.com/users?per_page=10') + }) + }) + + describe('Validation Functions', () => { + it('should create reusable validators', () => { + const isGreaterThan = min => value => value > min + const isLessThan = max => value => value < max + const hasLength = length => str => str.length === length + + const isAdult = isGreaterThan(17) + const isValidAge = isLessThan(120) + const isValidZipCode = hasLength(5) + + expect(isAdult(18)).toBe(true) + expect(isAdult(15)).toBe(false) + expect(isValidAge(50)).toBe(true) + expect(isValidAge(150)).toBe(false) + expect(isValidZipCode('12345')).toBe(true) + expect(isValidZipCode('1234')).toBe(false) + }) + + it('should work with array methods', () => { + const isGreaterThan = min => value => value > min + const isAdult = isGreaterThan(17) + + const ages = [15, 22, 45, 8, 67] + const adults = ages.filter(isAdult) + + expect(adults).toEqual([22, 45, 67]) + }) + }) + + describe('Discount Calculator', () => { + it('should create specialized discount functions', () => { + const applyDiscount = discountPercent => price => { + return price * (1 - discountPercent / 100) + } + + const tenPercentOff = applyDiscount(10) + const twentyPercentOff = applyDiscount(20) + const blackFridayDeal = applyDiscount(50) + + expect(tenPercentOff(100)).toBe(90) + expect(twentyPercentOff(100)).toBe(80) + expect(blackFridayDeal(100)).toBe(50) + }) + + it('should work with array map', () => { + const applyDiscount = discountPercent => price => { + return price * (1 - discountPercent / 100) + } + + const tenPercentOff = applyDiscount(10) + const prices = [100, 200, 50, 75] + + const discountedPrices = prices.map(tenPercentOff) + + expect(discountedPrices).toEqual([90, 180, 45, 67.5]) + }) + }) + + describe('Event Handler Configuration', () => { + it('should configure event handlers step by step', () => { + const handlers = [] + + const handleEvent = eventType => elementId => callback => { + const handler = { eventType, elementId, callback } + handlers.push(handler) + return handler + } + + const onClick = handleEvent('click') + const onClickButton = onClick('myButton') + + const handler = onClickButton(() => 'clicked!') + + expect(handler.eventType).toBe('click') + expect(handler.elementId).toBe('myButton') + expect(handler.callback()).toBe('clicked!') + }) + }) + }) + + describe('Function Composition', () => { + describe('pipe() Implementation', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + + it('should compose functions left-to-right', () => { + const add1 = x => x + 1 + const double = x => x * 2 + const square = x => x * x + + const process = pipe(add1, double, square) + + // 5 → 6 → 12 → 144 + expect(process(5)).toBe(144) + }) + + it('should process single functions', () => { + const double = x => x * 2 + const process = pipe(double) + + expect(process(5)).toBe(10) + }) + + it('should handle identity when empty', () => { + const pipe = (...fns) => x => fns.length ? fns.reduce((acc, fn) => fn(acc), x) : x + const identity = pipe() + + expect(identity(5)).toBe(5) + }) + }) + + describe('compose() Implementation', () => { + const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x) + + it('should compose functions right-to-left', () => { + const add1 = x => x + 1 + const double = x => x * 2 + const square = x => x * x + + // Functions listed in reverse execution order + const process = compose(square, double, add1) + + // 5 → 6 → 12 → 144 (same result as pipe(add1, double, square)) + expect(process(5)).toBe(144) + }) + + it('should be equivalent to pipe with reversed arguments', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x) + + const add1 = x => x + 1 + const double = x => x * 2 + + const piped = pipe(add1, double) + const composed = compose(double, add1) + + expect(piped(5)).toBe(composed(5)) + }) + }) + + describe('String Transformation Pipeline', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + + it('should transform strings through a pipeline', () => { + const getName = obj => obj.name + const trim = str => str.trim() + const toUpperCase = str => str.toUpperCase() + const addExclaim = str => str + '!' + + const shout = pipe(getName, trim, toUpperCase, addExclaim) + + expect(shout({ name: ' alice ' })).toBe('ALICE!') + }) + + it('should convert to camelCase', () => { + const trim = str => str.trim() + const toLowerCase = str => str.toLowerCase() + const splitWords = str => str.split(' ') + const capitalizeFirst = words => words.map((w, i) => + i === 0 ? w : w[0].toUpperCase() + w.slice(1) + ) + const joinWords = words => words.join('') + + const toCamelCase = pipe( + trim, + toLowerCase, + splitWords, + capitalizeFirst, + joinWords + ) + + expect(toCamelCase(' HELLO WORLD ')).toBe('helloWorld') + expect(toCamelCase('my variable name')).toBe('myVariableName') + }) + }) + + describe('Data Transformation Pipeline', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + + it('should process array data through pipeline', () => { + const users = [ + { name: 'Alice', age: 25, active: true }, + { name: 'Bob', age: 17, active: true }, + { name: 'Charlie', age: 30, active: false }, + { name: 'Diana', age: 22, active: true } + ] + + const processUsers = pipe( + users => users.filter(u => u.active), + users => users.filter(u => u.age >= 18), + users => users.map(u => u.name), + names => names.sort() + ) + + expect(processUsers(users)).toEqual(['Alice', 'Diana']) + }) + }) + }) + + describe('Currying + Composition Together', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + + describe('Data-Last Parameter Order', () => { + it('should enable composition with data-last curried functions', () => { + const map = fn => arr => arr.map(fn) + const filter = fn => arr => arr.filter(fn) + + const double = x => x * 2 + const isEven = x => x % 2 === 0 + + const doubleEvens = pipe( + filter(isEven), + map(double) + ) + + expect(doubleEvens([1, 2, 3, 4, 5, 6])).toEqual([4, 8, 12]) + }) + + it('should show why data-first is harder to compose', () => { + // Data-first: harder to compose + const mapFirst = (arr, fn) => arr.map(fn) + const filterFirst = (arr, fn) => arr.filter(fn) + + // Can't easily pipe these without wrapping + const double = x => x * 2 + const isEven = x => x % 2 === 0 + + // Would need manual wrapping: + const result = mapFirst(filterFirst([1, 2, 3, 4, 5, 6], isEven), double) + expect(result).toEqual([4, 8, 12]) + }) + }) + + describe('Curried Functions in Pipelines', () => { + it('should compose curried arithmetic functions', () => { + const add = a => b => a + b + const multiply = a => b => a * b + const subtract = a => b => b - a + + const add5 = add(5) + const double = multiply(2) + const subtract3 = subtract(3) + + const process = pipe(add5, double, subtract3) + + // 10 → 15 → 30 → 27 + expect(process(10)).toBe(27) + }) + + it('should demonstrate point-free style', () => { + const prop = key => obj => obj[key] + const toUpper = str => str.toUpperCase() + + // Point-free: no explicit data parameter + const getUpperName = pipe( + prop('name'), + toUpper + ) + + expect(getUpperName({ name: 'alice' })).toBe('ALICE') + expect(getUpperName({ name: 'bob' })).toBe('BOB') + }) + }) + + describe('Complex Pipeline with Currying', () => { + it('should process user data through curried pipeline', () => { + const prop = key => obj => obj[key] + const map = fn => arr => arr.map(fn) + const filter = pred => arr => arr.filter(pred) + const sort = compareFn => arr => [...arr].sort(compareFn) + const take = n => arr => arr.slice(0, n) + + const users = [ + { id: 1, name: 'Zara', score: 85 }, + { id: 2, name: 'Alice', score: 92 }, + { id: 3, name: 'Bob', score: 78 }, + { id: 4, name: 'Charlie', score: 95 } + ] + + const getTopScorers = pipe( + filter(u => u.score >= 80), + sort((a, b) => b.score - a.score), + take(2), + map(prop('name')) + ) + + expect(getTopScorers(users)).toEqual(['Charlie', 'Alice']) + }) + }) + }) + + describe('Interview Questions', () => { + describe('Implement sum(1)(2)(3)...(n)()', () => { + it('should return sum when called with no arguments', () => { + function sum(a) { + return function next(b) { + if (b === undefined) { + return a + } + return sum(a + b) + } + } + + expect(sum(1)(2)(3)()).toBe(6) + expect(sum(1)(2)(3)(4)(5)()).toBe(15) + expect(sum(10)()).toBe(10) + }) + }) + + describe('Infinite Currying with valueOf', () => { + it('should return sum when coerced to number', () => { + function sum(a) { + const fn = b => sum(a + b) + fn.valueOf = () => a + return fn + } + + expect(+sum(1)(2)(3)).toBe(6) + expect(+sum(1)(2)(3)(4)(5)).toBe(15) + }) + }) + + describe('Fix map + parseInt Issue', () => { + it('should demonstrate the problem', () => { + const result = ['1', '2', '3'].map(parseInt) + // parseInt receives (value, index, array) + // parseInt('1', 0) → 1 + // parseInt('2', 1) → NaN (base 1 is invalid) + // parseInt('3', 2) → NaN (3 is not valid in base 2) + expect(result).toEqual([1, NaN, NaN]) + }) + + it('should fix with unary wrapper', () => { + const unary = fn => arg => fn(arg) + + const result = ['1', '2', '3'].map(unary(parseInt)) + expect(result).toEqual([1, 2, 3]) + }) + }) + }) + + describe('Common Mistakes', () => { + describe('Forgetting Curried Functions Return Functions', () => { + it('should demonstrate the mistake', () => { + const add = a => b => a + b + + // Mistake: forgot second call + const result = add(1) + + expect(typeof result).toBe('function') + expect(result).not.toBe(1) // Not a number! + + // Correct + expect(add(1)(2)).toBe(3) + }) + }) + + describe('fn.length with Rest Parameters', () => { + it('should show fn.length is 0 for rest parameters', () => { + function withRest(...args) { + return args.reduce((a, b) => a + b, 0) + } + + function withDefault(a, b = 2) { + return a + b + } + + expect(withRest.length).toBe(0) + expect(withDefault.length).toBe(1) // Only counts params before default + }) + }) + + describe('Type Mismatches in Pipelines', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + + it('should show type mismatch issues', () => { + const getAge = obj => obj.age // Returns number + const getLength = arr => arr.length // Expects array + + // This would cause issues + const broken = pipe(getAge, getLength) + + // Numbers have no .length property + expect(broken({ age: 25 })).toBe(undefined) + }) + }) + }) + + describe('Vanilla JS Utility Functions', () => { + describe('Complete Utility Set', () => { + // Curry + const curry = fn => { + return function curried(...args) { + return args.length >= fn.length + ? fn(...args) + : (...next) => curried(...args, ...next) + } + } + + // Pipe and Compose + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x) + + // Partial Application + const partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs) + + // Data-last utilities + const map = fn => arr => arr.map(fn) + const filter = fn => arr => arr.filter(fn) + const reduce = (fn, initial) => arr => arr.reduce(fn, initial) + + it('should demonstrate all utilities working together', () => { + const sum = (a, b, c) => a + b + c + const curriedSum = curry(sum) + + expect(curriedSum(1)(2)(3)).toBe(6) + expect(curriedSum(1, 2)(3)).toBe(6) + + const double = x => x * 2 + const add1 = x => x + 1 + + const process = pipe(add1, double) + expect(process(5)).toBe(12) + + const processReverse = compose(add1, double) + expect(processReverse(5)).toBe(11) // double first, then add1 + + const greet = (greeting, name) => `${greeting}, ${name}!` + const sayHello = partial(greet, 'Hello') + expect(sayHello('Alice')).toBe('Hello, Alice!') + + const nums = [1, 2, 3, 4, 5] + const isEven = x => x % 2 === 0 + + const sumOfDoubledEvens = pipe( + filter(isEven), + map(double), + reduce((a, b) => a + b, 0) + ) + + expect(sumOfDoubledEvens(nums)).toBe(12) // [2,4] → [4,8] → 12 + }) + }) + }) + + describe('Practical Examples from Documentation', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + + describe('Logging Factory', () => { + it('should create specialized loggers from documentation example', () => { + const logs = [] + + const createLogger = level => withTimestamp => message => { + const timestamp = withTimestamp ? '2024-01-15T10:30:00Z' : '' + const logEntry = `[${level}]${timestamp ? ' ' + timestamp : ''} ${message}` + logs.push(logEntry) + return logEntry + } + + const info = createLogger('INFO')(true) + const quickLog = createLogger('LOG')(false) + + expect(info('Application started')).toBe('[INFO] 2024-01-15T10:30:00Z Application started') + expect(quickLog('Quick debug')).toBe('[LOG] Quick debug') + }) + }) + + describe('Assembly Line Pipeline', () => { + it('should transform user data as shown in documentation', () => { + const getName = obj => obj.name + const trim = str => str.trim() + const toLowerCase = str => str.toLowerCase() + + const processUser = pipe(getName, trim, toLowerCase) + + expect(processUser({ name: ' ALICE ' })).toBe('alice') + }) + }) + }) + + describe('Additional Documentation Examples', () => { + const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) + const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x) + + describe('Real-World Pipeline: Processing API Data (doc lines 729-756)', () => { + it('should process API response through complete pipeline', () => { + // Mock API response matching documentation example + const apiResponse = { + data: [ + { id: 1, firstName: 'Charlie', lastName: 'Brown', email: 'charlie@test.com', isActive: true }, + { id: 2, firstName: 'Alice', lastName: 'Smith', email: 'alice@test.com', isActive: false }, + { id: 3, firstName: 'Bob', lastName: 'Jones', email: 'bob@test.com', isActive: true }, + { id: 4, firstName: 'Diana', lastName: 'Prince', email: 'diana@test.com', isActive: true }, + { id: 5, firstName: 'Eve', lastName: 'Wilson', email: 'eve@test.com', isActive: true }, + { id: 6, firstName: 'Frank', lastName: 'Miller', email: 'frank@test.com', isActive: true }, + { id: 7, firstName: 'Grace', lastName: 'Lee', email: 'grace@test.com', isActive: true }, + { id: 8, firstName: 'Henry', lastName: 'Taylor', email: 'henry@test.com', isActive: true }, + { id: 9, firstName: 'Ivy', lastName: 'Chen', email: 'ivy@test.com', isActive: true }, + { id: 10, firstName: 'Jack', lastName: 'Davis', email: 'jack@test.com', isActive: true }, + { id: 11, firstName: 'Kate', lastName: 'Moore', email: 'kate@test.com', isActive: true }, + { id: 12, firstName: 'Leo', lastName: 'Garcia', email: 'leo@test.com', isActive: true } + ] + } + + // Transform API response into display format (matching doc example) + const processApiResponse = pipe( + // Extract data from response + response => response.data, + + // Filter active users only + users => users.filter(u => u.isActive), + + // Sort by name (using lastName for sorting) + users => users.sort((a, b) => a.firstName.localeCompare(b.firstName)), + + // Transform to display format + users => users.map(u => ({ + id: u.id, + displayName: `${u.firstName} ${u.lastName}`, + email: u.email + })), + + // Take first 10 + users => users.slice(0, 10) + ) + + const result = processApiResponse(apiResponse) + + // Verify pipeline worked correctly + expect(result).toHaveLength(10) + + // Alice was filtered out (isActive: false) + expect(result.find(u => u.displayName === 'Alice Smith')).toBeUndefined() + + // First user should be Bob (alphabetically first among active users) + expect(result[0].displayName).toBe('Bob Jones') + + // Verify display format + expect(result[0]).toHaveProperty('id') + expect(result[0]).toHaveProperty('displayName') + expect(result[0]).toHaveProperty('email') + + // Verify sorting (alphabetical by firstName) + const names = result.map(u => u.displayName.split(' ')[0]) + const sortedNames = [...names].sort() + expect(names).toEqual(sortedNames) + }) + }) + + describe('compose() Direction Example (doc lines 658-664)', () => { + it('should process right-to-left with getName/toUpperCase/addExclaim', () => { + const getName = obj => obj.name + const toUpperCase = str => str.toUpperCase() + const addExclaim = str => str + '!' + + // compose processes right-to-left + const shout = compose(addExclaim, toUpperCase, getName) + + expect(shout({ name: 'alice' })).toBe('ALICE!') + + // This is equivalent to nested calls: + const manualResult = addExclaim(toUpperCase(getName({ name: 'alice' }))) + expect(shout({ name: 'alice' })).toBe(manualResult) + }) + }) + + describe('pipe/compose Equivalence (doc lines 669-672)', () => { + it('should produce same result: pipe(a, b, c)(x) === compose(c, b, a)(x)', () => { + const a = x => x + 1 + const b = x => x * 2 + const c = x => x - 3 + + const input = 10 + + // pipe: a first, then b, then c + const pipedResult = pipe(a, b, c)(input) + + // compose: c(b(a(x))) - reversed argument order + const composedResult = compose(c, b, a)(input) + + expect(pipedResult).toBe(composedResult) + + // Verify the actual value: (10 + 1) * 2 - 3 = 19 + expect(pipedResult).toBe(19) + }) + + it('should demonstrate both directions with same functions', () => { + const add5 = x => x + 5 + const double = x => x * 2 + const square = x => x * x + + const input = 3 + + // pipe(add5, double, square)(3) = ((3 + 5) * 2)² = 256 + expect(pipe(add5, double, square)(input)).toBe(256) + + // compose(add5, double, square)(3) = (3² * 2) + 5 = 23 + expect(compose(add5, double, square)(input)).toBe(23) + + // To get same result with compose, reverse the order + expect(compose(square, double, add5)(input)).toBe(256) + }) + }) + + describe('Why Multi-Argument Functions Do Not Compose (doc lines 769-775)', () => { + it('should demonstrate NaN problem with non-curried functions', () => { + const add = (a, b) => a + b + const multiply = (a, b) => a * b + + // This doesn't work as expected! + const addThenMultiply = pipe(add, multiply) + + // When called: add(1, 2) returns 3 + // Then multiply(3) is called with only one argument + // multiply(3, undefined) = 3 * undefined = NaN + const result = addThenMultiply(1, 2) + + expect(result).toBeNaN() + }) + + it('should work correctly with curried versions', () => { + // Curried versions + const add = a => b => a + b + const multiply = a => b => a * b + + // Now we can compose! + const add5 = add(5) // x => 5 + x + const double = multiply(2) // x => 2 * x + + const add5ThenDouble = pipe(add5, double) + + // (10 + 5) * 2 = 30 + expect(add5ThenDouble(10)).toBe(30) + }) + }) + + describe('Data-First vs Data-Last Argument Order (doc lines 984-994)', () => { + it('should show data-first makes composition harder', () => { + // Data-first: hard to compose + const multiplyFirst = (value, factor) => value * factor + + // Can't easily create a reusable "double" function for pipelines + // Would need to wrap it: + const doubleFirst = value => multiplyFirst(value, 2) + const tripleFirst = value => multiplyFirst(value, 3) + + // Works, but requires manual wrapping each time + expect(pipe(doubleFirst, tripleFirst)(5)).toBe(30) + }) + + it('should show data-last composes naturally', () => { + // Data-last: composes well + const multiply = factor => value => value * factor + + const double = multiply(2) + const triple = multiply(3) + + // Composes naturally without any wrapping + expect(pipe(double, triple)(5)).toBe(30) + + // Can easily create new specialized functions + const quadruple = multiply(4) + expect(pipe(double, quadruple)(5)).toBe(40) + }) + }) + + describe('Manual Composition with Nested Calls (doc lines 526-538)', () => { + it('should work with nested function calls', () => { + const add10 = x => x + 10 + const multiply2 = x => x * 2 + const subtract5 = x => x - 5 + + // Manual composition (nested calls) + // Step by step: 5 → 15 → 30 → 25 + const result = subtract5(multiply2(add10(5))) + + expect(result).toBe(25) + }) + + it('should produce same result with compose function', () => { + const add10 = x => x + 10 + const multiply2 = x => x * 2 + const subtract5 = x => x - 5 + + // With a compose function + const composed = compose(subtract5, multiply2, add10) + + expect(composed(5)).toBe(25) + + // Verify it matches manual nesting + const manual = subtract5(multiply2(add10(5))) + expect(composed(5)).toBe(manual) + }) + + it('should be more readable with pipe', () => { + const add10 = x => x + 10 + const multiply2 = x => x * 2 + const subtract5 = x => x - 5 + + // With pipe (reads in execution order) + const piped = pipe(add10, multiply2, subtract5) + + expect(piped(5)).toBe(25) + }) + }) + + describe('Opening Example from Documentation (doc lines 9-20)', () => { + it('should demonstrate the opening currying example', () => { + // Currying: one argument at a time + const add = a => b => c => a + b + c + expect(add(1)(2)(3)).toBe(6) + }) + + it('should demonstrate the opening composition example', () => { + const getName = obj => obj.name + const trim = str => str.trim() + const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() + + // Composition: chain functions together + const process = pipe( + getName, + trim, + capitalize + ) + + expect(process({ name: " alice " })).toBe("Alice") + }) + }) + }) +}) From b46c651d41655f53056689e3d6ccc18f6d3a1ad8 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Thu, 1 Jan 2026 14:50:52 -0300 Subject: [PATCH 091/128] docs: add comprehensive Recursion concept page with tests - Rewrite recursion.mdx with complete concept coverage (1,044 lines) - Add opening hook with countdown example - Include Russian Dolls analogy with ASCII diagram - Cover classic patterns: factorial, Fibonacci, sumTo, power, powerFast - Add practical use cases: nested objects, DOM traversal, linked lists, file systems - Include recursion vs iteration comparison with tabs - Document 5 common mistakes with wrong/correct examples - Cover optimization: memoization and tail recursion - Add 10 key takeaways and 6 Q&A test questions - Link to MDN references and curated articles/videos - Add comprehensive test suite (75 tests) - Test all code examples from MDX including: - powerFast O(log n) optimization - findAllCounts for nested objects - walkDOM with jsdom - getTotalSize for file systems - factorialIterative and sumTreeIterative - arrayLength Q&A example - Fix meta description length (186 -> 148 chars) - Add MDN link for execution context - Add internal links to data-structures and higher-order-functions --- docs/concepts/recursion.mdx | 1044 ++++++++++++++++- .../recursion/recursion.test.js | 932 +++++++++++++++ 2 files changed, 1948 insertions(+), 28 deletions(-) create mode 100644 tests/functional-programming/recursion/recursion.test.js diff --git a/docs/concepts/recursion.mdx b/docs/concepts/recursion.mdx index 343ef6e7..01e58459 100644 --- a/docs/concepts/recursion.mdx +++ b/docs/concepts/recursion.mdx @@ -1,54 +1,1042 @@ --- title: "Recursion: Functions That Call Themselves in JavaScript" sidebarTitle: "Recursion: Functions That Call Themselves" -description: "Learn recursion in JavaScript — functions that call themselves to solve problems. Master base cases, recursive cases, stack overflow prevention, and when to use recursion vs iteration." +description: "Learn recursion in JavaScript. Understand base cases, recursive calls, the call stack, and patterns like factorial, tree traversal, and memoization." --- -## Overview +How do you solve a problem by breaking it into smaller versions of the same problem? What if a function could call itself to chip away at a task until it's done? -**Recursion** is a programming technique where a function calls itself to solve a problem. A recursive function typically has a base case (stopping condition) and a recursive case. Recursion is particularly useful for solving problems that can be broken down into smaller, similar subproblems. +```javascript +function countdown(n) { + if (n === 0) { + console.log("Done!") + return + } + console.log(n) + countdown(n - 1) // The function calls itself! +} + +countdown(3) +// 3 +// 2 +// 1 +// Done! +``` + +This is **[recursion](https://developer.mozilla.org/en-US/docs/Glossary/Recursion)**. The `countdown` function calls itself with a smaller number each time until it reaches zero. It's a powerful technique that lets you solve complex problems by breaking them into simpler, self-similar pieces. + +<Info> +**What you'll learn in this guide:** +- What recursion is and its two essential parts (base case and recursive case) +- How recursion relates to the call stack +- Classic recursive algorithms: factorial, Fibonacci, sum +- Practical applications: traversing trees, nested objects, linked lists +- Recursion vs iteration: when to use each +- Common mistakes and how to avoid stack overflow +- Optimization techniques: memoization and tail recursion +</Info> <Warning> -Always ensure your recursive function has a proper base case to prevent infinite recursion and stack overflow errors. +**Prerequisite:** This guide assumes you understand [JavaScript functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions). It also helps to know how the [call stack](/concepts/call-stack) works, though we'll cover that relationship here. </Warning> +--- + +## What is Recursion? + +**[Recursion](https://developer.mozilla.org/en-US/docs/Glossary/Recursion)** is a programming technique where a function calls itself to solve a problem. Instead of using loops, the function breaks a problem into smaller versions of the same problem until it reaches a case simple enough to solve directly. + +<Note> +**Recursion isn't unique to JavaScript.** It's a fundamental computer science concept found in virtually every programming language. The patterns you learn here apply whether you're writing Python, C++, Java, or any other language. +</Note> + +Every recursive function has two essential parts: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE TWO PARTS OF RECURSION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ function solve(problem) { │ +│ │ +│ if (problem is simple enough) { ← BASE CASE │ +│ return solution directly Stops the recursion │ +│ } │ +│ │ +│ return solve(smaller problem) ← RECURSIVE CASE │ +│ } Calls itself with simpler input│ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +1. **Base Case**: The condition that stops the recursion. Without it, the function would call itself forever. + +2. **Recursive Case**: The part where the function calls itself with a simpler or smaller version of the problem. + +Here's a simple example that sums numbers from 1 to `n`: + +```javascript +function sumTo(n) { + // Base case: when n is 1, we know the answer + if (n === 1) { + return 1 + } + + // Recursive case: n plus the sum of everything below it + return n + sumTo(n - 1) +} + +console.log(sumTo(5)) // 15 (5 + 4 + 3 + 2 + 1) +``` + +The function asks: "What's the sum from 1 to 5?" It answers: "5 plus the sum from 1 to 4." Then it asks the same question with 4, then 3, then 2, until it reaches 1, which it knows is just 1. + +--- + +## The Russian Dolls Analogy + +Think of recursion like opening a set of Russian nesting dolls (matryoshka). Each doll contains a smaller version of itself, and you keep opening them until you reach the smallest one that can't be opened. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE RUSSIAN DOLLS ANALOGY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Opening the dolls (making recursive calls): │ +│ │ +│ ╔═══════════════════════════════╗ │ +│ ║ ║ │ +│ ║ ╔═══════════════════════╗ ║ │ +│ ║ ║ ║ ║ │ +│ ║ ║ ╔═══════════════╗ ║ ║ │ +│ ║ ║ ║ ║ ║ ║ │ +│ ║ ║ ║ ╔═══════╗ ║ ║ ║ │ +│ ║ ║ ║ ║ ◆ ║ ║ ║ ║ ← Smallest doll (BASE CASE) │ +│ ║ ║ ║ ╚═══════╝ ║ ║ ║ Can't open further │ +│ ║ ║ ║ ║ ║ ║ │ +│ ║ ║ ╚═══════════════╝ ║ ║ │ +│ ║ ║ ║ ║ │ +│ ║ ╚═══════════════════════╝ ║ │ +│ ║ ║ │ +│ ╚═══════════════════════════════╝ │ +│ │ +│ Each doll = a function call │ +│ Opening a doll = making a recursive call │ +│ Smallest doll = base case (stop recursing) │ +│ Closing dolls back up = returning values back up the chain │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +When you find the smallest doll, you start closing them back up. In recursion, once you hit the base case, the return values bubble back up through each function call until you get your final answer. + +--- + +## How Recursion Works Under the Hood + +To understand recursion, you need to understand how the [call stack](/concepts/call-stack) works. Every time a function is called, JavaScript creates an **[execution context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#function_context)** and pushes it onto the call stack. When the function returns, its context is popped off. + +With recursion, multiple execution contexts for the *same function* stack up: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE CALL STACK DURING RECURSION │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ sumTo(3) calls sumTo(2) calls sumTo(1) │ +│ │ +│ STACK GROWING: STACK SHRINKING: │ +│ ─────────────── ───────────────── │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ │ +│ │ sumTo(1) │ ← current │ │ (popped) │ +│ │ returns 1 │ └───────────────┘ │ +│ ├───────────────┤ ┌───────────────┐ │ +│ │ sumTo(2) │ │ sumTo(2) │ ← current │ +│ │ waiting... │ │ returns 2+1=3│ │ +│ ├───────────────┤ ├───────────────┤ │ +│ │ sumTo(3) │ │ sumTo(3) │ │ +│ │ waiting... │ │ waiting... │ │ +│ └───────────────┘ └───────────────┘ │ +│ │ +│ Each call waits for the one above it to return │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Let's trace through `sumTo(3)` step by step: + +<Steps> + <Step title="sumTo(3) is called"> + `n` is 3, not 1, so we need to calculate `3 + sumTo(2)`. But we can't add yet because we don't know what `sumTo(2)` returns. This call waits. + </Step> + <Step title="sumTo(2) is called"> + `n` is 2, not 1, so we need `2 + sumTo(1)`. This call also waits. + </Step> + <Step title="sumTo(1) is called — base case!"> + `n` is 1, so we return `1` immediately. No more recursive calls. + </Step> + <Step title="sumTo(2) resumes"> + Now it knows `sumTo(1)` returned 1, so it calculates `2 + 1 = 3` and returns 3. + </Step> + <Step title="sumTo(3) resumes"> + Now it knows `sumTo(2)` returned 3, so it calculates `3 + 3 = 6` and returns 6. + </Step> +</Steps> + +```javascript +function sumTo(n) { + console.log(`Called sumTo(${n})`) + + if (n === 1) { + console.log(` Base case! Returning 1`) + return 1 + } + + const result = n + sumTo(n - 1) + console.log(` sumTo(${n}) returning ${result}`) + return result +} + +sumTo(3) +// Called sumTo(3) +// Called sumTo(2) +// Called sumTo(1) +// Base case! Returning 1 +// sumTo(2) returning 3 +// sumTo(3) returning 6 +``` + +<Tip> +**Key insight:** Each recursive call creates its own copy of the function's local variables. The `n` in `sumTo(3)` is separate from the `n` in `sumTo(2)`. They don't interfere with each other. +</Tip> + +--- + +## Classic Recursive Patterns + +Here are the most common recursive algorithms you'll encounter. Understanding these patterns will help you recognize when recursion is the right tool. + +<AccordionGroup> + <Accordion title="Factorial (n!)"> + The factorial of a number `n` (written as `n!`) is the product of all positive integers from 1 to n: + + - `5! = 5 × 4 × 3 × 2 × 1 = 120` + - `3! = 3 × 2 × 1 = 6` + - `1! = 1` + - `0! = 1` (by definition) + + The recursive insight: `n! = n × (n-1)!` + + ```javascript + function factorial(n) { + // Base case: 0! and 1! both equal 1 + if (n <= 1) { + return 1 + } + + // Recursive case: n! = n × (n-1)! + return n * factorial(n - 1) + } + + console.log(factorial(5)) // 120 + console.log(factorial(0)) // 1 + console.log(factorial(1)) // 1 + ``` + + **Trace of `factorial(4)`:** + ``` + factorial(4) = 4 * factorial(3) + = 4 * (3 * factorial(2)) + = 4 * (3 * (2 * factorial(1))) + = 4 * (3 * (2 * 1)) + = 4 * (3 * 2) + = 4 * 6 + = 24 + ``` + </Accordion> + + <Accordion title="Fibonacci Sequence"> + The Fibonacci sequence starts with 0 and 1, and each subsequent number is the sum of the two before it: + + `0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...` + + The recursive definition: + - `fib(0) = 0` + - `fib(1) = 1` + - `fib(n) = fib(n-1) + fib(n-2)` for n > 1 + + ```javascript + function fibonacci(n) { + // Base cases + if (n === 0) return 0 + if (n === 1) return 1 + + // Recursive case: sum of two preceding numbers + return fibonacci(n - 1) + fibonacci(n - 2) + } + + console.log(fibonacci(0)) // 0 + console.log(fibonacci(1)) // 1 + console.log(fibonacci(6)) // 8 + console.log(fibonacci(10)) // 55 + ``` + + <Warning> + **Performance trap!** This naive implementation is very slow for large numbers. `fibonacci(40)` makes over 300 million function calls because it recalculates the same values repeatedly. We'll fix this with memoization later. + </Warning> + + ``` + fibonacci(5) calls: + fib(5) + / \ + fib(4) fib(3) ← fib(3) calculated twice! + / \ / \ + fib(3) fib(2) fib(2) fib(1) + / \ + fib(2) fib(1) + ``` + </Accordion> + + <Accordion title="Sum of Numbers (1 to n)"> + Sum all integers from 1 to n: + + ```javascript + function sumTo(n) { + if (n === 1) return 1 + return n + sumTo(n - 1) + } + + console.log(sumTo(5)) // 15 (1+2+3+4+5) + console.log(sumTo(100)) // 5050 + ``` + + **Note:** There's a mathematical formula for this: `n * (n + 1) / 2`, which is O(1) instead of O(n). For simple sums, the formula is better. But the recursive approach teaches the pattern. + </Accordion> + + <Accordion title="Power Function (x^n)"> + Calculate `x` raised to the power of `n`: + + ```javascript + function power(x, n) { + // Base case: anything to the power of 0 is 1 + if (n === 0) return 1 + + // Recursive case: x^n = x * x^(n-1) + return x * power(x, n - 1) + } + + console.log(power(2, 0)) // 1 + console.log(power(2, 3)) // 8 + console.log(power(2, 10)) // 1024 + console.log(power(3, 4)) // 81 + ``` + + **Optimized version** using the property that `x^n = (x^(n/2))^2`: + + ```javascript + function powerFast(x, n) { + if (n === 0) return 1 + + if (n % 2 === 0) { + // Even exponent: x^n = (x^(n/2))^2 + const half = powerFast(x, n / 2) + return half * half + } else { + // Odd exponent: x^n = x * x^(n-1) + return x * powerFast(x, n - 1) + } + } + + console.log(powerFast(2, 10)) // 1024 (but faster!) + ``` + + The optimized version runs in O(log n) time instead of O(n). + </Accordion> + + <Accordion title="Reverse a String"> + Reverse a string character by character: + + ```javascript + function reverse(str) { + // Base case: empty string or single character + if (str.length <= 1) { + return str + } + + // Recursive case: last char + reverse of the rest + return str[str.length - 1] + reverse(str.slice(0, -1)) + } + + console.log(reverse("hello")) // "olleh" + console.log(reverse("a")) // "a" + console.log(reverse("")) // "" + ``` + + **How it works:** + ``` + reverse("cat") + = "t" + reverse("ca") + = "t" + ("a" + reverse("c")) + = "t" + ("a" + "c") + = "t" + "ac" + = "tac" + ``` + </Accordion> +</AccordionGroup> + +--- + +## Practical Use Cases + +Recursion really shines when working with **nested or tree-like structures**. These [data structures](/concepts/data-structures) are naturally recursive, and recursion is often the most elegant solution. + +### Traversing Nested Objects + +Imagine you need to find all values in a deeply nested object: + +```javascript +const data = { + name: "Company", + departments: { + engineering: { + frontend: { count: 5 }, + backend: { count: 8 } + }, + sales: { count: 12 } + } +} + +function findAllCounts(obj) { + let total = 0 + + for (const key in obj) { + if (key === "count") { + total += obj[key] + } else if (typeof obj[key] === "object" && obj[key] !== null) { + // Recurse into nested objects + total += findAllCounts(obj[key]) + } + } + + return total +} + +console.log(findAllCounts(data)) // 25 +``` + +Without recursion, you'd need to know exactly how deep the nesting goes. With recursion, it handles any depth automatically. + +### Flattening Nested Arrays + +Turn a deeply nested array into a flat one: + +```javascript +function flatten(arr) { + let result = [] + + for (const item of arr) { + if (Array.isArray(item)) { + // Recurse into nested arrays + result = result.concat(flatten(item)) + } else { + result.push(item) + } + } + + return result +} + +console.log(flatten([1, [2, [3, 4]], 5])) +// [1, 2, 3, 4, 5] + +console.log(flatten([1, [2, [3, [4, [5]]]]])) +// [1, 2, 3, 4, 5] +``` + +### Walking the DOM Tree + +Traverse all elements in an HTML document: + +```javascript +function walkDOM(node, callback) { + // Process this node + callback(node) + + // Recurse into child nodes + for (const child of node.children) { + walkDOM(child, callback) + } +} + +// Example: log all tag names +walkDOM(document.body, (node) => { + console.log(node.tagName) +}) +``` + +This pattern combines recursion with [higher-order functions](/concepts/higher-order-functions) (the callback). It's how browser developer tools display the DOM tree and how libraries traverse HTML structures. + +### Processing Linked Lists + +A linked list is a classic recursive data structure where each node points to the next: + +```javascript +const list = { + value: 1, + next: { + value: 2, + next: { + value: 3, + next: null + } + } +} + +// Sum all values in the list +function sumList(node) { + if (node === null) return 0 + return node.value + sumList(node.next) +} + +console.log(sumList(list)) // 6 + +// Print list in reverse order +function printReverse(node) { + if (node === null) return + printReverse(node.next) // First, go to the end + console.log(node.value) // Then print on the way back +} + +printReverse(list) +// 3 +// 2 +// 1 +``` + +### File System Traversal + +A conceptual example of how file explorers work: + +```javascript +// Simulated file structure +const fileSystem = { + name: "root", + type: "folder", + children: [ + { name: "file1.txt", type: "file", size: 100 }, + { + name: "docs", + type: "folder", + children: [ + { name: "readme.md", type: "file", size: 50 }, + { name: "notes.txt", type: "file", size: 25 } + ] + } + ] +} + +function getTotalSize(node) { + if (node.type === "file") { + return node.size + } + + // Folder: sum sizes of all children + let total = 0 + for (const child of node.children) { + total += getTotalSize(child) + } + return total +} + +console.log(getTotalSize(fileSystem)) // 175 +``` + +--- + +## Recursion vs Iteration + +Every recursive solution can be rewritten using loops, and vice versa. Here's when to choose each: + +| Aspect | Recursion | Iteration (Loops) | +|--------|-----------|-------------------| +| **Readability** | Often cleaner for tree-like problems | Usually simpler for linear tasks | +| **Memory** | Uses call stack (one frame per call) | Uses fixed/minimal memory | +| **Performance** | Function call overhead | Generally faster | +| **Stack Risk** | Stack overflow possible (~10,000+ calls) | No stack overflow risk | +| **Best For** | Trees, graphs, nested structures | Simple counting, linear arrays | + +<Tabs> + <Tab title="Recursive"> + ```javascript + // Recursive factorial + function factorial(n) { + if (n <= 1) return 1 + return n * factorial(n - 1) + } + ``` + + **Pros:** Matches the mathematical definition exactly. Easy to read. + + **Cons:** Uses O(n) stack space. Could overflow for large n. + </Tab> + <Tab title="Iterative"> + ```javascript + // Iterative factorial + function factorial(n) { + let result = 1 + for (let i = 2; i <= n; i++) { + result *= i + } + return result + } + ``` + + **Pros:** Uses O(1) space. No stack overflow risk. Faster. + + **Cons:** Slightly less intuitive mapping to the math. + </Tab> +</Tabs> + +### When to Use Recursion + +- **Tree structures**: DOM traversal, file systems, org charts +- **Divide and conquer algorithms**: Merge sort, quick sort, binary search +- **Problems with self-similar subproblems**: Factorial, Fibonacci, fractals +- **When code clarity matters more than performance**: Prototyping, readable code + +### When to Use Iteration + +- **Simple loops**: Counting, summing arrays +- **Performance-critical code**: Tight loops in hot paths +- **Very deep structures**: Anything that might exceed ~10,000 levels +- **Memory-constrained environments**: Each recursive call uses stack space + +<Tip> +**Rule of thumb:** Start with whichever approach feels more natural for the problem. If you run into stack overflow issues or performance problems, consider converting to iteration. +</Tip> + +--- + +## Common Mistakes + +Here are the most frequent bugs when writing recursive functions: + +### Mistake #1: Missing or Incorrect Base Case + +Without a base case, the function calls itself forever until the stack overflows: + +```javascript +// ❌ WRONG - No base case! +function countdown(n) { + console.log(n) + countdown(n - 1) // Never stops! +} + +countdown(3) // 3, 2, 1, 0, -1, -2... CRASH! +// RangeError: Maximum call stack size exceeded +``` + +```javascript +// ✓ CORRECT - Has a base case +function countdown(n) { + if (n < 0) return // Base case: stop at negative + console.log(n) + countdown(n - 1) +} + +countdown(3) // 3, 2, 1, 0 (then stops) +``` + +<Warning> +**The error you'll see:** `RangeError: Maximum call stack size exceeded`. This means you've made too many recursive calls without returning. Check your base case! +</Warning> + +### Mistake #2: Base Case That's Never Reached + +Even with a base case, if your logic never reaches it, you'll still crash: + +```javascript +// ❌ WRONG - Base case can never be reached +function countdown(n) { + if (n === 0) return // Only stops at exactly 0 + console.log(n) + countdown(n - 2) // Skips over 0 when starting with odd number! +} + +countdown(5) // 5, 3, 1, -1, -3... CRASH! +``` + +```javascript +// ✓ CORRECT - Base case is reachable +function countdown(n) { + if (n <= 0) return // Stops at 0 or below + console.log(n) + countdown(n - 2) +} + +countdown(5) // 5, 3, 1 (then stops) +``` + +### Mistake #3: Forgetting to Return the Recursive Call + +If you call the function recursively but don't return its result, you lose the value: + +```javascript +// ❌ WRONG - Missing return +function sum(n) { + if (n === 1) return 1 + sum(n - 1) + n // Calculated but not returned! +} + +console.log(sum(5)) // undefined +``` + +```javascript +// ✓ CORRECT - Returns the result +function sum(n) { + if (n === 1) return 1 + return sum(n - 1) + n // Return the calculation +} + +console.log(sum(5)) // 15 +``` + +### Mistake #4: Modifying Shared State + +Be careful about variables outside the function that recursive calls might all modify: + +```javascript +// ❌ PROBLEMATIC - Shared mutable state +let count = 0 + +function countNodes(node) { + if (node === null) return + count++ // All calls modify the same variable + countNodes(node.left) + countNodes(node.right) +} +// If you call countNodes twice, count keeps increasing! +``` + +```javascript +// ✓ BETTER - Return values instead of mutating +function countNodes(node) { + if (node === null) return 0 + return 1 + countNodes(node.left) + countNodes(node.right) +} +// Each call is independent +``` + +### Mistake #5: Inefficient Overlapping Subproblems + +The naive Fibonacci implementation recalculates the same values many times: + +```javascript +// ❌ VERY SLOW - Exponential time complexity +function fib(n) { + if (n <= 1) return n + return fib(n - 1) + fib(n - 2) +} + +fib(40) // Takes several seconds! +fib(50) // Takes minutes or crashes +``` + +This is fixed with memoization, covered in the next section. + +--- + +## Optimizing Recursive Functions + +### Memoization + +**Memoization** means caching the results of function calls so you don't recompute the same thing twice. It's especially useful for recursive functions with overlapping subproblems. + +```javascript +// Fibonacci with memoization +function fibonacci(n, memo = {}) { + // Check if we already calculated this + if (n in memo) { + return memo[n] + } + + // Base cases + if (n <= 1) return n + + // Calculate and cache the result + memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo) + return memo[n] +} + +console.log(fibonacci(50)) // 12586269025 (instant!) +console.log(fibonacci(100)) // 354224848179262000000 (still instant!) +``` + +The naive Fibonacci has O(2^n) time complexity. With memoization, it's O(n). That's the difference between billions of operations and just 100. + +### Tail Recursion + +A **tail recursive** function is one where the recursive call is the very last thing the function does. There's no computation after the call returns. + +```javascript +// NOT tail recursive - multiplication happens AFTER the recursive call +function factorial(n) { + if (n <= 1) return 1 + return n * factorial(n - 1) // Still need to multiply after call returns +} + +// Tail recursive version - uses an accumulator +function factorialTail(n, accumulator = 1) { + if (n <= 1) return accumulator + return factorialTail(n - 1, accumulator * n) // Nothing to do after this returns +} +``` + +**Why does this matter?** In theory, tail recursive functions can be optimized by the JavaScript engine to reuse the same stack frame, avoiding stack overflow entirely. This is called **Tail Call Optimization (TCO)**. + +<Note> +**Reality check:** Most JavaScript engines (V8 in Chrome/Node, SpiderMonkey in Firefox) **do not implement TCO**. Safari's JavaScriptCore is the notable exception. So in practice, tail recursion doesn't prevent stack overflow in most environments. Still, it's good to understand the concept, as it's important in functional programming languages like Haskell and Scheme. +</Note> + +### Converting to Iteration + +If you're hitting stack limits, consider converting your recursion to a loop with an explicit stack: + +```javascript +// Recursive tree traversal +function sumTreeRecursive(node) { + if (node === null) return 0 + return node.value + sumTreeRecursive(node.left) + sumTreeRecursive(node.right) +} + +// Iterative version using explicit stack +function sumTreeIterative(root) { + if (root === null) return 0 + + let sum = 0 + const stack = [root] + + while (stack.length > 0) { + const node = stack.pop() + sum += node.value + + if (node.right) stack.push(node.right) + if (node.left) stack.push(node.left) + } + + return sum +} +``` + +The iterative version uses heap memory (the array) instead of stack memory, so it can handle much deeper structures. + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Recursion = a function calling itself** to solve smaller versions of the same problem + +2. **Every recursive function needs a base case** that stops the recursion without making another call + +3. **The recursive case** breaks the problem into a smaller piece and calls the function again + +4. **Recursion uses the call stack** — each call adds a new frame with its own local variables + +5. **The base case must be reachable** — if it's not, you'll get infinite recursion and a stack overflow + +6. **Recursion shines for tree-like structures**: DOM traversal, nested objects, file systems, linked lists + +7. **Loops are often better for simple iteration** — less overhead, no stack overflow risk + +8. **Watch for stack overflow** on deep recursion (most browsers limit to ~10,000 calls) + +9. **Memoization fixes inefficient recursion** by caching results of repeated subproblems + +10. **Recursion isn't JavaScript-specific** — it's a universal programming technique you'll use in any language +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="What are the two essential parts of a recursive function?"> + **Answer:** + + 1. **Base case**: The condition that stops the recursion. It returns a value without making another recursive call. + + 2. **Recursive case**: The part where the function calls itself with a simpler or smaller version of the problem. + + ```javascript + function example(n) { + if (n === 0) return "done" // Base case + return example(n - 1) // Recursive case + } + ``` + </Accordion> + + <Accordion title="What happens if you forget the base case?"> + **Answer:** + + The function calls itself infinitely until JavaScript throws a `RangeError: Maximum call stack size exceeded`. This is called a **stack overflow** because each call adds a frame to the call stack until it runs out of memory. + + ```javascript + // This will crash + function broken(n) { + return broken(n - 1) // No base case to stop! + } + broken(5) // RangeError: Maximum call stack size exceeded + ``` + </Accordion> + + <Accordion title="Write a recursive function to find the length of an array without using .length"> + **Answer:** + + ```javascript + function arrayLength(arr) { + // Base case: empty array has length 0 + if (arr.length === 0) return 0 + + // Recursive case: 1 + length of the rest + return 1 + arrayLength(arr.slice(1)) + } + + console.log(arrayLength([1, 2, 3, 4])) // 4 + console.log(arrayLength([])) // 0 + ``` + + Note: We use `arr.length === 0` for the check, but we're counting by recurring through elements, not using `.length` to get the answer. + </Accordion> + + <Accordion title="Why is naive Fibonacci recursion inefficient, and how would you fix it?"> + **Answer:** + + Naive Fibonacci recalculates the same values many times. For example, `fib(5)` calculates `fib(3)` twice, `fib(2)` three times, etc. This leads to exponential O(2^n) time complexity. + + **The fix: Memoization.** Cache results so each value is only calculated once: + + ```javascript + function fibonacci(n, memo = {}) { + if (n in memo) return memo[n] + if (n <= 1) return n + + memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo) + return memo[n] + } + ``` + + This reduces the time complexity to O(n). + </Accordion> + + <Accordion title="When should you choose recursion over a loop?"> + **Answer:** + + **Choose recursion when:** + - Working with tree-like or nested structures (DOM, file systems, JSON) + - The problem naturally divides into self-similar subproblems + - Code clarity is more important than maximum performance + - Implementing divide-and-conquer algorithms + + **Choose loops when:** + - Iterating through flat, linear data + - Performance is critical + - You might recurse more than ~10,000 levels deep + - Memory is constrained + </Accordion> + + <Accordion title="How does recursion relate to the call stack?"> + **Answer:** + + Each recursive call creates a new **execution context** that gets pushed onto the call stack. The function waits for its recursive call to return, keeping its context on the stack. + + When the base case is reached, contexts start popping off the stack as return values bubble back up. This is why deep recursion can cause stack overflow — too many contexts waiting at once. + + ``` + sumTo(3) calls → sumTo(2) calls → sumTo(1) + + Stack: [sumTo(3), sumTo(2), sumTo(1)] + ↓ returns 1 + Stack: [sumTo(3), sumTo(2)] + ↓ returns 3 + Stack: [sumTo(3)] + ↓ returns 6 + Stack: [] + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Call Stack" icon="layer-group" href="/concepts/call-stack"> + How JavaScript tracks function execution — the foundation of how recursion works under the hood. + </Card> + <Card title="Higher-Order Functions" icon="function" href="/concepts/higher-order-functions"> + Functions that take or return other functions. Many recursive patterns combine with higher-order functions. + </Card> + <Card title="Data Structures" icon="sitemap" href="/concepts/data-structures"> + Trees, linked lists, and graphs — data structures that are naturally recursive. + </Card> + <Card title="Pure Functions" icon="sparkles" href="/concepts/pure-functions"> + Functions with no side effects. Recursive functions work best when they're pure. + </Card> +</CardGroup> + +--- + +## Reference + +<CardGroup cols={2}> + <Card title="Recursion — MDN Glossary" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Recursion"> + Official MDN definition with common examples including factorial, Fibonacci, and reduce. + </Card> + <Card title="Functions Guide: Recursion — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#recursion"> + MDN's guide on recursive functions in JavaScript with DOM traversal examples. + </Card> +</CardGroup> + ## Articles <CardGroup cols={2}> - <Card title="Recursion in JavaScript" icon="newspaper" href="https://medium.freecodecamp.org/recursion-in-javascript-1608032c7a1f"> - By Kevin Ennis + <Card title="Recursion and Stack" icon="newspaper" href="https://javascript.info/recursion"> + The definitive JavaScript recursion tutorial. Covers execution context, linked lists, and recursive data structures with interactive examples. </Card> - <Card title="Understanding Recursion in JavaScript" icon="newspaper" href="https://medium.com/@zfrisch/understanding-recursion-in-javascript-992e96449e03"> - By Zak Frisch + <Card title="What is Recursion? A Recursive Function Explained" icon="newspaper" href="https://www.freecodecamp.org/news/what-is-recursion-in-javascript/"> + Beginner-friendly introduction with step-by-step breakdowns. Great for understanding the "why" behind recursion. </Card> </CardGroup> -- [Learn and Understand Recursion in JavaScript — Brandon Morelli](https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea) -- [Recursion in Functional JavaScript — M. David Green](https://www.sitepoint.com/recursion-functional-javascript/) -- [Programming with JS: Recursion — Alexander Kondov](https://hackernoon.com/programming-with-js-recursion-31371e2bf808) -- [Anonymous Recursion in JavaScript — simo](https://dev.to/simov/anonymous-recursion-in-javascript) -- [Recursion, iteration and tail calls in JS — loverajoel](http://www.jstips.co/en/javascript/recursion-iteration-and-tail-calls-in-js/) -- [What is Recursion? A Recursive Function Explained with JavaScript Code Examples — Nathan Sebhastian](https://www.freecodecamp.org/news/what-is-recursion-in-javascript/) -- [Intro to Recursion — Brad Newman](https://medium.com/@newmanbradm/intro-to-recursion-984a8bd50f4b) -- [Accio Recursion!: Your New Favorite JavaScript Spell — Leanne Cabey](https://medium.datadriveninvestor.com/accio-recursion-your-new-favorite-javascript-spell-7e10d3125fb3) -- [Recursion Explained (with Examples) — Christina](https://dev.to/christinamcmahon/recursion-explained-with-examples-4k1m) +<CardGroup cols={2}> + <Card title="Recursion Explained (with Examples)" icon="newspaper" href="https://dev.to/christinamcmahon/recursion-explained-with-examples-4k1m"> + Visual explanation of factorial and Fibonacci with tree diagrams. Includes memoization introduction. + </Card> + <Card title="Learn and Understand Recursion in JavaScript" icon="newspaper" href="https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea"> + Practical guide with real-world examples and tips for thinking recursively. + </Card> +</CardGroup> ## Videos <CardGroup cols={2}> - <Card title="Recursion In JavaScript" icon="video" href="https://www.youtube.com/watch?v=VtG0WAUvq2w"> - By techsith + <Card title="What Is Recursion - In Depth" icon="video" href="https://www.youtube.com/watch?v=6oDQaB2one8"> + Web Dev Simplified breaks down recursion with clear visuals and practical examples. Great for visual learners. </Card> <Card title="Recursion" icon="video" href="https://www.youtube.com/watch?v=k7-N8R0-KY4"> - By Fun Fun Function + Fun Fun Function's engaging explanation of recursion with personality and deeper conceptual insights. </Card> </CardGroup> -- [Recursion and Recursive Functions — Hexlet](https://www.youtube.com/watch?v=vLhHyGTkjCs) -- [Recursion: Recursion() — JS Monthly — Lucas da Costa](https://www.youtube.com/watch?v=kGXVsd8pBLw) -- [Recursive Function in JavaScript — kudvenkat](https://www.youtube.com/watch?v=uyjsR9eNTIw) -- [What on Earth is Recursion? — Computerphile](https://www.youtube.com/watch?v=Mv9NEXX1VHc) -- [Javascript Tutorial 34: Introduction To Recursion — codedamn](https://www.youtube.com/watch?v=9NO5dXSlbv8) -- [Recursion, Iteration, and JavaScript: A Love Story | JSHeroes 2018 — Anjana Vakil](https://www.youtube.com/watch?v=FmiQr4nfoPQ) -- [Recursion crash course - Colt Steele](https://www.youtube.com/watch?v=lMBVwYrmFZQ&ab_channel=ColtSteele) -- [What Is Recursion - In Depth - Web Dev Simplified](https://www.youtube.com/watch?v=6oDQaB2one8) +<CardGroup cols={2}> + <Card title="Recursion Crash Course" icon="video" href="https://www.youtube.com/watch?v=lMBVwYrmFZQ"> + Colt Steele's practical crash course on recursion, perfect for interview preparation. + </Card> + <Card title="What on Earth is Recursion?" icon="video" href="https://www.youtube.com/watch?v=Mv9NEXX1VHc"> + Computerphile explains recursion from a computer science perspective with great conceptual depth. + </Card> +</CardGroup> diff --git a/tests/functional-programming/recursion/recursion.test.js b/tests/functional-programming/recursion/recursion.test.js new file mode 100644 index 00000000..f6c8d196 --- /dev/null +++ b/tests/functional-programming/recursion/recursion.test.js @@ -0,0 +1,932 @@ +import { describe, it, expect } from 'vitest' +import { JSDOM } from 'jsdom' + +describe('Recursion', () => { + describe('Base Case Handling', () => { + it('should return immediately when base case is met', () => { + function countdown(n) { + if (n <= 0) return 'done' + return countdown(n - 1) + } + + expect(countdown(0)).toBe('done') + expect(countdown(-1)).toBe('done') + }) + + it('should demonstrate countdown pattern from MDX opening example', () => { + // Exact implementation from MDX lines 9-17 (modified to collect output) + // Original uses console.log, we collect to array for testing + function countdown(n, output = []) { + if (n === 0) { + output.push('Done!') + return output + } + output.push(n) + return countdown(n - 1, output) + } + + // MDX example: countdown(3) outputs 3, 2, 1, Done! + expect(countdown(3)).toEqual([3, 2, 1, 'Done!']) + expect(countdown(1)).toEqual([1, 'Done!']) + expect(countdown(0)).toEqual(['Done!']) + }) + + it('should throw RangeError for infinite recursion (missing base case)', () => { + function infiniteRecursion(n) { + // No base case - will crash + return infiniteRecursion(n - 1) + } + + expect(() => infiniteRecursion(5)).toThrow(RangeError) + }) + + it('should handle base case that returns a value', () => { + function sumTo(n) { + if (n === 1) return 1 + return n + sumTo(n - 1) + } + + expect(sumTo(1)).toBe(1) + }) + }) + + describe('Classic Algorithms', () => { + describe('Factorial', () => { + function factorial(n) { + if (n <= 1) return 1 + return n * factorial(n - 1) + } + + it('should calculate factorial correctly', () => { + expect(factorial(5)).toBe(120) + expect(factorial(4)).toBe(24) + expect(factorial(3)).toBe(6) + }) + + it('should handle edge cases (0! = 1, 1! = 1)', () => { + expect(factorial(0)).toBe(1) + expect(factorial(1)).toBe(1) + }) + + it('should handle larger numbers', () => { + expect(factorial(10)).toBe(3628800) + }) + }) + + describe('Fibonacci', () => { + // Memoized version for efficiency + function fibonacci(n, memo = {}) { + if (n in memo) return memo[n] + if (n <= 1) return n + memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo) + return memo[n] + } + + it('should return correct Fibonacci numbers', () => { + expect(fibonacci(6)).toBe(8) + expect(fibonacci(7)).toBe(13) + expect(fibonacci(10)).toBe(55) + }) + + it('should handle base cases (fib(0) = 0, fib(1) = 1)', () => { + expect(fibonacci(0)).toBe(0) + expect(fibonacci(1)).toBe(1) + }) + + it('should handle larger numbers efficiently with memoization', () => { + expect(fibonacci(50)).toBe(12586269025) + }) + + it('should follow the Fibonacci sequence pattern', () => { + // Each number is sum of two preceding ones + const sequence = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] + sequence.forEach((expected, index) => { + expect(fibonacci(index)).toBe(expected) + }) + }) + }) + + describe('Sum to N', () => { + function sumTo(n) { + if (n === 1) return 1 + return n + sumTo(n - 1) + } + + it('should sum numbers from 1 to n', () => { + expect(sumTo(5)).toBe(15) // 1+2+3+4+5 + expect(sumTo(10)).toBe(55) + expect(sumTo(100)).toBe(5050) + }) + + it('should handle base case', () => { + expect(sumTo(1)).toBe(1) + }) + }) + + describe('Power Function', () => { + function power(x, n) { + if (n === 0) return 1 + return x * power(x, n - 1) + } + + it('should calculate x^n correctly', () => { + expect(power(2, 3)).toBe(8) + expect(power(2, 10)).toBe(1024) + expect(power(3, 4)).toBe(81) + }) + + it('should handle power of 0', () => { + expect(power(5, 0)).toBe(1) + expect(power(100, 0)).toBe(1) + }) + + it('should handle power of 1', () => { + expect(power(7, 1)).toBe(7) + }) + }) + + describe('Power Function (Optimized O(log n))', () => { + function powerFast(x, n) { + if (n === 0) return 1 + + if (n % 2 === 0) { + // Even exponent: x^n = (x^(n/2))^2 + const half = powerFast(x, n / 2) + return half * half + } else { + // Odd exponent: x^n = x * x^(n-1) + return x * powerFast(x, n - 1) + } + } + + it('should calculate x^n correctly with O(log n) complexity', () => { + expect(powerFast(2, 10)).toBe(1024) + expect(powerFast(3, 4)).toBe(81) + expect(powerFast(2, 3)).toBe(8) + }) + + it('should handle even exponents efficiently', () => { + expect(powerFast(2, 8)).toBe(256) + expect(powerFast(2, 16)).toBe(65536) + expect(powerFast(5, 4)).toBe(625) + }) + + it('should handle odd exponents', () => { + expect(powerFast(3, 5)).toBe(243) + expect(powerFast(2, 7)).toBe(128) + }) + + it('should handle edge cases', () => { + expect(powerFast(5, 0)).toBe(1) + expect(powerFast(7, 1)).toBe(7) + expect(powerFast(100, 0)).toBe(1) + }) + + it('should produce same results as naive power function', () => { + function powerNaive(x, n) { + if (n === 0) return 1 + return x * powerNaive(x, n - 1) + } + + // Test that both implementations produce identical results + for (let x = 1; x <= 5; x++) { + for (let n = 0; n <= 10; n++) { + expect(powerFast(x, n)).toBe(powerNaive(x, n)) + } + } + }) + }) + + describe('String Reversal', () => { + function reverse(str) { + if (str.length <= 1) return str + return str[str.length - 1] + reverse(str.slice(0, -1)) + } + + it('should reverse a string', () => { + expect(reverse('hello')).toBe('olleh') + expect(reverse('world')).toBe('dlrow') + expect(reverse('recursion')).toBe('noisrucer') + }) + + it('should handle edge cases', () => { + expect(reverse('')).toBe('') + expect(reverse('a')).toBe('a') + }) + }) + }) + + describe('Practical Patterns', () => { + describe('Array Flattening', () => { + function flatten(arr) { + let result = [] + for (const item of arr) { + if (Array.isArray(item)) { + result = result.concat(flatten(item)) + } else { + result.push(item) + } + } + return result + } + + it('should flatten nested arrays', () => { + expect(flatten([1, [2, [3, 4]], 5])).toEqual([1, 2, 3, 4, 5]) + expect(flatten([1, [2, [3, [4, [5]]]]])).toEqual([1, 2, 3, 4, 5]) + }) + + it('should handle already flat arrays', () => { + expect(flatten([1, 2, 3])).toEqual([1, 2, 3]) + }) + + it('should handle empty arrays', () => { + expect(flatten([])).toEqual([]) + expect(flatten([[], []])).toEqual([]) + }) + }) + + describe('Nested Object Traversal', () => { + function findAllValues(obj, key) { + let results = [] + for (const k in obj) { + if (k === key) { + results.push(obj[k]) + } else if (typeof obj[k] === 'object' && obj[k] !== null) { + results = results.concat(findAllValues(obj[k], key)) + } + } + return results + } + + it('should find all values for a given key in nested objects', () => { + const data = { + name: 'root', + children: { + a: { name: 'a', value: 1 }, + b: { name: 'b', value: 2 } + } + } + + expect(findAllValues(data, 'name')).toEqual(['root', 'a', 'b']) + expect(findAllValues(data, 'value')).toEqual([1, 2]) + }) + + it('should return empty array if key not found', () => { + const data = { a: 1, b: 2 } + expect(findAllValues(data, 'notfound')).toEqual([]) + }) + }) + + describe('Finding All Counts (MDX Example)', () => { + // Exact implementation from MDX lines 410-423 + function findAllCounts(obj) { + let total = 0 + + for (const key in obj) { + if (key === 'count') { + total += obj[key] + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + // Recurse into nested objects + total += findAllCounts(obj[key]) + } + } + + return total + } + + it('should find and sum all count values in nested object (MDX example)', () => { + const data = { + name: 'Company', + departments: { + engineering: { + frontend: { count: 5 }, + backend: { count: 8 } + }, + sales: { count: 12 } + } + } + + expect(findAllCounts(data)).toBe(25) // 5 + 8 + 12 + }) + + it('should return 0 for empty object', () => { + expect(findAllCounts({})).toBe(0) + }) + + it('should return 0 when no count keys exist', () => { + const data = { + name: 'Test', + nested: { + value: 10, + deeper: { something: 'else' } + } + } + expect(findAllCounts(data)).toBe(0) + }) + + it('should handle flat object with count', () => { + expect(findAllCounts({ count: 42 })).toBe(42) + }) + + it('should handle deeply nested counts', () => { + const data = { + level1: { + level2: { + level3: { + level4: { + count: 100 + } + } + } + } + } + expect(findAllCounts(data)).toBe(100) + }) + }) + + describe('Linked List Operations', () => { + function sumList(node) { + if (node === null) return 0 + return node.value + sumList(node.next) + } + + function listLength(node) { + if (node === null) return 0 + return 1 + listLength(node.next) + } + + // Modified version of MDX printReverse that collects results instead of console.log + // MDX implementation (lines 505-509): + // function printReverse(node) { + // if (node === null) return + // printReverse(node.next) // First, go to the end + // console.log(node.value) // Then print on the way back + // } + function collectReverse(node, results = []) { + if (node === null) return results + collectReverse(node.next, results) // First, go to the end + results.push(node.value) // Then collect on the way back + return results + } + + const list = { + value: 1, + next: { + value: 2, + next: { + value: 3, + next: null + } + } + } + + it('should sum all values in a linked list', () => { + expect(sumList(list)).toBe(6) + }) + + it('should count nodes in a linked list', () => { + expect(listLength(list)).toBe(3) + }) + + it('should handle empty list (null)', () => { + expect(sumList(null)).toBe(0) + expect(listLength(null)).toBe(0) + }) + + it('should handle single node list', () => { + const single = { value: 5, next: null } + expect(sumList(single)).toBe(5) + expect(listLength(single)).toBe(1) + }) + + it('should collect values in reverse order (printReverse pattern)', () => { + // MDX shows: printReverse(list) outputs 3, 2, 1 + expect(collectReverse(list)).toEqual([3, 2, 1]) + }) + + it('should return empty array for null list (printReverse pattern)', () => { + expect(collectReverse(null)).toEqual([]) + }) + + it('should handle single node for reverse collection', () => { + const single = { value: 42, next: null } + expect(collectReverse(single)).toEqual([42]) + }) + }) + + describe('Tree Node Counting', () => { + function countNodes(node) { + if (node === null) return 0 + return 1 + countNodes(node.left) + countNodes(node.right) + } + + function sumTree(node) { + if (node === null) return 0 + return node.value + sumTree(node.left) + sumTree(node.right) + } + + const tree = { + value: 1, + left: { + value: 2, + left: { value: 4, left: null, right: null }, + right: { value: 5, left: null, right: null } + }, + right: { + value: 3, + left: null, + right: null + } + } + + it('should count all nodes in a tree', () => { + expect(countNodes(tree)).toBe(5) + }) + + it('should sum all values in a tree', () => { + expect(sumTree(tree)).toBe(15) // 1+2+3+4+5 + }) + + it('should handle empty tree', () => { + expect(countNodes(null)).toBe(0) + expect(sumTree(null)).toBe(0) + }) + }) + + describe('File System Traversal (getTotalSize)', () => { + // Exact implementation from MDX lines 539-550 + function getTotalSize(node) { + if (node.type === 'file') { + return node.size + } + + // Folder: sum sizes of all children + let total = 0 + for (const child of node.children) { + total += getTotalSize(child) + } + return total + } + + it('should calculate total size of file system (MDX example)', () => { + // Exact data structure from MDX lines 522-537 + const fileSystem = { + name: 'root', + type: 'folder', + children: [ + { name: 'file1.txt', type: 'file', size: 100 }, + { + name: 'docs', + type: 'folder', + children: [ + { name: 'readme.md', type: 'file', size: 50 }, + { name: 'notes.txt', type: 'file', size: 25 } + ] + } + ] + } + + expect(getTotalSize(fileSystem)).toBe(175) // 100 + 50 + 25 + }) + + it('should return size of single file', () => { + const singleFile = { name: 'test.js', type: 'file', size: 42 } + expect(getTotalSize(singleFile)).toBe(42) + }) + + it('should return 0 for empty folder', () => { + const emptyFolder = { name: 'empty', type: 'folder', children: [] } + expect(getTotalSize(emptyFolder)).toBe(0) + }) + + it('should handle deeply nested folders', () => { + const deepStructure = { + name: 'level0', + type: 'folder', + children: [ + { + name: 'level1', + type: 'folder', + children: [ + { + name: 'level2', + type: 'folder', + children: [{ name: 'deep.txt', type: 'file', size: 999 }] + } + ] + } + ] + } + expect(getTotalSize(deepStructure)).toBe(999) + }) + + it('should sum files across multiple nested folders', () => { + const multiFolder = { + name: 'root', + type: 'folder', + children: [ + { name: 'a.txt', type: 'file', size: 10 }, + { + name: 'sub1', + type: 'folder', + children: [ + { name: 'b.txt', type: 'file', size: 20 }, + { name: 'c.txt', type: 'file', size: 30 } + ] + }, + { + name: 'sub2', + type: 'folder', + children: [{ name: 'd.txt', type: 'file', size: 40 }] + } + ] + } + expect(getTotalSize(multiFolder)).toBe(100) // 10 + 20 + 30 + 40 + }) + }) + + describe('DOM Traversal (walkDOM)', () => { + // Exact implementation from MDX lines 461-470 + function walkDOM(node, callback) { + // Process this node + callback(node) + + // Recurse into child nodes + for (const child of node.children) { + walkDOM(child, callback) + } + } + + it('should collect all tag names in document order (MDX example)', () => { + const dom = new JSDOM(` + <body> + <div> + <p></p> + <span></span> + </div> + <footer></footer> + </body> + `) + + const tagNames = [] + walkDOM(dom.window.document.body, (node) => { + tagNames.push(node.tagName) + }) + + expect(tagNames).toEqual(['BODY', 'DIV', 'P', 'SPAN', 'FOOTER']) + }) + + it('should handle single element with no children', () => { + const dom = new JSDOM(`<body></body>`) + + const tagNames = [] + walkDOM(dom.window.document.body, (node) => { + tagNames.push(node.tagName) + }) + + expect(tagNames).toEqual(['BODY']) + }) + + it('should handle deeply nested structure', () => { + const dom = new JSDOM(` + <body> + <div> + <div> + <div> + <p></p> + </div> + </div> + </div> + </body> + `) + + const tagNames = [] + walkDOM(dom.window.document.body, (node) => { + tagNames.push(node.tagName) + }) + + expect(tagNames).toEqual(['BODY', 'DIV', 'DIV', 'DIV', 'P']) + }) + + it('should process nodes in depth-first order', () => { + const dom = new JSDOM(` + <body> + <nav> + <a></a> + </nav> + <main> + <article> + <h1></h1> + <p></p> + </article> + </main> + </body> + `) + + const tagNames = [] + walkDOM(dom.window.document.body, (node) => { + tagNames.push(node.tagName) + }) + + expect(tagNames).toEqual(['BODY', 'NAV', 'A', 'MAIN', 'ARTICLE', 'H1', 'P']) + }) + + it('should allow custom callbacks', () => { + const dom = new JSDOM(` + <body> + <div id="first"></div> + <div id="second"></div> + </body> + `) + + const ids = [] + walkDOM(dom.window.document.body, (node) => { + if (node.id) { + ids.push(node.id) + } + }) + + expect(ids).toEqual(['first', 'second']) + }) + }) + }) + + describe('Common Mistakes', () => { + it('should demonstrate stack overflow without proper base case', () => { + function badRecursion(n) { + // Base case uses === instead of <=, causing overflow for negative inputs + if (n === 0) return 0 + return badRecursion(n - 2) // Skips 0 when starting with odd number + } + + // Odd number will skip past 0 and cause stack overflow + expect(() => badRecursion(5)).toThrow(RangeError) + }) + + it('should show difference between returning and not returning recursive call', () => { + function withReturn(n) { + if (n === 1) return 1 + return n + withReturn(n - 1) + } + + function withoutReturn(n) { + if (n === 1) return 1 + n + withoutReturn(n - 1) // Missing return! + } + + expect(withReturn(5)).toBe(15) + expect(withoutReturn(5)).toBeUndefined() + }) + }) + + describe('Optimization', () => { + it('should demonstrate memoized fibonacci is much faster than naive', () => { + // Naive implementation (would be very slow for large n) + function fibNaive(n) { + if (n <= 1) return n + return fibNaive(n - 1) + fibNaive(n - 2) + } + + // Memoized implementation + function fibMemo(n, memo = {}) { + if (n in memo) return memo[n] + if (n <= 1) return n + memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo) + return memo[n] + } + + // Both should return the same result + expect(fibNaive(10)).toBe(55) + expect(fibMemo(10)).toBe(55) + + // But memoized can handle much larger numbers + expect(fibMemo(50)).toBe(12586269025) + // fibNaive(50) would take minutes or crash + }) + + it('should demonstrate tail recursive vs non-tail recursive', () => { + // Non-tail recursive: multiplication happens AFTER recursive call returns + function factorialNonTail(n) { + if (n <= 1) return 1 + return n * factorialNonTail(n - 1) + } + + // Tail recursive: recursive call is the LAST operation + function factorialTail(n, acc = 1) { + if (n <= 1) return acc + return factorialTail(n - 1, acc * n) + } + + // Both produce the same result + expect(factorialNonTail(5)).toBe(120) + expect(factorialTail(5)).toBe(120) + expect(factorialNonTail(10)).toBe(3628800) + expect(factorialTail(10)).toBe(3628800) + }) + }) + + describe('Edge Cases', () => { + it('should handle recursive function with multiple base cases', () => { + function fibonacci(n) { + if (n === 0) return 0 // First base case + if (n === 1) return 1 // Second base case + return fibonacci(n - 1) + fibonacci(n - 2) + } + + expect(fibonacci(0)).toBe(0) + expect(fibonacci(1)).toBe(1) + expect(fibonacci(2)).toBe(1) + }) + + it('should handle recursion with multiple recursive calls', () => { + function sumTree(node) { + if (node === null) return 0 + // Two recursive calls + return node.value + sumTree(node.left) + sumTree(node.right) + } + + const tree = { + value: 10, + left: { value: 5, left: null, right: null }, + right: { value: 15, left: null, right: null } + } + + expect(sumTree(tree)).toBe(30) + }) + + it('should handle mutual recursion', () => { + function isEven(n) { + if (n === 0) return true + return isOdd(n - 1) + } + + function isOdd(n) { + if (n === 0) return false + return isEven(n - 1) + } + + expect(isEven(4)).toBe(true) + expect(isEven(5)).toBe(false) + expect(isOdd(3)).toBe(true) + expect(isOdd(4)).toBe(false) + }) + }) + + describe('Recursion vs Iteration', () => { + describe('Factorial (Iterative vs Recursive)', () => { + // Recursive version (from Classic Algorithms) + function factorialRecursive(n) { + if (n <= 1) return 1 + return n * factorialRecursive(n - 1) + } + + // Exact iterative implementation from MDX lines 585-592 + function factorialIterative(n) { + let result = 1 + for (let i = 2; i <= n; i++) { + result *= i + } + return result + } + + it('should produce same results as recursive version', () => { + expect(factorialIterative(5)).toBe(factorialRecursive(5)) + expect(factorialIterative(10)).toBe(factorialRecursive(10)) + }) + + it('should calculate factorial correctly', () => { + expect(factorialIterative(5)).toBe(120) + expect(factorialIterative(10)).toBe(3628800) + }) + + it('should handle edge cases (0! = 1, 1! = 1)', () => { + expect(factorialIterative(0)).toBe(1) + expect(factorialIterative(1)).toBe(1) + }) + + it('should handle larger numbers without stack overflow', () => { + // Iterative can handle larger numbers without stack concerns + expect(factorialIterative(20)).toBe(2432902008176640000) + }) + }) + + describe('Sum Tree (Iterative with Explicit Stack)', () => { + // Recursive version + function sumTreeRecursive(node) { + if (node === null) return 0 + return node.value + sumTreeRecursive(node.left) + sumTreeRecursive(node.right) + } + + // Exact iterative implementation from MDX lines 814-829 + function sumTreeIterative(root) { + if (root === null) return 0 + + let sum = 0 + const stack = [root] + + while (stack.length > 0) { + const node = stack.pop() + sum += node.value + + if (node.right) stack.push(node.right) + if (node.left) stack.push(node.left) + } + + return sum + } + + const tree = { + value: 1, + left: { + value: 2, + left: { value: 4, left: null, right: null }, + right: { value: 5, left: null, right: null } + }, + right: { + value: 3, + left: null, + right: null + } + } + + it('should produce same results as recursive version', () => { + expect(sumTreeIterative(tree)).toBe(sumTreeRecursive(tree)) + }) + + it('should sum all values in a tree', () => { + expect(sumTreeIterative(tree)).toBe(15) // 1+2+3+4+5 + }) + + it('should handle empty tree (null)', () => { + expect(sumTreeIterative(null)).toBe(0) + }) + + it('should handle single node tree', () => { + const single = { value: 42, left: null, right: null } + expect(sumTreeIterative(single)).toBe(42) + }) + + it('should handle left-only tree', () => { + const leftOnly = { + value: 1, + left: { + value: 2, + left: { value: 3, left: null, right: null }, + right: null + }, + right: null + } + expect(sumTreeIterative(leftOnly)).toBe(6) + }) + + it('should handle right-only tree', () => { + const rightOnly = { + value: 1, + left: null, + right: { + value: 2, + left: null, + right: { value: 3, left: null, right: null } + } + } + expect(sumTreeIterative(rightOnly)).toBe(6) + }) + }) + }) + + describe('Q&A Examples', () => { + describe('Array Length (Recursive)', () => { + // Exact implementation from MDX lines 899-910 + function arrayLength(arr) { + // Base case: empty array has length 0 + if (arr.length === 0) return 0 + + // Recursive case: 1 + length of the rest + return 1 + arrayLength(arr.slice(1)) + } + + it('should calculate array length recursively (MDX example)', () => { + expect(arrayLength([1, 2, 3, 4])).toBe(4) + }) + + it('should return 0 for empty array', () => { + expect(arrayLength([])).toBe(0) + }) + + it('should handle single element array', () => { + expect(arrayLength([42])).toBe(1) + }) + + it('should work with arrays of different types', () => { + expect(arrayLength(['a', 'b', 'c'])).toBe(3) + expect(arrayLength([{ a: 1 }, { b: 2 }])).toBe(2) + expect(arrayLength([1, 'two', { three: 3 }, [4]])).toBe(4) + }) + + it('should handle longer arrays', () => { + const longArray = Array.from({ length: 100 }, (_, i) => i) + expect(arrayLength(longArray)).toBe(100) + }) + }) + }) +}) From 8dc4509f8ae820134247940fd9e217dc23db0d31 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:48:42 -0300 Subject: [PATCH 092/128] docs: add comprehensive JavaScript Engines concept page with tests - Explain V8 architecture: parsing, AST, Ignition interpreter, TurboFan compiler - Cover JIT compilation, hidden classes, inline caching, and garbage collection - Include ASCII diagrams, code examples, and practical optimization tips - Add 36 tests covering engine optimization concepts - Follow write-concept skill guidelines with SEO-optimized headings --- docs/concepts/javascript-engines.mdx | 948 +++++++++++++++++- .../javascript-engines.test.js | 592 +++++++++++ 2 files changed, 1515 insertions(+), 25 deletions(-) create mode 100644 tests/fundamentals/javascript-engines/javascript-engines.test.js diff --git a/docs/concepts/javascript-engines.mdx b/docs/concepts/javascript-engines.mdx index 719fded0..219523d8 100644 --- a/docs/concepts/javascript-engines.mdx +++ b/docs/concepts/javascript-engines.mdx @@ -1,43 +1,941 @@ --- -title: "JavaScript Engines: How Your Code Gets Executed" -sidebarTitle: "JavaScript Engines: How Code Gets Executed" -description: "Learn how JavaScript engines work — V8, SpiderMonkey, parsing, compilation, JIT optimization, and the execution pipeline. Understand what happens when your code runs." +title: "JavaScript Engines: How V8 Runs Your Code in JavaScript" +sidebarTitle: "JavaScript Engines: How V8 Runs Your Code" +description: "Learn how JavaScript engines work. Understand V8's architecture, parsing, compilation, JIT optimization, hidden classes, inline caching, and garbage collection." --- -## Overview +What happens when you run JavaScript code? How does a browser turn `const x = 1 + 2` into something your computer actually executes? When you write a function, what transforms those characters into instructions your CPU understands? -JavaScript engines are programs that execute JavaScript code. The most well-known engines include **V8** (Chrome, Node.js), **SpiderMonkey** (Firefox), and **JavaScriptCore** (Safari). These engines parse, compile, and execute JavaScript code, often using Just-In-Time (JIT) compilation for performance optimization. +```javascript +function greet(name) { + return "Hello, " + name + "!" +} -## Articles +greet("World") // "Hello, World!" +``` + +Behind every line of JavaScript is a **JavaScript engine**. It's the program that reads your code, understands it, and makes it run. The most popular engine is **[V8](https://v8.dev/)**, which powers Chrome, Node.js, Deno, and Electron. Understanding how V8 works helps you write faster code and debug performance issues. + +<Info> +**What you'll learn in this guide:** +- What a JavaScript engine is and what it does +- How V8 parses your code and builds an Abstract Syntax Tree +- How Ignition (interpreter) and TurboFan (compiler) work together +- What JIT compilation is and why it makes JavaScript fast +- How hidden classes and inline caching optimize property access +- How garbage collection automatically manages memory +- Practical tips for writing engine-friendly code +</Info> + +<Warning> +**Prerequisite:** This guide assumes you're comfortable with basic JavaScript syntax. Some concepts connect to the [Call Stack](/concepts/call-stack) and [Event Loop](/concepts/event-loop), so reading those first helps! +</Warning> + +--- + +## What is a JavaScript Engine? + +A **JavaScript engine** is a program that executes JavaScript code. It takes the source code you write and converts it into machine code that your computer's processor can run. + +Every browser has its own JavaScript engine: + +| Browser | Engine | Also Used By | +|---------|--------|--------------| +| Chrome | **V8** | Node.js, Deno, Electron | +| Firefox | SpiderMonkey | — | +| Safari | JavaScriptCore | Bun | +| Edge | V8 (since 2020) | — | + +We'll focus on **V8** since it's the most widely used engine and powers both browser and server-side JavaScript. + +<Note> +All JavaScript engines implement the [ECMAScript specification](https://tc39.es/ecma262/), which defines how the language should work. That's why JavaScript behaves the same way whether you run it in Chrome, Firefox, or Node.js. +</Note> + +--- + +## How Does a JavaScript Engine Work? + +Think of V8 as a **factory** that manufactures results from your code: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE V8 JAVASCRIPT FACTORY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ RAW MATERIALS QUALITY CONTROL BLUEPRINT │ +│ (Source Code) (Parser) (AST) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ function │ │ Break into │ │ Tree of │ │ +│ │ add(a, b) { │ ─► │ tokens, │ ─► │ operations │ │ +│ │ return a+b │ │ check │ │ to perform │ │ +│ │ } │ │ syntax │ │ │ │ +│ └──────────────┘ └──────────────┘ └──────┬───────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────────────────────────────────────────────────────────────┐ │ +│ │ ASSEMBLY LINE │ │ +│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │ +│ │ │ IGNITION │ │ TURBOFAN │ │ │ +│ │ │ (Interpreter) │ ─────────► │ (Optimizing Compiler) │ │ │ +│ │ │ │ "hot" │ │ │ │ +│ │ │ Steady workers │ code │ Fast robotic assembly │ │ │ +│ │ │ Start quickly │ │ Takes time to set up │ │ │ +│ │ └─────────────────┘ └─────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ OUTPUT │ │ +│ │ (Result) │ │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Here's the analogy: + +- **Raw materials (source code)**: Your JavaScript files come in as text +- **Quality control (parser)**: Checks for syntax errors, breaks code into pieces +- **Blueprint (AST)**: A structured representation of what needs to be built +- **Assembly line workers (Ignition)**: Start working immediately, steady pace +- **Robotic automation (TurboFan)**: Takes time to set up, but once running, it's much faster + +Just like a factory might start with manual workers and add robots for repetitive tasks, V8 starts interpreting code immediately, then optimizes the parts that run frequently. + +--- + +## How Does V8 Execute Your Code? + +When you run JavaScript, V8 processes your code through several stages. Let's trace through what happens when V8 executes this code: + +```javascript +function add(a, b) { + return a + b +} + +add(1, 2) // 3 +``` + +### Step 1: Parsing + +First, V8 needs to understand your code. The **parser** reads the source text and converts it into a structured format. + +<Steps> + <Step title="Tokenization (Lexical Analysis)"> + The code is broken into **tokens**, the smallest meaningful pieces: + + ``` + 'function' 'add' '(' 'a' ',' 'b' ')' '{' 'return' 'a' '+' 'b' '}' + ``` + + Each token is classified: `function` is a keyword, `add` is an identifier, `+` is an operator. + </Step> + + <Step title="Building the AST (Syntactic Analysis)"> + Tokens are organized into an **Abstract Syntax Tree (AST)**, a tree structure that represents your code's meaning: + + ``` + FunctionDeclaration + ├── name: "add" + ├── params: ["a", "b"] + └── body: ReturnStatement + └── BinaryExpression + ├── left: Identifier "a" + ├── operator: "+" + └── right: Identifier "b" + ``` + + The AST captures *what* your code does, without the original syntax (semicolons, whitespace, etc.). + </Step> +</Steps> + +<Tip> +**See it yourself:** You can explore how JavaScript is parsed using [AST Explorer](https://astexplorer.net/). Paste any JavaScript code and see the resulting tree structure. +</Tip> + +### Step 2: Ignition (The Interpreter) + +Once V8 has the AST, **Ignition** takes over. Ignition is V8's interpreter. It walks through the AST and generates **bytecode**, a compact representation of your code. + +``` +Bytecode for add(a, b): + Ldar a1 // Load argument 'a' into accumulator + Add a2 // Add argument 'b' to accumulator + Return // Return the accumulator value +``` + +Ignition then **executes** this bytecode immediately. No waiting around for optimization. Your code starts running right away. + +While executing, Ignition also collects **profiling data**: +- Which functions are called often? +- What types of values does each variable hold? +- Which branches of if/else statements are taken? + +This profiling data becomes important for the next step. + +### Step 3: TurboFan (The Optimizing Compiler) + +When Ignition notices a function is called many times (it becomes "hot"), V8 decides it's worth spending time to optimize it. Enter **TurboFan**, V8's optimizing compiler. + +TurboFan takes the bytecode and profiling data, then generates **highly optimized machine code**. It makes assumptions based on the profiling data: + +```javascript +function add(a, b) { + return a + b +} + +// V8 observes: add() is always called with numbers +add(1, 2) +add(3, 4) +add(5, 6) +// ... called many more times with numbers + +// TurboFan thinks: "This always gets numbers. I'll optimize for that!" +// Generates machine code that assumes a and b are numbers +``` + +The optimized code runs **much faster** than interpreted bytecode because: +- It's native machine code, not bytecode that needs interpretation +- It makes type assumptions (no need to check "is this a number?" every time) +- It can inline function calls, eliminate dead code, and apply other optimizations + +### Step 4: Deoptimization (The Fallback) + +But what if TurboFan's assumptions are wrong? + +```javascript +// After 1000 calls with numbers... +add("hello", "world") // Strings! TurboFan assumed numbers! +``` + +When this happens, V8 performs **deoptimization**. It throws away the optimized machine code and falls back to Ignition's bytecode. The function runs slower temporarily, but at least it runs correctly. + +V8 might try to optimize again later, this time with better information about the actual types being used. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE OPTIMIZATION CYCLE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Source Code │ +│ │ │ +│ ▼ │ +│ ┌─────────┐ │ +│ │ Parse │ │ +│ └────┬────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────┐ profile ┌───────────┐ │ +│ │ Ignition │ ───────────────────► │ TurboFan │ │ +│ │(bytecode)│ │(optimized)│ │ +│ └────┬────┘ ◄─────────────────── └─────┬─────┘ │ +│ │ deoptimize │ │ +│ │ │ │ +│ ▼ ▼ │ +│ [Execute] [Execute] │ +│ (slower) (faster!) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## What is JIT Compilation? + +You might have heard that JavaScript is an "interpreted language." That's only half the story. Modern JavaScript engines use **JIT compilation** (Just-In-Time), which combines interpretation and compilation. + +### The Three Approaches + +<Tabs> + <Tab title="Interpreted"> + **Pure Interpretation** (like old JavaScript engines) + + - Source code is executed line by line + - No compilation step + - Starts fast, but runs slow + - Every time a function runs, it's re-interpreted + + ``` + Source → Execute → Execute → Execute... + ``` + </Tab> + + <Tab title="Compiled"> + **Ahead-of-Time Compilation** (like C/C++) + + - Source code is compiled to machine code before running + - Slow startup (must compile everything first) + - Very fast execution + - Can't adapt to runtime information + + ``` + Source → Compile (wait...) → Execute (fast!) + ``` + </Tab> + + <Tab title="JIT (V8)"> + **Just-In-Time Compilation** (V8's approach) + + - Start executing immediately with interpreter + - Compile "hot" code to machine code while running + - Best of both worlds: fast startup AND fast execution + - Can use runtime information for smarter optimizations + + ``` + Source → Interpret (start fast!) → Compile hot code → Execute (faster!) + ``` + </Tab> +</Tabs> + +### Why JavaScript Needs JIT + +JavaScript is a **dynamic language**. Variables can hold any type, objects can change shape, and functions can be redefined at runtime. This makes ahead-of-time compilation difficult because the compiler doesn't know what types to expect. + +```javascript +function process(x) { + return x.value * 2 +} + +// x could be anything! +process({ value: 10 }) // Object with number +process({ value: "hello" }) // Object with string (NaN result) +process({ value: 10, extra: 5 }) // Different shape +``` + +JIT compilation solves this by: +1. Starting with interpretation (works for any types) +2. Observing what types actually appear at runtime +3. Compiling optimized code based on real observations +4. Falling back to interpretation if observations were wrong + +<Warning> +**The "warm-up" period:** When you first run JavaScript code, it's slower because it's being interpreted. After functions run many times, they get optimized and become faster. This is why benchmarks often include a "warm-up" phase. +</Warning> + +--- + +## What Are Hidden Classes? + +**Hidden classes** (called "Maps" in V8, "Shapes" in other engines) are internal data structures that V8 uses to track object shapes. They let V8 know exactly where to find properties like `obj.x` without searching through every property name. + +Why does V8 need them? JavaScript objects are dynamic. You can add or remove properties at any time. This flexibility creates a problem: how does V8 efficiently access `obj.x` if objects can have any shape? + +### The Problem + +Consider accessing a property: + +```javascript +function getX(obj) { + return obj.x +} +``` + +Without optimization, every call to `getX` would need to: +1. Look up the object's list of properties +2. Search for a property named "x" +3. Get the value at that property's location + +That's slow, especially for hot code. + +### The Solution: Hidden Classes + +V8 assigns a **hidden class** to every object. Objects with the same properties in the same order share the same hidden class. + +```javascript +const point1 = { x: 1, y: 2 } +const point2 = { x: 5, y: 10 } + +// point1 and point2 have the SAME hidden class! +// V8 knows: "For objects with this hidden class, 'x' is at offset 0, 'y' is at offset 1" +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ HIDDEN CLASSES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Hidden Class HC1 point1 point2 │ +│ ┌────────────────────┐ ┌────────┐ ┌────────┐ │ +│ │ x: offset 0 │ ◄────── │ HC1 │ │ HC1 │ ◄──┐ │ +│ │ y: offset 1 │ ├────────┤ ├────────┤ │ │ +│ └────────────────────┘ │ [0]: 1 │ │ [0]: 5 │ │ │ +│ ▲ │ [1]: 2 │ │ [1]: 10│ │ │ +│ │ └────────┘ └────────┘ │ │ +│ │ │ │ +│ └───────────────────── Same hidden class! ──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Now, when V8 sees `getX(point1)`, it can: +1. Check the hidden class (one comparison) +2. Read the value at offset 0 (direct memory access) + +No property name lookup needed! + +### Transition Chains + +What happens when you add properties to an object? V8 creates **transition chains**: + +```javascript +const obj = {} // Hidden class: HC0 (empty) +obj.x = 1 // Transition to HC1 (has x at offset 0) +obj.y = 2 // Transition to HC2 (has x at 0, y at 1) +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ TRANSITION CHAIN │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ const obj = {} obj.x = 1 obj.y = 2 │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ HC0 │ ───► │ HC1 │ ───► │ HC2 │ │ +│ │ (empty) │ add x │ x: off 0 │ add y │ x: off 0 │ │ +│ └──────────┘ └──────────┘ │ y: off 1 │ │ +│ └──────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +<Warning> +**Property order matters!** These two objects have **different** hidden classes: + +```javascript +const a = { x: 1, y: 2 } // HC with x then y +const b = { y: 2, x: 1 } // Different HC with y then x +``` + +This means V8 can't share optimizations between them. Always add properties in the same order! +</Warning> + +--- + +## What is Inline Caching? + +**Inline Caching (IC)** is an optimization where V8 remembers where it found a property and reuses that information on subsequent calls. Instead of looking up property locations every time, V8 caches: "For this hidden class, property X is at memory offset Y." + +This optimization is possible because of hidden classes. When V8 knows an object's shape, it can cache the exact memory location of each property. + +### How Inline Caching Works + +```javascript +function getX(obj) { + return obj.x // V8 caches: "For HC1, x is at offset 0" +} + +const p1 = { x: 1, y: 2 } +const p2 = { x: 5, y: 10 } + +getX(p1) // First call: look up x, cache the location +getX(p2) // Second call: same hidden class! Use cached location +getX(p1) // Third call: cache hit again! +``` + +The first time `getX` runs, V8 does the full property lookup. But it **caches** the result: "For objects with hidden class HC1, property 'x' is at memory offset 0." + +Subsequent calls with the same hidden class skip the lookup entirely. + +### IC States: Monomorphic, Polymorphic, Megamorphic + +The inline cache can be in different states depending on how many different hidden classes it encounters: + +<AccordionGroup> + <Accordion title="Monomorphic (Fastest)"> + The function always sees objects with the **same** hidden class. + + ```javascript + function getX(obj) { + return obj.x + } + + // All objects have the same shape + getX({ x: 1, y: 2 }) + getX({ x: 3, y: 4 }) + getX({ x: 5, y: 6 }) + + // IC: "Always HC1, x at offset 0" - ONE entry, super fast! + ``` + + **Performance:** Excellent. Single comparison, direct memory access. + </Accordion> + + <Accordion title="Polymorphic (Still Good)"> + The function sees a **few** different hidden classes (typically 2-4). + + ```javascript + function getX(obj) { + return obj.x + } + + getX({ x: 1 }) // Shape A + getX({ x: 2, y: 3 }) // Shape B + getX({ x: 4, y: 5, z: 6 }) // Shape C + + // IC: "Could be A, B, or C" - checks a few options + ``` + + **Performance:** Good. Checks a small list of known shapes. + </Accordion> + + <Accordion title="Megamorphic (Slowest)"> + The function sees **many** different hidden classes. + + ```javascript + function getX(obj) { + return obj.x + } + + // Every call has a completely different shape + getX({ x: 1 }) + getX({ x: 2, a: 1 }) + getX({ x: 3, b: 2 }) + getX({ x: 4, c: 3 }) + getX({ x: 5, d: 4 }) + // ... many more different shapes + + // IC gives up: "Too many shapes, doing full lookup every time" + ``` + + **Performance:** Poor. Falls back to generic property lookup. + </Accordion> +</AccordionGroup> + +<Tip> +**For best performance:** Pass objects with consistent shapes to your functions. Factory functions help: + +```javascript +// Good: Factory creates consistent shapes +function createPoint(x, y) { + return { x, y } +} + +getX(createPoint(1, 2)) +getX(createPoint(3, 4)) // Same shape, monomorphic IC! +``` +</Tip> + +--- + +## How Does Garbage Collection Work? + +Unlike languages like C where you manually allocate and free memory, JavaScript automatically manages memory through **garbage collection (GC)**. V8's garbage collector is called **Orinoco**. + +### The Generational Hypothesis + +V8's GC is based on an observation about how programs use memory: **most objects die young**. + +Think about it: temporary variables, intermediate calculation results, short-lived callbacks. They're created, used briefly, and never needed again. Only some objects (your app's state, cached data) live for a long time. + +V8 exploits this by splitting memory into generations: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ V8 MEMORY HEAP │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ YOUNG GENERATION OLD GENERATION │ +│ (Short-lived objects) (Long-lived objects) │ +│ │ +│ ┌─────────────────────────┐ ┌─────────────────────────┐ │ +│ │ Nursery │ Intermediate │ ───► │ Survived multiple GCs │ │ +│ │ │ │ survives │ │ │ +│ │ New │ Survived │ │ App state, caches, │ │ +│ │ objects │ one GC │ │ long-lived data │ │ +│ └─────────────────────────┘ └─────────────────────────┘ │ +│ │ +│ Minor GC (Scavenger) Major GC (Mark-Compact) │ +│ • Very fast • Slower but thorough │ +│ • Runs frequently • Runs less often │ +│ • Only scans young gen • Scans entire heap │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Minor GC: The Scavenger + +New objects are allocated in the **young generation**. When it fills up, V8 runs a **minor GC** (called the Scavenger): + +1. Find all live objects in the young generation +2. Copy survivors to a new space +3. Objects that survive multiple collections get promoted to the old generation + +This is fast because: +- Most young objects are dead (no need to copy them) +- The young generation is small +- Only copying live objects means no fragmentation + +### Major GC: Mark-Compact + +The **old generation** is collected less frequently with a **major GC**: + +<Steps> + <Step title="Marking"> + Starting from "roots" (global variables, stack), V8 follows all references and marks every reachable object as "live." + </Step> + + <Step title="Sweeping"> + Dead objects (unmarked) leave gaps in memory. V8 adds these gaps to a "free list" for future allocations. + </Step> + + <Step title="Compaction"> + To reduce fragmentation, V8 may move live objects together, like defragmenting a hard drive. + </Step> +</Steps> + +### Concurrent and Parallel GC + +Modern V8 uses advanced techniques to minimize pauses: + +- **Parallel:** Multiple threads do GC work simultaneously +- **Incremental:** GC work is broken into small chunks, interleaved with JavaScript execution +- **Concurrent:** GC runs in the background while JavaScript continues executing + +This means you rarely notice GC pauses in modern JavaScript applications. + +--- + +## How Do You Write Engine-Friendly Code? + +Now that you understand how V8 works, here are practical tips to help the engine optimize your code: + +### 1. Initialize Objects Consistently + +Give objects the same shape by adding properties in the same order: + +```javascript +// ✓ Good: Consistent shape +function createUser(name, age) { + return { name, age } // Always name, then age +} + +// ❌ Bad: Inconsistent shapes +function createUser(name, age) { + const user = {} + if (name) user.name = name // Sometimes name first + if (age) user.age = age // Sometimes age first + return user +} +``` + +### 2. Avoid Changing Types + +Keep variables holding the same type throughout their lifetime: + +```javascript +// ✓ Good: Consistent types +let count = 0 +count = 1 +count = 2 + +// ❌ Bad: Type changes trigger deoptimization +let count = 0 +count = "none" // Now it's a string! +count = null // Now it's null! +``` + +### 3. Use Arrays Correctly + +Avoid "holes" in arrays and don't mix types: + +```javascript +// ✓ Good: Dense array with consistent types +const numbers = [1, 2, 3, 4, 5] + +// ❌ Bad: Sparse array with holes +const sparse = [] +sparse[0] = 1 +sparse[1000] = 2 // Creates 999 "holes" + +// ❌ Bad: Mixed types +const mixed = [1, "two", 3, null, { four: 4 }] +``` + +### 4. Avoid [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete) on Objects + +Using `delete` changes an object's hidden class and can cause deoptimization: + +```javascript +// ❌ Bad: Using delete +const user = { name: "Alice", age: 30, temp: true } +delete user.temp // Changes hidden class! + +// ✓ Good: Set to undefined or use a different structure +const user = { name: "Alice", age: 30, temp: true } +user.temp = undefined // Hidden class stays the same +``` + +<Note> +Setting a property to `undefined` keeps the property on the object (it just has no value). If you need to truly remove properties frequently, consider using a `Map` instead of a plain object. +</Note> + +### 5. Prefer Monomorphic Code + +Design functions to work with objects of the same shape: + +```javascript +// ✓ Good: Monomorphic - always same shape +class Point { + constructor(x, y) { + this.x = x + this.y = y + } +} + +function distance(p1, p2) { + const dx = p1.x - p2.x + const dy = p1.y - p2.y + return Math.sqrt(dx * dx + dy * dy) +} + +distance(new Point(0, 0), new Point(3, 4)) // All Points, same shape +``` + +--- + +## Common Misconceptions + +<AccordionGroup> + <Accordion title="'JavaScript is interpreted, not compiled'"> + **Partially true, but misleading.** Modern JavaScript engines use JIT compilation. Your code is initially interpreted, but hot functions are compiled to native machine code. V8's TurboFan generates highly optimized machine code that rivals traditionally compiled languages for computational tasks. + </Accordion> + + <Accordion title="'More code = slower execution'"> + **Not necessarily!** V8 performs dead code elimination and function inlining. A well-structured program with more lines can be faster than a "clever" one-liner that's hard to optimize. Write clear, predictable code and let the engine optimize it. + </Accordion> + + <Accordion title="'I need to manually manage memory in JavaScript'"> + **No!** JavaScript has automatic garbage collection. You don't need to (and can't) manually free memory. However, you should avoid creating unnecessary object references that prevent garbage collection (memory leaks). + + ```javascript + // Potential memory leak: event listener keeps reference + element.addEventListener("click", () => { + console.log(largeData) // largeData can't be GC'd + }) + + // Fix: Remove listener when done + element.removeEventListener("click", handler) + ``` + </Accordion> + + <Accordion title="'eval() is just slow'"> + **It's worse than slow.** [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) prevents many optimizations because V8 can't predict what code will run. Variables in scope become "unoptimizable" because `eval` might access them. Avoid `eval()` and [`new Function()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) with dynamic strings. + </Accordion> + + <Accordion title="'typeof null === 'object' is a V8 bug'"> + **No, it's in the ECMAScript specification.** This is a historical quirk from JavaScript's original implementation that was kept for backwards compatibility. All JavaScript engines must return `"object"` for [`typeof null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null) to comply with the spec. + </Accordion> +</AccordionGroup> + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **V8 powers Chrome, Node.js, and Deno.** It's the most widely used JavaScript engine and determines how your code runs. + +2. **Code goes through multiple stages:** Source → Parse → AST → Bytecode (Ignition) → Optimized Machine Code (TurboFan). + +3. **Ignition interprets immediately.** Your code starts running right away without waiting for compilation. + +4. **TurboFan optimizes hot code.** Functions called many times get compiled to fast machine code based on observed types. + +5. **Deoptimization happens when assumptions fail.** If you pass unexpected types, V8 falls back to slower bytecode. + +6. **Hidden classes enable fast property access.** Objects with the same properties in the same order share optimization metadata. + +7. **Inline caching remembers property locations.** Monomorphic code (same shapes) is fastest; megamorphic code (many shapes) is slowest. + +8. **Garbage collection is automatic and generational.** Most objects die young; V8 optimizes for this with separate young/old generations. + +9. **Write consistent, predictable code.** Same shapes, same types, dense arrays. Help the engine help you. + +10. **Avoid anti-patterns:** `delete` on objects, sparse arrays, changing variable types, and `eval()`. +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between Ignition and TurboFan?"> + **Answer:** + + **Ignition** is V8's interpreter. It generates bytecode from the AST and executes it immediately. It's fast to start but doesn't produce the fastest possible code. While running, it collects profiling data about types and execution patterns. + + **TurboFan** is V8's optimizing compiler. It takes bytecode and profiling data from Ignition, then generates highly optimized machine code. It takes longer to compile but produces much faster code. TurboFan kicks in for "hot" functions that run many times. + </Accordion> + + <Accordion title="Question 2: Why does property order matter when creating objects?"> + **Answer:** + + V8 assigns hidden classes to objects based on their properties **and the order those properties were added**. Objects with the same properties in the same order share a hidden class and can use the same optimizations. + + ```javascript + const a = { x: 1, y: 2 } // Hidden class A + const b = { y: 2, x: 1 } // Hidden class B (different!) + ``` + + Different hidden classes mean different inline cache entries and less optimization sharing. For best performance, always add properties in a consistent order. + </Accordion> + + <Accordion title="Question 3: What triggers deoptimization?"> + **Answer:** + + Deoptimization happens when TurboFan's assumptions about your code are violated. Common triggers include: + + - **Type changes:** A function optimized for numbers receives a string + - **Hidden class changes:** An object's shape changes (adding/deleting properties) + - **Unexpected values:** `undefined` where a number was expected + - **Megamorphic call sites:** Too many different object shapes at one location + + ```javascript + function add(a, b) { return a + b } + + // Optimized for numbers + add(1, 2) + add(3, 4) + + // Deoptimizes! + add("hello", "world") + ``` + </Accordion> + + <Accordion title="Question 4: What is inline caching and why does it speed up property access?"> + **Answer:** + + Inline caching (IC) is an optimization where V8 remembers where it found a property for a given hidden class. Instead of doing a full property lookup every time, it caches: "For objects with hidden class X, property 'foo' is at memory offset Y." + + On subsequent accesses with the same hidden class, V8 skips the lookup and reads directly from the cached offset. This turns an O(n) dictionary lookup into an O(1) memory access. + + ```javascript + function getX(obj) { + return obj.x // IC: "For HC1, x is at offset 0" + } + + getX({ x: 1, y: 2 }) // Cache miss, full lookup, cache result + getX({ x: 3, y: 4 }) // Cache hit! Direct access to offset 0 + ``` + </Accordion> + + <Accordion title="Question 5: What is the 'generational hypothesis' in garbage collection?"> + **Answer:** + + The generational hypothesis states that **most objects die young**. Temporary variables, function arguments, intermediate results. They're created, used briefly, and become garbage quickly. + + V8 exploits this by dividing the heap into: + - **Young generation:** Where new objects are allocated. Collected frequently with a fast "scavenger" algorithm. + - **Old generation:** Objects that survive multiple young generation collections. Collected less frequently with a slower but thorough algorithm. + + This is efficient because checking young objects frequently catches most garbage quickly, while long-lived objects aren't constantly re-checked. + </Accordion> + + <Accordion title="Question 6: Which code pattern is more engine-friendly?"> + ```javascript + // Pattern A + function createPoint(x, y) { + return { x: x, y: y } + } + + // Pattern B + function createPoint(x, y) { + const point = {} + point.x = x + point.y = y + return point + } + ``` + + **Answer:** + + **Pattern A is more engine-friendly.** + + In Pattern A, the object literal `{ x: x, y: y }` creates an object with a known shape immediately. V8 can skip the empty object transition. + + In Pattern B, the object goes through three hidden class transitions: + 1. `{}` - empty shape + 2. `{ x }` - after adding x + 3. `{ x, y }` - after adding y + + Pattern A is faster to create and produces the same final shape more directly. Modern engines optimize object literals with known properties, skipping intermediate shapes. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="JavaScript Engines" icon="newspaper" href="http://www.softwaremag.com/javascript-engines/"> - By Jen Looper + <Card title="Call Stack" icon="layer-group" href="/concepts/call-stack"> + How V8 tracks function execution and manages execution contexts </Card> - <Card title="Understanding How the Chrome V8 Engine Translates JavaScript into Machine Code" icon="newspaper" href="https://medium.freecodecamp.org/understanding-the-core-of-nodejs-the-powerful-chrome-v8-engine-79e7eb8af964"> - By DroidHead + <Card title="Event Loop" icon="rotate" href="/concepts/event-loop"> + How async code runs alongside the single-threaded JavaScript engine + </Card> + <Card title="Primitive Types" icon="cube" href="/concepts/primitive-types"> + How V8 represents and optimizes different value types + </Card> + <Card title="Value vs Reference Types" icon="code-branch" href="/concepts/value-reference-types"> + How the engine stores primitives vs objects in memory </Card> </CardGroup> -- [Understanding V8's Bytecode — Franziska Hinkelmann](https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775) -- [JavaScript essentials: why you should know how the engine works - Rainer Hahnekamp](https://www.freecodecamp.org/news/javascript-essentials-why-you-should-know-how-the-engine-works-c2cc0d321553) -- [JavaScript engine fundamentals: Shapes and Inline Caches](https://mathiasbynens.be/notes/shapes-ics) -- [JavaScript engine fundamentals: optimizing prototypes](https://mathiasbynens.be/notes/prototypes) -- [How V8 optimizes array operations](https://v8.dev/blog/elements-kinds) -- [JavaScript Internals: JavaScript engine, Run-time environment & setTimeout Web API — Rupesh Mishra](https://blog.bitsrc.io/javascript-internals-javascript-engine-run-time-environment-settimeout-web-api-eeed263b1617) +--- + +## Reference -## Videos +<CardGroup cols={2}> + <Card title="JavaScript technologies overview — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview"> + Overview of JavaScript engines, ECMAScript, and how the language relates to browser APIs. + </Card> + <Card title="Memory Management — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management"> + How JavaScript manages memory allocation and garbage collection. + </Card> + <Card title="V8 Documentation" icon="book" href="https://v8.dev/docs"> + Official V8 documentation covering Ignition, TurboFan, and engine internals. + </Card> +</CardGroup> + +## Articles <CardGroup cols={2}> - <Card title="JavaScript Engines: The Good Parts™" icon="video" href="https://www.youtube.com/watch?v=5nmpokoRaZI"> - By Mathias Bynens & Benedikt Meurer + <Card title="JavaScript engine fundamentals: Shapes and Inline Caches" icon="newspaper" href="https://mathiasbynens.be/notes/shapes-ics"> + Mathias Bynens and Benedikt Meurer explain how all JavaScript engines optimize property access. Includes excellent diagrams showing hidden classes and IC states. </Card> - <Card title="JS Engine EXPOSED - Google's V8 Architecture" icon="video" href="https://www.youtube.com/watch?v=2WJL19wDH68"> - By Akshay Saini — Namaste JavaScript + <Card title="JavaScript engine fundamentals: optimizing prototypes" icon="newspaper" href="https://mathiasbynens.be/notes/prototypes"> + The follow-up article covering how engines optimize prototype chain lookups. Essential reading for understanding object-oriented JavaScript performance. + </Card> + <Card title="Launching Ignition and TurboFan" icon="newspaper" href="https://v8.dev/blog/launching-ignition-and-turbofan"> + V8 team's announcement of the Ignition + TurboFan pipeline. Explains why the new architecture is faster and uses less memory. + </Card> + <Card title="Trash talk: the Orinoco garbage collector" icon="newspaper" href="https://v8.dev/blog/trash-talk"> + Deep dive into V8's modern garbage collector. Covers parallel, incremental, and concurrent techniques that minimize pause times. + </Card> + <Card title="How V8 optimizes array operations" icon="newspaper" href="https://v8.dev/blog/elements-kinds"> + V8 blog post explaining different "elements kinds" for arrays and how to write array code that V8 can optimize effectively. + </Card> + <Card title="Blazingly fast parsing, part 1: optimizing the scanner" icon="newspaper" href="https://v8.dev/blog/scanner"> + How V8 optimizes the first stage of code processing. Shows the engineering that makes JavaScript parsing fast. </Card> </CardGroup> -- [How JavaScript Code is executed? How Javascript works behind the scenes](https://youtu.be/iLWTnMzWtj4) -- [Understanding the V8 JavaScript Engine - freeCodeCamp Talks](https://www.youtube.com/watch?v=xckH5s3UuX4) -- [JavaScript Under The Hood - JavaScript Engine Overview - Traversy Media](https://www.youtube.com/watch?v=oc6faXVc54E) -- [Arindam Paul - JavaScript VM internals, EventLoop, Async and ScopeChains](https://www.youtube.com/watch?v=QyUFheng6J0) +## Videos + +<CardGroup cols={2}> + <Card title="JavaScript Engines: The Good Parts" icon="video" href="https://www.youtube.com/watch?v=5nmpokoRaZI"> + Mathias Bynens and Benedikt Meurer at JSConf EU 2018. The definitive talk on JavaScript engine internals with beautiful visualizations of shapes, ICs, and optimization. + </Card> + <Card title="JS Engine EXPOSED — Google's V8 Architecture" icon="video" href="https://www.youtube.com/watch?v=2WJL19wDH68"> + Akshay Saini's Namaste JavaScript episode on V8. Beginner-friendly explanation of parsing, compilation, and the execution pipeline. + </Card> + <Card title="Understanding the V8 JavaScript Engine" icon="video" href="https://www.youtube.com/watch?v=xckH5s3UuX4"> + freeCodeCamp talk covering V8's architecture from a Node.js perspective. Great for understanding how the engine powers server-side JavaScript. + </Card> + <Card title="A Sneak Peek Into Super Fast V8 Internals" icon="video" href="https://www.youtube.com/watch?v=wz7Znu6tqFw"> + Chrome DevSummit talk showing how V8 optimizes real-world patterns. Includes profiling examples and optimization tips. + </Card> +</CardGroup> diff --git a/tests/fundamentals/javascript-engines/javascript-engines.test.js b/tests/fundamentals/javascript-engines/javascript-engines.test.js new file mode 100644 index 00000000..85f56470 --- /dev/null +++ b/tests/fundamentals/javascript-engines/javascript-engines.test.js @@ -0,0 +1,592 @@ +import { describe, it, expect } from 'vitest' + +describe('JavaScript Engines', () => { + describe('Basic Examples from Documentation', () => { + it('should demonstrate basic function execution (opening example)', () => { + // From the opening of the documentation + function greet(name) { + return "Hello, " + name + "!" + } + + expect(greet("World")).toBe("Hello, World!") + expect(greet("JavaScript")).toBe("Hello, JavaScript!") + expect(greet("V8")).toBe("Hello, V8!") + }) + }) + + describe('Object Shape Consistency', () => { + it('should create objects with consistent property order', () => { + // Objects with same properties in same order share hidden classes + const point1 = { x: 1, y: 2 } + const point2 = { x: 5, y: 10 } + + // Both should have same property keys in same order + expect(Object.keys(point1)).toEqual(['x', 'y']) + expect(Object.keys(point2)).toEqual(['x', 'y']) + expect(Object.keys(point1)).toEqual(Object.keys(point2)) + }) + + it('should show different key order for differently created objects', () => { + // Property order matters for hidden classes + const a = { x: 1, y: 2 } + const b = { y: 2, x: 1 } + + // Keys are in different order + expect(Object.keys(a)).toEqual(['x', 'y']) + expect(Object.keys(b)).toEqual(['y', 'x']) + + // These have DIFFERENT hidden classes in V8! + expect(Object.keys(a)).not.toEqual(Object.keys(b)) + }) + + it('should maintain consistent shapes with factory functions', () => { + // Factory functions create consistent shapes (engine-friendly) + function createPoint(x, y) { + return { x, y } + } + + const p1 = createPoint(1, 2) + const p2 = createPoint(3, 4) + const p3 = createPoint(5, 6) + + // All have identical structure + expect(Object.keys(p1)).toEqual(Object.keys(p2)) + expect(Object.keys(p2)).toEqual(Object.keys(p3)) + }) + + it('should demonstrate transition chains when adding properties', () => { + const obj = {} + expect(Object.keys(obj)).toEqual([]) + + obj.x = 1 + expect(Object.keys(obj)).toEqual(['x']) + + obj.y = 2 + expect(Object.keys(obj)).toEqual(['x', 'y']) + + // Each step creates a new hidden class (transition chain) + }) + + it('should compare Pattern A (object literal) vs Pattern B (empty object + properties)', () => { + // Pattern A: Object literal - creates shape immediately + function createPointA(x, y) { + return { x: x, y: y } + } + + // Pattern B: Empty object + property additions - goes through transitions + function createPointB(x, y) { + const point = {} + point.x = x + point.y = y + return point + } + + const pointA = createPointA(1, 2) + const pointB = createPointB(3, 4) + + // Both produce same final shape + expect(Object.keys(pointA)).toEqual(['x', 'y']) + expect(Object.keys(pointB)).toEqual(['x', 'y']) + + // Both work correctly + expect(pointA.x).toBe(1) + expect(pointA.y).toBe(2) + expect(pointB.x).toBe(3) + expect(pointB.y).toBe(4) + + // Pattern A is more engine-friendly because: + // - V8 can optimize object literals with known properties + // - Pattern B goes through 3 hidden class transitions: {} -> {x} -> {x,y} + }) + }) + + describe('Type Consistency', () => { + it('should demonstrate consistent number operations', () => { + // V8 optimizes for consistent types + function add(a, b) { + return a + b + } + + // Consistent number usage (monomorphic, fast) + expect(add(1, 2)).toBe(3) + expect(add(3, 4)).toBe(7) + expect(add(5, 6)).toBe(11) + }) + + it('should handle type changes (triggers deoptimization)', () => { + function process(x) { + return x + x + } + + // Numbers + expect(process(5)).toBe(10) + expect(process(10)).toBe(20) + + // Strings (type change - would trigger deoptimization in V8) + expect(process("hello")).toBe("hellohello") + + // Mixed usage works but is slower due to deoptimization + }) + + it('should demonstrate dynamic object shapes with process function', () => { + // From JIT compilation section - shows why JS needs JIT + function process(x) { + return x.value * 2 + } + + // Object with number value + expect(process({ value: 10 })).toBe(20) + + // Object with string value (NaN result) + expect(process({ value: "hello" })).toBeNaN() + + // Different shape (extra property) - still works + expect(process({ value: 10, extra: 5 })).toBe(20) + + // Even more different shape + expect(process({ value: 5, a: 1, b: 2 })).toBe(10) + + // This demonstrates why JavaScript needs JIT: + // - x could be any object shape + // - x.value could be any type + // - AOT compilation can't optimize for all possibilities + }) + + it('should show typeof consistency', () => { + let num = 42 + expect(typeof num).toBe('number') + + // Changing types is valid JS but can cause deoptimization + // let num = "forty-two" // Would change type + + // Better: use separate variables + const numValue = 42 + const strValue = "forty-two" + + expect(typeof numValue).toBe('number') + expect(typeof strValue).toBe('string') + }) + }) + + describe('Array Optimization', () => { + it('should create dense arrays (engine-friendly)', () => { + // Dense array - all indices filled, same type + const dense = [1, 2, 3, 4, 5] + + expect(dense.length).toBe(5) + expect(dense[0]).toBe(1) + expect(dense[4]).toBe(5) + + // V8 can use optimized "packed" array representation + }) + + it('should demonstrate sparse arrays (slower)', () => { + // Sparse array with holes - V8 uses slower dictionary mode + const sparse = [] + sparse[0] = 1 + sparse[100] = 2 + + expect(sparse.length).toBe(101) + expect(sparse[0]).toBe(1) + expect(sparse[50]).toBe(undefined) // Hole + expect(sparse[100]).toBe(2) + + // This creates 99 "holes" - less efficient + }) + + it('should show typed array benefits', () => { + // Typed arrays are always optimized (single type, no holes) + const int32Array = new Int32Array([1, 2, 3, 4, 5]) + + expect(int32Array.length).toBe(5) + expect(int32Array[0]).toBe(1) + + // All elements guaranteed to be 32-bit integers + }) + + it('should demonstrate mixed-type arrays (polymorphic)', () => { + // Mixed types require more generic handling + const mixed = [1, "two", 3, null, { four: 4 }] + + expect(typeof mixed[0]).toBe('number') + expect(typeof mixed[1]).toBe('string') + expect(typeof mixed[3]).toBe('object') + expect(typeof mixed[4]).toBe('object') + + // V8 can't assume element types - slower operations + }) + + it('should preserve array type with consistent operations', () => { + const numbers = [1, 2, 3, 4, 5] + + // map preserves array structure + const doubled = numbers.map(n => n * 2) + expect(doubled).toEqual([2, 4, 6, 8, 10]) + + // filter preserves type consistency + const filtered = numbers.filter(n => n > 2) + expect(filtered).toEqual([3, 4, 5]) + }) + }) + + describe('Property Access Patterns', () => { + it('should demonstrate monomorphic property access', () => { + // Monomorphic: always same object shape + function getX(obj) { + return obj.x + } + + // All objects have same shape - fastest IC state + expect(getX({ x: 1, y: 2 })).toBe(1) + expect(getX({ x: 3, y: 4 })).toBe(3) + expect(getX({ x: 5, y: 6 })).toBe(5) + }) + + it('should show polymorphic access (multiple shapes)', () => { + function getX(obj) { + return obj.x + } + + // Different shapes - polymorphic IC + expect(getX({ x: 1 })).toBe(1) // Shape A + expect(getX({ x: 2, y: 3 })).toBe(2) // Shape B + expect(getX({ x: 4, y: 5, z: 6 })).toBe(4) // Shape C + + // Still works, but inline cache has multiple entries + }) + + it('should demonstrate computed property access', () => { + const obj = { a: 1, b: 2, c: 3 } + + // Direct property access (faster) + expect(obj.a).toBe(1) + + // Computed property access (slightly slower but necessary for dynamic keys) + const key = 'b' + expect(obj[key]).toBe(2) + }) + + it('should demonstrate megamorphic access (many different shapes)', () => { + function getX(obj) { + return obj.x + } + + // Every call has a completely different shape + // This would cause megamorphic IC state in V8 + expect(getX({ x: 1 })).toBe(1) + expect(getX({ x: 2, a: 1 })).toBe(2) + expect(getX({ x: 3, b: 2 })).toBe(3) + expect(getX({ x: 4, c: 3 })).toBe(4) + expect(getX({ x: 5, d: 4 })).toBe(5) + expect(getX({ x: 6, e: 5 })).toBe(6) + expect(getX({ x: 7, f: 6 })).toBe(7) + + // IC gives up after too many shapes - falls back to generic lookup + // Still works correctly, just slower than monomorphic/polymorphic + }) + }) + + describe('Class vs Object Literal Shapes', () => { + it('should create consistent shapes with classes', () => { + class Point { + constructor(x, y) { + this.x = x + this.y = y + } + } + + const p1 = new Point(1, 2) + const p2 = new Point(3, 4) + const p3 = new Point(5, 6) + + // All instances have identical shape + expect(Object.keys(p1)).toEqual(['x', 'y']) + expect(Object.keys(p2)).toEqual(['x', 'y']) + expect(Object.keys(p3)).toEqual(['x', 'y']) + }) + + it('should show prototype chain optimization', () => { + class Animal { + speak() { + return 'sound' + } + } + + class Dog extends Animal { + speak() { + return 'woof' + } + } + + const dog = new Dog() + + // Method lookup follows prototype chain + expect(dog.speak()).toBe('woof') + expect(dog instanceof Dog).toBe(true) + expect(dog instanceof Animal).toBe(true) + }) + }) + + describe('Avoiding Deoptimization Patterns', () => { + it('should show delete causing shape change', () => { + const user = { name: 'Alice', age: 30, temp: true } + + expect(Object.keys(user)).toEqual(['name', 'age', 'temp']) + + // delete changes hidden class (bad for performance) + delete user.temp + + expect(Object.keys(user)).toEqual(['name', 'age']) + expect(user.temp).toBe(undefined) + + // Better alternative: set to undefined + const user2 = { name: 'Bob', age: 25, temp: true } + user2.temp = undefined // Hidden class stays the same + + expect('temp' in user2).toBe(true) // Property still exists + expect(user2.temp).toBe(undefined) + }) + + it('should demonstrate object spread for immutable updates', () => { + const original = { x: 1, y: 2, z: 3 } + + // Instead of mutating, create new object + const updated = { ...original, z: 10 } + + expect(original.z).toBe(3) // Original unchanged + expect(updated.z).toBe(10) // New object with update + + // Both have consistent shapes + expect(Object.keys(original)).toEqual(['x', 'y', 'z']) + expect(Object.keys(updated)).toEqual(['x', 'y', 'z']) + }) + + it('should show inconsistent shapes with conditional property assignment', () => { + // Bad pattern: conditional property assignment creates different shapes + function createUserBad(name, age) { + const user = {} + if (name) user.name = name + if (age) user.age = age + return user + } + + const user1 = createUserBad('Alice', 30) + const user2 = createUserBad('Bob', null) // Only name + const user3 = createUserBad(null, 25) // Only age + const user4 = createUserBad(null, null) // Empty + + // All have different shapes! + expect(Object.keys(user1)).toEqual(['name', 'age']) + expect(Object.keys(user2)).toEqual(['name']) + expect(Object.keys(user3)).toEqual(['age']) + expect(Object.keys(user4)).toEqual([]) + + // Compare with good pattern + function createUserGood(name, age) { + return { name, age } // Always same shape + } + + const goodUser1 = createUserGood('Alice', 30) + const goodUser2 = createUserGood('Bob', null) + const goodUser3 = createUserGood(null, 25) + + // Same shape regardless of values (nulls are still properties) + expect(Object.keys(goodUser1)).toEqual(['name', 'age']) + expect(Object.keys(goodUser2)).toEqual(['name', 'age']) + expect(Object.keys(goodUser3)).toEqual(['name', 'age']) + }) + }) + + describe('Function Optimization Patterns', () => { + it('should demonstrate consistent function signatures', () => { + function multiply(a, b) { + return a * b + } + + // Consistent argument types enable optimization + expect(multiply(2, 3)).toBe(6) + expect(multiply(4, 5)).toBe(20) + expect(multiply(6, 7)).toBe(42) + }) + + it('should show inlining with small functions', () => { + // Small functions are candidates for inlining + function square(x) { + return x * x + } + + function sumOfSquares(a, b) { + return square(a) + square(b) + } + + // V8 may inline square() into sumOfSquares() + expect(sumOfSquares(3, 4)).toBe(25) // 9 + 16 + }) + + it('should demonstrate closure optimization', () => { + function createAdder(x) { + // Closure captures x + return function(y) { + return x + y + } + } + + const add5 = createAdder(5) + const add10 = createAdder(10) + + // Closures with consistent captured values can be optimized + expect(add5(3)).toBe(8) + expect(add10(3)).toBe(13) + }) + }) + + describe('Garbage Collection Concepts', () => { + it('should demonstrate object references', () => { + let obj = { data: 'important' } + const ref = obj + + // Both point to same object + expect(ref.data).toBe('important') + + // Setting obj to null doesn't GC the object + // because ref still holds a reference + obj = null + expect(ref.data).toBe('important') + }) + + it('should show circular references', () => { + const a = { name: 'a' } + const b = { name: 'b' } + + // Circular reference + a.ref = b + b.ref = a + + expect(a.ref.name).toBe('b') + expect(b.ref.name).toBe('a') + expect(a.ref.ref.name).toBe('a') + + // Modern GC can handle circular references + // (mark-and-sweep doesn't rely on reference counting) + }) + + it('should demonstrate WeakRef for GC-friendly references', () => { + // WeakRef allows object to be garbage collected + let obj = { data: 'temporary' } + const weakRef = new WeakRef(obj) + + // Can access while object exists + expect(weakRef.deref()?.data).toBe('temporary') + + // Note: We can't force GC in tests, but WeakRef + // allows the referenced object to be collected + }) + + it('should show Map vs WeakMap for memory management', () => { + // Regular Map holds strong references + const map = new Map() + let key = { id: 1 } + map.set(key, 'value') + + expect(map.get(key)).toBe('value') + + // WeakMap allows keys to be garbage collected + const weakMap = new WeakMap() + let weakKey = { id: 2 } + weakMap.set(weakKey, 'value') + + expect(weakMap.get(weakKey)).toBe('value') + + // If weakKey is set to null and no other references exist, + // the entry can be garbage collected + }) + }) + + describe('JIT Compilation Observable Behavior', () => { + it('should handle hot function calls', () => { + function hotFunction(n) { + return n * 2 + } + + // Simulating many calls (would trigger JIT in real V8) + let result = 0 + for (let i = 0; i < 1000; i++) { + result = hotFunction(i) + } + + expect(result).toBe(1998) // Last iteration: 999 * 2 + }) + + it('should demonstrate deoptimization scenario', () => { + function add(a, b) { + return a + b + } + + // Many calls with numbers (would be optimized for numbers) + for (let i = 0; i < 100; i++) { + add(i, i + 1) + } + + // Then a call with strings (triggers deoptimization) + const result = add('hello', 'world') + + // Still produces correct result despite deoptimization + expect(result).toBe('helloworld') + }) + + it('should show consistent returns for optimization', () => { + // Always returns same type (optimizer-friendly) + function maybeDouble(n, shouldDouble) { + if (shouldDouble) { + return n * 2 + } + return n // Always returns number + } + + expect(maybeDouble(5, true)).toBe(10) + expect(maybeDouble(5, false)).toBe(5) + expect(typeof maybeDouble(5, true)).toBe('number') + expect(typeof maybeDouble(5, false)).toBe('number') + }) + }) + + describe('Hidden Class Interview Questions', () => { + it('should explain why object literal order matters', () => { + // Creating objects with different property orders + function createA() { + return { first: 1, second: 2 } + } + + function createB() { + return { second: 2, first: 1 } + } + + const objA = createA() + const objB = createB() + + // Same values, but different hidden classes + expect(objA.first).toBe(objB.first) + expect(objA.second).toBe(objB.second) + + // Property order is different + expect(Object.keys(objA)[0]).toBe('first') + expect(Object.keys(objB)[0]).toBe('second') + }) + + it('should demonstrate best practice with constructor pattern', () => { + // Constructor ensures consistent shape + function User(name, email, age) { + this.name = name + this.email = email + this.age = age + } + + const user1 = new User('Alice', 'alice@example.com', 30) + const user2 = new User('Bob', 'bob@example.com', 25) + + // Guaranteed same property order + expect(Object.keys(user1)).toEqual(['name', 'email', 'age']) + expect(Object.keys(user2)).toEqual(['name', 'email', 'age']) + }) + }) +}) From 6c97c8fe577904360f6af1ac62d72072a3106797 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:49:03 -0300 Subject: [PATCH 093/128] docs: add comprehensive Error Handling concept page with tests - Add complete error-handling.mdx covering try/catch/finally, Error types, custom errors, async error patterns, and best practices - Include 44 passing tests validating all code examples - Cover built-in error types: TypeError, ReferenceError, SyntaxError, RangeError, URIError, AggregateError - Document common mistakes with clear wrong/right code examples - Add real-world patterns: retry, validation errors, graceful degradation - Include ASCII diagram, tables, and test-your-knowledge questions --- docs/concepts/error-handling.mdx | 999 +++++++++++++++++- .../error-handling/error-handling.test.js | 902 ++++++++++++++++ 2 files changed, 1870 insertions(+), 31 deletions(-) create mode 100644 tests/advanced-topics/error-handling/error-handling.test.js diff --git a/docs/concepts/error-handling.mdx b/docs/concepts/error-handling.mdx index f074bca0..f3f52142 100644 --- a/docs/concepts/error-handling.mdx +++ b/docs/concepts/error-handling.mdx @@ -1,62 +1,999 @@ --- -title: "Error Handling: Managing Errors & Exceptions in JavaScript" -sidebarTitle: "Error Handling: Managing Errors & Exceptions" -description: "Learn JavaScript error handling — try/catch/finally, Error types, custom errors, and async error patterns. Master throwing, catching, and handling errors in sync and async code." +title: "Error Handling: Managing Errors Gracefully in JavaScript" +sidebarTitle: "Error Handling: Managing Errors Gracefully" +description: "Learn JavaScript error handling with try/catch/finally. Understand Error types, custom errors, async error patterns, and best practices for robust code." --- -## Overview +What happens when something goes wrong in your JavaScript code? How do you prevent one small error from crashing your entire application? How do you give users helpful feedback instead of a cryptic error message? -**Error handling** is how you detect, respond to, and recover from errors in your code. JavaScript provides the `try...catch...finally` statement for synchronous errors, and special patterns for handling Promise rejections and async/await errors. Proper error handling makes your applications more robust and debuggable. +```javascript +// Without error handling - your app crashes +const userData = JSON.parse('{ invalid json }') // SyntaxError! + +// With error handling - you stay in control +try { + const userData = JSON.parse('{ invalid json }') +} catch (error) { + console.log('Could not parse user data:', error.message) + // Show user a friendly message, use default data, etc. +} +``` + +**[Error handling](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling#exception_handling_statements)** is how you detect, respond to, and recover from errors in your code. JavaScript provides the `try...catch...finally` statement for synchronous errors and special patterns for handling async errors in [Promises](/concepts/promises) and [async/await](/concepts/async-await). <Info> **What you'll learn in this guide:** -- The `try...catch...finally` statement -- Built-in Error types (TypeError, ReferenceError, SyntaxError, etc.) -- Throwing errors with the `throw` statement -- Creating custom Error classes -- Async error handling (Promise `.catch()`, async/await) -- Global error handlers (`window.onerror`, `unhandledrejection`) -- Error handling best practices +- The `try...catch...finally` statement and when to use each block +- The Error object and its properties (name, message, stack) +- Built-in Error types: TypeError, ReferenceError, SyntaxError, and more +- How to throw your own errors with meaningful messages +- Creating custom Error classes for better error categorization +- Error handling patterns for async code +- Global error handlers for catching uncaught errors +- Common mistakes and real-world patterns +</Info> + +<Warning> +**Helpful prerequisite:** This guide covers async error handling briefly. For a deeper dive into async patterns, check out [Promises](/concepts/promises) and [async/await](/concepts/async-await) first. +</Warning> + +--- + +## What is Error Handling in JavaScript? + +Errors happen. Users enter invalid data, network requests fail, APIs return unexpected responses, and sometimes we just make typos. **Error handling** is your strategy for detecting, responding to, and recovering from these problems gracefully. In JavaScript, you use the `try...catch` statement to catch errors, the `throw` statement to create them, and the `Error` object to describe what went wrong. + +<CardGroup cols={2}> + <Card title="Error — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"> + Official MDN documentation for the Error object + </Card> + <Card title="try...catch — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"> + MDN documentation for the try...catch statement + </Card> +</CardGroup> + +--- + +## The Safety Net Analogy + +Think of error handling like a trapeze act at a circus. The acrobat (your code) performs risky moves high above the ground. The safety net (your catch block) is there to catch them if they fall. And no matter what happens, the show must go on (your finally block). + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE SAFETY NET ANALOGY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ try { TRAPEZE ACT │ +│ riskyMove() ┌─────────┐ │ +│ } │ ACROBAT │ ← Your risky code │ +│ └────┬────┘ │ +│ │ │ +│ catch (error) { ▼ FALLS! │ +│ recover() ═══════════════════════ │ +│ } SAFETY NET ← Catches the error │ +│ │ +│ finally { The show continues! │ +│ cleanup() (runs no matter what) │ +│ } │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +| Circus | JavaScript | Purpose | +|--------|------------|---------| +| Trapeze act | `try` block | Code that might fail | +| Safety net | `catch` block | Handles the error if one occurs | +| Show continues | `finally` block | Cleanup that always runs | +| Acrobat falls | Error is thrown | Something went wrong | + +--- + +## The try/catch/finally Statement + +The **[`try...catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch)** statement is JavaScript's primary tool for handling errors. Here's the full syntax: + +```javascript +try { + // Code that might throw an error + const result = riskyOperation() + console.log(result) + +} catch (error) { + // Code that runs if an error is thrown + console.error('Something went wrong:', error.message) + +} finally { + // Code that ALWAYS runs, error or not + cleanup() +} +``` + +### The try Block + +The `try` block contains code that might throw an error. If an error occurs, execution immediately jumps to the `catch` block. + +```javascript +try { + console.log('Starting...') // Runs + JSON.parse('{ bad json }') // Error! Jump to catch + console.log('This never runs') // Skipped +} +``` + +### The catch Block + +The `catch` block receives the error object and handles it. This is where you log errors, show user messages, or attempt recovery. + +```javascript +try { + const data = JSON.parse(userInput) +} catch (error) { + // error contains information about what went wrong + console.log(error.name) // "SyntaxError" + console.log(error.message) // "Unexpected token b in JSON..." + + // You can recover gracefully + const data = { fallback: true } +} +``` + +<Tip> +**Optional catch binding:** If you don't need the error object, you can omit it (ES2019+): + +```javascript +try { + JSON.parse(maybeJson) +} catch { + // No (error) parameter needed if you don't use it + return null +} +``` +</Tip> + +### The finally Block + +The `finally` block always runs, whether an error occurred or not. It's perfect for cleanup code like closing connections or hiding loading spinners. + +```javascript +let isLoading = true + +try { + const data = await fetchData() + displayData(data) +} catch (error) { + showErrorMessage(error) +} finally { + // This runs no matter what! + isLoading = false + hideLoadingSpinner() +} +``` + +<Warning> +**finally runs even with return:** If you return from a try or catch block, finally still executes before the function returns: + +```javascript +function example() { + try { + return 'from try' + } finally { + console.log('finally runs!') // This still logs! + } +} + +example() // Logs "finally runs!", then returns "from try" +``` +</Warning> + +### try/catch Only Works Synchronously + +This trips people up: `try/catch` won't catch errors in callbacks that run later. + +```javascript +// ❌ WRONG - catch won't catch this error! +try { + setTimeout(() => { + throw new Error('Async error') + }, 1000) +} catch (error) { + console.log('This never runs') +} + +// ✓ CORRECT - try/catch inside the callback +setTimeout(() => { + try { + throw new Error('Async error') + } catch (error) { + console.log('Caught:', error.message) + } +}, 1000) +``` + +For async code, see the [Async Error Handling](#async-error-handling) section. + +--- + +## The Error Object + +When an error occurs, JavaScript creates an **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** object with information about what went wrong. + +### Error Properties + +| Property | Description | Example | +|----------|-------------|---------| +| `name` | The type of error | `"TypeError"`, `"ReferenceError"` | +| `message` | Human-readable description | `"Cannot read property 'x' of undefined"` | +| `stack` | Call stack when error occurred (non-standard but widely supported) | Shows file names, line numbers | +| `cause` | Original error (ES2022+) | Used for error chaining | + +```javascript +try { + undefinedVariable +} catch (error) { + console.log(error.name) // "ReferenceError" + console.log(error.message) // "undefinedVariable is not defined" + console.log(error.stack) // Full stack trace with line numbers +} +``` + +The `stack` property is essential for debugging. It shows exactly where the error occurred and the chain of function calls that led to it. + +--- + +## Built-in Error Types + +JavaScript has several built-in error types. Knowing them helps you understand what went wrong and how to fix it. + +| Error Type | When It Occurs | Common Cause | +|------------|----------------|--------------| +| **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** | Generic error | Base class, used for custom errors | +| **[TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError)** | Wrong type | `null.foo`, calling non-function | +| **[ReferenceError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError)** | Invalid reference | Using undefined variable | +| **[SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError)** | Invalid syntax | Bad JSON, missing brackets | +| **[RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError)** | Value out of range | `new Array(-1)` | +| **[URIError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError)** | Bad URI encoding | `decodeURIComponent('%')` | +| **[AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError)** | Multiple errors | `Promise.any()` all reject | + +<AccordionGroup> + <Accordion title="TypeError - The most common error"> + Occurs when a value is not the expected type, like calling a method on `null` or `undefined`: + + ```javascript + const user = null + console.log(user.name) // TypeError: Cannot read property 'name' of null + + const notAFunction = 42 + notAFunction() // TypeError: notAFunction is not a function + ``` + + **Fix:** Check if values exist before using them: + ```javascript + console.log(user?.name) // undefined (no error) + ``` + </Accordion> + + <Accordion title="ReferenceError - Variable doesn't exist"> + Occurs when you try to use a variable that hasn't been declared: + + ```javascript + console.log(userName) // ReferenceError: userName is not defined + ``` + + **Common causes:** Typos in variable names, forgetting to import, using variables before declaration. + </Accordion> + + <Accordion title="SyntaxError - Invalid code or JSON"> + Occurs when code has invalid syntax or when parsing invalid JSON: + + ```javascript + JSON.parse('{ name: "John" }') // SyntaxError: Unexpected token n + // JSON requires double quotes: { "name": "John" } + + JSON.parse('') // SyntaxError: Unexpected end of JSON input + ``` + + **Note:** Syntax errors in your source code are caught at parse time, not runtime. `try/catch` only catches runtime SyntaxErrors like invalid JSON. + </Accordion> + + <Accordion title="RangeError - Value out of bounds"> + Occurs when a value is outside its allowed range: + + ```javascript + new Array(-1) // RangeError: Invalid array length + (1.5).toFixed(200) // RangeError: precision out of range (max is 100) + 'x'.repeat(Infinity) // RangeError: Invalid count value + ``` + </Accordion> +</AccordionGroup> + +--- + +## The throw Statement + +The **[`throw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw)** statement lets you create your own errors. When you throw, execution stops and jumps to the nearest catch block. + +```javascript +function divide(a, b) { + if (b === 0) { + throw new Error('Cannot divide by zero') + } + return a / b +} + +try { + const result = divide(10, 0) +} catch (error) { + console.log(error.message) // "Cannot divide by zero" +} +``` + +### Always Throw Error Objects + +Technically you can throw anything, but always throw Error objects. They include a stack trace for debugging. + +```javascript +// ❌ BAD - No stack trace, hard to debug +throw 'Something went wrong' +throw 404 +throw { message: 'Error' } + +// ✓ GOOD - Includes stack trace +throw new Error('Something went wrong') +throw new TypeError('Expected a string') +throw new RangeError('Value must be between 0 and 100') +``` + +### Creating Meaningful Error Messages + +Good error messages tell you what went wrong and ideally how to fix it: + +```javascript +// ❌ Vague +throw new Error('Invalid input') + +// ✓ Specific +throw new Error('Email address is invalid: missing @ symbol') +throw new TypeError(`Expected string but got ${typeof value}`) +throw new RangeError(`Age must be between 0 and 150, got ${age}`) +``` + +--- + +## Custom Error Classes + +For larger applications, create custom error classes to categorize errors and add extra information. + +```javascript +class ValidationError extends Error { + constructor(message) { + super(message) + this.name = 'ValidationError' + } +} + +class NetworkError extends Error { + constructor(message, statusCode) { + super(message) + this.name = 'NetworkError' + this.statusCode = statusCode + } +} +``` + +### The Auto-Naming Pattern + +Instead of manually setting `this.name` in every class, use the constructor name: + +```javascript +class AppError extends Error { + constructor(message, options) { + super(message, options) + this.name = this.constructor.name // Automatically uses class name + } +} + +class ValidationError extends AppError {} +class DatabaseError extends AppError {} +class NetworkError extends AppError {} + +// All have correct names automatically +throw new ValidationError('Invalid email') // error.name === "ValidationError" +``` + +### Using instanceof for Error Handling + +Custom errors let you handle different error types differently: + +```javascript +try { + await saveUser(userData) +} catch (error) { + if (error instanceof ValidationError) { + // Show validation message to user + showFieldErrors(error.fields) + } else if (error instanceof NetworkError) { + // Network issue - maybe retry + showRetryButton() + } else { + // Unknown error - log and show generic message + console.error('Unexpected error:', error) + showGenericError() + } +} +``` + +### Error Chaining with cause (ES2022+) + +When catching and re-throwing errors, preserve the original error using the `cause` option: + +```javascript +async function fetchUserData(userId) { + try { + const response = await fetch(`/api/users/${userId}`) + return await response.json() + } catch (error) { + // Wrap the original error with more context + throw new Error(`Failed to load user ${userId}`, { cause: error }) + } +} + +// Later, you can access the original error +try { + await fetchUserData(123) +} catch (error) { + console.log(error.message) // "Failed to load user 123" + console.log(error.cause.message) // Original fetch error +} +``` + +--- + +## Async Error Handling + +Error handling works differently with asynchronous code. Here's a quick overview. For comprehensive coverage, see our [Promises](/concepts/promises) and [async/await](/concepts/async-await) guides. + +### With Promises: .catch() + +Use `.catch()` to handle errors in Promise chains: + +```javascript +fetch('/api/users') + .then(response => response.json()) + .then(users => displayUsers(users)) + .catch(error => { + // Catches errors from fetch, json parsing, or displayUsers + console.error('Failed to load users:', error) + }) + .finally(() => { + hideLoadingSpinner() + }) +``` + +### With async/await: try/catch + +With async/await, use regular try/catch blocks: + +```javascript +async function loadUsers() { + try { + const response = await fetch('/api/users') + const users = await response.json() + return users + } catch (error) { + console.error('Failed to load users:', error) + throw error // Re-throw if caller should handle it + } +} +``` + +### The fetch() Trap: Check response.ok + +This catches many developers off guard: **`fetch()` doesn't throw on HTTP errors** like 404 or 500. It only throws on network failures. + +```javascript +// ❌ WRONG - This won't catch 404 or 500 errors! +try { + const response = await fetch('/api/users/999') + const user = await response.json() // Might fail on error response +} catch (error) { + // Only catches network errors, not HTTP errors +} + +// ✓ CORRECT - Check response.ok +try { + const response = await fetch('/api/users/999') + + if (!response.ok) { + throw new Error(`HTTP error: ${response.status}`) + } + + const user = await response.json() +} catch (error) { + // Now catches both network AND HTTP errors + console.error('Request failed:', error.message) +} +``` + +<Warning> +**The #1 async mistake:** Using `forEach` with async callbacks doesn't work as expected. Use `for...of` for sequential or `Promise.all` for parallel. See our [async/await guide](/concepts/async-await) for details. +</Warning> + +--- + +## Global Error Handlers + +Global error handlers catch errors that slip through your try/catch blocks. They're a safety net of last resort, not a replacement for proper error handling. + +### window.onerror - Synchronous Errors + +Catches uncaught errors in the browser: + +```javascript +window.onerror = function(message, source, lineno, colno, error) { + console.log('Uncaught error:', message) + console.log('Source:', source, 'Line:', lineno) + + // Send to error tracking service + logErrorToService(error) + + // Return true to prevent default browser error handling + return true +} +``` + +### unhandledrejection - Promise Rejections + +Catches unhandled Promise rejections: + +```javascript +window.addEventListener('unhandledrejection', event => { + console.warn('Unhandled promise rejection:', event.reason) + + // Prevent the default browser warning + event.preventDefault() + + // Log to error tracking service + logErrorToService(event.reason) +}) +``` + +<Tip> +**When to use global handlers:** +- Logging errors to a service like Sentry or LogRocket +- Showing a generic "something went wrong" message +- Tracking errors in production + +**Not for:** Regular error handling. Always prefer specific try/catch blocks. +</Tip> + +--- + +## Common Mistakes + +### Mistake 1: Empty catch Blocks (Swallowing Errors) + +```javascript +// ❌ WRONG - Error is silently lost +try { + riskyOperation() +} catch (error) { + // Nothing here - you'll never know something failed +} + +// ✓ CORRECT - At minimum, log the error +try { + riskyOperation() +} catch (error) { + console.error('Operation failed:', error) +} +``` + +### Mistake 2: Catching Too Broadly + +```javascript +// ❌ WRONG - Hides programming bugs +try { + processData(data) + undefinedVriable // Typo! This bug is now hidden +} catch (error) { + return 'Something went wrong' +} + +// ✓ CORRECT - Only catch expected errors +try { + return JSON.parse(userInput) +} catch (error) { + if (error instanceof SyntaxError) { + return null // Expected: invalid JSON + } + throw error // Unexpected: re-throw +} +``` + +### Mistake 3: Throwing Strings Instead of Errors + +```javascript +// ❌ WRONG - No stack trace +throw 'User not found' + +// ✓ CORRECT - Has stack trace for debugging +throw new Error('User not found') +``` + +### Mistake 4: Not Re-throwing When Needed + +```javascript +// ❌ WRONG - Caller doesn't know an error occurred +async function fetchData() { + try { + return await fetch('/api/data') + } catch (error) { + console.log('Error:', error) + // Returns undefined - caller thinks it succeeded! + } +} + +// ✓ CORRECT - Re-throw or return meaningful value +async function fetchData() { + try { + return await fetch('/api/data') + } catch (error) { + console.log('Error:', error) + throw error // Let caller handle it + // OR: return null with explicit meaning + } +} +``` + +### Mistake 5: Forgetting try/catch is Synchronous + +```javascript +// ❌ WRONG - Won't catch async errors +try { + setTimeout(() => { + throw new Error('Async error') // Uncaught! + }, 1000) +} catch (error) { + console.log('Never runs') +} + +// ✓ CORRECT - Put try/catch inside callback +setTimeout(() => { + try { + throw new Error('Async error') + } catch (error) { + console.log('Caught:', error.message) + } +}, 1000) +``` + +--- + +## Real-World Patterns + +### Retry Pattern + +Automatically retry failed operations, useful for flaky network requests: + +```javascript +async function fetchWithRetry(url, retries = 3) { + for (let i = 0; i < retries; i++) { + try { + const response = await fetch(url) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + return await response.json() + } catch (error) { + if (i === retries - 1) throw error // Last attempt, give up + + // Wait before retrying (exponential backoff) + await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))) + } + } +} +``` + +### Validation Error Pattern + +Collect multiple validation errors at once: + +```javascript +class ValidationError extends Error { + constructor(errors) { + super('Validation failed') + this.name = 'ValidationError' + this.errors = errors // { email: "Invalid email", age: "Must be positive" } + } +} + +function validateUser(data) { + const errors = {} + + if (!data.email?.includes('@')) { + errors.email = 'Invalid email address' + } + if (data.age < 0) { + errors.age = 'Age must be positive' + } + + if (Object.keys(errors).length > 0) { + throw new ValidationError(errors) + } +} + +// Usage +try { + validateUser({ email: 'bad', age: -5 }) +} catch (error) { + if (error instanceof ValidationError) { + // Show errors next to form fields + Object.entries(error.errors).forEach(([field, message]) => { + showFieldError(field, message) + }) + } +} +``` + +### Graceful Degradation + +Try the ideal path, fall back to alternatives: + +```javascript +async function loadUserPreferences(userId) { + try { + // Try to fetch from API + return await fetchFromApi(`/preferences/${userId}`) + } catch (apiError) { + console.warn('API unavailable, trying cache:', apiError.message) + + try { + // Fall back to local storage + const cached = localStorage.getItem(`prefs_${userId}`) + if (cached) return JSON.parse(cached) + } catch (cacheError) { + console.warn('Cache unavailable:', cacheError.message) + } + + // Fall back to defaults + return { theme: 'light', language: 'en' } + } +} +``` + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Use try/catch for synchronous code** — Wrap risky operations and handle errors appropriately + +2. **try/catch is synchronous** — It won't catch errors in callbacks. Use `.catch()` for Promises or try/catch inside async functions + +3. **Always throw Error objects, not strings** — Error objects include stack traces that are essential for debugging + +4. **Always check response.ok with fetch** — `fetch()` doesn't throw on HTTP errors like 404 or 500 + +5. **Create custom Error classes** — They help categorize errors and add context for better handling + +6. **Use finally for cleanup** — Code in finally always runs, perfect for hiding spinners or closing connections + +7. **Don't swallow errors** — Empty catch blocks hide bugs. Always log or re-throw + +8. **Use error.cause for chaining** — Preserve original errors when wrapping them with more context + +9. **Re-throw errors you can't handle** — If you catch an error you didn't expect, re-throw it + +10. **Use global handlers as a safety net** — They're for logging and tracking, not for regular error handling </Info> +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between try/catch and Promise .catch()?"> + **Answer:** + + `try/catch` only catches **synchronous** errors. If you have async code inside the try block (like setTimeout callbacks), errors won't be caught. + + Promise `.catch()` catches **Promise rejections**, which are async. With async/await, you can use try/catch because `await` converts rejections to thrown errors. + + ```javascript + // try/catch with async/await - works! + try { + await fetch('/api/data') + } catch (error) { + // Catches rejections because await converts them + } + + // try/catch with callbacks - doesn't work! + try { + setTimeout(() => { throw new Error() }, 1000) + } catch (error) { + // Never runs - the error is thrown later + } + ``` + </Accordion> + + <Accordion title="Question 2: Why doesn't fetch() throw on 404 or 500 errors?"> + **Answer:** + + `fetch()` only throws on **network failures** (can't reach the server). HTTP errors like 404 (Not Found) or 500 (Server Error) are valid HTTP responses, so `fetch()` resolves successfully. + + You must check `response.ok` to detect HTTP errors: + + ```javascript + const response = await fetch('/api/users/999') + + if (!response.ok) { + // 404, 500, etc. + throw new Error(`HTTP error: ${response.status}`) + } + + const data = await response.json() + ``` + </Accordion> + + <Accordion title="Question 3: Why should you throw Error objects instead of strings?"> + **Answer:** + + Error objects include a **stack trace** showing where the error occurred and the chain of function calls. Strings don't have this information. + + ```javascript + throw 'Something went wrong' // No stack trace + throw new Error('Something went wrong') // Has stack trace + ``` + + The stack trace is essential for debugging, especially in production where you can't use a debugger. + </Accordion> + + <Accordion title="Question 4: What does the finally block do?"> + **Answer:** + + The `finally` block **always runs**, whether an error occurred or not, and even if there's a `return` statement in try or catch. It's ideal for cleanup code. + + ```javascript + function example() { + try { + return 'success' + } catch (error) { + return 'error' + } finally { + console.log('Cleanup!') // Always runs! + } + } + + example() // Logs "Cleanup!" then returns "success" + ``` + + Use it for: hiding loading spinners, closing connections, releasing resources. + </Accordion> + + <Accordion title="Question 5: How do you handle different error types differently?"> + **Answer:** + + Use `instanceof` to check the error type, or check `error.name`: + + ```javascript + try { + riskyOperation() + } catch (error) { + if (error instanceof TypeError) { + console.log('Type error:', error.message) + } else if (error instanceof SyntaxError) { + console.log('Syntax error:', error.message) + } else { + // Unknown error - re-throw it + throw error + } + } + ``` + + This is especially useful with custom error classes: + + ```javascript + if (error instanceof ValidationError) { + showFormErrors(error.errors) + } else if (error instanceof NetworkError) { + showOfflineMessage() + } + ``` + </Accordion> + + <Accordion title="Question 6: What's wrong with this code?"> + ```javascript + try { + const result = riskyOperation() + } catch (e) { + // Handle error + } + + console.log(result) // ??? + ``` + + **Answer:** + + `result` is scoped to the try block. It doesn't exist outside of it, so `console.log(result)` throws a ReferenceError. + + **Fix:** Declare the variable outside the try block: + + ```javascript + let result + + try { + result = riskyOperation() + } catch (e) { + result = 'fallback value' + } + + console.log(result) // Works! + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Promises" icon="handshake" href="/concepts/promises"> + Error handling with .catch() and Promise rejection patterns + </Card> + <Card title="async/await" icon="hourglass" href="/concepts/async-await"> + Using try/catch with async functions for cleaner async error handling + </Card> + <Card title="Callbacks" icon="phone" href="/concepts/callbacks"> + Error-first callbacks: the original async error handling pattern + </Card> + <Card title="Event Loop" icon="arrows-spin" href="/concepts/event-loop"> + Understand why try/catch doesn't work with async callbacks + </Card> +</CardGroup> + +--- + ## Reference <CardGroup cols={2}> <Card title="Error — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"> - MDN documentation for the Error object + Complete reference for the Error object and its properties </Card> <Card title="try...catch — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"> - MDN documentation for try...catch statement + Documentation for try, catch, and finally blocks + </Card> + <Card title="throw — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw"> + How to throw your own errors + </Card> + <Card title="Control Flow — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling"> + MDN guide covering error handling in context </Card> </CardGroup> ## Articles <CardGroup cols={2}> - <Card title="Error handling, try...catch" icon="newspaper" href="https://javascript.info/try-catch"> - By JavaScript.info + <Card title="Error handling, try...catch — JavaScript.info" icon="newspaper" href="https://javascript.info/try-catch"> + The definitive guide to JavaScript error handling. Covers everything from basics to rethrowing, with clear examples and interactive exercises. + </Card> + <Card title="Custom errors, extending Error — JavaScript.info" icon="newspaper" href="https://javascript.info/custom-errors"> + Learn to create custom error classes with proper inheritance. The wrapping exceptions pattern here is essential for larger applications. </Card> - <Card title="A Guide to Error Handling in JavaScript" icon="newspaper" href="https://www.sitepoint.com/javascript-error-handling/"> - By SitePoint + <Card title="A Definitive Guide to Handling Errors in JavaScript — Kinsta" icon="newspaper" href="https://kinsta.com/blog/errors-in-javascript/"> + Comprehensive overview covering all error types, stack traces, and production error handling strategies with middleware examples. </Card> </CardGroup> -- [JavaScript Error Handling — A Beginner's Guide — freeCodeCamp](https://www.freecodecamp.org/news/javascript-error-handling/) -- [Error Handling in JavaScript — Dmitri Pavlutin](https://dmitripavlutin.com/javascript-error-handling/) -- [Custom errors, extending Error — JavaScript.info](https://javascript.info/custom-errors) -- [A Definitive Guide to Handling Errors in JavaScript — Kinsta](https://kinsta.com/blog/errors-in-javascript/) -- [How to Handle Errors in JavaScript — Rollbar](https://rollbar.com/guides/javascript/how-to-handle-errors-in-javascript/) - ## Videos <CardGroup cols={2}> - <Card title="JavaScript Error Handling" icon="video" href="https://www.youtube.com/watch?v=blBoIyNhGvY"> - By Web Dev Simplified + <Card title="JavaScript Error Handling — Web Dev Simplified" icon="video" href="https://www.youtube.com/watch?v=blBoIyNhGvY"> + Clear 15-minute walkthrough of try/catch/finally with practical examples. Perfect if you prefer watching code being written. </Card> - <Card title="Error Handling in JavaScript" icon="video" href="https://www.youtube.com/watch?v=cFTFtuEQ-10"> - By Fireship + <Card title="try, catch, finally, throw — Fireship" icon="video" href="https://www.youtube.com/watch?v=cFTFtuEQ-10"> + Fast-paced overview of error handling fundamentals. Great for a quick refresher or introduction to the topic. + </Card> + <Card title="JavaScript Error Handling — The Coding Train" icon="video" href="https://www.youtube.com/watch?v=1Rq_LrpcgIM"> + Beginner-friendly explanation with live coding examples showing exactly when errors occur and how to handle them. </Card> </CardGroup> - -- [Try, Catch, Finally in JavaScript — techsith](https://www.youtube.com/watch?v=s6M1nd7DtCE) -- [JavaScript Error Handling — The Coding Train](https://www.youtube.com/watch?v=1Rq_LrpcgIM) diff --git a/tests/advanced-topics/error-handling/error-handling.test.js b/tests/advanced-topics/error-handling/error-handling.test.js new file mode 100644 index 00000000..f0b8efb3 --- /dev/null +++ b/tests/advanced-topics/error-handling/error-handling.test.js @@ -0,0 +1,902 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +describe('Error Handling', () => { + + // ============================================================ + // TRY/CATCH/FINALLY BASICS + // ============================================================ + + describe('try/catch/finally Basics', () => { + it('should catch errors thrown in try block', () => { + // From: The try block contains code that might throw an error + let caught = false + + try { + throw new Error('Test error') + } catch (error) { + caught = true + expect(error.message).toBe('Test error') + } + + expect(caught).toBe(true) + }) + + it('should skip catch block if no error occurs', () => { + const order = [] + + try { + order.push('try') + } catch (error) { + order.push('catch') + } + + expect(order).toEqual(['try']) + }) + + it('should stop try block execution at the error', () => { + // From: If an error occurs, execution immediately jumps to the catch block + const order = [] + + try { + order.push('before error') + throw new Error('stop') + order.push('after error') // Never runs + } catch (error) { + order.push('catch') + } + + expect(order).toEqual(['before error', 'catch']) + }) + + it('should always run finally block - success case', () => { + // From: The finally block always runs + const order = [] + + try { + order.push('try') + } catch (error) { + order.push('catch') + } finally { + order.push('finally') + } + + expect(order).toEqual(['try', 'finally']) + }) + + it('should always run finally block - error case', () => { + const order = [] + + try { + order.push('try') + throw new Error('test') + } catch (error) { + order.push('catch') + } finally { + order.push('finally') + } + + expect(order).toEqual(['try', 'catch', 'finally']) + }) + + it('should run finally even with return in try', () => { + // From: finally runs even with return + const order = [] + + function example() { + try { + order.push('try') + return 'from try' + } finally { + order.push('finally') + } + } + + const result = example() + + expect(result).toBe('from try') + expect(order).toEqual(['try', 'finally']) + }) + + it('should run finally even with return in catch', () => { + const order = [] + + function example() { + try { + throw new Error('test') + } catch (error) { + order.push('catch') + return 'from catch' + } finally { + order.push('finally') + } + } + + const result = example() + + expect(result).toBe('from catch') + expect(order).toEqual(['catch', 'finally']) + }) + + it('should support optional catch binding (ES2019+)', () => { + // From: Optional catch binding + let result = 'default' + + try { + JSON.parse('{ invalid json }') + } catch { + // No error parameter needed + result = 'caught' + } + + expect(result).toBe('caught') + }) + }) + + // ============================================================ + // THE ERROR OBJECT + // ============================================================ + + describe('The Error Object', () => { + it('should have name, message, and stack properties', () => { + // From: Error Properties table + try { + undefinedVariable + } catch (error) { + expect(error.name).toBe('ReferenceError') + expect(error.message).toContain('undefinedVariable') + expect(typeof error.stack).toBe('string') + expect(error.stack).toContain('ReferenceError') + } + }) + + it('should convert error to string as "name: message"', () => { + const error = new Error('Something went wrong') + + expect(String(error)).toBe('Error: Something went wrong') + }) + + it('should support error cause (ES2022+)', () => { + // From: Error chaining with cause + const originalError = new Error('Original error') + const wrappedError = new Error('Wrapped error', { cause: originalError }) + + expect(wrappedError.message).toBe('Wrapped error') + expect(wrappedError.cause).toBe(originalError) + expect(wrappedError.cause.message).toBe('Original error') + }) + }) + + // ============================================================ + // BUILT-IN ERROR TYPES + // ============================================================ + + describe('Built-in Error Types', () => { + it('should throw TypeError for wrong type operations', () => { + // From: TypeError - calling method on null + expect(() => { + const obj = null + obj.method() + }).toThrow(TypeError) + + // Calling non-function + expect(() => { + const notAFunction = 42 + notAFunction() + }).toThrow(TypeError) + }) + + it('should throw ReferenceError for undefined variables', () => { + // From: ReferenceError - using undefined variable + expect(() => { + undefinedVariableName + }).toThrow(ReferenceError) + }) + + it('should throw SyntaxError for invalid JSON', () => { + // From: SyntaxError - invalid JSON + expect(() => { + JSON.parse('{ name: "John" }') // Missing quotes around key + }).toThrow(SyntaxError) + + expect(() => { + JSON.parse('') // Empty string + }).toThrow(SyntaxError) + }) + + it('should throw RangeError for out-of-range values', () => { + // From: RangeError - value out of range + expect(() => { + new Array(-1) + }).toThrow(RangeError) + + expect(() => { + (1.5).toFixed(200) // Max is 100 + }).toThrow(RangeError) + + // From: 'x'.repeat(Infinity) example + expect(() => { + 'x'.repeat(Infinity) + }).toThrow(RangeError) + }) + + it('should use optional chaining to avoid TypeError', () => { + // From: Fix for TypeError - Check if values exist before using them + const user = null + + // Without optional chaining - throws TypeError + expect(() => { + user.name + }).toThrow(TypeError) + + // With optional chaining - returns undefined (no error) + expect(user?.name).toBeUndefined() + }) + + it('should throw URIError for bad URI encoding', () => { + // From: URIError - bad URI encoding + expect(() => { + decodeURIComponent('%') + }).toThrow(URIError) + }) + + it('should throw AggregateError when all promises reject', async () => { + // From: AggregateError - Promise.any() all reject + await expect( + Promise.any([ + Promise.reject(new Error('Error 1')), + Promise.reject(new Error('Error 2')), + Promise.reject(new Error('Error 3')) + ]) + ).rejects.toThrow(AggregateError) + + try { + await Promise.any([ + Promise.reject(new Error('Error 1')), + Promise.reject(new Error('Error 2')) + ]) + } catch (error) { + expect(error.name).toBe('AggregateError') + expect(error.errors).toHaveLength(2) + } + }) + }) + + // ============================================================ + // THE THROW STATEMENT + // ============================================================ + + describe('The throw Statement', () => { + it('should throw and catch custom errors', () => { + // From: The throw statement lets you create your own errors + function divide(a, b) { + if (b === 0) { + throw new Error('Cannot divide by zero') + } + return a / b + } + + expect(divide(10, 2)).toBe(5) + expect(() => divide(10, 0)).toThrow('Cannot divide by zero') + }) + + it('should demonstrate throwing non-Error types (bad practice)', () => { + // From: Always Throw Error Objects - BAD examples + // These all work but lack stack traces for debugging + + // Throwing a string - no stack trace + try { + throw 'Something went wrong' + } catch (error) { + expect(typeof error).toBe('string') + expect(error).toBe('Something went wrong') + expect(error.stack).toBeUndefined() + } + + // Throwing a number - no stack trace + try { + throw 404 + } catch (error) { + expect(typeof error).toBe('number') + expect(error).toBe(404) + expect(error.stack).toBeUndefined() + } + + // Throwing an object - no stack trace + try { + throw { message: 'Error' } + } catch (error) { + expect(typeof error).toBe('object') + expect(error.message).toBe('Error') + expect(error.stack).toBeUndefined() + } + }) + + it('should throw errors with proper type', () => { + // From: Always throw Error objects + function validateAge(age) { + if (typeof age !== 'number') { + throw new TypeError(`Expected number but got ${typeof age}`) + } + if (age < 0 || age > 150) { + throw new RangeError(`Age must be between 0 and 150, got ${age}`) + } + return true + } + + expect(validateAge(25)).toBe(true) + expect(() => validateAge('25')).toThrow(TypeError) + expect(() => validateAge(-5)).toThrow(RangeError) + }) + + it('should include stack trace when throwing Error objects', () => { + try { + throw new Error('Test') + } catch (error) { + expect(error.stack).toBeDefined() + expect(error.stack).toContain('Error: Test') + } + }) + + it('should create meaningful error messages with context', () => { + // From: Creating Meaningful Error Messages + + // Specific error message with details + const email = 'invalid-email' + const emailError = new Error(`Email address is invalid: missing @ symbol`) + expect(emailError.message).toBe('Email address is invalid: missing @ symbol') + + // TypeError with actual type in message + const value = 42 + const typeError = new TypeError(`Expected string but got ${typeof value}`) + expect(typeError.message).toBe('Expected string but got number') + expect(typeError.name).toBe('TypeError') + + // RangeError with actual value in message + const age = 200 + const rangeError = new RangeError(`Age must be between 0 and 150, got ${age}`) + expect(rangeError.message).toBe('Age must be between 0 and 150, got 200') + expect(rangeError.name).toBe('RangeError') + }) + }) + + // ============================================================ + // CUSTOM ERROR CLASSES + // ============================================================ + + describe('Custom Error Classes', () => { + it('should create custom error classes', () => { + // From: Custom error classes for better categorization + class ValidationError extends Error { + constructor(message) { + super(message) + this.name = 'ValidationError' + } + } + + const error = new ValidationError('Invalid email') + + expect(error.name).toBe('ValidationError') + expect(error.message).toBe('Invalid email') + expect(error instanceof ValidationError).toBe(true) + expect(error instanceof Error).toBe(true) + }) + + it('should support auto-naming pattern', () => { + // From: The Auto-Naming Pattern + class AppError extends Error { + constructor(message, options) { + super(message, options) + this.name = this.constructor.name + } + } + + class ValidationError extends AppError {} + class NetworkError extends AppError {} + + const validationError = new ValidationError('Bad input') + const networkError = new NetworkError('Connection failed') + + expect(validationError.name).toBe('ValidationError') + expect(networkError.name).toBe('NetworkError') + }) + + it('should add custom properties to error classes', () => { + class NetworkError extends Error { + constructor(message, statusCode) { + super(message) + this.name = 'NetworkError' + this.statusCode = statusCode + } + } + + const error = new NetworkError('Not found', 404) + + expect(error.message).toBe('Not found') + expect(error.statusCode).toBe(404) + }) + + it('should use instanceof for error type checking', () => { + // From: Using instanceof for Error Handling + class ValidationError extends Error { + constructor(message) { + super(message) + this.name = 'ValidationError' + } + } + + class NetworkError extends Error { + constructor(message) { + super(message) + this.name = 'NetworkError' + } + } + + const errors = [ + new ValidationError('Bad input'), + new NetworkError('Offline'), + new Error('Unknown') + ] + + const results = errors.map(error => { + if (error instanceof ValidationError) return 'validation' + if (error instanceof NetworkError) return 'network' + return 'unknown' + }) + + expect(results).toEqual(['validation', 'network', 'unknown']) + }) + + it('should chain errors with cause', () => { + // From: Error Chaining with cause + class DataLoadError extends Error { + constructor(message, options) { + super(message, options) + this.name = 'DataLoadError' + } + } + + const originalError = new Error('Network timeout') + const wrappedError = new DataLoadError('Failed to load user data', { + cause: originalError + }) + + expect(wrappedError.cause).toBe(originalError) + expect(wrappedError.cause.message).toBe('Network timeout') + }) + }) + + // ============================================================ + // ASYNC ERROR HANDLING + // ============================================================ + + describe('Async Error Handling', () => { + it('should catch Promise rejections with .catch()', async () => { + // From: With Promises: .catch() + const result = await Promise.reject(new Error('Failed')) + .catch(error => `Caught: ${error.message}`) + + expect(result).toBe('Caught: Failed') + }) + + it('should catch async/await errors with try/catch', async () => { + // From: With async/await: try/catch + async function failingOperation() { + throw new Error('Async failure') + } + + let caught = null + + try { + await failingOperation() + } catch (error) { + caught = error.message + } + + expect(caught).toBe('Async failure') + }) + + it('should run .finally() regardless of outcome', async () => { + const order = [] + + // Success case + await Promise.resolve('success') + .then(v => { order.push('then') }) + .finally(() => { order.push('finally-success') }) + + // Failure case + await Promise.reject(new Error('fail')) + .catch(e => { order.push('catch') }) + .finally(() => { order.push('finally-fail') }) + + expect(order).toEqual(['then', 'finally-success', 'catch', 'finally-fail']) + }) + + it('should demonstrate try/catch only catches synchronous errors', async () => { + // From: try/catch Only Works Synchronously + const order = [] + + try { + setTimeout(() => { + order.push('timeout executed') + // If we threw here, try/catch wouldn't catch it + }, 0) + order.push('after setTimeout') + } catch (error) { + order.push('catch') + } + + expect(order).toEqual(['after setTimeout']) + + // Let setTimeout execute + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(order).toEqual(['after setTimeout', 'timeout executed']) + }) + }) + + // ============================================================ + // GLOBAL ERROR HANDLERS (Not Tested - Browser-Specific) + // ============================================================ + // + // The following patterns from the concept page are browser-specific + // and cannot be tested in a Node.js/Vitest environment: + // + // - window.onerror = function(message, source, lineno, colno, error) { ... } + // Catches uncaught synchronous errors in the browser + // + // - window.addEventListener('unhandledrejection', event => { ... }) + // Catches unhandled Promise rejections in the browser + // + // These are documented in the concept page (lines 531-557) for browser usage. + // In production, these are typically used for: + // - Logging errors to services like Sentry or LogRocket + // - Showing generic "something went wrong" messages + // - Tracking errors in production environments + // + // They should be used as a safety net, not as primary error handling. + // ============================================================ + + // ============================================================ + // COMMON MISTAKES + // ============================================================ + + describe('Common Mistakes', () => { + it('Mistake 1: Empty catch blocks swallow errors', () => { + // From: Empty catch Blocks (Swallowing Errors) + let errorLogged = false + + // Bad: error is silently swallowed + try { + throw new Error('Silent error') + } catch (error) { + // Empty - bad practice + } + + // Good: at least log the error + try { + throw new Error('Logged error') + } catch (error) { + errorLogged = true + // console.error('Error:', error) in real code + } + + expect(errorLogged).toBe(true) + }) + + it('Mistake 2: Catching too broadly hides bugs', () => { + // From: Catching Too Broadly + function parseWithBugHidden(input) { + try { + const result = JSON.parse(input) + // Bug: typo in variable name would be hidden + return result + } catch (error) { + return 'Something went wrong' + } + } + + function parseCorrectly(input) { + try { + return JSON.parse(input) + } catch (error) { + if (error instanceof SyntaxError) { + return null // Expected case + } + throw error // Unexpected: re-throw + } + } + + expect(parseWithBugHidden('{ invalid }')).toBe('Something went wrong') + expect(parseCorrectly('{ invalid }')).toBe(null) + expect(parseCorrectly('{"valid": true}')).toEqual({ valid: true }) + }) + + it('Mistake 3: Throwing strings instead of Error objects', () => { + // From: Throwing Strings Instead of Errors + + // Bad: no stack trace + try { + throw 'String error' + } catch (error) { + expect(typeof error).toBe('string') + expect(error.stack).toBeUndefined() + } + + // Good: has stack trace + try { + throw new Error('Error object') + } catch (error) { + expect(error instanceof Error).toBe(true) + expect(error.stack).toBeDefined() + } + }) + + it('Mistake 4: Not re-throwing when needed', async () => { + // From: Not Re-throwing When Needed + + // Bad: returns undefined, caller thinks success + async function badFetch() { + try { + throw new Error('Network error') + } catch (error) { + // Just logs, doesn't re-throw or return meaningful value + } + } + + // Good: re-throws for caller to handle + async function goodFetch() { + try { + throw new Error('Network error') + } catch (error) { + throw error // Let caller decide what to do + } + } + + const badResult = await badFetch() + expect(badResult).toBeUndefined() // Silent failure! + + await expect(goodFetch()).rejects.toThrow('Network error') + }) + + it('Mistake 5: Expecting try/catch to catch async callback errors', async () => { + // From: Forgetting try/catch is Synchronous + let syncCaughtError = null + let callbackError = null + + // This catch won't catch the setTimeout error + try { + setTimeout(() => { + try { + throw new Error('Callback error') + } catch (e) { + callbackError = e.message + } + }, 0) + } catch (error) { + syncCaughtError = error.message + } + + expect(syncCaughtError).toBeNull() // Sync catch doesn't catch async + + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(callbackError).toBe('Callback error') // Inner catch works + }) + }) + + // ============================================================ + // REAL-WORLD PATTERNS + // ============================================================ + + describe('Real-World Patterns', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('should implement retry pattern', async () => { + // From: Retry Pattern + let attempts = 0 + + async function flakyOperation() { + attempts++ + if (attempts < 3) { + throw new Error('Temporary failure') + } + return 'success' + } + + async function fetchWithRetry(operation, retries = 3) { + for (let i = 0; i < retries; i++) { + try { + return await operation() + } catch (error) { + if (i === retries - 1) throw error + await new Promise(r => setTimeout(r, 100 * Math.pow(2, i))) + } + } + } + + const promise = fetchWithRetry(flakyOperation, 3) + + // First attempt fails + await vi.advanceTimersByTimeAsync(0) + expect(attempts).toBe(1) + + // Wait for first retry (100ms) + await vi.advanceTimersByTimeAsync(100) + expect(attempts).toBe(2) + + // Wait for second retry (200ms) + await vi.advanceTimersByTimeAsync(200) + expect(attempts).toBe(3) + + const result = await promise + expect(result).toBe('success') + }) + + it('should implement validation error pattern', () => { + // From: Validation Error Pattern + class ValidationError extends Error { + constructor(errors) { + super('Validation failed') + this.name = 'ValidationError' + this.errors = errors + } + } + + function validateUser(data) { + const errors = {} + + if (!data.email?.includes('@')) { + errors.email = 'Invalid email address' + } + if (data.age < 0) { + errors.age = 'Age must be positive' + } + + if (Object.keys(errors).length > 0) { + throw new ValidationError(errors) + } + + return true + } + + // Valid data + expect(validateUser({ email: 'test@example.com', age: 25 })).toBe(true) + + // Invalid data + try { + validateUser({ email: 'invalid', age: -5 }) + } catch (error) { + expect(error instanceof ValidationError).toBe(true) + expect(error.errors.email).toBe('Invalid email address') + expect(error.errors.age).toBe('Age must be positive') + } + }) + + it('should implement graceful degradation', async () => { + // From: Graceful Degradation + let apiCalled = false + let cacheCalled = false + + async function fetchFromApi() { + apiCalled = true + throw new Error('API unavailable') + } + + async function loadFromCache() { + cacheCalled = true + return { cached: true } + } + + async function loadUserPreferences() { + try { + return await fetchFromApi() + } catch (apiError) { + try { + return await loadFromCache() + } catch (cacheError) { + return { theme: 'light', language: 'en' } // Defaults + } + } + } + + const result = await loadUserPreferences() + + expect(apiCalled).toBe(true) + expect(cacheCalled).toBe(true) + expect(result).toEqual({ cached: true }) + }) + }) + + // ============================================================ + // RETHROWING ERRORS + // ============================================================ + + describe('Rethrowing Errors', () => { + it('should rethrow errors you cannot handle', () => { + // From: Catch should only process errors that it knows + function parseUserData(json) { + try { + return JSON.parse(json) + } catch (error) { + if (error instanceof SyntaxError) { + // We know how to handle this + return null + } + // Unknown error, rethrow + throw error + } + } + + expect(parseUserData('{"name": "John"}')).toEqual({ name: 'John' }) + expect(parseUserData('invalid')).toBeNull() + }) + + it('should wrap errors with additional context', () => { + function processOrder(order) { + try { + if (!order.items) { + throw new Error('Order has no items') + } + return { processed: true } + } catch (error) { + throw new Error(`Failed to process order ${order.id}`, { cause: error }) + } + } + + try { + processOrder({ id: '123' }) + } catch (error) { + expect(error.message).toBe('Failed to process order 123') + expect(error.cause.message).toBe('Order has no items') + } + }) + }) + + // ============================================================ + // SCOPING IN TRY/CATCH + // ============================================================ + + describe('Variable Scoping in try/catch', () => { + it('should demonstrate variable scoping issue', () => { + // From: Test Your Knowledge Question 6 + + // Wrong: result is scoped to try block + let wrongResult + try { + const result = 'value' + wrongResult = result + } catch (e) { + // handle error + } + // console.log(result) would throw ReferenceError + + // Correct: declare outside try block + let correctResult + try { + correctResult = 'value' + } catch (e) { + correctResult = 'fallback' + } + + expect(correctResult).toBe('value') + }) + + it('should use fallback value when error occurs', () => { + let result + + try { + result = JSON.parse('invalid') + } catch (e) { + result = { fallback: true } + } + + expect(result).toEqual({ fallback: true }) + }) + }) +}) From a26c663920cc2c0bfc4dbd1a3eef39537c7428d5 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:50:21 -0300 Subject: [PATCH 094/128] fix: correct toCamelCase example, video attribution, and compose example - Fix toCamelCase to produce actual camelCase (helloWorld) not PascalCase - Correct video attribution from Traversy Media to Anjana Vakil (JSUnconf) - Simplify compose example to use consistent string operations --- docs/concepts/currying-composition.mdx | 20 +- docs/concepts/regular-expressions.mdx | 652 +++++++++++++++++- .../regular-expressions.test.js | 366 ++++++++++ 3 files changed, 1000 insertions(+), 38 deletions(-) create mode 100644 tests/advanced-topics/regular-expressions/regular-expressions.test.js diff --git a/docs/concepts/currying-composition.mdx b/docs/concepts/currying-composition.mdx index 8092a025..a226f1d6 100644 --- a/docs/concepts/currying-composition.mdx +++ b/docs/concepts/currying-composition.mdx @@ -541,17 +541,15 @@ composed(5) // 25 Why compose instead of nesting? Because this: ```javascript -subtract5(multiply2(add10(toString(trim(getName(user)))))) +addGreeting(capitalize(trim(getName(user)))) ``` Becomes this: ```javascript const processUser = compose( - subtract5, - multiply2, - add10, - toNumber, + addGreeting, + capitalize, trim, getName ) @@ -708,7 +706,9 @@ const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x) const removeSpaces = str => str.trim() const toLowerCase = str => str.toLowerCase() const splitWords = str => str.split(' ') -const capitalizeFirst = words => words.map(w => w[0].toUpperCase() + w.slice(1)) +const capitalizeFirst = words => words.map((w, i) => + i === 0 ? w : w[0].toUpperCase() + w.slice(1) +) const joinWords = words => words.join('') // Compose them into a pipeline @@ -720,8 +720,8 @@ const toCamelCase = pipe( joinWords ) -toCamelCase(' HELLO WORLD ') // 'HelloWorld' -toCamelCase('my variable name') // 'MyVariableName' +toCamelCase(' HELLO WORLD ') // 'helloWorld' +toCamelCase('my variable name') // 'myVariableName' ``` ### Real-World Pipeline: Processing API Data @@ -1345,7 +1345,7 @@ function getThemeEmoji(user) { <Card title="Compose and Pipe — Web Dev Simplified" icon="video" href="https://www.youtube.com/watch?v=yd2FZ1kP5wE"> Kyle Cook's clear, beginner-friendly walkthrough of compose and pipe with practical examples you can follow along with. </Card> - <Card title="JavaScript Functional Programming — Traversy Media" icon="video" href="https://www.youtube.com/watch?v=e-5obm1G_FY"> - Brad Traversy covers functional programming patterns including currying and composition in his practical, project-focused style. + <Card title="Learning Functional Programming with JavaScript — Anjana Vakil" icon="video" href="https://www.youtube.com/watch?v=e-5obm1G_FY"> + A beloved JSUnconf talk that explains functional programming concepts with clarity and humor. Anjana's approachable style makes abstract concepts feel tangible. </Card> </CardGroup> diff --git a/docs/concepts/regular-expressions.mdx b/docs/concepts/regular-expressions.mdx index 2625a0d3..befec72c 100644 --- a/docs/concepts/regular-expressions.mdx +++ b/docs/concepts/regular-expressions.mdx @@ -1,65 +1,661 @@ --- title: "Regular Expressions: Pattern Matching in JavaScript" sidebarTitle: "Regular Expressions: Pattern Matching" -description: "Learn regular expressions in JavaScript — pattern syntax, flags, methods like test/match/replace, and common patterns. Master regex for validation, parsing, and text manipulation." +description: "Learn regular expressions in JavaScript. Covers pattern syntax, character classes, quantifiers, flags, capturing groups, and methods like test, match, and replace." --- -## Overview +How do you check if an email address is valid? How do you find and replace all phone numbers in a document? How can you extract hashtags from a tweet? -**Regular expressions** (regex) are patterns used to match character combinations in strings. JavaScript provides built-in regex support through the `RegExp` object and string methods. Mastering regex enables powerful text validation, searching, and transformation — essential skills for form validation, data parsing, and text processing. +```javascript +// Check if a string contains only digits +const isAllDigits = /^\d+$/.test('12345') +console.log(isAllDigits) // true + +// Find all words starting with capital letters +const text = 'Hello World from JavaScript' +const capitalWords = text.match(/\b[A-Z][a-z]*\b/g) +console.log(capitalWords) // ["Hello", "World"] +``` + +The answer is **[regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions)** (often called "regex" or "regexp"). They're patterns that describe what you're looking for in text, and JavaScript has powerful built-in support for them. <Info> **What you'll learn in this guide:** -- Regex literal syntax (`/pattern/flags`) vs `RegExp` constructor +- Creating regex with literals (`/pattern/`) and the `RegExp` constructor - Character classes, quantifiers, and anchors -- Capturing groups and backreferences -- Lookahead and lookbehind assertions -- Common methods: `test()`, `match()`, `matchAll()`, `replace()`, `split()` -- Flags: `g`, `i`, `m`, `s`, `u`, `y` -- Common patterns (email, phone, URL validation) -- Performance considerations and regex best practices +- Key methods: `test()`, `match()`, `replace()`, `split()` +- Capturing groups for extracting parts of matches +- Flags that change how patterns match +- Common real-world patterns (email, phone, URL) +</Info> + +<Warning> +**Prerequisite:** This guide assumes you're comfortable with [strings](/concepts/primitive-types) in JavaScript. You don't need any prior regex experience — we'll start from the basics. +</Warning> + +--- + +## What Are Regular Expressions? + +A **[regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)** is a pattern used to match character combinations in strings. In JavaScript, regex are objects that you can use with string methods to search, validate, extract, and replace text. They use a special syntax where characters like `\d`, `*`, and `^` have special meanings beyond their literal values. + +### Two Ways to Create Regex + +```javascript +// 1. Literal syntax (preferred for static patterns) +const pattern1 = /hello/ + +// 2. Constructor syntax (useful for dynamic patterns) +const pattern2 = new RegExp('hello') + +// Both work the same way +console.log(pattern1.test('hello world')) // true +console.log(pattern2.test('hello world')) // true +``` + +Use the literal syntax when you know the pattern ahead of time. Use the constructor when you need to build patterns dynamically, like from user input: + +```javascript +function findWord(text, word) { + const pattern = new RegExp(word, 'gi') // case-insensitive, global + return text.match(pattern) +} + +console.log(findWord('Hello hello HELLO', 'hello')) // ["Hello", "hello", "HELLO"] +``` + +--- + +## The Detective Analogy + +Think of regex like giving a detective a description to find suspects in a crowd: + +- **Literal characters** (`abc`) — "Find someone named 'abc'" +- **Character classes** (`[aeiou]`) — "Find someone with a vowel in their name" +- **Quantifiers** (`a+`) — "Find someone with one or more 'a's in their name" +- **Anchors** (`^`, `$`) — "They must be at the start/end of the line" + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ REGEX PATTERN MATCHING │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Pattern: /\d{3}-\d{4}/ String: "Call 555-1234 today" │ +│ │ +│ Step 1: Find 3 digits (\d{3}) → "555" ✓ │ +│ Step 2: Find a hyphen (-) → "-" ✓ │ +│ Step 3: Find 4 digits (\d{4}) → "1234" ✓ │ +│ │ +│ Result: Match found! → "555-1234" │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Building Blocks: Character Classes + +Character classes let you match *types* of characters rather than specific ones. + +| Pattern | Matches | Example | +|---------|---------|---------| +| `.` | Any character except newline | `/a.c/` matches "abc", "a1c", "a-c" | +| `\d` | Any digit [0-9] | `/\d{3}/` matches "123" | +| `\D` | Any non-digit | `/\D+/` matches "abc" | +| `\w` | Word character [A-Za-z0-9_] | `/\w+/` matches "hello_123" | +| `\W` | Non-word character | `/\W/` matches "!" or " " | +| `\s` | Whitespace (space, tab, newline) | `/\s+/` matches " " | +| `\S` | Non-whitespace | `/\S+/` matches "hello" | +| `[abc]` | Any of a, b, or c | `/[aeiou]/` matches any vowel | +| `[^abc]` | Not a, b, or c | `/[^0-9]/` matches non-digits | +| `[a-z]` | Character range | `/[A-Za-z]/` matches any letter | + +```javascript +// Match a phone number pattern: 3 digits, hyphen, 4 digits +const phone = /\d{3}-\d{4}/ +console.log(phone.test('555-1234')) // true +console.log(phone.test('55-1234')) // false + +// Match words (letters, digits, underscores) +const words = 'hello_world 123 test!' +console.log(words.match(/\w+/g)) // ["hello_world", "123", "test"] +``` + +--- + +## Building Blocks: Quantifiers + +Quantifiers specify how many times a pattern should repeat. + +| Quantifier | Meaning | Example | +|------------|---------|---------| +| `*` | 0 or more | `/ab*c/` matches "ac", "abc", "abbbbc" | +| `+` | 1 or more | `/ab+c/` matches "abc", "abbbbc" (not "ac") | +| `?` | 0 or 1 (optional) | `/colou?r/` matches "color", "colour" | +| `{n}` | Exactly n times | `/\d{4}/` matches "2024" | +| `{n,}` | n or more times | `/\d{2,}/` matches "12", "123", "1234" | +| `{n,m}` | Between n and m times | `/\d{2,4}/` matches "12", "123", "1234" | + +```javascript +// Match optional 's' for plural +const plural = /apple(s)?/ +console.log(plural.test('apple')) // true +console.log(plural.test('apples')) // true + +// Match 1 or more digits +const numbers = 'I have 42 apples and 7 oranges' +console.log(numbers.match(/\d+/g)) // ["42", "7"] +``` + +--- + +## Building Blocks: Anchors + +Anchors match *positions* in the string, not characters. + +| Anchor | Position | +|--------|----------| +| `^` | Start of string (or line with `m` flag) | +| `$` | End of string (or line with `m` flag) | +| `\b` | Word boundary | +| `\B` | Not a word boundary | + +```javascript +// Must start with "Hello" +console.log(/^Hello/.test('Hello World')) // true +console.log(/^Hello/.test('Say Hello')) // false + +// Must end with a digit +console.log(/\d$/.test('Room 42')) // true +console.log(/\d$/.test('42 rooms')) // false + +// Word boundaries prevent partial matches +console.log(/\bcat\b/.test('cat')) // true +console.log(/\bcat\b/.test('category')) // false (cat is part of a larger word) +``` + +--- + +## Methods for Using Regex + +JavaScript provides several methods for working with regular expressions: + +| Method | Returns | Use Case | +|--------|---------|----------| +| `regex.test(str)` | `true` or `false` | Simple validation | +| `str.match(regex)` | Array or `null` | Find matches | +| `str.matchAll(regex)` | Iterator | Find all matches with details | +| `str.search(regex)` | Index or `-1` | Find position of first match | +| `str.replace(regex, replacement)` | New string | Replace matches | +| `str.split(regex)` | Array | Split by pattern | +| `regex.exec(str)` | Match array or `null` | Detailed match info (stateful) | + +### test() — Simple Validation + +```javascript +const emailPattern = /\S+@\S+\.\S+/ + +console.log(emailPattern.test('user@example.com')) // true +console.log(emailPattern.test('invalid-email')) // false +``` + +### match() — Find Matches + +```javascript +const text = 'My numbers: 123, 456, 789' + +// Without 'g' flag: returns first match with details +console.log(text.match(/\d+/)) +// ["123", index: 12, input: "My numbers: 123, 456, 789"] + +// With 'g' flag: returns all matches +console.log(text.match(/\d+/g)) +// ["123", "456", "789"] +``` + +### matchAll() — All Matches with Details + +When you need all matches AND details (like captured groups), use `matchAll()`. It requires the `g` flag and returns an iterator: + +```javascript +const text = 'Call 555-1234 or 555-5678' +const pattern = /(\d{3})-(\d{4})/g + +for (const match of text.matchAll(pattern)) { + console.log(`Found: ${match[0]}, Prefix: ${match[1]}, Number: ${match[2]}`) +} +// "Found: 555-1234, Prefix: 555, Number: 1234" +// "Found: 555-5678, Prefix: 555, Number: 5678" +``` + +### search() — Find Position + +```javascript +const text = 'Hello World' +console.log(text.search(/World/)) // 6 (index where match starts) +console.log(text.search(/xyz/)) // -1 (not found) +``` + +### replace() — Replace Matches + +```javascript +// Replace first occurrence +console.log('hello world'.replace(/o/, '0')) +// "hell0 world" + +// Replace all occurrences (with 'g' flag) +console.log('hello world'.replace(/o/g, '0')) +// "hell0 w0rld" + +// Use captured groups in replacement +console.log('John Smith'.replace(/(\w+) (\w+)/, '$2, $1')) +// "Smith, John" +``` + +### split() — Split by Pattern + +```javascript +// Split on one or more whitespace characters +const words = 'hello world foo'.split(/\s+/) +console.log(words) // ["hello", "world", "foo"] + +// Split on commas with optional spaces +const items = 'a, b,c , d'.split(/\s*,\s*/) +console.log(items) // ["a", "b", "c", "d"] +``` + +### exec() — Detailed Match Info + +`exec()` is similar to `match()` but is called on the regex. With the `g` flag, calling it repeatedly finds the next match each time: + +```javascript +const pattern = /\d+/g +const text = 'a1b22c333' + +console.log(pattern.exec(text)) // ["1", index: 1] +console.log(pattern.exec(text)) // ["22", index: 3] +console.log(pattern.exec(text)) // ["333", index: 6] +console.log(pattern.exec(text)) // null (no more matches) +``` + +--- + +## Flags + +Flags modify how the pattern matches. Add them after the closing slash. + +| Flag | Name | Effect | +|------|------|--------| +| `g` | Global | Find all matches, not just the first | +| `i` | Case-insensitive | `a` matches `A` | +| `m` | Multiline | `^` and `$` match at each line's start/end | +| `s` | DotAll | `.` matches newlines too | + +```javascript +// Case-insensitive matching +console.log(/hello/i.test('HELLO')) // true + +// Global: find all matches +console.log('abcabc'.match(/a/g)) // ["a", "a"] +console.log('abcabc'.match(/a/)) // ["a", index: 0, input: "abcabc", ...] (first match with details) + +// Multiline: ^ and $ match each line +const multiline = 'line1\nline2\nline3' +console.log(multiline.match(/^line\d/gm)) // ["line1", "line2", "line3"] +``` + +--- + +## Capturing Groups + +Parentheses `()` create **capturing groups** that let you extract parts of a match. + +```javascript +// Extract area code and number separately +const phonePattern = /\((\d{3})\) (\d{3}-\d{4})/ +const match = '(555) 123-4567'.match(phonePattern) + +console.log(match[0]) // "(555) 123-4567" (full match) +console.log(match[1]) // "555" (first group) +console.log(match[2]) // "123-4567" (second group) +``` + +### Named Groups + +Use `(?<name>pattern)` to give groups meaningful names: + +```javascript +const datePattern = /(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/ +const match = '12-25-2024'.match(datePattern) + +console.log(match.groups.month) // "12" +console.log(match.groups.day) // "25" +console.log(match.groups.year) // "2024" +``` + +### Using Groups in Replace + +Reference captured groups with `$1`, `$2`, etc. (or `$<name>` for named groups): + +```javascript +// Reformat date from MM-DD-YYYY to YYYY/MM/DD +const date = '12-25-2024' +const reformatted = date.replace( + /(\d{2})-(\d{2})-(\d{4})/, + '$3/$1/$2' +) +console.log(reformatted) // "2024/12/25" +``` + +--- + +## The #1 Regex Mistake: Greedy vs Lazy + +By default, quantifiers are **greedy**. They match as much as possible. Add `?` to make them **lazy** (match as little as possible). + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ GREEDY VS LAZY │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ String: "<div>Hello</div><div>World</div>" │ +│ │ +│ GREEDY: /<div>.*<\/div>/ LAZY: /<div>.*?<\/div>/ │ +│ Matches: "<div>Hello</div> Matches: "<div>Hello</div>" │ +│ <div>World</div>" │ +│ (Everything from first (Just the first div) │ +│ <div> to LAST </div>) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +```javascript +const html = '<div>Hello</div><div>World</div>' + +// Greedy: matches everything between first <div> and LAST </div> +console.log(html.match(/<div>.*<\/div>/)[0]) +// "<div>Hello</div><div>World</div>" + +// Lazy: stops at first </div> +console.log(html.match(/<div>.*?<\/div>/)[0]) +// "<div>Hello</div>" +``` + +<Tip> +**Rule of Thumb:** When matching content between delimiters (like HTML tags, quotes, or brackets), prefer lazy quantifiers (`*?`, `+?`) to avoid matching too much. +</Tip> + +--- + +## Common Patterns + +Here are some practical patterns you can use in your projects: + +```javascript +// Email (basic validation) +const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ +console.log(email.test('user@example.com')) // true + +// URL +const url = /^https?:\/\/[^\s]+$/ +console.log(url.test('https://example.com/path')) // true + +// Phone (US format: 123-456-7890 or (123) 456-7890) +const phone = /^(\(\d{3}\)|\d{3})[-.\s]?\d{3}[-.\s]?\d{4}$/ +console.log(phone.test('(555) 123-4567')) // true +console.log(phone.test('555-123-4567')) // true + +// Username (alphanumeric, 3-16 chars) +const username = /^[a-zA-Z0-9_]{3,16}$/ +console.log(username.test('john_doe123')) // true +``` + +<Warning> +**Don't go overboard.** Regex is great for pattern matching, but it's not always the best tool. For complex validation like email addresses (which have a surprisingly complex spec), consider using a dedicated validation library. The email regex above works for most cases but won't catch every edge case. +</Warning> + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Regex = patterns for strings** — They describe what you're looking for, not literal text + +2. **Two ways to create** — `/pattern/` literals or `new RegExp('pattern')` + +3. **Character classes** — `\d` (digits), `\w` (word chars), `\s` (whitespace), `.` (any) + +4. **Quantifiers** — `*` (0+), `+` (1+), `?` (0-1), `{n,m}` (specific range) + +5. **Anchors** — `^` (start), `$` (end), `\b` (word boundary) + +6. **test() for validation** — Returns true/false + +7. **match() for extraction** — Returns matches or null + +8. **Flags change behavior** — `g` (global), `i` (case-insensitive), `m` (multiline) + +9. **Groups capture parts** — Use `()` to extract portions of matches + +10. **Greedy vs lazy** — Add `?` after quantifiers to match minimally </Info> +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the difference between /pattern/ and new RegExp('pattern')?"> + **Answer:** + + Both create a regex object, but they differ in when to use them: + + - **Literal `/pattern/`** — Use for static patterns known at write time. The pattern is compiled when the script loads. + - **`new RegExp('pattern')`** — Use for dynamic patterns built at runtime (e.g., from user input). Remember to escape backslashes: `new RegExp('\\d+')`. + + ```javascript + // Static pattern - use literal + const digits = /\d+/ + + // Dynamic pattern - use constructor + const searchTerm = 'hello' + const dynamic = new RegExp(searchTerm, 'gi') + ``` + </Accordion> + + <Accordion title="Question 2: What does \b match?"> + **Answer:** + + `\b` matches a **word boundary** — the position between a word character (`\w`) and a non-word character. It doesn't match any actual character; it matches a position. + + ```javascript + // \b prevents partial matches + console.log(/\bcat\b/.test('cat')) // true + console.log(/\bcat\b/.test('category')) // false + console.log(/\bcat\b/.test('the cat')) // true + ``` + + Word boundaries are useful when you want to match whole words only. + </Accordion> + + <Accordion title="Question 3: How do you make a quantifier lazy?"> + **Answer:** + + Add a `?` after the quantifier to make it lazy (non-greedy): + + - `*?` — Match 0 or more, as few as possible + - `+?` — Match 1 or more, as few as possible + - `??` — Match 0 or 1, preferring 0 + - `{n,m}?` — Match between n and m, as few as possible + + ```javascript + const text = '<b>bold</b> and <b>more bold</b>' + + // Greedy: matches everything between first <b> and last </b> + text.match(/<b>.*<\/b>/)[0] // "<b>bold</b> and <b>more bold</b>" + + // Lazy: matches just the first <b>...</b> + text.match(/<b>.*?<\/b>/)[0] // "<b>bold</b>" + ``` + </Accordion> + + <Accordion title="Question 4: What's the difference between match() with and without the g flag?"> + **Answer:** + + - **Without `g`**: Returns first match with full details (captured groups, index, input) + - **With `g`**: Returns array of all matches (just the matched strings, no details) + + ```javascript + const text = 'cat and cat' + + // Without g: detailed info about first match + text.match(/cat/) + // ["cat", index: 0, input: "cat and cat"] + + // With g: all matches, no details + text.match(/cat/g) + // ["cat", "cat"] + ``` + + Use `matchAll()` if you need both all matches AND details for each. + </Accordion> + + <Accordion title="Question 5: How do you reference a captured group in a replacement string?"> + **Answer:** + + Use `$1`, `$2`, etc. for numbered groups, or `$<name>` for named groups: + + ```javascript + // Numbered groups + 'John Smith'.replace(/(\w+) (\w+)/, '$2, $1') + // "Smith, John" + + // Named groups + '2024-12-25'.replace( + /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/, + '$<month>/$<day>/$<year>' + ) + // "12/25/2024" + + // $& references the entire match + 'hello'.replace(/\w+/, '[$&]') + // "[hello]" + ``` + </Accordion> + + <Accordion title="Question 6: How do you match special regex characters literally?"> + **Answer:** + + Escape special characters with a backslash `\`. Characters that need escaping: `. * + ? ^ $ { } [ ] \ | ( )` and `/` in literal syntax + + ```javascript + // Match a literal period + /\./.test('file.txt') // true + /\./.test('filetxt') // false + + // Match a literal dollar sign + /\$\d+/.test('$100') // true + + // When using RegExp constructor, double-escape + new RegExp('\\d+\\.\\d+') // matches "3.14" + ``` + + For dynamic patterns from user input, escape all special chars: + + ```javascript + function escapeRegex(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } + + const userInput = 'hello.world' + const pattern = new RegExp(escapeRegex(userInput)) + pattern.test('hello.world') // true + pattern.test('helloXworld') // false + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Primitive Types" icon="cube" href="/concepts/primitive-types"> + Strings are one of JavaScript's primitive types + </Card> + <Card title="Map, Reduce, Filter" icon="filter" href="/concepts/map-reduce-filter"> + Process arrays of matches from regex operations + </Card> + <Card title="Error Handling" icon="triangle-exclamation" href="/concepts/error-handling"> + Invalid regex patterns throw SyntaxError + </Card> + <Card title="Clean Code" icon="broom" href="/concepts/clean-code"> + Write maintainable regex with comments and named groups + </Card> +</CardGroup> + +--- + ## Reference <CardGroup cols={2}> <Card title="Regular Expressions — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"> - MDN guide to regular expressions + Comprehensive MDN guide covering all regex syntax and features + </Card> + <Card title="RegExp Object — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp"> + Reference for the RegExp constructor, methods, and properties </Card> - <Card title="RegExp — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp"> - MDN documentation for the RegExp object + <Card title="String.prototype.match() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match"> + Documentation for the match() method + </Card> + <Card title="String.prototype.replace() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace"> + Documentation for the replace() method </Card> </CardGroup> ## Articles <CardGroup cols={2}> - <Card title="Regular Expressions" icon="newspaper" href="https://javascript.info/regular-expressions"> - By JavaScript.info — comprehensive tutorial + <Card title="Regular Expressions — JavaScript.info" icon="newspaper" href="https://javascript.info/regular-expressions"> + Multi-chapter deep dive covering every regex feature with interactive examples. The go-to tutorial for learning regex thoroughly. + </Card> + <Card title="Learn Regex the Easy Way" icon="newspaper" href="https://github.com/ziishaned/learn-regex"> + Visual cheatsheet with clear examples for each pattern type. Great reference when you forget specific syntax. 46k+ GitHub stars. + </Card> + <Card title="Regular Expressions — Eloquent JavaScript" icon="newspaper" href="https://eloquentjavascript.net/09_regexp.html"> + Chapter from the classic free JavaScript book. Explains the theory and mechanics behind regex with elegant examples. </Card> <Card title="A Practical Guide to Regular Expressions" icon="newspaper" href="https://www.freecodecamp.org/news/practical-regex-guide-with-real-life-examples/"> - By freeCodeCamp + Hands-on freeCodeCamp guide focused on real-world use cases like log parsing, file renaming, and form validation. </Card> </CardGroup> -- [Learn Regex the Easy Way — Zeeshan Ahmad](https://github.com/ziishaned/learn-regex) -- [Regular Expressions Demystified — Vijay Kumar](https://dev.to/vijay/regular-expressions-demystified-4hcn) -- [Regex Cheat Sheet — Quick Reference](https://quickref.me/regex) -- [Regular Expressions in JavaScript — Eloquent JavaScript](https://eloquentjavascript.net/09_regexp.html) -- [JavaScript Regular Expressions — W3Schools](https://www.w3schools.com/js/js_regexp.asp) -- [Named Capture Groups in JavaScript Regex — V8 Blog](https://v8.dev/features/regexp-named-captures) - ## Videos <CardGroup cols={2}> <Card title="Learn Regular Expressions In 20 Minutes" icon="video" href="https://www.youtube.com/watch?v=rhzKDrUiJVk"> - By Web Dev Simplified + Web Dev Simplified covers all the essentials without filler. Great if you want to learn regex quickly and start using it. </Card> <Card title="Regular Expressions (Regex) in JavaScript" icon="video" href="https://www.youtube.com/watch?v=909NfO1St0A"> - By Fireship + Fireship's fast-paced 100 seconds style overview. Perfect for a quick refresher or introduction to what regex can do. + </Card> + <Card title="JavaScript Regex — Programming with Mosh" icon="video" href="https://www.youtube.com/watch?v=VrT3TRDDE4M"> + Mosh Hamedani's beginner-friendly walkthrough with practical JavaScript examples you can follow along with. </Card> </CardGroup> -- [Regular Expressions (RegEx) Tutorial — The Coding Train](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6YEypLuls7iidwHMdCM6o2w) -- [JavaScript Regex — Programming with Mosh](https://www.youtube.com/watch?v=VrT3TRDDE4M) -- [Regex Tutorial — Corey Schafer](https://www.youtube.com/watch?v=sa-TUpSx1JA) +## Tools + +<CardGroup cols={2}> + <Card title="regex101" icon="flask" href="https://regex101.com/"> + Interactive regex tester with real-time explanation of your pattern. Shows match groups, explains each part, and lets you test against sample text. + </Card> + <Card title="RegExr" icon="wand-magic-sparkles" href="https://regexr.com/"> + Visual regex editor with community patterns and a helpful cheatsheet sidebar. Great for learning and building patterns. + </Card> + <Card title="Regexlearn" icon="graduation-cap" href="https://regexlearn.com/"> + Interactive step-by-step tutorial that teaches regex through practice. Gamified learning with progressive difficulty. + </Card> +</CardGroup> diff --git a/tests/advanced-topics/regular-expressions/regular-expressions.test.js b/tests/advanced-topics/regular-expressions/regular-expressions.test.js new file mode 100644 index 00000000..9e291a61 --- /dev/null +++ b/tests/advanced-topics/regular-expressions/regular-expressions.test.js @@ -0,0 +1,366 @@ +import { describe, it, expect } from 'vitest' + +describe('Regular Expressions', () => { + describe('Creating Regex', () => { + it('should create regex with literal syntax', () => { + const pattern = /hello/ + expect(pattern.test('hello world')).toBe(true) + expect(pattern.test('world')).toBe(false) + }) + + it('should create regex with RegExp constructor', () => { + const pattern = new RegExp('hello') + expect(pattern.test('hello world')).toBe(true) + expect(pattern.test('world')).toBe(false) + }) + + it('should create dynamic patterns with RegExp constructor', () => { + const searchTerm = 'cat' + const pattern = new RegExp(searchTerm, 'gi') + expect('Cat CAT cat'.match(pattern)).toEqual(['Cat', 'CAT', 'cat']) + }) + }) + + describe('Character Classes', () => { + it('should match digits with \\d', () => { + const pattern = /\d+/ + expect(pattern.test('123')).toBe(true) + expect(pattern.test('abc')).toBe(false) + expect('abc123def'.match(pattern)[0]).toBe('123') + }) + + it('should match word characters with \\w', () => { + const pattern = /\w+/g + expect('hello_world 123'.match(pattern)).toEqual(['hello_world', '123']) + }) + + it('should match whitespace with \\s', () => { + const pattern = /\s+/ + expect(pattern.test('hello world')).toBe(true) + expect(pattern.test('helloworld')).toBe(false) + }) + + it('should match any character with .', () => { + const pattern = /a.c/ + expect(pattern.test('abc')).toBe(true) + expect(pattern.test('a1c')).toBe(true) + expect(pattern.test('ac')).toBe(false) + }) + + it('should match character sets with []', () => { + const vowelPattern = /[aeiou]/g + expect('hello'.match(vowelPattern)).toEqual(['e', 'o']) + }) + + it('should match negated character sets with [^]', () => { + const nonDigitPattern = /[^0-9]+/g + expect('abc123def'.match(nonDigitPattern)).toEqual(['abc', 'def']) + }) + + it('should match character ranges', () => { + const lowercasePattern = /[a-z]+/g + expect('Hello World'.match(lowercasePattern)).toEqual(['ello', 'orld']) + }) + }) + + describe('Quantifiers', () => { + it('should match 0 or more with *', () => { + const pattern = /ab*c/ + expect(pattern.test('ac')).toBe(true) + expect(pattern.test('abc')).toBe(true) + expect(pattern.test('abbbbc')).toBe(true) + }) + + it('should match 1 or more with +', () => { + const pattern = /ab+c/ + expect(pattern.test('ac')).toBe(false) + expect(pattern.test('abc')).toBe(true) + expect(pattern.test('abbbbc')).toBe(true) + }) + + it('should match 0 or 1 with ?', () => { + const pattern = /colou?r/ + expect(pattern.test('color')).toBe(true) + expect(pattern.test('colour')).toBe(true) + expect(pattern.test('colouur')).toBe(false) + }) + + it('should match exact count with {n}', () => { + const pattern = /\d{4}/ + expect(pattern.test('2024')).toBe(true) + expect(pattern.test('123')).toBe(false) + }) + + it('should match range with {n,m}', () => { + const pattern = /\d{2,4}/ + expect('1'.match(pattern)).toBe(null) + expect('12'.match(pattern)[0]).toBe('12') + expect('12345'.match(pattern)[0]).toBe('1234') + }) + + it('should match n or more with {n,}', () => { + const pattern = /\d{2,}/ + expect(pattern.test('1')).toBe(false) + expect(pattern.test('12')).toBe(true) + expect(pattern.test('12345')).toBe(true) + }) + }) + + describe('Anchors', () => { + it('should match start of string with ^', () => { + const pattern = /^Hello/ + expect(pattern.test('Hello World')).toBe(true) + expect(pattern.test('Say Hello')).toBe(false) + }) + + it('should match end of string with $', () => { + const pattern = /World$/ + expect(pattern.test('Hello World')).toBe(true) + expect(pattern.test('World Hello')).toBe(false) + }) + + it('should match entire string with ^ and $', () => { + const pattern = /^\d+$/ + expect(pattern.test('12345')).toBe(true) + expect(pattern.test('123abc')).toBe(false) + expect(pattern.test('abc123')).toBe(false) + }) + + it('should match word boundaries with \\b', () => { + const pattern = /\bcat\b/ + expect(pattern.test('cat')).toBe(true) + expect(pattern.test('the cat sat')).toBe(true) + expect(pattern.test('category')).toBe(false) + expect(pattern.test('concatenate')).toBe(false) + }) + }) + + describe('Methods', () => { + describe('test()', () => { + it('should return true for matches', () => { + expect(/\d+/.test('123')).toBe(true) + }) + + it('should return false for non-matches', () => { + expect(/\d+/.test('abc')).toBe(false) + }) + }) + + describe('match()', () => { + it('should return first match without g flag', () => { + const result = 'cat and cat'.match(/cat/) + expect(result[0]).toBe('cat') + expect(result.index).toBe(0) + }) + + it('should return all matches with g flag', () => { + const result = 'cat and cat'.match(/cat/g) + expect(result).toEqual(['cat', 'cat']) + }) + + it('should return null when no match', () => { + expect('hello'.match(/\d+/)).toBe(null) + }) + }) + + describe('replace()', () => { + it('should replace first match without g flag', () => { + expect('hello world'.replace(/o/, '0')).toBe('hell0 world') + }) + + it('should replace all matches with g flag', () => { + expect('hello world'.replace(/o/g, '0')).toBe('hell0 w0rld') + }) + + it('should use captured groups in replacement', () => { + expect('John Smith'.replace(/(\w+) (\w+)/, '$2, $1')).toBe('Smith, John') + }) + }) + + describe('split()', () => { + it('should split by regex pattern', () => { + expect('a, b, c'.split(/,\s*/)).toEqual(['a', 'b', 'c']) + }) + + it('should split on whitespace', () => { + expect('hello world foo'.split(/\s+/)).toEqual(['hello', 'world', 'foo']) + }) + }) + + describe('exec()', () => { + it('should return match with details', () => { + const result = /\d+/.exec('abc123def') + expect(result[0]).toBe('123') + expect(result.index).toBe(3) + }) + + it('should return null for no match', () => { + expect(/\d+/.exec('abc')).toBe(null) + }) + }) + }) + + describe('Flags', () => { + it('should match case-insensitively with i flag', () => { + const pattern = /hello/i + expect(pattern.test('HELLO')).toBe(true) + expect(pattern.test('Hello')).toBe(true) + expect(pattern.test('hello')).toBe(true) + }) + + it('should find all matches with g flag', () => { + const pattern = /a/g + expect('banana'.match(pattern)).toEqual(['a', 'a', 'a']) + }) + + it('should match line boundaries with m flag', () => { + const text = 'line1\nline2\nline3' + const pattern = /^line\d/gm + expect(text.match(pattern)).toEqual(['line1', 'line2', 'line3']) + }) + + it('should combine multiple flags', () => { + const pattern = /hello/gi + expect('Hello HELLO hello'.match(pattern)).toEqual(['Hello', 'HELLO', 'hello']) + }) + }) + + describe('Capturing Groups', () => { + it('should capture groups with parentheses', () => { + const pattern = /(\d{3})-(\d{4})/ + const match = '555-1234'.match(pattern) + expect(match[0]).toBe('555-1234') + expect(match[1]).toBe('555') + expect(match[2]).toBe('1234') + }) + + it('should support named groups', () => { + const pattern = /(?<area>\d{3})-(?<number>\d{4})/ + const match = '555-1234'.match(pattern) + expect(match.groups.area).toBe('555') + expect(match.groups.number).toBe('1234') + }) + + it('should use groups in replace with $n', () => { + const result = '12-25-2024'.replace( + /(\d{2})-(\d{2})-(\d{4})/, + '$3/$1/$2' + ) + expect(result).toBe('2024/12/25') + }) + + it('should use named groups in replace', () => { + const result = '12-25-2024'.replace( + /(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, + '$<year>/$<month>/$<day>' + ) + expect(result).toBe('2024/12/25') + }) + + it('should support non-capturing groups with (?:)', () => { + const pattern = /(?:ab)+/ + const match = 'ababab'.match(pattern) + expect(match[0]).toBe('ababab') + expect(match[1]).toBeUndefined() + }) + }) + + describe('Greedy vs Lazy', () => { + it('should match greedily by default', () => { + const html = '<div>Hello</div><div>World</div>' + const greedy = /<div>.*<\/div>/ + expect(html.match(greedy)[0]).toBe('<div>Hello</div><div>World</div>') + }) + + it('should match lazily with ?', () => { + const html = '<div>Hello</div><div>World</div>' + const lazy = /<div>.*?<\/div>/ + expect(html.match(lazy)[0]).toBe('<div>Hello</div>') + }) + + it('should find all lazy matches with g flag', () => { + const html = '<div>Hello</div><div>World</div>' + const lazy = /<div>.*?<\/div>/g + expect(html.match(lazy)).toEqual(['<div>Hello</div>', '<div>World</div>']) + }) + }) + + describe('Common Patterns', () => { + it('should validate basic email format', () => { + const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + expect(email.test('user@example.com')).toBe(true) + expect(email.test('user.name@example.co.uk')).toBe(true) + expect(email.test('invalid-email')).toBe(false) + expect(email.test('missing@domain')).toBe(false) + expect(email.test('@missing-local.com')).toBe(false) + }) + + it('should validate URL format', () => { + const url = /^https?:\/\/[^\s]+$/ + expect(url.test('https://example.com')).toBe(true) + expect(url.test('http://example.com/path')).toBe(true) + expect(url.test('ftp://example.com')).toBe(false) + expect(url.test('not a url')).toBe(false) + }) + + it('should validate US phone number formats', () => { + const phone = /^(\(\d{3}\)|\d{3})[-.\s]?\d{3}[-.\s]?\d{4}$/ + expect(phone.test('555-123-4567')).toBe(true) + expect(phone.test('(555) 123-4567')).toBe(true) + expect(phone.test('555.123.4567')).toBe(true) + expect(phone.test('5551234567')).toBe(true) + expect(phone.test('55-123-4567')).toBe(false) + }) + + it('should validate username format', () => { + const username = /^[a-zA-Z0-9_]{3,16}$/ + expect(username.test('john_doe')).toBe(true) + expect(username.test('user123')).toBe(true) + expect(username.test('ab')).toBe(false) // too short + expect(username.test('this_is_way_too_long_username')).toBe(false) // too long + expect(username.test('invalid-user')).toBe(false) // hyphen not allowed + }) + + it('should extract hashtags from text', () => { + const hashtags = /#\w+/g + const text = 'Learning #JavaScript and #regex is fun! #coding' + expect(text.match(hashtags)).toEqual(['#JavaScript', '#regex', '#coding']) + }) + + it('should extract numbers from text', () => { + const numbers = /\d+/g + const text = 'I have 42 apples and 7 oranges' + expect(text.match(numbers)).toEqual(['42', '7']) + }) + }) + + describe('Edge Cases', () => { + it('should escape special characters in RegExp constructor', () => { + const searchTerm = 'hello.world' + const escaped = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const pattern = new RegExp(escaped) + expect(pattern.test('hello.world')).toBe(true) + expect(pattern.test('helloXworld')).toBe(false) + }) + + it('should handle empty strings', () => { + expect(/.*/.test('')).toBe(true) + expect(/.+/.test('')).toBe(false) + const emptyMatch = ''.match(/\d*/) + expect(emptyMatch[0]).toBe('') + }) + + it('should handle alternation with |', () => { + const pattern = /cat|dog|bird/ + expect(pattern.test('I have a cat')).toBe(true) + expect(pattern.test('I have a dog')).toBe(true) + expect(pattern.test('I have a fish')).toBe(false) + }) + + it('should handle backreferences', () => { + const pattern = /(\w+)\s+\1/ + expect(pattern.test('hello hello')).toBe(true) + expect(pattern.test('hello world')).toBe(false) + }) + }) +}) From 0d35e2889a10f146b80194935ac7c0bc07666d5a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:50:38 -0300 Subject: [PATCH 095/128] fix: improve recursion page accuracy and fix broken link - Fix execution context MDN link to point to Call Stack glossary - Improve sumTo base case to handle n <= 0 edge cases - Add input validation note for production code - Clarify array length Q&A explanation - Replace broken codeburst.io link with javascripttutorial.net --- docs/concepts/recursion.mdx | 23 ++++++++++++------- .../recursion/recursion.test.js | 5 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/concepts/recursion.mdx b/docs/concepts/recursion.mdx index 01e58459..b6d129e9 100644 --- a/docs/concepts/recursion.mdx +++ b/docs/concepts/recursion.mdx @@ -77,9 +77,9 @@ Here's a simple example that sums numbers from 1 to `n`: ```javascript function sumTo(n) { - // Base case: when n is 1, we know the answer - if (n === 1) { - return 1 + // Base case: when n is 1 or less, return n + if (n <= 1) { + return n } // Recursive case: n plus the sum of everything below it @@ -87,6 +87,8 @@ function sumTo(n) { } console.log(sumTo(5)) // 15 (5 + 4 + 3 + 2 + 1) +console.log(sumTo(1)) // 1 +console.log(sumTo(0)) // 0 ``` The function asks: "What's the sum from 1 to 5?" It answers: "5 plus the sum from 1 to 4." Then it asks the same question with 4, then 3, then 2, until it reaches 1, which it knows is just 1. @@ -134,7 +136,7 @@ When you find the smallest doll, you start closing them back up. In recursion, o ## How Recursion Works Under the Hood -To understand recursion, you need to understand how the [call stack](/concepts/call-stack) works. Every time a function is called, JavaScript creates an **[execution context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#function_context)** and pushes it onto the call stack. When the function returns, its context is popped off. +To understand recursion, you need to understand how the [call stack](/concepts/call-stack) works. Every time a function is called, JavaScript creates an **[execution context](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack)** and pushes it onto the call stack. When the function returns, its context is popped off. With recursion, multiple execution contexts for the *same function* stack up: @@ -217,6 +219,10 @@ sumTo(3) Here are the most common recursive algorithms you'll encounter. Understanding these patterns will help you recognize when recursion is the right tool. +<Note> +The examples below assume valid, non-negative integer inputs. In production code, you'd want to validate inputs and handle edge cases like negative numbers or non-integers. +</Note> + <AccordionGroup> <Accordion title="Factorial (n!)"> The factorial of a number `n` (written as `n!`) is the product of all positive integers from 1 to n: @@ -303,12 +309,13 @@ Here are the most common recursive algorithms you'll encounter. Understanding th ```javascript function sumTo(n) { - if (n === 1) return 1 + if (n <= 1) return n return n + sumTo(n - 1) } console.log(sumTo(5)) // 15 (1+2+3+4+5) console.log(sumTo(100)) // 5050 + console.log(sumTo(0)) // 0 ``` **Note:** There's a mathematical formula for this: `n * (n + 1) / 2`, which is O(1) instead of O(n). For simple sums, the formula is better. But the recursive approach teaches the pattern. @@ -909,7 +916,7 @@ The iterative version uses heap memory (the array) instead of stack memory, so i console.log(arrayLength([])) // 0 ``` - Note: We use `arr.length === 0` for the check, but we're counting by recurring through elements, not using `.length` to get the answer. + Note: We use `.length` only to check if the array is empty (our base case). The actual counting happens through recursion, not by directly returning `.length`. </Accordion> <Accordion title="Why is naive Fibonacci recursion inefficient, and how would you fix it?"> @@ -1016,8 +1023,8 @@ The iterative version uses heap memory (the array) instead of stack memory, so i <Card title="Recursion Explained (with Examples)" icon="newspaper" href="https://dev.to/christinamcmahon/recursion-explained-with-examples-4k1m"> Visual explanation of factorial and Fibonacci with tree diagrams. Includes memoization introduction. </Card> - <Card title="Learn and Understand Recursion in JavaScript" icon="newspaper" href="https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea"> - Practical guide with real-world examples and tips for thinking recursively. + <Card title="JavaScript Recursive Function" icon="newspaper" href="https://www.javascripttutorial.net/javascript-recursive-function/"> + Clear tutorial covering recursive function basics, countdowns, and sum calculations with detailed step-by-step explanations. </Card> </CardGroup> diff --git a/tests/functional-programming/recursion/recursion.test.js b/tests/functional-programming/recursion/recursion.test.js index f6c8d196..6f3d56ca 100644 --- a/tests/functional-programming/recursion/recursion.test.js +++ b/tests/functional-programming/recursion/recursion.test.js @@ -108,7 +108,7 @@ describe('Recursion', () => { describe('Sum to N', () => { function sumTo(n) { - if (n === 1) return 1 + if (n <= 1) return n return n + sumTo(n - 1) } @@ -118,8 +118,9 @@ describe('Recursion', () => { expect(sumTo(100)).toBe(5050) }) - it('should handle base case', () => { + it('should handle base cases', () => { expect(sumTo(1)).toBe(1) + expect(sumTo(0)).toBe(0) }) }) From cceba5ea7321c12f0173062edce2f9609f109925 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:50:42 -0300 Subject: [PATCH 096/128] docs: enhance map-reduce-filter with async handling, ES2023 methods, and Object.groupBy - Add comprehensive async callbacks section with Promise.all() patterns - Add ES2023 non-mutating alternatives: toSorted, toReversed, toSpliced - Add reduceRight() to methods reference table - Add Object.groupBy() as modern alternative to reduce-based grouping - Add tests for ES2023+ features and async patterns - Update key takeaways and learning objectives --- docs/concepts/map-reduce-filter.mdx | 91 ++++++++++++++++++- .../map-reduce-filter.test.js | 90 ++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) diff --git a/docs/concepts/map-reduce-filter.mdx b/docs/concepts/map-reduce-filter.mdx index 9d6dfc3a..9d31b0d1 100644 --- a/docs/concepts/map-reduce-filter.mdx +++ b/docs/concepts/map-reduce-filter.mdx @@ -34,6 +34,7 @@ That's **[map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference - Real-world patterns for extracting, filtering, and aggregating data - Other useful array methods like find(), some(), and every() - How to implement map and filter using reduce (advanced) +- How to handle async callbacks with these methods </Info> <Warning> @@ -584,6 +585,16 @@ console.log(byDepartment) // } ``` +<Note> +**Modern Alternative (ES2024):** JavaScript now has `Object.groupBy()` which does this in one line: + +```javascript +const byDepartment = Object.groupBy(people, person => person.department) +``` + +This is cleaner for simple grouping, but reduce is still useful when you need custom accumulation logic. Note: `Object.groupBy()` requires Node.js 21+ or modern browsers (Chrome 117+, Firefox 119+, Safari 17.4+). +</Note> + #### Building Objects from Arrays ```javascript @@ -964,10 +975,14 @@ JavaScript has many more array methods beyond map, filter, and reduce. Here's a | `flat(depth)` | Array | Flattens nested arrays | | `flatMap(fn)` | Array | map() then flat(1) | | `forEach(fn)` | undefined | Executes function for side effects | +| `reduceRight(fn, init)` | Any | Like reduce(), but right-to-left | | `sort(fn)` | Array | Sorts in place (mutates!) | +| `toSorted(fn)` | Array | Returns sorted copy (no mutation) — ES2023 | | `reverse()` | Array | Reverses in place (mutates!) | +| `toReversed()` | Array | Returns reversed copy (no mutation) — ES2023 | | `slice(start, end)` | Array | Returns portion (no mutation) | | `splice(start, count)` | Array | Removes/adds elements (mutates!) | +| `toSpliced(start, count)` | Array | Returns modified copy (no mutation) — ES2023 | ### Quick Examples @@ -994,6 +1009,17 @@ numbers.includes(3) // true // flatMap: Map and flatten [1, 2].flatMap(n => [n, n * 2]) // [1, 2, 2, 4] + +// reduceRight: Reduce from right to left +['a', 'b', 'c'].reduceRight((acc, s) => acc + s, '') // 'cba' + +// toSorted: Non-mutating sort (ES2023) +const nums = [3, 1, 2] +const sorted = nums.toSorted() // [1, 2, 3] +console.log(nums) // [3, 1, 2] — original unchanged! + +// toReversed: Non-mutating reverse (ES2023) +const reversed = nums.toReversed() // [2, 1, 3] ``` ### Which Method Should I Use? @@ -1022,6 +1048,69 @@ numbers.includes(3) // true --- +## Async Callbacks: The Hidden Gotcha + +One thing that catches developers off guard: `map()`, `filter()`, and `reduce()` don't wait for async callbacks. They run synchronously, which means you'll get an array of Promises instead of resolved values. + +```javascript +const userIds = [1, 2, 3] + +// ❌ WRONG: Returns array of Promises, not users +const users = userIds.map(async (id) => { + const response = await fetch(`/api/users/${id}`) + return response.json() +}) + +console.log(users) // [Promise, Promise, Promise] +``` + +### The Solution: Promise.all() + +Wrap the map result in `Promise.all()` to wait for all Promises to resolve: + +```javascript +const userIds = [1, 2, 3] + +// ✓ CORRECT: Wait for all Promises to resolve +const users = await Promise.all( + userIds.map(async (id) => { + const response = await fetch(`/api/users/${id}`) + return response.json() + }) +) + +console.log(users) // [{...}, {...}, {...}] — actual user objects +``` + +### Async Filter is Trickier + +For filter, you need a two-step approach since filter expects a boolean, not a Promise: + +```javascript +const numbers = [1, 2, 3, 4, 5] + +// Check if a number is "valid" via async operation +async function isValid(n) { + // Imagine this calls an API + return n % 2 === 0 +} + +// ❌ WRONG: filter doesn't await +const evens = numbers.filter(async (n) => await isValid(n)) +console.log(evens) // [1, 2, 3, 4, 5] — all items! (Promises are truthy) + +// ✓ CORRECT: Map to booleans first, then filter +const checks = await Promise.all(numbers.map(n => isValid(n))) +const evens2 = numbers.filter((_, index) => checks[index]) +console.log(evens2) // [2, 4] +``` + +<Tip> +**For sequential async operations** (when order matters or you need to limit concurrency), use a `for...of` loop instead of map. Array methods run all callbacks immediately in parallel. +</Tip> + +--- + ## Key Takeaways <Info> @@ -1045,7 +1134,7 @@ numbers.includes(3) // true 9. **Use find() for single item lookup** — It's more efficient than filter()[0] because it stops at the first match. -10. **Async callbacks need Promise.all** — map/filter/reduce don't wait for async callbacks. Wrap in Promise.all(). +10. **Async callbacks need Promise.all()** — map/filter/reduce don't wait for async callbacks. Wrap in Promise.all() to resolve them. </Info> --- diff --git a/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js b/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js index 6dbce049..eb0a675e 100644 --- a/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js +++ b/tests/functional-programming/map-reduce-filter/map-reduce-filter.test.js @@ -686,4 +686,94 @@ describe('map, reduce, filter', () => { expect(result).toBe(18) }) }) + + describe('ES2023+ Array Methods', () => { + it('reduceRight() reduces from right to left', () => { + const letters = ['a', 'b', 'c'] + const result = letters.reduceRight((acc, s) => acc + s, '') + + expect(result).toBe('cba') + }) + + it('toSorted() returns sorted copy without mutating original', () => { + const nums = [3, 1, 2] + const sorted = nums.toSorted() + + expect(sorted).toEqual([1, 2, 3]) + expect(nums).toEqual([3, 1, 2]) // Original unchanged + }) + + it('toReversed() returns reversed copy without mutating original', () => { + const nums = [1, 2, 3] + const reversed = nums.toReversed() + + expect(reversed).toEqual([3, 2, 1]) + expect(nums).toEqual([1, 2, 3]) // Original unchanged + }) + + it('toSpliced() returns modified copy without mutating original', () => { + const nums = [1, 2, 3, 4, 5] + const spliced = nums.toSpliced(1, 2, 'a', 'b') + + expect(spliced).toEqual([1, 'a', 'b', 4, 5]) + expect(nums).toEqual([1, 2, 3, 4, 5]) // Original unchanged + }) + + it('Object.groupBy() groups elements by key (ES2024, Node 21+)', () => { + // Skip test if Object.groupBy is not available (requires Node 21+) + if (typeof Object.groupBy !== 'function') { + console.log('Skipping: Object.groupBy not available in this Node version') + return + } + + const people = [ + { name: 'Alice', department: 'Engineering' }, + { name: 'Bob', department: 'Marketing' }, + { name: 'Charlie', department: 'Engineering' } + ] + + const byDepartment = Object.groupBy(people, person => person.department) + + expect(byDepartment.Engineering).toEqual([ + { name: 'Alice', department: 'Engineering' }, + { name: 'Charlie', department: 'Engineering' } + ]) + expect(byDepartment.Marketing).toEqual([ + { name: 'Bob', department: 'Marketing' } + ]) + }) + }) + + describe('Async Callbacks', () => { + it('map with async returns array of Promises', async () => { + const ids = [1, 2, 3] + + // Simulate async operation + const asyncDouble = async (n) => n * 2 + + // Without Promise.all, you get Promises + const promiseArray = ids.map(id => asyncDouble(id)) + + expect(promiseArray[0]).toBeInstanceOf(Promise) + + // With Promise.all, you get resolved values + const results = await Promise.all(promiseArray) + expect(results).toEqual([2, 4, 6]) + }) + + it('async filter workaround using map then filter', async () => { + const numbers = [1, 2, 3, 4, 5] + + // Simulate async predicate + const asyncIsEven = async (n) => n % 2 === 0 + + // Step 1: Get boolean results for each element + const checks = await Promise.all(numbers.map(n => asyncIsEven(n))) + + // Step 2: Filter using the boolean results + const evens = numbers.filter((_, index) => checks[index]) + + expect(evens).toEqual([2, 4]) + }) + }) }) From 40f45ef70b57e122592c4b03b8e99c31f8d4e2ea Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:55:21 -0300 Subject: [PATCH 097/128] fix: improve pure functions page accuracy and fix broken links - Remove broken MDN memoization link (returns 404) - Fix Fun Fun Function video link (was pointing to higher-order functions video) - Add nuance to exceptions as side effects claim (debated in FP community) - Add Note about structuredClone() limitations (cannot clone functions/DOM nodes) --- docs/concepts/pure-functions.mdx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/concepts/pure-functions.mdx b/docs/concepts/pure-functions.mdx index 7ec361b3..2d91ff01 100644 --- a/docs/concepts/pure-functions.mdx +++ b/docs/concepts/pure-functions.mdx @@ -232,7 +232,7 @@ A **side effect** is anything a function does besides computing and returning a | **DOM manipulation** | `element.innerHTML = '...'` | Changes the page state | | **HTTP requests** | `fetch('/api/data')` | Communicates with external systems | | **Writing to storage** | `localStorage.setItem()` | Persists data outside the function | -| **Throwing exceptions** | `throw new Error()` | Exits function in an unexpected way | +| **Throwing exceptions** | `throw new Error()` | Breaks normal control flow (debated) | ```javascript // ❌ IMPURE: Multiple side effects @@ -332,6 +332,10 @@ updatedUser.address.city = 'LA' console.log(user.address.city) // 'NYC' — Original safe! ``` +<Note> +**Limitation:** `structuredClone()` cannot clone functions or DOM nodes. It will throw a `DataCloneError` for these types. +</Note> + <Warning> **The Trap:** Spread operator (`...`) only copies one level deep. If you have nested objects or arrays, mutations to the nested data will affect the original. Use `structuredClone()` for deep copies, or see our [Value vs Reference Types](/concepts/value-reference-types) guide for more patterns. </Warning> @@ -467,7 +471,7 @@ Writing pure functions isn't just about following rules. It brings real, practic </Accordion> <Accordion title="3. Safe to Cache (Memoization)"> - Since pure functions always return the same output for the same input, you can safely cache their results. This is called [memoization](https://developer.mozilla.org/en-US/docs/Glossary/Memoization). + Since pure functions always return the same output for the same input, you can safely cache their results. This is called memoization. ```javascript // Expensive calculation - safe to cache because it's pure @@ -793,8 +797,8 @@ The pure functions (`formatUsername`, `isValidUsername`) are easy to test and re ## Videos <CardGroup cols={2}> - <Card title="Pure Functions — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=BMUiFMZr7vk"> - Mattias Petter Johansson's entertaining introduction to pure functions. Part of his excellent functional programming series that makes complex topics approachable. + <Card title="Refactoring Into Pure Functions — Fun Fun Function" icon="video" href="https://www.youtube.com/watch?v=cUrEedgvJSk"> + Mattias Petter Johansson demonstrates refactoring JavaScript code into tiny, pure, composable functions. Part of his excellent functional programming series that makes complex topics approachable. </Card> <Card title="Pure vs Impure Functions" icon="video" href="https://www.youtube.com/watch?v=AHbRVJzpB54"> Theodore Anderson's clear comparison of pure and impure functions with practical JavaScript examples and visual explanations. From 4c1fea58dd9ec1b5c9df5fdb49b5e8eeacd0f2eb Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 08:59:39 -0300 Subject: [PATCH 098/128] docs: add generator control methods and fix ASCII diagram spacing - Add new section covering .return() and .throw() generator methods - Document early termination with .return(value) - Document error injection with .throw(error) including try/catch handling - Fix inconsistent spacing in ASCII diagrams (vending machine and data flow) --- docs/concepts/generators-iterators.mdx | 147 ++++++++++++++++++------- 1 file changed, 109 insertions(+), 38 deletions(-) diff --git a/docs/concepts/generators-iterators.mdx b/docs/concepts/generators-iterators.mdx index 1d20279b..c11e38aa 100644 --- a/docs/concepts/generators-iterators.mdx +++ b/docs/concepts/generators-iterators.mdx @@ -119,29 +119,29 @@ Generators click when you have the right mental picture. Think of them like a ** ``` ┌─────────────────────────────────────────────────────────────────────────┐ -│ GENERATOR AS A VENDING MACHINE │ +│ GENERATOR AS A VENDING MACHINE │ ├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ YOU VENDING MACHINE │ -│ (caller) (generator) │ -│ │ -│ ┌─────────┐ ┌─────────────────┐ │ -│ │ │ │ ┌───────────┐ │ │ -│ │ "I'll │ ──── Press button ─────────► │ │ Snack A │ │ │ -│ │ have │ (call .next()) │ ├───────────┤ │ │ -│ │ one" │ │ │ Snack B │ │ │ -│ │ │ ◄─── Dispense one item ───── │ ├───────────┤ │ │ -│ │ │ (yield value) │ │ Snack C │ │ │ -│ │ │ │ └───────────┘ │ │ -│ │ │ * Machine PAUSES * │ │ │ -│ │ │ * Waits for next * │ [ PAUSED ] │ │ -│ │ │ * button press * │ │ │ -│ └─────────┘ └─────────────────┘ │ -│ │ -│ KEY INSIGHT: The machine remembers where it stopped! │ -│ When you press the button again, it gives you the NEXT item, │ -│ not the first one again. │ -│ │ +│ │ +│ YOU VENDING MACHINE │ +│ (caller) (generator) │ +│ │ +│ ┌─────────┐ ┌─────────────────┐ │ +│ │ │ │ ┌───────────┐ │ │ +│ │ "I'll │ ──── Press button ─────────► │ │ Snack A │ │ │ +│ │ have │ (call .next()) │ ├───────────┤ │ │ +│ │ one" │ │ │ Snack B │ │ │ +│ │ │ ◄─── Dispense one item ───── │ ├───────────┤ │ │ +│ │ │ (yield value) │ │ Snack C │ │ │ +│ │ │ │ └───────────┘ │ │ +│ │ │ * Machine PAUSES * │ │ │ +│ │ │ * Waits for next * │ [ PAUSED ] │ │ +│ │ │ * button press * │ │ │ +│ └─────────┘ └─────────────────┘ │ +│ │ +│ KEY INSIGHT: The machine remembers where it stopped! │ +│ When you press the button again, it gives you the NEXT item, │ +│ not the first one again. │ +│ │ └─────────────────────────────────────────────────────────────────────────┘ ``` @@ -376,23 +376,23 @@ console.log(chat.next('Blue').value) ``` ┌─────────────────────────────────────────────────────────────────────────┐ -│ DATA FLOW WITH yield │ +│ DATA FLOW WITH yield │ ├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ CALLER GENERATOR │ -│ │ -│ .next() ─────────────────────► starts execution │ -│ ◄───────────────────── yield 'question' │ -│ │ -│ .next('Alice') ─────────────────────► const name = 'Alice' │ -│ ◄───────────────────── yield 'Hello Alice' │ -│ │ -│ .next('Blue') ─────────────────────► const color = 'Blue' │ -│ ◄───────────────────── yield 'Blue is great' │ -│ │ -│ The value passed to .next() becomes the RESULT of the yield │ -│ expression inside the generator. │ -│ │ +│ │ +│ CALLER GENERATOR │ +│ │ +│ .next() ─────────────────────► starts execution │ +│ ◄───────────────────── yield 'question' │ +│ │ +│ .next('Alice') ─────────────────────► const name = 'Alice' │ +│ ◄───────────────────── yield 'Hello Alice' │ +│ │ +│ .next('Blue') ─────────────────────► const color = 'Blue' │ +│ ◄───────────────────── yield 'Blue is great' │ +│ │ +│ The value passed to .next() becomes the RESULT of the yield │ +│ expression inside the generator. │ +│ │ └─────────────────────────────────────────────────────────────────────────┘ ``` @@ -400,6 +400,77 @@ console.log(chat.next('Blue').value) **Why no value in the first `.next()`?** The first call starts the generator and runs until the first `yield`. There's no `yield` waiting to receive a value yet, so anything you pass gets ignored. </Note> +### Generator Control Methods: `.return()` and `.throw()` + +Beyond `.next()`, generators have two more control methods that give you full control over execution. + +#### Early Termination with `.return()` + +The `.return(value)` method ends the generator immediately and returns the specified value: + +```javascript +function* countdown() { + yield 3 + yield 2 + yield 1 + yield 'Liftoff!' +} + +const rocket = countdown() + +console.log(rocket.next()) // { value: 3, done: false } +console.log(rocket.return('Aborted')) // { value: 'Aborted', done: true } +console.log(rocket.next()) // { value: undefined, done: true } +// Generator is now closed — subsequent .next() calls return done: true +``` + +This is useful for cleanup or when you need to stop iteration early. + +#### Error Injection with `.throw()` + +The `.throw(error)` method throws an exception at the current `yield` point. If the generator has a `try/catch`, it can handle the error: + +```javascript +function* resilientGenerator() { + try { + yield 'A' + yield 'B' + yield 'C' + } catch (e) { + yield `Caught: ${e.message}` + } + yield 'Done' +} + +const gen = resilientGenerator() + +console.log(gen.next().value) // "A" +console.log(gen.throw(new Error('Oops!')).value) // "Caught: Oops!" +console.log(gen.next().value) // "Done" +``` + +If there's no `try/catch`, the error propagates out: + +```javascript +function* fragileGenerator() { + yield 'A' + yield 'B' // Error thrown here if we call .throw() after first yield +} + +const gen = fragileGenerator() +gen.next() // { value: 'A', done: false } + +try { + gen.throw(new Error('Boom!')) +} catch (e) { + console.log(e.message) // "Boom!" +} +``` + +<Tip> +These methods complete the generator's interface. While `.next()` is used most often, `.return()` and `.throw()` give you full control over generator execution — useful for resource cleanup and error handling in complex workflows. +</Tip> + --- ## The Iteration Protocol (`Symbol.iterator`) From 053b0dcf1560ae19cfd91c8407542df7a7fd01e8 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:00:35 -0300 Subject: [PATCH 099/128] fix: improve higher-order functions page accuracy and add MDN links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clarify Java lambda expressions vs JS first-class functions - Replace confusing sphere volume example with squaredRadius - Fix wrapper pattern diagram to match code example (3,1,4,1,5 → 5) - Add inline MDN links to all array HOF methods in table - Replace arbitrary temperature validator with practical rating validator - Update corresponding test file for rating validator --- docs/concepts/higher-order-functions.mdx | 28 ++++++++++--------- .../higher-order-functions.test.js | 16 +++++------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/concepts/higher-order-functions.mdx b/docs/concepts/higher-order-functions.mdx index 5a8db115..1c15385a 100644 --- a/docs/concepts/higher-order-functions.mdx +++ b/docs/concepts/higher-order-functions.mdx @@ -206,7 +206,7 @@ console.log(triple(5)) // 15 ``` <Note> -**Not all languages have first-class functions.** In older languages like C or Java (before version 8), you couldn't easily pass functions around. JavaScript's first-class functions make functional programming patterns natural and powerful. +**Not all languages have first-class functions.** In languages like C, you can't easily pass functions around as values. Java added lambda expressions in version 8, but they work differently than JavaScript functions. JavaScript's first-class functions make functional programming patterns natural and powerful. </Note> --- @@ -305,8 +305,9 @@ console.log(calculate(radii, diameter)) Now adding a new calculation is easy. Just write a new formula function: ```javascript -const volume = r => (4/3) * Math.PI * r * r * r -console.log(calculate(radii, volume)) +// Works for any formula that takes a radius! +const squaredRadius = r => r * r +console.log(calculate(radii, squaredRadius)) // [1, 4, 9] ``` ### Example: An `unless` Function @@ -417,7 +418,7 @@ The original functions (`Math.max`, `Math.floor`) are unchanged. We've created n │ │ │ │ 1. Log the arguments │ │ │ │ Math.max │ noisy() │ 2. Call Math.max │ │ │ │ │ ────────► │ 3. Log the result │ │ -│ │ (3,1,4) → 4 │ │ 4. Return the result │ │ +│ │ (3,1,4,1,5) → 5 │ │ 4. Return the result │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────────────────────┘ │ │ │ @@ -444,12 +445,13 @@ function createValidator(min, max) { const isValidAge = createValidator(0, 120) const isValidPercentage = createValidator(0, 100) -const isValidTemperature = createValidator(-273.15, 1000000) +const isValidRating = createValidator(1, 5) console.log(isValidAge(25)) // true console.log(isValidAge(150)) // false console.log(isValidPercentage(50)) // true console.log(isValidPercentage(101)) // false +console.log(isValidRating(3)) // true ``` ### Example: Creating Formatters @@ -592,14 +594,14 @@ JavaScript provides many built-in higher-order functions, especially for working | Method | What it does | Returns | |--------|--------------|---------| -| `forEach(fn)` | Calls `fn` on each element | `undefined` | -| `map(fn)` | Transforms each element with `fn` | New array | -| `filter(fn)` | Keeps elements where `fn` returns `true` | New array | -| `reduce(fn, init)` | Accumulates elements into single value | Single value | -| `find(fn)` | Returns first element where `fn` returns `true` | Element or `undefined` | -| `some(fn)` | Tests if any element passes `fn` | `boolean` | -| `every(fn)` | Tests if all elements pass `fn` | `boolean` | -| `sort(fn)` | Sorts elements using comparator `fn` | Sorted array (mutates!) | +| [`forEach(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) | Calls `fn` on each element | `undefined` | +| [`map(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) | Transforms each element with `fn` | New array | +| [`filter(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) | Keeps elements where `fn` returns `true` | New array | +| [`reduce(fn, init)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) | Accumulates elements into single value | Single value | +| [`find(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) | Returns first element where `fn` returns `true` | Element or `undefined` | +| [`some(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) | Tests if any element passes `fn` | `boolean` | +| [`every(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) | Tests if all elements pass `fn` | `boolean` | +| [`sort(fn)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) | Sorts elements using comparator `fn` | Sorted array (mutates!) | ```javascript const numbers = [1, 2, 3, 4, 5] diff --git a/tests/functional-programming/higher-order-functions/higher-order-functions.test.js b/tests/functional-programming/higher-order-functions/higher-order-functions.test.js index 197c7ccf..56827fe7 100644 --- a/tests/functional-programming/higher-order-functions/higher-order-functions.test.js +++ b/tests/functional-programming/higher-order-functions/higher-order-functions.test.js @@ -263,21 +263,21 @@ describe('Higher-Order Functions', () => { expect(askHowAreYou('Charlie')).toBe('How are you, Charlie?') }) - it('should create temperature validator', () => { + it('should create rating validator', () => { function createValidator(min, max) { return function(value) { return value >= min && value <= max } } - // Absolute zero in Celsius is -273.15 - const isValidTemperature = createValidator(-273.15, 1000000) + // Rating from 1 to 5 stars + const isValidRating = createValidator(1, 5) - expect(isValidTemperature(25)).toBe(true) - expect(isValidTemperature(-273.15)).toBe(true) // Exactly at absolute zero - expect(isValidTemperature(-300)).toBe(false) // Below absolute zero - expect(isValidTemperature(1000000)).toBe(true) // At max - expect(isValidTemperature(1000001)).toBe(false) // Above max + expect(isValidRating(3)).toBe(true) + expect(isValidRating(1)).toBe(true) // At min + expect(isValidRating(5)).toBe(true) // At max + expect(isValidRating(0)).toBe(false) // Below min + expect(isValidRating(6)).toBe(false) // Above max }) }) From f77ee83e5a8a7b7ee91248adaaa318520dd8ec54 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:01:06 -0300 Subject: [PATCH 100/128] fix: correct return await explanation and reduce em-dash overuse in async-await - Fix Mistake #5: correctly explain that return await IS needed inside try/catch to catch errors (was incorrectly calling it a 'subtle bug') - Fix Interview Q5: change question to show actual bug pattern (missing await in try/catch) with proper explanation - Add technical note about await always pausing until next microtask - Reduce em-dash overuse throughout document (8 instances converted to periods/commas for better readability) --- docs/concepts/async-await.mdx | 111 ++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/docs/concepts/async-await.mdx b/docs/concepts/async-await.mdx index bcb3a203..4b9599d1 100644 --- a/docs/concepts/async-await.mdx +++ b/docs/concepts/async-await.mdx @@ -325,9 +325,13 @@ console.log(result) // "thenable value" (after 1 second) **Pro tip:** Only use `await` when you're actually waiting for a Promise. Awaiting non-Promise values works but adds unnecessary overhead and confuses anyone reading your code. </Tip> +<Note> +**Technical detail:** Even when awaiting an already-resolved Promise or a non-Promise value, execution still pauses until the next microtask. This is why `await` always yields control back to the caller before continuing. +</Note> + ### await Pauses the Function, Not the Thread -This trips people up: `await` pauses only the async function it's in, not the entire JavaScript thread. Other code can run while waiting: +This trips people up. `await` pauses only the async function it's in, not the entire JavaScript thread. Other code can run while waiting: ```javascript async function slowOperation() { @@ -436,7 +440,7 @@ This is why understanding the [Event Loop](/concepts/event-loop) is so important ## Error Handling with try/catch -Finally, **error handling that doesn't make you want to flip a table**. Instead of chaining `.catch()` after `.then()` after `.catch()`, you get to use good old try/catch blocks. +Finally, error handling that doesn't make you want to flip a table. Instead of chaining `.catch()` after `.then()` after `.catch()`, you get to use good old try/catch blocks. ### Basic try/catch Pattern @@ -585,7 +589,7 @@ async function fetchData() { ## Sequential vs Parallel Execution -This is a big one. **By default, await makes operations sequential**, but often you want them to run in parallel. +This is a big one. By default, `await` makes operations sequential, but often you want them to run in parallel. ### The Problem: Unnecessary Sequential Execution @@ -675,9 +679,9 @@ async function getUserDashboard(userId) { ### Promise.all vs Promise.allSettled -**[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all):** Fails fast. If any Promise rejects, the whole thing rejects. +**[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)** fails fast. If any Promise rejects, the whole thing rejects. -**[Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled):** Waits for all Promises, gives you results for each (fulfilled or rejected). +**[Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)** waits for all Promises and gives you results for each (fulfilled or rejected). ```javascript // Promise.all - fails fast @@ -755,7 +759,7 @@ async function processOrder(orderId) { ### Mistake #1: Forgetting await -Without `await`, you get a Promise object instead of the resolved value: +Without `await`, you get a Promise object instead of the resolved value. ```javascript // ❌ WRONG - response is a Promise, not a Response! @@ -779,7 +783,7 @@ async function fetchUser() { ### Mistake #2: Using await in forEach -`forEach` and async don't play well together — it just fires and forgets: +`forEach` and async don't play well together. It just fires and forgets: ```javascript // ❌ WRONG - forEach doesn't await! @@ -838,7 +842,7 @@ async function getData() { ### Mistake #4: Not Handling Errors -Unhandled Promise rejections can crash your application: +Unhandled Promise rejections can crash your application. ```javascript // ❌ WRONG - No error handling @@ -865,51 +869,67 @@ async function safeOperation() { riskyOperation().catch(err => console.error('Failed:', err)) ``` -### Mistake #5: await Inside try Block Returns +### Mistake #5: Missing await Before return in try/catch -Be careful with where you place `await` relative to `return`: +If you want to catch errors from a Promise inside a try/catch, you **must** use `await`. Without it, the Promise is returned before it settles, and the catch block never runs: ```javascript -// ❌ SUBTLE BUG - The await is pointless here +// ❌ WRONG - catch block won't catch fetch errors! async function fetchData() { try { - return await fetch('/api/data') // await is unnecessary + return fetch('/api/data') // Promise returned before it settles } catch (error) { + // This NEVER runs for fetch errors! console.error('Error:', error) } } -// This is equivalent and cleaner: +// ✓ CORRECT - await lets catch block handle errors async function fetchData() { try { - return fetch('/api/data') // No await needed + return await fetch('/api/data') // await IS needed here } catch (error) { - // BUT: This catch won't catch fetch errors! console.error('Error:', error) + throw error } } +``` -// ✓ CORRECT - If you want to catch errors, process the result -async function fetchData() { +**Why does this happen?** When you `return fetch(...)` without `await`, the Promise is immediately returned to the caller. If that Promise later rejects, the rejection happens *outside* the try/catch block, so the catch never sees it. + +<Warning> +**Common misconception:** Some guides say `return await` is redundant. That's only true *outside* of try/catch blocks. Inside try/catch, you need `await` to catch errors from the Promise. +</Warning> + +```javascript +// Outside try/catch, these ARE equivalent: +async function noTryCatch() { + return await fetch('/api/data') // await is optional here +} + +async function noTryCatchSimpler() { + return fetch('/api/data') // Same result, slightly cleaner +} + +// But inside try/catch, they behave DIFFERENTLY: +async function withTryCatch() { try { - const response = await fetch('/api/data') - return response // Now errors ARE caught - } catch (error) { - console.error('Error:', error) - throw error - } + return await fetch('/api/data') // Errors ARE caught + } catch (e) { /* handles errors */ } } -``` -<Tip> -**Quick check:** If you have a try/catch and want to catch errors from a Promise, make sure to `await` it on a separate line before returning. -</Tip> +async function brokenTryCatch() { + try { + return fetch('/api/data') // Errors NOT caught! + } catch (e) { /* never runs for fetch errors */ } +} +``` --- ## async/await vs Promise Chains -Both async/await and Promise chains achieve the same thing. The choice often comes down to readability and personal preference. +Both async/await and Promise chains achieve the same result. The choice often comes down to readability and personal preference. ### Comparison Table @@ -996,7 +1016,7 @@ async function robustFetch(url, retries = 3) { ## Top-Level await -[Top-level await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top-level-await) allows you to use `await` outside of async functions, but only in ES modules. +[Top-level await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top-level-await) allows you to use `await` outside of async functions. This only works in ES modules. ```javascript // config.js (ES module) @@ -1039,7 +1059,7 @@ export const supportsWebGL = await checkWebGLSupport() ``` <Warning> -**Careful:** Top-level await blocks the loading of the module and any modules that import it. Use it sparingly and only when you truly need the value before the module can be used. +**Careful:** Top-level await blocks the loading of the module and any modules that import it. Use it sparingly, only when you truly need the value before the module can be used. </Warning> --- @@ -1317,32 +1337,43 @@ Done ```javascript async function getData() { - return await fetch('/api/data') + try { + return fetch('/api/data') + } catch (error) { + console.error('Failed:', error) + return null + } } ``` <Accordion title="Answer"> -**Issue:** The `await` is unnecessary. +**Issue:** The `catch` block will never catch fetch errors. -Since `getData` is already async, it will return a Promise. You don't need to `await` before returning. The Promise will be returned directly. +When you `return fetch(...)` without `await`, the Promise is returned *before* it settles. If the fetch later fails, the rejection happens outside the try/catch block. ```javascript -// Equivalent, simpler: +// ❌ WRONG - catch never runs for fetch errors async function getData() { - return fetch('/api/data') + try { + return fetch('/api/data') // Promise returned immediately + } catch (error) { + console.error('Failed:', error) // Never runs! + return null + } } -// The 'await' only matters if you need to do something with the result -// or if you're in a try/catch and want to catch errors: +// ✓ CORRECT - await lets catch block handle errors async function getData() { try { - return await fetch('/api/data') // await IS needed here to catch errors + return await fetch('/api/data') // await IS needed } catch (error) { - console.error(error) - throw error + console.error('Failed:', error) // Now this runs on error + return null } } ``` + +**Note:** Outside of try/catch, `return await` and `return` behave the same. The `await` only matters when you need to catch errors or do something with the value before returning. </Accordion> --- From 4f2caca8d192e0f5f5970b4f90bdd6c6d5fd3e22 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:03:12 -0300 Subject: [PATCH 101/128] docs: add Promise.try(), thenables section, and edge case behaviors to promises page --- docs/concepts/promises.mdx | 110 ++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/docs/concepts/promises.mdx b/docs/concepts/promises.mdx index 15e7d51f..dc7c1fd4 100644 --- a/docs/concepts/promises.mdx +++ b/docs/concepts/promises.mdx @@ -32,7 +32,7 @@ A **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ - How to create Promises with the Promise constructor - How to consume Promises with `.then()`, `.catch()`, and `.finally()` - How Promise chaining works and why it's powerful -- All the Promise static methods: `all`, `allSettled`, `race`, `any`, `resolve`, `reject` +- All the Promise static methods: `all`, `allSettled`, `race`, `any`, `resolve`, `reject`, `withResolvers`, `try` - Common patterns and mistakes to avoid </Info> @@ -282,6 +282,37 @@ outerPromise.then(value => { When you resolve a Promise with another Promise, the outer Promise "adopts" the state of the inner one. This is called **Promise unwrapping**. The outer Promise automatically follows whatever happens to the inner Promise. +### Thenables + +JavaScript doesn't just work with native Promises — it also supports **thenables**. A thenable is any object with a `.then()` method. This allows Promises to interoperate with Promise-like objects from libraries: + +```javascript +// A thenable is any object with a .then() method +const thenable = { + then(onFulfilled, onRejected) { + onFulfilled(42) + } +} + +// Promise.resolve() unwraps thenables +Promise.resolve(thenable).then(value => { + console.log(value) // 42 +}) + +// Returning a thenable from .then() also works +Promise.resolve('start') + .then(() => thenable) + .then(value => console.log(value)) // 42 +``` + +This is why `Promise.resolve()` doesn't always return a new Promise — if you pass it a native Promise, it returns the same Promise: + +```javascript +const p = Promise.resolve('hello') +const p2 = Promise.resolve(p) +console.log(p === p2) // true +``` + --- ## Creating Promises @@ -930,6 +961,8 @@ Promise.all([ - You need ALL results to proceed - Any single failure should abort the whole operation - You want to run Promises in parallel and wait for all + +**Note:** `Promise.all([])` with an empty array resolves immediately with `[]`. Also, non-Promise values in the array are automatically wrapped with `Promise.resolve()`. </Tip> ### Promise.allSettled() — Wait for All (No Short-Circuit) @@ -1024,6 +1057,8 @@ Promise.race([ ]) .catch(error => console.log(error.message)) // "Fast failure" ``` + +**Edge case:** `Promise.race([])` with an empty array returns a Promise that **never settles** (stays pending forever). This is rarely useful and usually indicates a bug. </Warning> ### Promise.any() — First to Fulfill Wins @@ -1085,16 +1120,18 @@ fetchFromFastestMirror(mirrors) - You only need one successful result - You have multiple sources/fallbacks and want the first success - Rejections should be ignored unless everything fails + +**Edge case:** `Promise.any([])` with an empty array immediately rejects with an `AggregateError` (since there are no Promises that could fulfill). </Tip> ### Comparison Table -| Method | Fulfills when... | Rejects when... | Use case | -|--------|-----------------|-----------------|----------| -| `Promise.all()` | ALL fulfill | ANY rejects | Need all results, fail-fast | -| `Promise.allSettled()` | ALL settle | Never | Need all results, tolerate failures | -| `Promise.race()` | First settles | First settles (if rejection) | Timeout, fastest response | -| `Promise.any()` | First fulfills | ALL reject | First success, ignore failures | +| Method | Fulfills when... | Rejects when... | Empty array `[]` | Use case | +|--------|-----------------|-----------------|------------------|----------| +| `Promise.all()` | ALL fulfill | ANY rejects | Fulfills with `[]` | Need all results, fail-fast | +| `Promise.allSettled()` | ALL settle | Never | Fulfills with `[]` | Need all results, tolerate failures | +| `Promise.race()` | First to settle fulfills | First to settle rejects | Never settles | Timeout, fastest response | +| `Promise.any()` | ANY fulfills | ALL reject | Rejects (AggregateError) | First success, ignore failures | ### Promise.withResolvers() @@ -1121,6 +1158,59 @@ const promise = new Promise((res, rej) => { // Now resolve/reject are available outside ``` +### Promise.try() + +**[`Promise.try()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/try)** (Baseline 2025) takes a callback of any kind and wraps its result in a Promise. This is useful when you have a function that might be synchronous or asynchronous and you want to handle both cases uniformly: + +```javascript +// The problem: func() might throw synchronously OR return a Promise +// This doesn't catch synchronous errors: +Promise.resolve(func()).catch(handleError) // Sync throw escapes! + +// This works but is verbose: +new Promise((resolve) => resolve(func())) + +// Promise.try() is cleaner: +Promise.try(func) +``` + +**Real example: handling callbacks that might be sync or async** + +```javascript +function processData(callback) { + return Promise.try(callback) + .then(result => console.log('Result:', result)) + .catch(error => console.error('Error:', error)) + .finally(() => console.log('Done')) +} + +// Works with sync functions +processData(() => 'sync result') + +// Works with async functions +processData(async () => 'async result') + +// Catches sync throws +processData(() => { throw new Error('sync error') }) + +// Catches async rejections +processData(async () => { throw new Error('async error') }) +``` + +You can also pass arguments to the callback: + +```javascript +// Instead of creating a closure: +Promise.try(() => fetchUser(userId)) + +// You can pass arguments directly: +Promise.try(fetchUser, userId) +``` + +<Note> +`Promise.try()` calls the function **synchronously** (like the Promise constructor executor), unlike `.then()` which always runs callbacks asynchronously. If possible, it resolves the promise immediately. +</Note> + --- ## Common Patterns @@ -1601,6 +1691,12 @@ Promise callbacks are scheduled as **microtasks**, which run after the current s <Card title="Promise.allSettled() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled"> Documentation for Promise.allSettled() with examples </Card> + <Card title="Promise.try() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/try"> + Documentation for Promise.try() (Baseline 2025) + </Card> + <Card title="Promise.withResolvers() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers"> + Documentation for Promise.withResolvers() (ES2024) + </Card> </CardGroup> ## Articles From 2d4012257942d05be3388af8d66602cb67ebb08a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:03:21 -0300 Subject: [PATCH 102/128] fix: remove broken external links from callbacks page - Remove callbackhell.com reference (returns 403 Forbidden) - Remove codeburst.io article link (SSL certificate error) - Keep working links from freeCodeCamp and javascript.info --- docs/concepts/callbacks.mdx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/concepts/callbacks.mdx b/docs/concepts/callbacks.mdx index 4e338743..31cff35c 100644 --- a/docs/concepts/callbacks.mdx +++ b/docs/concepts/callbacks.mdx @@ -752,9 +752,7 @@ getUser(userId, function(error, user) { 5. **Variable scope** — Variables from outer callbacks are hard to track 6. **Testing** — Nearly impossible to unit test individual steps -<Note> -Want to dive deeper into callback hell? Check out [callbackhell.com](http://callbackhell.com/), a dedicated guide to understanding and escaping the pyramid of doom. -</Note> + --- @@ -1417,12 +1415,7 @@ Callbacks aren't obsolete. They're the foundation that everything else builds up <Card title="Callback Functions in JavaScript" icon="newspaper" href="https://javascript.info/callbacks"> Uses a script-loading example to show why callbacks exist and how they solve real problems. The "pyramid of doom" section shows exactly how callback hell develops. </Card> - <Card title="Callback Hell" icon="newspaper" href="http://callbackhell.com/"> - A single-page site dedicated entirely to callback hell. Shows the problem visually, then walks through three concrete solutions with before/after code. - </Card> - <Card title="Understand Callback Functions in JavaScript" icon="newspaper" href="https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced"> - Breaks down callbacks into two simple rules you can remember. Short read that cuts through the jargon and gets to the point fast. - </Card> + </CardGroup> ## Videos From e4e8e4ada7f0e6e4fb7f3984627e815e43d9ae93 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:05:41 -0300 Subject: [PATCH 103/128] fix: improve technical accuracy in callbacks page - Fix phrasing: 'callbacks are passed to higher-order functions' (not 'are higher-order functions in action') - Replace Node.js-specific setImmediate with cross-platform setTimeout(..., 0) --- docs/concepts/callbacks.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/concepts/callbacks.mdx b/docs/concepts/callbacks.mdx index 31cff35c..bd61a787 100644 --- a/docs/concepts/callbacks.mdx +++ b/docs/concepts/callbacks.mdx @@ -36,7 +36,7 @@ The answer is **callbacks**: functions you pass to other functions, saying "call </Info> <Warning> -**Prerequisites:** This guide assumes familiarity with [the Event Loop](/concepts/event-loop). It's the mechanism that makes async callbacks work! You should also understand [higher-order functions](/concepts/higher-order-functions), since callbacks are higher-order functions in action. +**Prerequisites:** This guide assumes familiarity with [the Event Loop](/concepts/event-loop). It's the mechanism that makes async callbacks work! You should also understand [higher-order functions](/concepts/higher-order-functions), since callbacks are passed to higher-order functions. </Warning> --- @@ -1025,10 +1025,10 @@ console.log(value) // "initial" or the data? Depends on cache! // ✓ CORRECT - always async function getData(cache, callback) { if (cache.has('data')) { - // Use setImmediate, process.nextTick, or setTimeout to make it async - setImmediate(function() { + // Use setTimeout to make it async (works in browsers and Node.js) + setTimeout(function() { callback(null, cache.get('data')) - }) + }, 0) return } From 09818ebcaca14fb07e6a17aa14c54fcd95b4696b Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:07:19 -0300 Subject: [PATCH 104/128] fix: improve accuracy in inheritance-polymorphism concept page - Add battleCry() to Warrior.prototype in ASCII diagram for accuracy - Clarify Gorilla-Banana quote attribution to Joe Armstrong (Coders at Work) - Improve Traversy Media video description to be more specific --- docs/concepts/inheritance-polymorphism.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/concepts/inheritance-polymorphism.mdx b/docs/concepts/inheritance-polymorphism.mdx index 1c8958da..961f9e51 100644 --- a/docs/concepts/inheritance-polymorphism.mdx +++ b/docs/concepts/inheritance-polymorphism.mdx @@ -610,6 +610,7 @@ This is why inheritance "just works" — methods defined on parent classes are a │ └─────────────────┘ │ │ │ ▼ │ │ Warrior.prototype ┌─────────────────┐ │ +│ │ battleCry() │ │ │ │ constructor │ │ │ │ [[Prototype]] ──┼──┐ │ │ └─────────────────┘ │ │ @@ -1273,7 +1274,7 @@ class Stack { </Card> <Card title="The Gorilla-Banana Problem" icon="newspaper" href="https://www.johndcook.com/blog/2011/07/19/you-wanted-banana/"> - The origin of the famous inheritance criticism + Joe Armstrong's famous OOP criticism from "Coders at Work" — you wanted a banana but got the whole jungle </Card> </CardGroup> @@ -1281,7 +1282,7 @@ class Stack { <CardGroup cols={2}> <Card title="JavaScript ES6 Classes and Inheritance" icon="video" href="https://www.youtube.com/watch?v=RBLIm5LMrmc"> - Traversy Media's tutorial on ES6 class inheritance + Traversy Media's ES6 series covers class syntax and inheritance with clear, practical examples </Card> <Card title="Inheritance in JavaScript" icon="video" href="https://www.youtube.com/watch?v=yXlFR81tDBM"> Detailed walkthrough of inheritance concepts by kudvenkat From 81482ebc057ce285f0118b3349c11906d76f1c4a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:10:17 -0300 Subject: [PATCH 105/128] fix: improve accuracy and fix broken links in object-creation-prototypes - Replace broken patterns.dev link with MDN learning path - Add Object.hasOwn() as modern alternative to hasOwnProperty() - Clarify new operator step 2 edge case (non-object prototype) - Add edge case note for constructors returning functions - Improve prototype pollution example with realistic attack vector - Add MDN link to prototype pollution security docs --- docs/concepts/object-creation-prototypes.mdx | 59 ++++++++++++++------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/docs/concepts/object-creation-prototypes.mdx b/docs/concepts/object-creation-prototypes.mdx index ee65c9e2..cb4d2517 100644 --- a/docs/concepts/object-creation-prototypes.mdx +++ b/docs/concepts/object-creation-prototypes.mdx @@ -417,13 +417,13 @@ The [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Op JavaScript creates a fresh object: `const obj = {}` </Step> <Step title="Link the prototype"> - Sets `obj`'s `[[Prototype]]` to `Constructor.prototype` + Sets `obj`'s `[[Prototype]]` to `Constructor.prototype` (if it's an object). If `Constructor.prototype` is not an object (e.g., a primitive), the new object uses `Object.prototype` instead. </Step> <Step title="Execute the constructor"> Runs the constructor with `this` bound to the new object </Step> <Step title="Return the object"> - Returns `obj` (unless the constructor explicitly returns a different object) + Returns `obj` (unless the constructor explicitly returns a non-primitive value) </Step> </Steps> @@ -501,7 +501,9 @@ function myNew(Constructor, ...args) { // Step 3: Run constructor with 'this' = obj const result = Constructor.apply(obj, args) - // Step 4: Return result if it's an object, otherwise return obj + // Step 4: Return result if it's a non-primitive, otherwise return obj + // Note: Functions are also objects, so constructors returning functions + // will override the default return as well return (result !== null && typeof result === 'object') ? result : obj } @@ -513,6 +515,10 @@ console.log(player1 instanceof Player) // true console.log(player2 instanceof Player) // true ``` +<Note> +**Edge case:** If a constructor returns a function, that function is returned instead of the new object (since functions are objects in JavaScript). This is rare in practice but technically allowed by the spec. +</Note> + <Warning> **Don't forget `new`!** Without it, `this` in a constructor refers to the global object (or `undefined` in strict mode), causing bugs. ES6 classes throw an error if you forget `new`, which is safer. </Warning> @@ -709,6 +715,22 @@ console.log("own" in obj) // true console.log("inherited" in obj) // true ``` +<Tip> +**Modern alternative: `Object.hasOwn()`** (ES2022+) + +Use [`Object.hasOwn()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn) instead of `hasOwnProperty()`. It's safer because it works on objects with a `null` prototype and can't be shadowed: + +```javascript +// hasOwnProperty can be shadowed or unavailable +const nullProto = Object.create(null) +nullProto.key = "value" +// nullProto.hasOwnProperty("key") // TypeError: not a function + +// Object.hasOwn always works +Object.hasOwn(nullProto, "key") // true +``` +</Tip> + ### Object.keys() vs for...in ```javascript @@ -813,27 +835,32 @@ console.log(Player.prototype) // { constructor: Player } ### Mistake 3: Prototype Pollution -When using objects as dictionaries, user input could overwrite prototype properties: +[Prototype pollution](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution) occurs when attackers can modify `Object.prototype`, affecting all objects. This is a real security vulnerability: ```javascript -// ❌ DANGEROUS with user input -const userData = {} +// ❌ DANGEROUS - merging untrusted data can pollute prototypes +const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}') -// Imagine this comes from user input -const key = "__proto__" -const value = { isAdmin: true } +const user = {} +Object.assign(user, maliciousPayload) // Pollution via Object.assign! -userData[key] = value // In some environments, this pollutes the prototype! +// Now ALL objects have isAdmin! +const anotherUser = {} +console.log(anotherUser.isAdmin) // true - polluted! -// Safer approach: use null prototype or Map +// ✓ SAFER - use null prototype objects for dictionaries const safeDict = Object.create(null) -safeDict[key] = value // No prototype to pollute +safeDict["__proto__"] = "safe" // Just a regular property, no pollution -// Or use Map for dictionaries +// ✓ SAFEST - use Map for key-value storage with untrusted keys const map = new Map() -map.set(key, value) // Completely safe +map.set("__proto__", "value") // Completely safe ``` +<Warning> +**Prototype pollution attacks** can occur through `Object.assign()`, object spread (`{...obj}`), deep merge utilities, and JSON parsing. Always sanitize untrusted input and consider using `Object.create(null)` or `Map` for user-controlled keys. +</Warning> + ### Mistake 4: Shared Reference on Prototype ```javascript @@ -1078,8 +1105,8 @@ function Player(name) { <Card title="Understanding Prototypes in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript"> Walks through building a full inheritance hierarchy from scratch with runnable examples. Great for developers who learn by building rather than reading theory. </Card> - <Card title="JavaScript Object Creation Patterns" icon="newspaper" href="https://www.patterns.dev/vanilla/object-creation-patterns"> - Compares factory functions, constructor patterns, and Object.create() side by side. Helps you choose the right pattern for your specific use case. + <Card title="Object-Oriented JavaScript" icon="newspaper" href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects"> + MDN's learning path covering object basics, prototypes, and classes. Includes hands-on exercises and a practical project to solidify your understanding. </Card> <Card title="The Prototype Chain Explained" icon="newspaper" href="https://javascript.info/prototype-inheritance"> Includes interactive code examples you can edit and run in the browser. The "tasks" section at the end tests your understanding with practical challenges. From bb34d764eb325463c3bde4d8b7887f6f5f85bb1a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:10:45 -0300 Subject: [PATCH 106/128] fix: improve accuracy and fix broken links in this-call-apply-bind - Fix simulateNew implementation to properly handle non-object prototypes - Update return value check from instanceof to typeof for accuracy - Fix broken links: /concepts/new-constructor -> /concepts/object-creation-prototypes - Fix broken links: /concepts/prototype -> /concepts/inheritance-polymorphism - Clarify arrow function arguments behavior with enclosing scope example - Add test case for constructor with non-object prototype edge case --- docs/concepts/this-call-apply-bind.mdx | 35 +++++++++++------ .../this-call-apply-bind.test.js | 38 ++++++++++++++++--- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/docs/concepts/this-call-apply-bind.mdx b/docs/concepts/this-call-apply-bind.mdx index 56e018da..3827cdd5 100644 --- a/docs/concepts/this-call-apply-bind.mdx +++ b/docs/concepts/this-call-apply-bind.mdx @@ -237,14 +237,17 @@ function simulateNew(Constructor, ...args) { // Step 1: Create empty object const newObject = {}; - // Step 2: Link prototype (see Object.setPrototypeOf on MDN) - Object.setPrototypeOf(newObject, Constructor.prototype); + // Step 2: Link prototype if it's an object + // (If prototype isn't an object, newObject keeps Object.prototype) + if (Constructor.prototype !== null && typeof Constructor.prototype === 'object') { + Object.setPrototypeOf(newObject, Constructor.prototype); + } // Step 3: Bind this and execute const result = Constructor.apply(newObject, args); - // Step 4: Return object (unless constructor returns an object) - return result instanceof Object ? result : newObject; + // Step 4: Return object (unless constructor returns a non-primitive) + return result !== null && typeof result === 'object' ? result : newObject; } // These are equivalent: @@ -273,7 +276,7 @@ console.log(rect.getArea()); // 50 ``` <Tip> -For more on constructors, the `new` keyword, and `instanceof`, see the [new, Constructor, instanceof](/concepts/new-constructor) concept page. +For more on constructors, the `new` keyword, and prototypes, see the [Object Creation & Prototypes](/concepts/object-creation-prototypes) concept page. </Tip> --- @@ -1226,17 +1229,27 @@ class SearchBox { const ArrowClass = () => {}; new ArrowClass(); // TypeError: ArrowClass is not a constructor -// 2. No 'arguments' object (arrow functions don't have their own arguments binding) +// 2. No own 'arguments' object +// Arrow functions inherit 'arguments' from enclosing function scope (if any) +function outer() { + const arrow = () => { + console.log(arguments); // Works! Uses outer's arguments + }; + arrow(); +} +outer(1, 2, 3); // logs [1, 2, 3] + +// But at module/global scope with no enclosing function: const arrow = () => { console.log(arguments); // ReferenceError: arguments is not defined }; -// Use rest parameters instead +// Use rest parameters instead (recommended) const arrowWithRest = (...args) => { - console.log(args); // Works! + console.log(args); // Works everywhere! }; -// 3. No 'super' in standalone arrows (works in class methods) +// 3. No own 'super' binding (inherits from enclosing class method if any) // 4. Cannot be used as generators // There's no arrow generator syntax - you must use function* @@ -1402,13 +1415,13 @@ Try to figure out what `this` refers to in each example before revealing the ans <Card title="Scope & Closures" icon="eye" href="/concepts/scope-and-closures"> How variables are accessed — related to lexical this in arrow functions </Card> - <Card title="new, Constructor, instanceof" icon="hammer" href="/concepts/new-constructor"> + <Card title="Object Creation & Prototypes" icon="hammer" href="/concepts/object-creation-prototypes"> How the new keyword creates objects and binds this </Card> <Card title="Factories and Classes" icon="industry" href="/concepts/factories-classes"> Object creation patterns that rely on this binding </Card> - <Card title="Prototype" icon="link" href="/concepts/prototype"> + <Card title="Inheritance & Polymorphism" icon="link" href="/concepts/inheritance-polymorphism"> Understanding the prototype chain and method inheritance </Card> </CardGroup> diff --git a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js index 36c26582..de1a107e 100644 --- a/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js +++ b/tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js @@ -1222,14 +1222,16 @@ describe('this, call, apply and bind', () => { // Step 1: Create empty object const newObject = {} - // Step 2: Link prototype - Object.setPrototypeOf(newObject, Constructor.prototype) + // Step 2: Link prototype if it's an object + if (Constructor.prototype !== null && typeof Constructor.prototype === 'object') { + Object.setPrototypeOf(newObject, Constructor.prototype) + } // Step 3: Bind this and execute const result = Constructor.apply(newObject, args) - // Step 4: Return object (unless constructor returns an object) - return result instanceof Object ? result : newObject + // Step 4: Return object (unless constructor returns a non-primitive) + return result !== null && typeof result === 'object' ? result : newObject } function Person(name) { @@ -1252,9 +1254,11 @@ describe('this, call, apply and bind', () => { it('should return custom object if constructor returns one', () => { function simulateNew(Constructor, ...args) { const newObject = {} - Object.setPrototypeOf(newObject, Constructor.prototype) + if (Constructor.prototype !== null && typeof Constructor.prototype === 'object') { + Object.setPrototypeOf(newObject, Constructor.prototype) + } const result = Constructor.apply(newObject, args) - return result instanceof Object ? result : newObject + return result !== null && typeof result === 'object' ? result : newObject } function ReturnsObject() { @@ -1266,6 +1270,28 @@ describe('this, call, apply and bind', () => { expect(obj.custom).toBe("object") expect(obj.name).toBeUndefined() }) + + it('should handle constructor with non-object prototype', () => { + function simulateNew(Constructor, ...args) { + const newObject = {} + if (Constructor.prototype !== null && typeof Constructor.prototype === 'object') { + Object.setPrototypeOf(newObject, Constructor.prototype) + } + const result = Constructor.apply(newObject, args) + return result !== null && typeof result === 'object' ? result : newObject + } + + function WeirdConstructor(value) { + this.value = value + } + // Set prototype to a primitive (edge case) + WeirdConstructor.prototype = null + + const obj = simulateNew(WeirdConstructor, 42) + expect(obj.value).toBe(42) + // When prototype is null, object keeps Object.prototype + expect(Object.getPrototypeOf(obj)).toBe(Object.prototype) + }) }) describe('apply with args array', () => { From adc4097d5a1b6a3948ebca2b0d533c23bf31e340 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:10:52 -0300 Subject: [PATCH 107/128] fix: improve accuracy and fix broken links in factories-classes concept page - Fix broken Medium article URL (redirected to talkingtech.io) - Correct private fields introduction year from ES2022 to ES2020 - Fix misleading statement about this binding in factory functions - Clarify that primitive returns are ignored by new keyword - Update Aten article description to match actual content - Add tip about arrow function class fields for auto-binding this - Add tests for arrow function class field behavior --- docs/concepts/factories-classes.mdx | 32 +++++++++++-- .../factories-classes.test.js | 47 +++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/concepts/factories-classes.mdx b/docs/concepts/factories-classes.mdx index c52cf593..8d48d262 100644 --- a/docs/concepts/factories-classes.mdx +++ b/docs/concepts/factories-classes.mdx @@ -505,7 +505,7 @@ When you call `new Player("Alice")`, JavaScript performs **4 steps**: Runs the constructor with `this` bound to the new object </Step> <Step title="Return the object"> - Returns `obj` automatically (unless the constructor explicitly returns a different object) + Returns `obj` automatically (unless the constructor explicitly returns a different non-null object; primitive return values are ignored) </Step> </Steps> @@ -876,7 +876,7 @@ console.log(temp.celsius); // ~37.78 (converted) ### [Private Fields (#)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields) — True Privacy -ES2022 introduced **private fields** with the `#` prefix. Unlike the `_underscore` convention, these are **truly private**: +ES2020 introduced **private fields** with the `#` prefix. Unlike the `_underscore` convention, these are **truly private**: ```javascript class BankAccount { @@ -1157,6 +1157,28 @@ console.log(safeCounter.getCount()); // 1 **The `this` Trap:** When you extract a method from an object and call it standalone, `this` is no longer bound to the original object. Factory functions that use closures instead of `this` avoid this problem entirely. </Warning> +<Tip> +**Arrow Function Class Fields:** In classes, you can use arrow functions as class fields to auto-bind `this`: + +```javascript +class Button { + count = 0; + + // Arrow function automatically binds 'this' to the instance + handleClick = () => { + this.count++; + console.log(`Clicked ${this.count} times`); + }; +} + +const button = new Button(); +const handler = button.handleClick; +handler(); // Works! 'this' is still bound to button +``` + +This is an alternative to manually binding methods with `.bind(this)` in the constructor. +</Tip> + --- ## Classic Interview Questions @@ -1660,7 +1682,7 @@ nemo.swim(); // Works | **`instanceof`** | Doesn't work | Works | | **True privacy** | Closures | Private fields (#) | | **Memory efficiency** | Each instance has own methods | Methods shared via prototype | -| **`this` binding** | Not an issue (no `this` in returned object) | Must be careful with `this` | +| **`this` binding** | Can avoid `this` with closures | Must be careful with `this` | | **Inheritance** | Composition (flexible) | `extends` (hierarchical) | | **Familiarity** | Functional style | OOP style (familiar to Java/C# devs) | @@ -1974,11 +1996,11 @@ const cache = createCache({ maxSize: 100, ttl: 3600 }); <Card title="How To Use Classes in JavaScript" icon="newspaper" href="https://www.digitalocean.com/community/tutorials/understanding-classes-in-javascript"> Tania builds a Character class step by step, adding features one at a time. Great if you want to follow along and type the code yourself. </Card> - <Card title="JavaScript Classes — Under The Hood" icon="newspaper" href="https://medium.com/tech-tajawal/javascript-classes-under-the-hood-6b26d2667677"> + <Card title="JavaScript Classes — Under The Hood" icon="newspaper" href="https://talkingtech.io/javascript-classes-under-the-hood/"> Shows the ES5 equivalent of every ES6 class feature side by side. Read this to understand what JavaScript is really doing when you write a class. </Card> <Card title="Factory Functions in JavaScript" icon="newspaper" href="https://atendesigngroup.com/blog/factory-functions-javascript"> - Focuses on the closure-based privacy pattern with a bank account example. Short and focused if you just want to learn factories. + A classic introduction to factory functions using a Car example. Shows the self-pattern for avoiding `this` issues and private variables with closures. </Card> <Card title="Class vs Factory function" icon="newspaper" href="https://medium.freecodecamp.org/class-vs-factory-function-exploring-the-way-forward-73258b6a8d15"> Cristi Salcescu's comparison of both approaches with pros, cons, and when to use each. diff --git a/tests/object-oriented/factories-classes/factories-classes.test.js b/tests/object-oriented/factories-classes/factories-classes.test.js index 337e0bad..0cd99538 100644 --- a/tests/object-oriented/factories-classes/factories-classes.test.js +++ b/tests/object-oriented/factories-classes/factories-classes.test.js @@ -1361,6 +1361,53 @@ describe('Factories and Classes', () => { }) }) + describe('Arrow Function Class Fields', () => { + it('should auto-bind this with arrow function class fields', () => { + class Button { + count = 0 + + // Arrow function automatically binds 'this' to the instance + handleClick = () => { + this.count++ + return this.count + } + } + + const button = new Button() + + // Works when called directly + expect(button.handleClick()).toBe(1) + + // Extract the method + const handler = button.handleClick + + // Works even when extracted! 'this' is still bound to button + expect(handler()).toBe(2) + expect(button.count).toBe(2) + }) + + it('should show regular methods lose this when extracted', () => { + class Counter { + count = 0 + + // Regular method - 'this' depends on call context + increment() { + this.count++ + return this.count + } + } + + const counter = new Counter() + expect(counter.increment()).toBe(1) + + // Extract the method + const increment = counter.increment + + // 'this' is undefined in strict mode when called standalone + expect(() => increment()).toThrow() + }) + }) + describe('Method Chaining', () => { it('should support method chaining in class', () => { class Builder { From f78c9eada244e3c7da53648156137274cb719645 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:13:24 -0300 Subject: [PATCH 108/128] fix: remove dead links and add bytes() method to http-fetch concept page --- docs/concepts/http-fetch.mdx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/concepts/http-fetch.mdx b/docs/concepts/http-fetch.mdx index 19fbcb48..de5e5893 100644 --- a/docs/concepts/http-fetch.mdx +++ b/docs/concepts/http-fetch.mdx @@ -386,11 +386,12 @@ response.type // "basic", "cors", etc. response.redirected // true if response came from a redirect // Body methods (each returns a Promise) -response.json() // Parse body as JSON -response.text() // Parse body as plain text -response.blob() // Parse body as binary Blob -response.formData() // Parse body as FormData +response.json() // Parse body as JSON +response.text() // Parse body as plain text +response.blob() // Parse body as binary Blob +response.formData() // Parse body as FormData response.arrayBuffer() // Parse body as ArrayBuffer +response.bytes() // Parse body as Uint8Array ``` <Warning> @@ -1085,9 +1086,6 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. <Card title="Fetch API Error Handling" icon="newspaper" href="https://www.tjvantoll.com/2015/09/13/fetch-and-errors/"> The article that explains why fetch doesn't reject on 404/500. Short read that saves you hours of debugging the "#1 fetch mistake." </Card> - <Card title="Abort Controller: Why and How" icon="newspaper" href="https://blog.openreplay.com/abort-controller--why-and-how-to-use-it/"> - Shows the timeout and search-input patterns with working code examples. Read this when you need to cancel requests in a real project. - </Card> </CardGroup> ## Videos @@ -1102,7 +1100,4 @@ This example uses browser DOM APIs (`addEventListener`, `searchInput`). In Node. <Card title="Async JS Crash Course - Callbacks, Promises, Async/Await" icon="video" href="https://www.youtube.com/watch?v=PoRJizFvM7s"> Covers callbacks, Promises, and async/await before getting to fetch. Watch this if you want the full async picture, not just fetch. </Card> - <Card title="JavaScript Fetch with Request and Headers Objects" icon="video" href="https://www.youtube.com/watch?v=CVZhFBjn8Rw"> - Goes deeper into the Request and Headers objects most tutorials skip. Watch after you know the basics and want to level up. - </Card> </CardGroup> From b76e257802afecd824ff038dcb8e10d30f77f3d9 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:14:02 -0300 Subject: [PATCH 109/128] fix: improve accuracy of reflow claims and update broken link in DOM concept page --- docs/concepts/dom.mdx | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/concepts/dom.mdx b/docs/concepts/dom.mdx index 88dd5c2f..83fe1a5f 100644 --- a/docs/concepts/dom.mdx +++ b/docs/concepts/dom.mdx @@ -729,25 +729,25 @@ clone.id = 'new-unique-id' When adding many elements, using a **[`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment)** is more efficient: ```javascript -// Bad: Multiple reflows +// Bad: Multiple DOM updates (potentially multiple reflows) const ul = document.querySelector('ul') for (let i = 0; i < 1000; i++) { const li = document.createElement('li') li.textContent = `Item ${i}` - ul.appendChild(li) // Triggers reflow each time! + ul.appendChild(li) // Modifies live DOM each iteration } -// Good: Single reflow +// Good: Single DOM update const ul = document.querySelector('ul') const fragment = document.createDocumentFragment() for (let i = 0; i < 1000; i++) { const li = document.createElement('li') li.textContent = `Item ${i}` - fragment.appendChild(li) // No reflow (fragment is detached) + fragment.appendChild(li) // No DOM update (fragment is detached) } -ul.appendChild(fragment) // Single reflow! +ul.appendChild(fragment) // Single DOM update! ``` A `DocumentFragment` is a lightweight container that: @@ -755,6 +755,10 @@ A `DocumentFragment` is a lightweight container that: - Has no parent - When appended, only its **children** are inserted (the fragment itself disappears) +<Note> +**Modern browser optimization:** Browsers may batch consecutive DOM modifications and perform a single reflow. However, using DocumentFragment is still the recommended pattern because it's explicit, works consistently across all browsers, and avoids any risk of forced synchronous layouts if you read layout properties between writes. +</Note> + --- ## Modifying Content @@ -1220,15 +1224,15 @@ result.textContent = text ### Batch DOM Updates ```javascript -// Bad: 3 separate reflows +// Avoid: Multiple style changes (may trigger multiple reflows) element.style.width = '100px' element.style.height = '200px' element.style.margin = '10px' -// Good: Single reflow with cssText +// Better: Single style assignment with cssText element.style.cssText = 'width: 100px; height: 200px; margin: 10px;' -// Good: Single reflow with class +// Best: Use a CSS class (cleanest and most maintainable) element.classList.add('my-styles') // Good: DocumentFragment for multiple elements @@ -1241,6 +1245,10 @@ items.forEach(item => { ul.appendChild(fragment) // Single DOM update ``` +<Tip> +**Why batch?** While modern browsers often optimize consecutive style changes into a single reflow, this optimization breaks if you read a layout property (like `offsetWidth`) between writes. Batching explicitly avoids this risk and makes your intent clear. +</Tip> + ### Avoid Layout Thrashing **Layout thrashing** occurs when you alternate between reading and writing DOM properties: @@ -2108,7 +2116,7 @@ input.value // "hello" <Card title="How to traverse the DOM in JavaScript" icon="newspaper" href="https://medium.com/javascript-in-plain-english/how-to-traverse-the-dom-in-javascript-d6555c335b4e"> Covers every traversal method with console output screenshots. Useful reference when you forget which property to use for siblings vs children. </Card> - <Card title="Render Tree Construction" icon="newspaper" href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction"> + <Card title="Render Tree Construction" icon="newspaper" href="https://web.dev/articles/critical-rendering-path/render-tree-construction"> Google's official explanation of the Critical Rendering Path. Essential reading if you want to understand why some DOM operations are slow. </Card> <Card title="What, exactly, is the DOM?" icon="newspaper" href="https://bitsofco.de/what-exactly-is-the-dom/"> From 383e0a190c2d2f58be723149f69c5549595adae0 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:17:33 -0300 Subject: [PATCH 110/128] fix: correct inaccuracies and update outdated info in web-workers concept page - Fix Error objects cloning claim (moved from 'Cannot Clone' to 'Can Clone') - Update Safari Shared Workers support (now supported in Safari 16+) - Change 'neutered' to 'detached' terminology for ArrayBuffer - Expand transferable objects table (add AudioData, VideoFrame, RTCDataChannel) - Add structuredClone() mention with code example - Clarify requestAnimationFrame is dedicated workers only - Simplify inline worker URL revocation explanation --- docs/concepts/web-workers.mdx | 58 +++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/docs/concepts/web-workers.mdx b/docs/concepts/web-workers.mdx index 15f1bac2..2d753804 100644 --- a/docs/concepts/web-workers.mdx +++ b/docs/concepts/web-workers.mdx @@ -345,7 +345,26 @@ Communication between workers and the main thread happens through [`postMessage( ### The Structured Clone Algorithm -When you send data via `postMessage`, it's **copied** using the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This is deeper than `JSON.stringify`: it handles more types and preserves object references within the data. +When you send data via `postMessage`, it's **copied** using the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This is deeper than `JSON.stringify`: it handles more types, preserves object references within the data, and even supports circular references. + +<Tip> +You can use the global [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) function to deep-clone objects using the same algorithm. This is useful for copying complex data outside of worker communication: + +```javascript +const original = { + name: 'Alice', + date: new Date(), + nested: { deep: true } +} + +// Deep clone with structuredClone (handles Date, Map, Set, etc.) +const clone = structuredClone(original) + +clone.name = 'Bob' +console.log(original.name) // 'Alice' (unchanged) +console.log(clone.date instanceof Date) // true (Date preserved!) +``` +</Tip> ```javascript // main.js @@ -369,12 +388,17 @@ worker.postMessage(data) |-----------|--------------| | Primitives (string, number, boolean, null, undefined) | Functions | | Plain objects and arrays | DOM nodes | -| Date objects | Error objects (partially) | -| RegExp objects | Symbols | -| Blob, File, FileList | WeakMap, WeakSet | -| ArrayBuffer, TypedArrays | Objects with prototype chains | -| Map, Set | Getters/setters | -| ImageBitmap, ImageData | Proxies | +| Date objects | Symbols | +| RegExp objects | WeakMap, WeakSet | +| Blob, File, FileList | Objects with prototype chains | +| ArrayBuffer, TypedArrays | Getters/setters | +| Map, Set | Proxies | +| Error objects (standard types) | | +| ImageBitmap, ImageData | | + +<Note> +**Error cloning:** Only standard error types can be cloned (`Error`, `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`). The `name` and `message` properties are preserved, and browsers may also preserve `stack` and `cause`. +</Note> ```javascript // ✓ These work @@ -490,7 +514,7 @@ console.time('transfer') worker.postMessage(hugeBuffer, [hugeBuffer]) console.timeEnd('transfer') // Nearly instant! -// WARNING: hugeBuffer is now "neutered" (empty) +// WARNING: hugeBuffer is now "detached" (unusable) console.log(hugeBuffer.byteLength) // 0 console.log(array.length) // 0 ``` @@ -520,6 +544,13 @@ self.onmessage = (event) => { | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | Streaming data | | [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) | Streaming data | | [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) | Streaming transforms | +| [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData) | Audio processing (WebCodecs) | +| [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame) | Video processing (WebCodecs) | +| [`RTCDataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel) | WebRTC data channels | + +<Note> +This table shows the most commonly used transferable objects. For a complete list including newer APIs like `MediaStreamTrack` and `WebTransportSendStream`, see MDN's [Transferable objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) documentation. +</Note> ``` ┌─────────────────────────────────────────────────────────────────────────┐ @@ -626,7 +657,7 @@ Use shared workers for: - Reducing resource usage for identical workers <Warning> -Shared Workers have limited browser support. Check [caniuse.com](https://caniuse.com/sharedworkers) before using. They're not supported in Safari at all. +Shared Workers have limited browser support. They work in Chrome, Firefox, Edge, and Safari 16+, but are **not supported on Android browsers** (Chrome for Android, Samsung Internet). Check [caniuse.com](https://caniuse.com/sharedworkers) before using in production. </Warning> ### Service Workers (Brief Overview) @@ -718,7 +749,8 @@ function animate() { 100 ) - // Request next frame (in worker context) + // Request next frame + // Note: requestAnimationFrame is available in dedicated workers only requestAnimationFrame(animate) } ``` @@ -1304,9 +1336,9 @@ function createWorkerFromString(code) { const url = URL.createObjectURL(blob) const worker = new Worker(url) - // Clean up the URL when done - worker.addEventListener('message', () => {}, { once: true }) - URL.revokeObjectURL(url) // Revoke after worker starts + // The URL can be revoked immediately after the worker is created. + // The browser keeps the blob data until the worker finishes loading. + URL.revokeObjectURL(url) return worker } From 1493bac210df057248dcf4caf850c7b0f93dc606 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:23:40 -0300 Subject: [PATCH 111/128] fix: correct event loop algorithm order and improve technical accuracy - Fix pseudocode to match HTML spec: task -> microtasks -> render (was render before task) - Add clarification that 4ms rule output varies by browser/system - Replace misleading 'separate threads' claims with accurate description - Fix 'continuously monitors' to 'coordinates execution when stack empty' - Add setTimeout third parameter as alternative closure fix - Clarify requestIdleCallback timing (runs if idle time remains) - Update Loupe tool links from HTTP to HTTPS --- docs/concepts/event-loop.mdx | 51 +++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/docs/concepts/event-loop.mdx b/docs/concepts/event-loop.mdx index c20470a3..731c4577 100644 --- a/docs/concepts/event-loop.mdx +++ b/docs/concepts/event-loop.mdx @@ -39,7 +39,7 @@ Even with a 0ms delay, `Timeout` prints last. The answer lies in the **[event lo ## What is the Event Loop? -The **event loop** is JavaScript's mechanism for executing code, handling events, and managing asynchronous operations. It continuously monitors the call stack and callback queues, pushing queued tasks to the stack when it's empty. This enables non-blocking behavior despite JavaScript being single-threaded. +The **event loop** is JavaScript's mechanism for executing code, handling events, and managing asynchronous operations. It coordinates execution by checking callback queues when the call stack is empty, then pushing queued tasks to the stack for execution. This enables non-blocking behavior despite JavaScript being single-threaded. ### The Restaurant Analogy @@ -177,7 +177,7 @@ To understand the Event Loop, you need to see the full picture: │ │ setTimeout() setInterval() fetch() DOM events │ │ │ │ requestAnimationFrame() IndexedDB WebSockets │ │ │ │ │ │ -│ │ (These run in SEPARATE THREADS managed by the browser!) │ │ +│ │ (These are handled outside of JavaScript execution!) │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ callbacks │ @@ -267,7 +267,7 @@ To understand the Event Loop, you need to see the full picture: - Timers - Child processes - These run in **separate threads** managed by the browser/Node.js, allowing JavaScript to remain non-blocking. + These are handled by the browser/Node.js runtime outside of JavaScript execution, allowing JavaScript to remain non-blocking. </Accordion> <Accordion title="Task Queue (Macrotask Queue)"> @@ -502,29 +502,30 @@ Even though the second promise is created AFTER setTimeout was registered, it st ### The Event Loop Algorithm (Simplified) ```javascript -// Pseudocode for the Event Loop +// Pseudocode for the Event Loop (per HTML specification) while (true) { - // 1. Run all synchronous code until call stack is empty + // 1. Process ONE task from the task queue (if available) + if (taskQueue.hasItems()) { + const task = taskQueue.dequeue(); + execute(task); + } - // 2. Process ALL microtasks + // 2. Process ALL microtasks (until queue is empty) while (microtaskQueue.hasItems()) { const microtask = microtaskQueue.dequeue(); execute(microtask); // New microtasks added during execution are also processed! } - // 3. Render if needed (browser decides) + // 3. Render if needed (browser decides, typically ~60fps) if (shouldRender()) { + // 3a. Run requestAnimationFrame callbacks + runAnimationFrameCallbacks(); + // 3b. Perform style calculation, layout, and paint render(); } - // 4. Process ONE task - if (taskQueue.hasItems()) { - const task = taskQueue.dequeue(); - execute(task); - } - - // 5. Repeat + // 4. Repeat (go back to step 1) } ``` @@ -616,8 +617,8 @@ setTimeout(function run() { } }, 0); -// Typical output: [1, 1, 1, 1, 4, 9, 14, 19, 24, 29] -// First 4 are fast, then 4ms minimum kicks in +// Typical output (varies by browser/system): [1, 1, 1, 1, 4, 9, 14, 19, 24, 29] +// First 4-5 are fast, then 4ms minimum kicks in ``` <Warning> @@ -813,8 +814,8 @@ One Event Loop Iteration: │ a. Run requestAnimationFrame callbacks ← HERE! │ │ b. Render/paint the screen │ ├─────────────────────────────────────────────────────────────────┤ -│ 4. If idle time available: │ -│ Run requestIdleCallback callbacks │ +│ 4. If idle time remains before next frame: │ +│ Run requestIdleCallback callbacks (non-essential work) │ └─────────────────────────────────────────────────────────────────┘ ``` @@ -1009,7 +1010,7 @@ for (let i = 0; i < 3; i++) { // Output: 0, 1, 2 ``` -**Fix with closure:** +**Fix with closure (IIFE):** ```javascript for (var i = 0; i < 3; i++) { ((j) => { @@ -1018,6 +1019,14 @@ for (var i = 0; i < 3; i++) { } // Output: 0, 1, 2 ``` + +**Fix with setTimeout's third parameter:** +```javascript +for (var i = 0; i < 3; i++) { + setTimeout((j) => console.log(j), 0, i); +} +// Output: 0, 1, 2 +``` </Accordion> ### Question 5: What's Wrong Here? @@ -1446,7 +1455,7 @@ requestAnimationFrame(() => { The best way to truly understand the Event Loop is to **see it in action**. -<Card title="Loupe - Event Loop Visualizer" icon="play" href="http://latentflip.com/loupe/"> +<Card title="Loupe - Event Loop Visualizer" icon="play" href="https://latentflip.com/loupe/"> Created by Philip Roberts (author of the famous "What the heck is the event loop anyway?" talk). This tool lets you write JavaScript code and watch how it moves through the call stack, Web APIs, and callback queue in real-time. </Card> @@ -1657,7 +1666,7 @@ Watch how: ## Tools -<Card title="Loupe - Event Loop Visualizer" icon="play" href="http://latentflip.com/loupe/"> +<Card title="Loupe - Event Loop Visualizer" icon="play" href="https://latentflip.com/loupe/"> Interactive tool by Philip Roberts to visualize how the call stack, Web APIs, and callback queue work together. Write code and watch it execute step by step. </Card> From 2249c84ed8fd50100350a258b840660365ecba61 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:24:09 -0300 Subject: [PATCH 112/128] fix: update broken links and improve technical accuracy in IIFE/modules page - Replace 3 broken article links (Google Developers, SitePoint) with working alternatives - Replace unavailable YouTube video with Programming with Mosh tutorial - Clarify var global scope claim applies to script mode, not modules - Soften default exports best practice to 'common convention' - Add note that error messages vary by browser --- docs/concepts/iife-modules.mdx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/concepts/iife-modules.mdx b/docs/concepts/iife-modules.mdx index 454765a5..3bc1033c 100644 --- a/docs/concepts/iife-modules.mdx +++ b/docs/concepts/iife-modules.mdx @@ -181,6 +181,7 @@ const greet = () => "Hello!"; function() { console.log("This causes a syntax error!"); }(); // SyntaxError: Function statements require a function name + // (exact error message varies by browser) // ✓ This WORKS — Parentheses make it an expression (function() { @@ -258,7 +259,7 @@ There are several ways to write an IIFE. They all do the same thing: ### Why Were IIFEs Invented? -Before ES6 modules, JavaScript had a big problem: **everything was global**. Variables declared with [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) outside of functions became global, leading to conflicts: +Before ES6 modules, JavaScript had a big problem: **everything was global**. When scripts were loaded with regular `<script>` tags, variables declared with [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var) outside of functions became global and were shared across all scripts on the page, leading to conflicts: ```javascript // file1.js @@ -1097,7 +1098,9 @@ import { sharedThing } from './shared.js'; import { sharedThing } from './shared.js'; ``` -### 4. Use Default Exports for Components/Classes +### 4. Consider Default Exports for Components/Classes + +A common convention is to use default exports when a module has one main purpose: ```javascript // Components are usually one-per-file @@ -1458,17 +1461,17 @@ Try to answer each question before revealing the solution: <Card title="ES6 Modules in Depth" icon="newspaper" href="https://ponyfoo.com/articles/es6-modules-in-depth"> Nicolás Bevacqua's thorough exploration of edge cases like circular dependencies and live bindings. Read this after you understand the basics. </Card> - <Card title="Using JavaScript modules on the web" icon="newspaper" href="https://developers.google.com/web/fundamentals/primers/modules"> - Google's guide to using modules in browsers, including performance tips. + <Card title="JavaScript modules — V8" icon="newspaper" href="https://v8.dev/features/modules"> + The V8 team's comprehensive guide covering native module loading, performance recommendations, and future developments. Includes practical tips on bundling vs unbundled deployment. </Card> - <Card title="Understanding ES6 Modules" icon="newspaper" href="https://www.sitepoint.com/understanding-es6-modules/"> - Walks through setting up modules in both browser and Node.js environments. Includes a complete example project structure you can copy. + <Card title="Modules — javascript.info" icon="newspaper" href="https://javascript.info/modules-intro"> + Interactive tutorial walking through module basics with live code examples. Covers both browser and Node.js usage patterns with clear, beginner-friendly explanations. </Card> <Card title="All you need to know about Expressions, Statements and Expression Statements" icon="newspaper" href="https://dev.to/promhize/javascript-in-depth-all-you-need-to-know-about-expressions-statements-and-expression-statements-5k2"> Explains why `function(){}()` fails but `(function(){})()` works. The expression vs statement distinction finally makes sense after reading this. </Card> - <Card title="Function Expressions vs Function Declarations" icon="newspaper" href="https://www.sitepoint.com/function-expressions-vs-declarations/"> - Covers hoisting behavior, named vs anonymous expressions, and arrow functions. Helps you choose the right syntax for each situation. + <Card title="Function Expressions — MDN" icon="newspaper" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function"> + MDN's official reference on function expressions, covering syntax, hoisting behavior differences from declarations, and named function expressions. Includes interactive examples. </Card> </CardGroup> @@ -1490,7 +1493,7 @@ Try to answer each question before revealing the solution: <Card title="Expressions vs. Statements in JavaScript" icon="video" href="https://www.youtube.com/watch?v=WVyCrI1cHi8"> Uses simple examples to show why expressions produce values and statements perform actions. Essential for understanding IIFE syntax. </Card> - <Card title="JavaScript - Expression vs. Statement" icon="video" href="https://www.youtube.com/watch?v=3jDpNGJkupA"> - Answers "can I use this in an if condition?" and similar practical questions. Helps you recognize expressions vs statements in real code. + <Card title="JavaScript Functions — Programming with Mosh" icon="video" href="https://www.youtube.com/watch?v=N8ap4k_1QEQ"> + Comprehensive overview of JavaScript functions covering declarations, expressions, hoisting, and scope. Clear explanations with practical examples. </Card> </CardGroup> From a49ab5909160f4c30d50396fb2f039b9d3446a06 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:47:25 -0300 Subject: [PATCH 113/128] fix: improve technical accuracy in call stack concept page - Update MDN links for execution contexts to point to correct documentation - Clarify distinction between execution context and stack frame terminology - Standardize terminology from 'Callback Queue' to 'Task Queue' - Add note about Firefox's non-standard InternalError for stack overflow --- docs/concepts/call-stack.mdx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/concepts/call-stack.mdx b/docs/concepts/call-stack.mdx index 9a195849..0cf70308 100644 --- a/docs/concepts/call-stack.mdx +++ b/docs/concepts/call-stack.mdx @@ -100,7 +100,7 @@ JavaScript is **[single-threaded](https://developer.mozilla.org/en-US/docs/Gloss 3. **Keep track of local variables** — Each function has its own variables that shouldn't interfere with others <Info> -**ECMAScript Specification**: According to the official JavaScript specification, the call stack is implemented through "[execution contexts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#description)." Each function call creates a new execution context that gets pushed onto the stack. +**ECMAScript Specification**: According to the official JavaScript specification, the call stack is implemented through "[execution contexts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model#stack_and_execution_contexts)." Each function call creates a new execution context that gets pushed onto the stack. </Info> --- @@ -219,7 +219,7 @@ console.log("Done!"); ## Execution Context: What's Actually on the Stack? -When we say a function is "on the stack," what does that actually mean? Each entry on the call stack is called an **[execution context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#description)** (or **stack frame**). It contains everything JavaScript needs to execute that function. +When we say a function is "on the stack," what does that actually mean? Each entry on the call stack is called an **[execution context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model#stack_and_execution_contexts)**, sometimes referred to as a **stack frame** in general computer science terms. It contains everything JavaScript needs to execute that function. <AccordionGroup> <Accordion title="Function Arguments"> @@ -476,9 +476,13 @@ Stack: [ empty ] | Browser | Error Message | |---------|---------------| | Chrome | `RangeError: Maximum call stack size exceeded` | -| Firefox | `InternalError: too much recursion` | +| Firefox | `InternalError: too much recursion` (non-standard) | | Safari | `RangeError: Maximum call stack size exceeded` | +<Note> +Firefox uses `InternalError` which is a non-standard error type specific to the SpiderMonkey engine. Chrome and Safari use the standard `RangeError`. +</Note> + ### Common Causes of Stack Overflow <AccordionGroup> @@ -619,7 +623,7 @@ You might be wondering: "If JavaScript can only do one thing at a time, how does Great question! The call stack is only **part** of the picture. <Note> -When you use asynchronous functions like [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), or event listeners, JavaScript doesn't put them on the call stack immediately. Instead, they go through a different system involving the **[Event Loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop)** and **Callback Queue**. +When you use asynchronous functions like [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), or event listeners, JavaScript doesn't put them on the call stack immediately. Instead, they go through a different system involving the **[Event Loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop)** and **Task Queue**. This is covered in detail in the [Event Loop](/concepts/event-loop) section. </Note> From 906653bfc807142b3143f517a6b0d2a406dc54d7 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:49:23 -0300 Subject: [PATCH 114/128] fix: update outdated typeof null bug reference to evergreen phrasing --- docs/concepts/equality-operators.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/equality-operators.mdx b/docs/concepts/equality-operators.mdx index 96f0ed02..f33775b3 100644 --- a/docs/concepts/equality-operators.mdx +++ b/docs/concepts/equality-operators.mdx @@ -12,7 +12,7 @@ console.log(1 == "1"); // true — loose equality converts types console.log(1 === "1"); // false — strict equality checks type first // The famous quirks -console.log(typeof null); // "object" — a 28-year-old bug! +console.log(typeof null); // "object" — a bug from 1995! console.log(NaN === NaN); // false — NaN never equals anything ``` From 9ed80420b36a1e0c1aa654d82cc766b43c101087 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 09:49:34 -0300 Subject: [PATCH 115/128] fix: improve technical accuracy in scope and closures concept page - Update MDN closures URL to canonical path (/Guide/Closures) - Fix closure definition wording to match MDN exactly - Improve memory leak example to actually reference the variable - Add note about modern engine optimizations for closures - Add note about module scope for completeness --- docs/concepts/scope-and-closures.mdx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/concepts/scope-and-closures.mdx b/docs/concepts/scope-and-closures.mdx index ca46dfaf..6b584f7c 100644 --- a/docs/concepts/scope-and-closures.mdx +++ b/docs/concepts/scope-and-closures.mdx @@ -136,6 +136,10 @@ Scope exists for three critical reasons: JavaScript has three main types of scope. Understanding each one is fundamental to writing predictable code. +<Note> +ES6 modules also introduce **module scope**, where top-level variables are scoped to the module rather than being global. Learn more in our [IIFE, Modules and Namespaces](/concepts/iife-modules) guide. +</Note> + ### 1. Global Scope Variables declared outside of any function or block are in the **global scope**. They're accessible from anywhere in your code. @@ -523,7 +527,7 @@ Shadowing can be confusing. While sometimes intentional, accidental shadowing is ## What is a Closure in JavaScript? -A **[closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)** is a function bundled together with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to variables from an outer (enclosing) scope, even after that outer function has finished executing and returned. Every function in JavaScript creates a closure at creation time. +A **[closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures)** is the combination of a function bundled together with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to variables from an outer (enclosing) scope, even after that outer function has finished executing and returned. Every function in JavaScript creates a closure at creation time. Remember our office building analogy? A closure is like someone who worked in the private office, left the building, but still remembers exactly where everything was, and can still use that knowledge! @@ -853,15 +857,19 @@ function createHeavyClosure() { const hugeData = new Array(1000000).fill('x'); // Large data return function() { - // Even if we don't use hugeData, it's still retained! - console.log('Hello'); + // This reference to hugeData keeps the entire array in memory + console.log(hugeData.length); }; } const leakyFunction = createHeavyClosure(); -// hugeData is still in memory because the closure might reference it +// hugeData is still in memory because the closure references it ``` +<Note> +Modern JavaScript engines like V8 can optimize closures that don't actually use outer variables. However, it's best practice to assume referenced variables are retained and explicitly clean up large data when you're done with it. +</Note> + ### Breaking Closure References When you're done with a closure, explicitly break the reference. Use [`removeEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) to clean up event handlers: @@ -1099,7 +1107,7 @@ cleanup(); // Removes listener, allows memory to be freed ## Reference <CardGroup cols={2}> - <Card title="Closures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures"> + <Card title="Closures — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures"> Official MDN documentation on closures and lexical scoping </Card> <Card title="Scope — MDN Glossary" icon="book" href="https://developer.mozilla.org/en-US/docs/Glossary/Scope"> From 8f7047e63ae6eeb816a7058004ea75108eafa67f Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 16:18:46 -0300 Subject: [PATCH 116/128] fix: improve technical accuracy in type coercion concept page --- docs/concepts/type-coercion.mdx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/concepts/type-coercion.mdx b/docs/concepts/type-coercion.mdx index a2a8b238..e15656a9 100644 --- a/docs/concepts/type-coercion.mdx +++ b/docs/concepts/type-coercion.mdx @@ -347,7 +347,7 @@ value || defaultValue // logical OR ### The 8 Falsy Values (Memorize These!) -There are exactly **8 values** that convert to `false`. Everything else is `true`. +There are **8 common values** that convert to `false`. Everything else is `true`. ```javascript // THE FALSY EIGHT @@ -361,6 +361,10 @@ Boolean(undefined) // false Boolean(NaN) // false ``` +<Info> +**Technical note:** There's actually a 9th falsy value: [`document.all`](https://developer.mozilla.org/en-US/docs/Web/API/Document/all). It's a legacy browser API that returns `false` in boolean context despite being an object. You'll rarely encounter it in modern code, but it exists for backwards compatibility with ancient websites. +</Info> + ### Everything Else Is Truthy! This includes some surprises: @@ -834,7 +838,7 @@ if (x == true) { } // Don't do this! if (x) { } // Do this instead // BAD: Using == with 0 or "" -if (x == 0) { } // Might match "", null behavior is weird +if (x == 0) { } // Matches "", but not null (null == 0 is false!) if (x === 0) { } // Clear intent // BAD: Truthy check when you need specific type @@ -860,7 +864,7 @@ function process(count) { 2. **Implicit vs Explicit** — Know when JS converts automatically vs when you control it -3. **The 8 falsy values** — `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy +3. **The 8 common falsy values** — `false`, `0`, `-0`, `0n`, `""`, `null`, `undefined`, `NaN` — everything else is truthy (plus the rare `document.all`) 4. **+ is special** — It prefers string concatenation if ANY operand is a string @@ -888,7 +892,7 @@ function process(count) { The `+` operator, when one operand is a string, performs string concatenation. The number `3` is converted to `"3"`, resulting in `"5" + "3" = "53"`. </Accordion> - <Accordion title="Question 2: What are the 8 falsy values in JavaScript?"> + <Accordion title="Question 2: What are the 8 common falsy values in JavaScript?"> **Answer:** 1. `false` 2. `0` @@ -900,6 +904,8 @@ function process(count) { 8. `NaN` Everything else is truthy, including `[]`, `{}`, `"0"`, and `"false"`. + + **Bonus:** There's also a 9th falsy value — `document.all` — a legacy browser API you'll rarely encounter. </Accordion> <Accordion title="Question 3: Why does [] == ![] return true?"> From 809cbf6519801dec593a5641d72af81ed64f5bcb Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 16:22:24 -0300 Subject: [PATCH 117/128] fix: improve technical accuracy in value vs reference types concept page --- docs/concepts/value-reference-types.mdx | 33 +++++++++++++---- .../value-reference-types.test.js | 37 ++++++++++++++++++- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/docs/concepts/value-reference-types.mdx b/docs/concepts/value-reference-types.mdx index 181bbc0f..5d9d1f29 100644 --- a/docs/concepts/value-reference-types.mdx +++ b/docs/concepts/value-reference-types.mdx @@ -42,20 +42,24 @@ JavaScript has two categories of data types that behave very differently: | Type | Example | Stored As | |------|---------|-----------| -| `string` | `"hello"` | The actual characters | -| `number` | `42` | The actual number | -| `bigint` | `9007199254740993n` | The actual large integer | -| `boolean` | `true` | The actual boolean | +| `string` | `"hello"` | The string value | +| `number` | `42` | The numeric value | +| `bigint` | `9007199254740993n` | The large integer value | +| `boolean` | `true` | The boolean value | | `undefined` | `undefined` | The undefined value | | `null` | `null` | The null value | | [`symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) | `Symbol("id")` | The unique symbol | **Key characteristics:** -- Stored directly in the variable +- Behave as if stored directly in the variable - Immutable — you can't change them, only replace them - Copied by value — copies are independent - Compared by value — same value = equal +<Info> +**Under the hood:** Modern JavaScript engines optimize string storage through a technique called "string interning" — identical strings may share the same memory location internally. However, this is an optimization detail; strings still *behave* as independent values when you work with them. +</Info> + ### Reference Types **Everything else** is a reference type: @@ -119,6 +123,10 @@ This difference between "storing the value itself" vs "storing a map to the valu To truly understand the difference, you need to know where JavaScript stores data. +<Info> +**Important note:** The stack/heap model described below is a **conceptual simplification** to help you understand how value types and reference types *behave*. The JavaScript specification doesn't define where values are stored in memory—actual engines (V8, SpiderMonkey, etc.) may optimize storage differently. What matters is understanding the *behavior* difference, not the physical memory location. +</Info> + ### The Stack: Home of Primitives The **stack** is a fast, organized region of memory. It stores: @@ -380,6 +388,8 @@ arr1.length === arr2.length && arr1.every((v, i) => v === arr2[i]) // For complex cases, use a library like Lodash _.isEqual(obj1, obj2) ``` + +**Caution with JSON.stringify:** Property order matters! `{a:1, b:2}` and `{b:2, a:1}` will produce different strings even though they're logically equal. It also fails with `undefined`, functions, Symbols, circular references, `NaN`, and `Infinity`. For reliable deep equality, use a library like Lodash's `_.isEqual()`. </Tip> --- @@ -608,7 +618,11 @@ console.log(user.address.city); // "LA" To freeze nested objects too, you need a recursive "deep freeze" function: ```javascript -function deepFreeze(obj) { +function deepFreeze(obj, seen = new WeakSet()) { + // Prevent infinite loops from circular references + if (seen.has(obj)) return obj; + seen.add(obj); + // Get all property names (including symbols) const propNames = Reflect.ownKeys(obj); @@ -616,7 +630,7 @@ function deepFreeze(obj) { for (const name of propNames) { const value = obj[name]; if (value && typeof value === "object") { - deepFreeze(value); + deepFreeze(value, seen); } } @@ -633,6 +647,10 @@ user.address.city = "LA"; // Now this is blocked too! console.log(user.address.city); // "NYC" ``` +<Info> +**Why the `seen` WeakSet?** Objects can have circular references (e.g., `obj.self = obj`). Without tracking visited objects, the function would recurse infinitely. The WeakSet ensures each object is only processed once. +</Info> + ### Related Methods: freeze vs seal vs preventExtensions | Method | Add Properties | Delete Properties | Change Values | @@ -776,6 +794,7 @@ const deep = JSON.parse(JSON.stringify(original)); - Cannot clone property descriptors, getters/setters - Cannot clone the prototype chain - Symbol-keyed properties are ignored + - Error objects lose their stack trace (only `message` is preserved) - Not available in older browsers (pre-2022) ```javascript diff --git a/tests/fundamentals/value-reference-types/value-reference-types.test.js b/tests/fundamentals/value-reference-types/value-reference-types.test.js index 5d6c96c6..6ab7fc56 100644 --- a/tests/fundamentals/value-reference-types/value-reference-types.test.js +++ b/tests/fundamentals/value-reference-types/value-reference-types.test.js @@ -253,13 +253,17 @@ describe('Value Types and Reference Types', () => { describe('Deep Freeze', () => { it('should freeze nested objects with deep freeze function', () => { - function deepFreeze(obj) { + function deepFreeze(obj, seen = new WeakSet()) { + // Prevent infinite loops from circular references + if (seen.has(obj)) return obj + seen.add(obj) + const propNames = Reflect.ownKeys(obj) for (const name of propNames) { const value = obj[name] if (value && typeof value === "object") { - deepFreeze(value) + deepFreeze(value, seen) } } @@ -275,6 +279,35 @@ describe('Value Types and Reference Types', () => { expect(() => { user.address.city = "LA" }).toThrow(TypeError) expect(user.address.city).toBe("NYC") // Now blocked! }) + + it('should handle circular references without infinite loop', () => { + function deepFreeze(obj, seen = new WeakSet()) { + if (seen.has(obj)) return obj + seen.add(obj) + + const propNames = Reflect.ownKeys(obj) + + for (const name of propNames) { + const value = obj[name] + if (value && typeof value === "object") { + deepFreeze(value, seen) + } + } + + return Object.freeze(obj) + } + + // Create object with circular reference + const obj = { name: "test" } + obj.self = obj // Circular reference + + // Should not throw or hang - handles circular reference + const frozen = deepFreeze(obj) + + expect(Object.isFrozen(frozen)).toBe(true) + expect(frozen.self).toBe(frozen) // Circular reference preserved + expect(() => { frozen.name = "changed" }).toThrow(TypeError) + }) }) describe('Object.seal() and Object.preventExtensions()', () => { From f4d52461ef0c387607abcca1e31d4e1fc162dc81 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 17:02:39 -0300 Subject: [PATCH 118/128] feat: add comprehensive Modern JavaScript ES6+ syntax concept page - Complete rewrite covering arrow functions, destructuring, spread/rest, template literals, optional chaining, nullish coalescing, Map/Set/Symbol - Add 57 tests covering all major ES6+ features - Include fact-checked code examples with correct edge case behavior - Add curated resources: 6 MDN refs, 6 articles, 4 videos --- docs/concepts/modern-js-syntax.mdx | 1183 ++++++++++++++++- .../modern-js-syntax/modern-js-syntax.test.js | 646 +++++++++ 2 files changed, 1792 insertions(+), 37 deletions(-) create mode 100644 tests/advanced-topics/modern-js-syntax/modern-js-syntax.test.js diff --git a/docs/concepts/modern-js-syntax.mdx b/docs/concepts/modern-js-syntax.mdx index 4e23a89b..4f77fddd 100644 --- a/docs/concepts/modern-js-syntax.mdx +++ b/docs/concepts/modern-js-syntax.mdx @@ -1,73 +1,1182 @@ --- -title: "Modern JavaScript Syntax: ES6+ Features You Need to Know" +title: "Modern JavaScript Syntax: ES6+ Features That Changed Everything" sidebarTitle: "Modern JavaScript Syntax: ES6+ Features" -description: "Learn modern JavaScript syntax — destructuring, spread/rest operators, optional chaining, nullish coalescing, and template literals. Master the ES6+ features used in every modern codebase." +description: "Learn modern JavaScript ES6+ syntax. Covers destructuring, spread/rest operators, arrow functions, optional chaining, nullish coalescing, and template literals with practical examples." --- -## Overview +Why does JavaScript code written in 2015 look so different from code written today? How do developers write such concise, readable code without all the boilerplate? -**Modern JavaScript syntax** (ES6 and beyond) introduced powerful features that make code more concise, readable, and expressive. Features like destructuring, spread operators, optional chaining (`?.`), and nullish coalescing (`??`) are now used in virtually every JavaScript codebase and framework. +```javascript +// The old way (pre-ES6) +var city = user && user.address && user.address.city; // undefined if missing +var copy = arr.slice(); +var merged = Object.assign({}, obj1, obj2); + +// The modern way +const city = user?.address?.city; // undefined if missing +const copy = [...arr]; +const merged = { ...obj1, ...obj2 }; +``` + +The answer is **ES6 (ECMAScript 2015)** and the yearly updates that followed. These additions didn't just add features. They transformed how we write JavaScript. Features like [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), and [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) are now everywhere: in tutorials, open-source projects, and job interviews. <Info> **What you'll learn in this guide:** -- Destructuring (objects and arrays) -- Spread operator (`...`) and rest parameters -- Optional chaining (`?.`) -- Nullish coalescing (`??`) +- Arrow functions and how they handle `this` differently +- Destructuring objects and arrays to extract values cleanly +- Spread operator (`...`) for copying and merging +- Rest parameters for collecting function arguments +- Template literals for string interpolation +- Optional chaining (`?.`) to avoid "cannot read property of undefined" +- Nullish coalescing (`??`) vs logical OR (`||`) - Logical assignment operators (`??=`, `||=`, `&&=`) -- Template literals and tagged templates -- Default parameters -- Short-circuit evaluation patterns -- Computed property names +- Default parameters for functions +- Enhanced object literals (shorthand syntax) +- Map, Set, and Symbol basics +- The `for...of` loop for iterating values +</Info> + +<Warning> +**Prerequisite:** This guide touches on `let`, `const`, and `var` briefly. For a deep dive into how they differ (block scope, hoisting, temporal dead zone), read our [Scope and Closures](/concepts/scope-and-closures) guide first. +</Warning> + +--- + +## A Quick Note on let, const, and var + +Before ES6, `var` was the only way to declare variables. Now we have `let` and `const`, which behave differently: + +| Feature | `var` | `let` | `const` | +|---------|-------|-------|---------| +| Scope | Function | Block | Block | +| Hoisting | Yes (undefined) | Yes (TDZ) | Yes (TDZ) | +| Redeclaration | Allowed | Error | Error | +| Reassignment | Allowed | Allowed | Error | + +```javascript +// var is function-scoped (can cause bugs) +for (var i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 3, 3, 3 + +// let is block-scoped (each iteration gets its own i) +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 0, 1, 2 +``` + +**The modern rule:** Use `const` by default. Use `let` when you need to reassign. Avoid `var`. + +For the full explanation of scope, hoisting, and the temporal dead zone, see [Scope and Closures](/concepts/scope-and-closures). + +--- + +## Arrow Functions + +[Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) provide a shorter syntax for writing functions. But the real difference is how they handle `this`. + +### The Syntax + +```javascript +// Traditional function +function add(a, b) { + return a + b; +} + +// Arrow function variations +const add = (a, b) => a + b; // Implicit return (single expression) +const add = (a, b) => { return a + b; }; // Block body (explicit return needed) +const square = x => x * x; // Single param: parentheses optional +const greet = () => 'Hello!'; // No params: parentheses required +``` + +### Arrow Functions and `this` + +Here's the big difference: arrow functions don't have their own `this`. They inherit `this` from the surrounding code (lexical scope). + +```javascript +// Problem with regular functions +const counter = { + count: 0, + start: function() { + setInterval(function() { + this.count++; // 'this' is NOT the counter object! + console.log(this.count); + }, 1000); + } +}; +counter.start(); // NaN, NaN, NaN... + +// Solution with arrow functions +const counter = { + count: 0, + start: function() { + setInterval(() => { + this.count++; // 'this' IS the counter object + console.log(this.count); + }, 1000); + } +}; +counter.start(); // 1, 2, 3... +``` + +For a complete exploration of `this` binding rules, see [this, call, apply and bind](/concepts/this-call-apply-bind). + +### When NOT to Use Arrow Functions + +Arrow functions aren't always the right choice: + +```javascript +// ❌ DON'T use as object methods +const user = { + name: 'Alice', + greet: () => { + console.log(`Hi, I'm ${this.name}`); // 'this' is NOT user! + } +}; +user.greet(); // "Hi, I'm undefined" + +// ✓ USE regular function for methods +const user = { + name: 'Alice', + greet() { + console.log(`Hi, I'm ${this.name}`); + } +}; +user.greet(); // "Hi, I'm Alice" + +// ❌ DON'T use as constructors +const Person = (name) => { this.name = name; }; +new Person('Alice'); // TypeError: Person is not a constructor + +// ❌ Arrow functions don't have their own 'arguments' +const logArgs = () => console.log(arguments); // ReferenceError (use ...rest instead) +``` + +### The Object Literal Trap + +Returning an object literal requires parentheses: + +```javascript +// ❌ WRONG - curly braces are interpreted as function body +const createUser = name => { name: name }; +console.log(createUser('Alice')); // undefined (it's a labeled statement!) + +// ❌ ALSO WRONG - adding more properties causes a SyntaxError +// const createUser = name => { name: name, active: true }; // SyntaxError! + +// ✓ CORRECT - wrap object literal in parentheses +const createUser = name => ({ name: name, active: true }); +console.log(createUser('Alice')); // { name: 'Alice', active: true } +``` + +--- + +## Destructuring Assignment + +[Destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) lets you unpack values from arrays or properties from objects into distinct variables. + +### Array Destructuring + +```javascript +const colors = ['red', 'green', 'blue']; + +// Basic destructuring +const [first, second, third] = colors; +console.log(first); // "red" +console.log(second); // "green" + +// Skip elements with empty slots +const [primary, , tertiary] = colors; +console.log(tertiary); // "blue" + +// Default values +const [a, b, c, d = 'yellow'] = colors; +console.log(d); // "yellow" + +// Rest pattern (collect remaining elements) +const [head, ...tail] = colors; +console.log(head); // "red" +console.log(tail); // ["green", "blue"] +``` + +**Swap variables without a temp:** + +```javascript +let x = 1; +let y = 2; + +[x, y] = [y, x]; + +console.log(x); // 2 +console.log(y); // 1 +``` + +### Object Destructuring + +```javascript +const user = { + name: 'Alice', + age: 25, + address: { + city: 'Portland', + country: 'USA' + } +}; + +// Basic destructuring +const { name, age } = user; +console.log(name); // "Alice" + +// Rename variables +const { name: userName, age: userAge } = user; +console.log(userName); // "Alice" + +// Default values +const { name, role = 'guest' } = user; +console.log(role); // "guest" + +// Nested destructuring +const { address: { city } } = user; +console.log(city); // "Portland" + +// Rest pattern +const { name, ...rest } = user; +console.log(rest); // { age: 25, address: { city: 'Portland', country: 'USA' } } +``` + +### Destructuring in Function Parameters + +This pattern is everywhere in modern JavaScript: + +```javascript +// Without destructuring +function createUser(options) { + const name = options.name; + const age = options.age || 18; + const role = options.role || 'user'; + return { name, age, role }; +} + +// With destructuring +function createUser({ name, age = 18, role = 'user' }) { + return { name, age, role }; +} + +// With default for the entire parameter (prevents error if called with no args) +function greet({ name = 'Guest' } = {}) { + return `Hello, ${name}!`; +} + +greet(); // "Hello, Guest!" +greet({ name: 'Alice' }); // "Hello, Alice!" +``` + +### Common Mistake: Destructuring to Existing Variables + +```javascript +let name, age; + +// ❌ WRONG - JavaScript thinks {} is a code block +{ name, age } = user; // SyntaxError + +// ✓ CORRECT - wrap in parentheses +({ name, age } = user); +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DESTRUCTURING VISUALIZED │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ARRAY DESTRUCTURING OBJECT DESTRUCTURING │ +│ ─────────────────── ──────────────────── │ +│ │ +│ const [a, b, c] = [1, 2, 3] const {x, y} = {x: 10, y: 20} │ +│ │ +│ [1, 2, 3] { x: 10, y: 20 } │ +│ │ │ │ │ │ │ +│ │ │ └──► c = 3 │ └──► y = 20 │ +│ │ └─────► b = 2 └──────────► x = 10 │ +│ └────────► a = 1 │ +│ │ +│ Position matters! Property name matters! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Spread and Rest Operators + +The `...` syntax does two different things depending on context: + +| Context | Name | What It Does | +|---------|------|--------------| +| Function call, array/object literal | **Spread** | Expands an iterable into individual elements | +| Function parameter, destructuring | **Rest** | Collects multiple elements into an array | + +### Spread Operator + +**Spreading arrays:** + +```javascript +const arr1 = [1, 2, 3]; +const arr2 = [4, 5, 6]; + +// Combine arrays +const combined = [...arr1, ...arr2]; +console.log(combined); // [1, 2, 3, 4, 5, 6] + +// Copy an array +const copy = [...arr1]; +console.log(copy); // [1, 2, 3] + +// Insert elements +const withMiddle = [0, ...arr1, 4]; +console.log(withMiddle); // [0, 1, 2, 3, 4] + +// Pass array as function arguments +console.log(Math.max(...arr1)); // 3 +``` + +**Spreading objects:** + +```javascript +const defaults = { theme: 'light', fontSize: 14 }; +const userPrefs = { theme: 'dark' }; + +// Merge objects (later properties override earlier) +const settings = { ...defaults, ...userPrefs }; +console.log(settings); // { theme: 'dark', fontSize: 14 } + +// Copy and update +const updated = { ...user, name: 'Bob' }; + +// Copy an object (shallow!) +const copy = { ...original }; +``` + +### Rest Parameters + +```javascript +// Collect all arguments into an array +function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0); +} +console.log(sum(1, 2, 3, 4)); // 10 + +// Collect remaining arguments +function logFirst(first, ...rest) { + console.log('First:', first); + console.log('Rest:', rest); +} +logFirst('a', 'b', 'c', 'd'); +// First: a +// Rest: ['b', 'c', 'd'] +``` + +**Rest in destructuring:** + +```javascript +// Arrays +const [first, second, ...others] = [1, 2, 3, 4, 5]; +console.log(others); // [3, 4, 5] + +// Objects +const { id, ...otherProps } = { id: 1, name: 'Alice', age: 25 }; +console.log(otherProps); // { name: 'Alice', age: 25 } +``` + +### The Shallow Copy Trap + +Spread creates **shallow copies**. Nested objects are still referenced: + +```javascript +const original = { + name: 'Alice', + address: { city: 'Portland' } +}; + +const copy = { ...original }; + +// Modifying nested object affects both! +copy.address.city = 'Seattle'; +console.log(original.address.city); // "Seattle" — oops! + +// For deep copies, use structuredClone (modern) or JSON (with limitations) +const deepCopy = structuredClone(original); +``` + +--- + +## Template Literals + +[Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) use backticks (`` ` ``) instead of quotes and support string interpolation and multi-line strings. + +### Basic Interpolation + +```javascript +const name = 'Alice'; +const age = 25; + +// Old way +const message = 'Hello, ' + name + '! You are ' + age + ' years old.'; + +// Template literal +const message = `Hello, ${name}! You are ${age} years old.`; + +// Expressions work too +const price = 19.99; +const tax = 0.1; +const total = `Total: $${(price * (1 + tax)).toFixed(2)}`; +console.log(total); // "Total: $21.99" +``` + +### Multi-line Strings + +```javascript +// Old way (awkward) +const html = '<div>\n' + + ' <h1>Title</h1>\n' + + ' <p>Content</p>\n' + + '</div>'; + +// Template literal (natural) +const html = ` + <div> + <h1>${title}</h1> + <p>${content}</p> + </div> +`; +``` + +### Tagged Templates + +Tagged templates let you process template literals with a function: + +```javascript +function highlight(strings, ...values) { + return strings.reduce((result, str, i) => { + const value = values[i] ? `<mark>${values[i]}</mark>` : ''; + return result + str + value; + }, ''); +} + +const query = 'JavaScript'; +const count = 42; + +const result = highlight`Found ${count} results for ${query}`; +console.log(result); +// "Found <mark>42</mark> results for <mark>JavaScript</mark>" +``` + +Tagged templates power libraries like [styled-components](https://styled-components.com/) (CSS-in-JS) and [GraphQL](https://graphql.org/) query builders. + +--- + +## Optional Chaining (`?.`) + +[Optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) lets you safely access nested properties without checking each level for null or undefined. + +### The Problem It Solves + +```javascript +const user = { + name: 'Alice', + // address is undefined +}; + +// Old way (verbose and error-prone) +const city = user && user.address && user.address.city; + +// Old way (slightly better) +const city = user.address ? user.address.city : undefined; + +// Modern way +const city = user?.address?.city; // undefined (no error!) +``` + +### Three Syntax Forms + +```javascript +// Property access +const city = user?.address?.city; + +// Bracket notation (for dynamic keys) +const prop = 'address'; +const value = user?.[prop]?.city; + +// Function calls (only call if function exists) +const result = user?.getName?.(); +``` + +### Short-Circuit Behavior + +When the left side is `null` or `undefined`, evaluation stops immediately and returns `undefined`: + +```javascript +const user = null; + +// Without optional chaining +user.address.city; // TypeError: Cannot read property 'address' of null + +// With optional chaining +user?.address?.city; // undefined (evaluation stops at user) +``` + +### Don't Overuse It + +```javascript +// ❌ BAD - if user should always exist, you're hiding bugs +function processUser(user) { + return user?.name?.toUpperCase(); // Silently returns undefined +} + +// ✓ GOOD - fail fast when data is invalid +function processUser(user) { + if (!user) throw new Error('User is required'); + return user.name.toUpperCase(); +} + +// ✓ GOOD - use when null/undefined is a valid possibility +const displayName = apiResponse?.data?.user?.displayName ?? 'Anonymous'; +``` + +--- + +## Nullish Coalescing (`??`) + +The [nullish coalescing operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing) returns the right-hand side when the left-hand side is `null` or `undefined`. This is different from `||`, which returns the right-hand side for any falsy value. + +### `??` vs `||` + +| Value | `value \|\| 'default'` | `value ?? 'default'` | +|-------|----------------------|---------------------| +| `null` | `'default'` | `'default'` | +| `undefined` | `'default'` | `'default'` | +| `0` | `'default'` | `0` | +| `''` | `'default'` | `''` | +| `false` | `'default'` | `false` | +| `NaN` | `'default'` | `NaN` | + +```javascript +// Problem with || +const count = response.count || 10; +// If response.count is 0, this incorrectly returns 10! + +// Solution with ?? +const count = response.count ?? 10; +// Only returns 10 if count is null or undefined +// Returns 0 if count is 0 (which is what we want) + +// Common use cases +const port = process.env.PORT ?? 3000; +const username = inputValue ?? 'guest'; +const timeout = options.timeout ?? 5000; +``` + +### Combining with Optional Chaining + +These two operators work great together: + +```javascript +const city = user?.address?.city ?? 'Unknown'; +const count = response?.data?.items?.length ?? 0; +``` + +### Logical Assignment Operators + +ES2021 added assignment versions of logical operators: + +```javascript +// Nullish coalescing assignment +user.name ??= 'Anonymous'; +// Only assigns if user.name is null or undefined +// (short-circuits: skips assignment if value already exists) + +// Logical OR assignment +options.debug ||= false; +// Only assigns if options.debug is falsy + +// Logical AND assignment +user.lastLogin &&= new Date(); +// Only assigns if user.lastLogin is truthy +``` + +```javascript +// Practical example: initializing config +function configure(options = {}) { + options.retries ??= 3; + options.timeout ??= 5000; + options.cache ??= true; + return options; +} + +configure({}); // { retries: 3, timeout: 5000, cache: true } +configure({ retries: 0 }); // { retries: 0, timeout: 5000, cache: true } +configure({ timeout: null }); // { retries: 3, timeout: 5000, cache: true } +``` + +--- + +## Default Parameters + +[Default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) let you specify fallback values for function arguments. + +```javascript +// Old way +function greet(name, greeting) { + name = name || 'Guest'; + greeting = greeting || 'Hello'; + return `${greeting}, ${name}!`; +} + +// Modern way +function greet(name = 'Guest', greeting = 'Hello') { + return `${greeting}, ${name}!`; +} + +greet(); // "Hello, Guest!" +greet('Alice'); // "Hello, Alice!" +greet('Alice', 'Hi'); // "Hi, Alice!" +``` + +### Only `undefined` Triggers Defaults + +```javascript +function example(value = 'default') { + return value; +} + +example(undefined); // "default" +example(null); // null (NOT "default"!) +example(0); // 0 +example(''); // '' +example(false); // false +``` + +### Defaults Can Reference Earlier Parameters + +```javascript +function createRect(width, height = width) { + return { width, height }; +} + +createRect(10); // { width: 10, height: 10 } +createRect(10, 20); // { width: 10, height: 20 } +``` + +### Defaults Can Be Expressions + +```javascript +function createId(prefix = 'id', timestamp = Date.now()) { + return `${prefix}_${timestamp}`; +} + +// Date.now() is called each time (not once at definition) +createId(); // "id_1704067200000" +createId(); // "id_1704067200001" (different!) +``` + +--- + +## Enhanced Object Literals + +ES6 added several shortcuts for creating objects. + +### Property Shorthand + +When the property name matches the variable name: + +```javascript +const name = 'Alice'; +const age = 25; + +// Old way +const user = { name: name, age: age }; + +// Shorthand +const user = { name, age }; +console.log(user); // { name: 'Alice', age: 25 } +``` + +### Method Shorthand + +```javascript +// Old way +const calculator = { + add: function(a, b) { + return a + b; + } +}; + +// Shorthand +const calculator = { + add(a, b) { + return a + b; + }, + + // Works with async too + async fetchData(url) { + const response = await fetch(url); + return response.json(); + } +}; +``` + +### Computed Property Names + +Use expressions as property names: + +```javascript +const key = 'dynamicKey'; +const index = 0; + +const obj = { + [key]: 'value', + [`item_${index}`]: 'first item', + ['get' + 'Name']() { + return this.name; + } +}; + +console.log(obj.dynamicKey); // "value" +console.log(obj.item_0); // "first item" +``` + +**Practical example:** + +```javascript +function createState(key, value) { + return { + [key]: value, + [`set${key.charAt(0).toUpperCase() + key.slice(1)}`](newValue) { + this[key] = newValue; + } + }; +} + +const state = createState('count', 0); +console.log(state); // { count: 0, setCount: [Function] } +state.setCount(5); +console.log(state.count); // 5 +``` + +--- + +## Map, Set, and Symbol + +ES6 introduced new built-in data structures and a new primitive type. + +### Map + +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of key-value pairs where keys can be any type (not just strings). + +```javascript +const map = new Map(); + +// Any value can be a key +const objKey = { id: 1 }; +map.set('string', 'value1'); +map.set(42, 'value2'); +map.set(objKey, 'value3'); + +console.log(map.get(objKey)); // "value3" +console.log(map.size); // 3 +console.log(map.has('string')); // true + +// Iteration (maintains insertion order) +for (const [key, value] of map) { + console.log(key, value); +} + +// Convert to/from arrays +const arr = [...map]; // [['string', 'value1'], [42, 'value2'], ...] +const map2 = new Map([['a', 1], ['b', 2]]); +``` + +**When to use Map vs Object:** + +| Use Case | Object | Map | +|----------|--------|-----| +| Keys are strings | ✓ | ✓ | +| Keys are any type | ✗ | ✓ | +| Need insertion order | ✓ (string keys) | ✓ | +| Need size property | ✗ | ✓ | +| Frequent add/remove | Slower | Faster | +| JSON serialization | ✓ | ✗ | + +### Set + +[Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a collection of unique values. + +```javascript +const set = new Set([1, 2, 3, 3, 3]); +console.log(set); // Set { 1, 2, 3 } + +set.add(4); +set.delete(1); +console.log(set.has(2)); // true +console.log(set.size); // 3 + +// Remove duplicates from array +const numbers = [1, 2, 2, 3, 3, 3]; +const unique = [...new Set(numbers)]; +console.log(unique); // [1, 2, 3] + +// Set operations +const a = new Set([1, 2, 3]); +const b = new Set([2, 3, 4]); + +const union = new Set([...a, ...b]); // {1, 2, 3, 4} +const intersection = [...a].filter(x => b.has(x)); // [2, 3] +const difference = [...a].filter(x => !b.has(x)); // [1] +``` + +### Symbol + +[Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) is a primitive type for unique identifiers. + +```javascript +// Every Symbol is unique +const sym1 = Symbol('description'); +const sym2 = Symbol('description'); +console.log(sym1 === sym2); // false + +// Use as object keys (hidden from normal iteration) +const ID = Symbol('id'); +const user = { + name: 'Alice', + [ID]: 12345 +}; + +console.log(user[ID]); // 12345 +console.log(Object.keys(user)); // ['name'] (Symbol not included) + +// Well-known Symbols customize object behavior +const collection = { + items: [1, 2, 3], + [Symbol.iterator]() { + let i = 0; + return { + next: () => ({ + value: this.items[i], + done: i++ >= this.items.length + }) + }; + } +}; + +for (const item of collection) { + console.log(item); // 1, 2, 3 +} +``` + +--- + +## for...of Loop + +The [for...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) loop iterates over iterable objects (arrays, strings, Maps, Sets, etc.). + +```javascript +// Arrays +const colors = ['red', 'green', 'blue']; +for (const color of colors) { + console.log(color); // "red", "green", "blue" +} + +// Strings +for (const char of 'hello') { + console.log(char); // "h", "e", "l", "l", "o" +} + +// Maps +const map = new Map([['a', 1], ['b', 2]]); +for (const [key, value] of map) { + console.log(key, value); // "a" 1, "b" 2 +} + +// Sets +const set = new Set([1, 2, 3]); +for (const num of set) { + console.log(num); // 1, 2, 3 +} + +// With destructuring +const users = [ + { name: 'Alice', age: 25 }, + { name: 'Bob', age: 30 } +]; +for (const { name, age } of users) { + console.log(`${name} is ${age}`); +} +``` + +### for...of vs for...in + +| | `for...of` | `for...in` | +|---|-----------|-----------| +| Iterates over | Values | Keys (property names) | +| Works with | Iterables (Array, String, Map, Set) | Objects | +| Array indices | Use `.entries()` | Yes (as strings) | + +```javascript +const arr = ['a', 'b', 'c']; + +for (const value of arr) { + console.log(value); // "a", "b", "c" (values) +} + +for (const index in arr) { + console.log(index); // "0", "1", "2" (keys as strings) +} +``` + +--- + +## Key Takeaways + +<Info> +**The key things to remember about modern JavaScript syntax:** + +1. **Arrow functions inherit `this`** from the enclosing scope. Don't use them as object methods or constructors. + +2. **Destructuring extracts values** from arrays (by position) and objects (by property name). Use it for cleaner function parameters. + +3. **Spread (`...`) expands**, rest (`...`) collects. Same syntax, different contexts. + +4. **`??` checks for null/undefined only**. Use it when `0`, `''`, or `false` are valid values. Use `||` when you want fallback for any falsy value. + +5. **Optional chaining (`?.`)** prevents "cannot read property of undefined" errors. Don't overuse it or you'll hide bugs. + +6. **Template literals** use backticks and support `${expressions}` and multi-line strings. + +7. **Default parameters trigger only on `undefined`**, not `null` or other falsy values. + +8. **Map keys can be any type**, maintain insertion order, and have a `.size` property. Use Map when Object doesn't fit. + +9. **Set stores unique values**. Spread a Set to deduplicate an array: `[...new Set(arr)]`. + +10. **`for...of` iterates values**, `for...in` iterates keys. Use `for...of` for arrays. </Info> +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the output of `0 ?? 'default'` vs `0 || 'default'`?"> + **Answer:** + + - `0 ?? 'default'` returns `0` + - `0 || 'default'` returns `'default'` + + The nullish coalescing operator (`??`) only returns the right side for `null` or `undefined`. Since `0` is neither, it returns `0`. + + The logical OR (`||`) returns the right side for any falsy value. Since `0` is falsy, it returns `'default'`. + + ```javascript + // Use ?? when 0 is a valid value + const count = response.count ?? 10; + + // Use || when any falsy value should trigger default + const name = input || 'Anonymous'; + ``` + </Accordion> + + <Accordion title="Question 2: How do you return an object literal from an arrow function?"> + **Answer:** + + Wrap the object literal in parentheses: + + ```javascript + // ❌ WRONG - braces interpreted as function body + const createUser = name => { name, active: true }; + // Returns undefined + + // ✓ CORRECT - parentheses make it an expression + const createUser = name => ({ name, active: true }); + // Returns { name: '...', active: true } + ``` + + Without parentheses, JavaScript interprets `{ }` as a function body block, not an object literal. The parentheses force it to be treated as an expression. + </Accordion> + + <Accordion title="Question 3: What's the difference between spread and rest?"> + **Answer:** + + They use the same `...` syntax but do opposite things: + + **Spread** expands an iterable into individual elements: + ```javascript + const arr = [1, 2, 3]; + console.log(...arr); // 1 2 3 (individual values) + const copy = [...arr]; // [1, 2, 3] (new array) + Math.max(...arr); // 3 (arguments spread) + ``` + + **Rest** collects multiple elements into an array: + ```javascript + function sum(...numbers) { // Collects all args + return numbers.reduce((a, b) => a + b, 0); + } + + const [first, ...rest] = [1, 2, 3, 4]; + // first = 1, rest = [2, 3, 4] + ``` + + **Rule of thumb:** In a function definition or destructuring pattern, it's rest. Everywhere else (function calls, array/object literals), it's spread. + </Accordion> + + <Accordion title="Question 4: Why shouldn't you use arrow functions as object methods?"> + **Answer:** + + Arrow functions don't have their own `this`. They inherit `this` from the enclosing lexical scope, which is usually the global object or `undefined` (in strict mode). + + ```javascript + const user = { + name: 'Alice', + + // ❌ Arrow function - 'this' is NOT the user object + greetArrow: () => { + console.log(`Hi, I'm ${this.name}`); + }, + + // ✓ Regular function - 'this' IS the user object + greetRegular() { + console.log(`Hi, I'm ${this.name}`); + } + }; + + user.greetArrow(); // "Hi, I'm undefined" + user.greetRegular(); // "Hi, I'm Alice" + ``` + + Use regular functions (or method shorthand) for object methods when you need access to `this`. + </Accordion> + + <Accordion title="Question 5: How do you swap two variables without a temporary variable?"> + **Answer:** + + Use array destructuring: + + ```javascript + let a = 1; + let b = 2; + + [a, b] = [b, a]; + + console.log(a); // 2 + console.log(b); // 1 + ``` + + This creates a temporary array `[b, a]` (which is `[2, 1]`), then destructures it back into `a` and `b` in the new order. + </Accordion> + + <Accordion title="Question 6: What does `user?.address?.city ?? 'Unknown'` return if user is null?"> + **Answer:** + + It returns `'Unknown'`. + + Here's the evaluation: + 1. `user?.address` — `user` is `null`, so optional chaining short-circuits and returns `undefined` + 2. `undefined?.city` — This never runs because we already got `undefined` + 3. `undefined ?? 'Unknown'` — `undefined` is nullish, so we get `'Unknown'` + + ```javascript + const user = null; + const city = user?.address?.city ?? 'Unknown'; + console.log(city); // "Unknown" + + // Without optional chaining, this would throw: + // TypeError: Cannot read property 'address' of null + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Scope and Closures" icon="layer-group" href="/concepts/scope-and-closures"> + Deep dive into let, const, var, block scope, and the temporal dead zone + </Card> + <Card title="this, call, apply and bind" icon="bullseye" href="/concepts/this-call-apply-bind"> + Understanding arrow function this binding in context of all binding rules + </Card> + <Card title="ES Modules" icon="box" href="/concepts/es-modules"> + Modern import/export syntax for organizing JavaScript code + </Card> + <Card title="Promises" icon="handshake" href="/concepts/promises"> + Async features that pair well with modern syntax like async/await + </Card> +</CardGroup> + +--- + ## Reference <CardGroup cols={2}> <Card title="Destructuring Assignment — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment"> - MDN documentation for destructuring + Complete reference for array and object destructuring patterns + </Card> + <Card title="Spread Syntax — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"> + Documentation for the spread operator in arrays, objects, and function calls + </Card> + <Card title="Arrow Functions — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions"> + Arrow function syntax, limitations, and this binding behavior </Card> <Card title="Optional Chaining — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"> - MDN documentation for optional chaining (?.) + Safe property access with the ?. operator </Card> <Card title="Nullish Coalescing — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"> - MDN documentation for nullish coalescing (??) + The ?? operator and how it differs from || </Card> - <Card title="Spread Syntax — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"> - MDN documentation for spread syntax (...) + <Card title="Template Literals — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"> + String interpolation, multi-line strings, and tagged templates </Card> </CardGroup> ## Articles <CardGroup cols={2}> - <Card title="ES6 Destructuring: The Complete Guide" icon="newspaper" href="https://codeburst.io/es6-destructuring-the-complete-guide-7f842d08b98f"> - By Glad Chinda + <Card title="Destructuring Assignment" icon="newspaper" href="https://javascript.info/destructuring-assignment"> + Thorough breakdown of both array and object destructuring with progressive examples from basic to nested patterns. Includes interactive exercises that reinforce each concept. </Card> - <Card title="Optional Chaining in JavaScript" icon="newspaper" href="https://javascript.info/optional-chaining"> - By JavaScript.info + <Card title="Rest Parameters and Spread Syntax" icon="newspaper" href="https://javascript.info/rest-parameters-spread"> + Clearly distinguishes between the visually identical `...` syntax for rest vs spread. The comparison with the legacy `arguments` object shows why modern features are preferred. + </Card> + <Card title="Optional Chaining" icon="newspaper" href="https://javascript.info/optional-chaining"> + Walks through the evolution from verbose `&&` chains to elegant optional chaining, covering all three syntax forms. Includes guidance on when NOT to overuse it. + </Card> + <Card title="Nullish Coalescing Operator" icon="newspaper" href="https://javascript.info/nullish-coalescing-operator"> + Explains the crucial difference between `??` and `||`. This distinction prevents common bugs when working with legitimate zero or empty string values. + </Card> + <Card title="A Dead Simple Intro to Destructuring" icon="newspaper" href="https://wesbos.com/destructuring-objects"> + Wes Bos's practical teaching style with real-world examples including API response handling and deeply nested data extraction. Short, focused, and immediately applicable. + </Card> + <Card title="Template Literals" icon="newspaper" href="https://css-tricks.com/template-literals/"> + Goes beyond basic interpolation to explore tagged template literals for building custom DSLs and sanitizing user input. Includes a practical reusable template function. </Card> </CardGroup> -- [JavaScript Destructuring — Wes Bos](https://wesbos.com/destructuring-objects) -- [ES6 Spread Operator — JavaScript.info](https://javascript.info/rest-parameters-spread) -- [Nullish Coalescing Operator Explained — freeCodeCamp](https://www.freecodecamp.org/news/nullish-coalescing-operator-in-javascript/) -- [ES6 Template Literals — CSS-Tricks](https://css-tricks.com/template-literals/) -- [Tagged Template Literals — Wes Bos](https://wesbos.com/tagged-template-literals) -- [JavaScript's Logical Assignment Operators — V8 Blog](https://v8.dev/features/logical-assignment) -- [Rest Parameters and Spread Syntax — JavaScript.info](https://javascript.info/rest-parameters-spread) - ## Videos <CardGroup cols={2}> - <Card title="JavaScript ES6 Destructuring" icon="video" href="https://www.youtube.com/watch?v=NIq3qLaHCIs"> - By Web Dev Simplified + <Card title="JavaScript Destructuring in 100 Seconds" icon="video" href="https://www.youtube.com/watch?v=UgEaJBz3bjY"> + Fireship's rapid-fire format packs array destructuring, object destructuring, default values, and nested patterns into a dense but digestible 100 seconds. + </Card> + <Card title="JavaScript ES6 Arrow Functions Tutorial" icon="video" href="https://www.youtube.com/watch?v=h33Srr5J9nY"> + Kyle from Web Dev Simplified walks through arrow function syntax variations, implicit returns, and the critical `this` binding differences from traditional functions. </Card> - <Card title="Optional Chaining & Nullish Coalescing" icon="video" href="https://www.youtube.com/watch?v=v2tJ3nzXh8I"> - By Web Dev Simplified + <Card title="Spread Operator and Rest Parameters" icon="video" href="https://www.youtube.com/watch?v=iLx4ma8ZqvQ"> + Practical use cases including array concatenation, object merging, and function argument collection with side-by-side comparisons to ES5 alternatives. + </Card> + <Card title="Optional Chaining Explained" icon="video" href="https://www.youtube.com/watch?v=v2tJ3nzXh8I"> + Shows how optional chaining eliminates defensive coding patterns when accessing deeply nested object properties. Includes real-world API response examples. </Card> </CardGroup> - -- [ES6 Spread Operator and Rest Parameters — Traversy Media](https://www.youtube.com/watch?v=iLx4ma8ZqvQ) -- [JavaScript Template Literals — Programming with Mosh](https://www.youtube.com/watch?v=NgF9-pdTDGs) -- [ES6 Features Every JavaScript Developer Should Know — Fireship](https://www.youtube.com/watch?v=NCwa_xi0Uuc) diff --git a/tests/advanced-topics/modern-js-syntax/modern-js-syntax.test.js b/tests/advanced-topics/modern-js-syntax/modern-js-syntax.test.js new file mode 100644 index 00000000..db8b6f79 --- /dev/null +++ b/tests/advanced-topics/modern-js-syntax/modern-js-syntax.test.js @@ -0,0 +1,646 @@ +import { describe, it, expect } from 'vitest' + +describe('Modern JavaScript Syntax (ES6+)', () => { + + // =========================================== + // ARROW FUNCTIONS + // =========================================== + describe('Arrow Functions', () => { + it('should have concise syntax for single expressions', () => { + const add = (a, b) => a + b + const square = x => x * x + const greet = () => 'Hello!' + + expect(add(2, 3)).toBe(5) + expect(square(4)).toBe(16) + expect(greet()).toBe('Hello!') + }) + + it('should require explicit return in block body', () => { + const withBlock = (a, b) => { return a + b } + const withoutReturn = (a, b) => { a + b } // Returns undefined + + expect(withBlock(2, 3)).toBe(5) + expect(withoutReturn(2, 3)).toBe(undefined) + }) + + it('should require parentheses when returning object literal', () => { + // Without parentheses, braces are interpreted as function body (labeled statement) + const wrong = name => { name: name } // This is a labeled statement, returns undefined + const correct = name => ({ name: name }) // Parentheses make it an object literal + + expect(wrong('Alice')).toBe(undefined) + expect(correct('Alice')).toEqual({ name: 'Alice' }) + + // Note: Adding a comma like { name: name, active: true } would be a SyntaxError + }) + + it('should inherit this from enclosing scope', () => { + const obj = { + value: 42, + getValueArrow: function() { + const arrow = () => this.value + return arrow() + }, + getValueRegular: function() { + // In strict mode, 'this' inside a plain function call is undefined + // This would throw an error if we try to access this.value + const regular = function() { return this } + return regular() + } + } + + expect(obj.getValueArrow()).toBe(42) + // Arrow function correctly inherits 'this' from getValueArrow + // Regular function loses 'this' binding (undefined in strict mode) + expect(obj.getValueRegular()).toBe(undefined) + }) + }) + + // =========================================== + // DESTRUCTURING + // =========================================== + describe('Destructuring', () => { + describe('Array Destructuring', () => { + it('should extract values by position', () => { + const colors = ['red', 'green', 'blue'] + const [first, second, third] = colors + + expect(first).toBe('red') + expect(second).toBe('green') + expect(third).toBe('blue') + }) + + it('should skip elements with empty slots', () => { + const numbers = [1, 2, 3, 4, 5] + const [first, , third, , fifth] = numbers + + expect(first).toBe(1) + expect(third).toBe(3) + expect(fifth).toBe(5) + }) + + it('should support default values', () => { + const [a, b, c = 'default'] = [1, 2] + + expect(a).toBe(1) + expect(b).toBe(2) + expect(c).toBe('default') + }) + + it('should support rest pattern', () => { + const [head, ...tail] = [1, 2, 3, 4, 5] + + expect(head).toBe(1) + expect(tail).toEqual([2, 3, 4, 5]) + }) + + it('should swap variables without temp', () => { + let x = 1 + let y = 2 + + ;[x, y] = [y, x] + + expect(x).toBe(2) + expect(y).toBe(1) + }) + }) + + describe('Object Destructuring', () => { + it('should extract properties by name', () => { + const user = { name: 'Alice', age: 25 } + const { name, age } = user + + expect(name).toBe('Alice') + expect(age).toBe(25) + }) + + it('should support renaming', () => { + const user = { name: 'Alice', age: 25 } + const { name: userName, age: userAge } = user + + expect(userName).toBe('Alice') + expect(userAge).toBe(25) + }) + + it('should support default values', () => { + const user = { name: 'Alice' } + const { name, role = 'guest' } = user + + expect(name).toBe('Alice') + expect(role).toBe('guest') + }) + + it('should support nested destructuring', () => { + const user = { + name: 'Alice', + address: { city: 'Portland', country: 'USA' } + } + const { address: { city } } = user + + expect(city).toBe('Portland') + }) + + it('should support rest pattern', () => { + const user = { id: 1, name: 'Alice', age: 25 } + const { id, ...rest } = user + + expect(id).toBe(1) + expect(rest).toEqual({ name: 'Alice', age: 25 }) + }) + }) + + describe('Function Parameter Destructuring', () => { + it('should destructure parameters', () => { + function greet({ name, greeting = 'Hello' }) { + return `${greeting}, ${name}!` + } + + expect(greet({ name: 'Alice' })).toBe('Hello, Alice!') + expect(greet({ name: 'Bob', greeting: 'Hi' })).toBe('Hi, Bob!') + }) + + it('should handle empty parameter with default', () => { + function greet({ name = 'Guest' } = {}) { + return `Hello, ${name}!` + } + + expect(greet()).toBe('Hello, Guest!') + expect(greet({})).toBe('Hello, Guest!') + expect(greet({ name: 'Alice' })).toBe('Hello, Alice!') + }) + }) + }) + + // =========================================== + // SPREAD AND REST OPERATORS + // =========================================== + describe('Spread and Rest Operators', () => { + describe('Spread Operator', () => { + it('should spread arrays', () => { + const arr1 = [1, 2, 3] + const arr2 = [4, 5, 6] + + expect([...arr1, ...arr2]).toEqual([1, 2, 3, 4, 5, 6]) + expect([0, ...arr1, 4]).toEqual([0, 1, 2, 3, 4]) + }) + + it('should copy arrays (shallow)', () => { + const original = [1, 2, 3] + const copy = [...original] + + expect(copy).toEqual(original) + expect(copy).not.toBe(original) + }) + + it('should spread objects', () => { + const defaults = { theme: 'light', fontSize: 14 } + const userPrefs = { theme: 'dark' } + + const merged = { ...defaults, ...userPrefs } + + expect(merged).toEqual({ theme: 'dark', fontSize: 14 }) + }) + + it('should spread function arguments', () => { + const numbers = [1, 5, 3, 9, 2] + + expect(Math.max(...numbers)).toBe(9) + expect(Math.min(...numbers)).toBe(1) + }) + + it('should create shallow copies only', () => { + const original = { nested: { value: 1 } } + const copy = { ...original } + + copy.nested.value = 2 + + // Both are affected because nested object is shared + expect(original.nested.value).toBe(2) + expect(copy.nested.value).toBe(2) + }) + }) + + describe('Rest Parameters', () => { + it('should collect remaining arguments', () => { + function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0) + } + + expect(sum(1, 2, 3)).toBe(6) + expect(sum(1, 2, 3, 4, 5)).toBe(15) + expect(sum()).toBe(0) + }) + + it('should work with named parameters', () => { + function log(first, ...rest) { + return { first, rest } + } + + expect(log('a', 'b', 'c', 'd')).toEqual({ + first: 'a', + rest: ['b', 'c', 'd'] + }) + }) + }) + }) + + // =========================================== + // TEMPLATE LITERALS + // =========================================== + describe('Template Literals', () => { + it('should interpolate expressions', () => { + const name = 'Alice' + const age = 25 + + expect(`Hello, ${name}!`).toBe('Hello, Alice!') + expect(`Age: ${age}`).toBe('Age: 25') + expect(`Next year: ${age + 1}`).toBe('Next year: 26') + }) + + it('should support multi-line strings', () => { + const multiLine = `line 1 +line 2 +line 3` + + expect(multiLine).toContain('line 1') + expect(multiLine).toContain('\n') + expect(multiLine.split('\n').length).toBe(3) + }) + + it('should work with tagged templates', () => { + function upper(strings, ...values) { + return strings.reduce((result, str, i) => { + const value = values[i] ? values[i].toString().toUpperCase() : '' + return result + str + value + }, '') + } + + const name = 'alice' + expect(upper`Hello, ${name}!`).toBe('Hello, ALICE!') + }) + }) + + // =========================================== + // OPTIONAL CHAINING + // =========================================== + describe('Optional Chaining', () => { + it('should safely access nested properties', () => { + const user = { name: 'Alice' } + const userWithAddress = { name: 'Bob', address: { city: 'Portland' } } + + expect(user?.address?.city).toBe(undefined) + expect(userWithAddress?.address?.city).toBe('Portland') + }) + + it('should short-circuit on null/undefined', () => { + const user = null + + expect(user?.name).toBe(undefined) + expect(user?.address?.city).toBe(undefined) + }) + + it('should work with bracket notation', () => { + const user = { profile: { name: 'Alice' } } + const prop = 'profile' + + expect(user?.[prop]?.name).toBe('Alice') + expect(user?.['nonexistent']?.name).toBe(undefined) + }) + + it('should work with function calls', () => { + const obj = { + greet: () => 'Hello!' + } + + expect(obj.greet?.()).toBe('Hello!') + expect(obj.nonexistent?.()).toBe(undefined) + }) + }) + + // =========================================== + // NULLISH COALESCING + // =========================================== + describe('Nullish Coalescing', () => { + it('should return right side only for null/undefined', () => { + expect(null ?? 'default').toBe('default') + expect(undefined ?? 'default').toBe('default') + expect(0 ?? 'default').toBe(0) + expect('' ?? 'default').toBe('') + expect(false ?? 'default').toBe(false) + expect(NaN ?? 'default').toBeNaN() + }) + + it('should differ from logical OR', () => { + // || returns right side for any falsy value + expect(0 || 'default').toBe('default') + expect('' || 'default').toBe('default') + expect(false || 'default').toBe('default') + + // ?? only returns right side for null/undefined + expect(0 ?? 'default').toBe(0) + expect('' ?? 'default').toBe('') + expect(false ?? 'default').toBe(false) + }) + + it('should combine with optional chaining', () => { + const user = null + + expect(user?.name ?? 'Anonymous').toBe('Anonymous') + + const userWithName = { name: 'Alice' } + expect(userWithName?.name ?? 'Anonymous').toBe('Alice') + }) + }) + + // =========================================== + // LOGICAL ASSIGNMENT OPERATORS + // =========================================== + describe('Logical Assignment Operators', () => { + it('should support nullish coalescing assignment (??=)', () => { + let a = null + let b = 'value' + let c = 0 + + a ??= 'default' + b ??= 'default' + c ??= 'default' + + expect(a).toBe('default') + expect(b).toBe('value') + expect(c).toBe(0) + }) + + it('should support logical OR assignment (||=)', () => { + let a = null + let b = 'value' + let c = 0 + + a ||= 'default' + b ||= 'default' + c ||= 'default' + + expect(a).toBe('default') + expect(b).toBe('value') + expect(c).toBe('default') // 0 is falsy + }) + + it('should support logical AND assignment (&&=)', () => { + let a = null + let b = 'value' + + a &&= 'updated' + b &&= 'updated' + + expect(a).toBe(null) // null is falsy, so no assignment + expect(b).toBe('updated') // 'value' is truthy, so assign + }) + }) + + // =========================================== + // DEFAULT PARAMETERS + // =========================================== + describe('Default Parameters', () => { + it('should provide default values', () => { + function greet(name = 'Guest', greeting = 'Hello') { + return `${greeting}, ${name}!` + } + + expect(greet()).toBe('Hello, Guest!') + expect(greet('Alice')).toBe('Hello, Alice!') + expect(greet('Bob', 'Hi')).toBe('Hi, Bob!') + }) + + it('should only trigger on undefined, not null', () => { + function example(value = 'default') { + return value + } + + expect(example(undefined)).toBe('default') + expect(example(null)).toBe(null) + expect(example(0)).toBe(0) + expect(example('')).toBe('') + expect(example(false)).toBe(false) + }) + + it('should allow earlier parameters as defaults', () => { + function createRect(width, height = width) { + return { width, height } + } + + expect(createRect(10)).toEqual({ width: 10, height: 10 }) + expect(createRect(10, 20)).toEqual({ width: 10, height: 20 }) + }) + + it('should evaluate default expressions each time', () => { + let counter = 0 + function getDefault() { return ++counter } + + function example(value = getDefault()) { + return value + } + + expect(example()).toBe(1) + expect(example()).toBe(2) + expect(example()).toBe(3) + expect(example(100)).toBe(100) // getDefault not called + expect(example()).toBe(4) + }) + }) + + // =========================================== + // ENHANCED OBJECT LITERALS + // =========================================== + describe('Enhanced Object Literals', () => { + it('should support property shorthand', () => { + const name = 'Alice' + const age = 25 + + const user = { name, age } + + expect(user).toEqual({ name: 'Alice', age: 25 }) + }) + + it('should support method shorthand', () => { + const calculator = { + add(a, b) { return a + b }, + subtract(a, b) { return a - b } + } + + expect(calculator.add(5, 3)).toBe(8) + expect(calculator.subtract(5, 3)).toBe(2) + }) + + it('should support computed property names', () => { + const key = 'dynamicKey' + const index = 0 + + const obj = { + [key]: 'value', + [`item_${index}`]: 'first' + } + + expect(obj.dynamicKey).toBe('value') + expect(obj.item_0).toBe('first') + }) + }) + + // =========================================== + // MAP, SET, AND SYMBOL + // =========================================== + describe('Map', () => { + it('should store key-value pairs with any key type', () => { + const map = new Map() + const objKey = { id: 1 } + + map.set('string', 'value1') + map.set(42, 'value2') + map.set(objKey, 'value3') + + expect(map.get('string')).toBe('value1') + expect(map.get(42)).toBe('value2') + expect(map.get(objKey)).toBe('value3') + expect(map.size).toBe(3) + }) + + it('should maintain insertion order', () => { + const map = new Map([['c', 3], ['a', 1], ['b', 2]]) + const keys = [...map.keys()] + + expect(keys).toEqual(['c', 'a', 'b']) + }) + + it('should be iterable', () => { + const map = new Map([['a', 1], ['b', 2]]) + const entries = [] + + for (const [key, value] of map) { + entries.push([key, value]) + } + + expect(entries).toEqual([['a', 1], ['b', 2]]) + }) + }) + + describe('Set', () => { + it('should store unique values', () => { + const set = new Set([1, 2, 2, 3, 3, 3]) + + expect(set.size).toBe(3) + expect([...set]).toEqual([1, 2, 3]) + }) + + it('should remove duplicates from arrays', () => { + const numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] + const unique = [...new Set(numbers)] + + expect(unique).toEqual([1, 2, 3, 4]) + }) + + it('should support set operations', () => { + const a = new Set([1, 2, 3]) + const b = new Set([2, 3, 4]) + + const union = new Set([...a, ...b]) + const intersection = [...a].filter(x => b.has(x)) + const difference = [...a].filter(x => !b.has(x)) + + expect([...union]).toEqual([1, 2, 3, 4]) + expect(intersection).toEqual([2, 3]) + expect(difference).toEqual([1]) + }) + }) + + describe('Symbol', () => { + it('should create unique values', () => { + const sym1 = Symbol('description') + const sym2 = Symbol('description') + + expect(sym1).not.toBe(sym2) + }) + + it('should work as object keys', () => { + const ID = Symbol('id') + const user = { + name: 'Alice', + [ID]: 12345 + } + + expect(user[ID]).toBe(12345) + expect(Object.keys(user)).toEqual(['name']) // Symbol not included + }) + + it('should support global registry with Symbol.for', () => { + const sym1 = Symbol.for('shared') + const sym2 = Symbol.for('shared') + + expect(sym1).toBe(sym2) + expect(Symbol.keyFor(sym1)).toBe('shared') + }) + }) + + // =========================================== + // FOR...OF LOOP + // =========================================== + describe('for...of Loop', () => { + it('should iterate over array values', () => { + const arr = ['a', 'b', 'c'] + const values = [] + + for (const value of arr) { + values.push(value) + } + + expect(values).toEqual(['a', 'b', 'c']) + }) + + it('should iterate over string characters', () => { + const chars = [] + + for (const char of 'hello') { + chars.push(char) + } + + expect(chars).toEqual(['h', 'e', 'l', 'l', 'o']) + }) + + it('should iterate over Map entries', () => { + const map = new Map([['a', 1], ['b', 2]]) + const entries = [] + + for (const [key, value] of map) { + entries.push({ key, value }) + } + + expect(entries).toEqual([ + { key: 'a', value: 1 }, + { key: 'b', value: 2 } + ]) + }) + + it('should iterate over Set values', () => { + const set = new Set([1, 2, 3]) + const values = [] + + for (const value of set) { + values.push(value) + } + + expect(values).toEqual([1, 2, 3]) + }) + + it('should work with destructuring', () => { + const users = [ + { name: 'Alice', age: 25 }, + { name: 'Bob', age: 30 } + ] + const names = [] + + for (const { name } of users) { + names.push(name) + } + + expect(names).toEqual(['Alice', 'Bob']) + }) + }) +}) From 1e7c22dc878f8f2eace4a000f3c394e63369043d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 17:03:25 -0300 Subject: [PATCH 119/128] feat: add comprehensive ES Modules concept page with tests - Complete rewrite of ES Modules documentation covering: - Why ES Modules matter (static analysis, tree-shaking) - ESM vs CommonJS comparison with detailed table - Live bindings explanation with ASCII diagrams - Export syntax (named, default, re-exports, barrel files) - Import syntax (named, default, namespace, side-effect) - Module characteristics (strict mode, scope, singleton) - Dynamic imports with real-world use cases - Top-level await patterns - Browser vs Node.js differences - Import maps for bare specifiers - Common mistakes section - Added 57 comprehensive tests covering: - Live bindings behavior vs CommonJS value copies - Read-only import bindings - Circular dependencies and TDZ (ReferenceError) - Module singleton behavior - Dynamic import patterns - Export/import syntax variations - Module characteristics (strict mode, this, scope) - Common mistakes (named vs default, extensions) --- docs/concepts/es-modules.mdx | 1329 ++++++++++++++++- .../es-modules/es-modules.test.js | 1207 +++++++++++++++ 2 files changed, 2498 insertions(+), 38 deletions(-) create mode 100644 tests/advanced-topics/es-modules/es-modules.test.js diff --git a/docs/concepts/es-modules.mdx b/docs/concepts/es-modules.mdx index bcc64c73..f6d1cd63 100644 --- a/docs/concepts/es-modules.mdx +++ b/docs/concepts/es-modules.mdx @@ -1,76 +1,1329 @@ --- -title: "ES Modules: Modern JavaScript Module System" -sidebarTitle: "ES Modules: Modern Module System" -description: "Learn ES Modules in JavaScript — import/export syntax, named and default exports, dynamic imports, and module patterns. Master the modern module system used in all JavaScript projects." +title: "ES Modules: JavaScript's Native Module System" +sidebarTitle: "ES Modules: Native Module System" +description: "Learn ES Modules in JavaScript. Understand import/export syntax, why ESM beats CommonJS, live bindings, dynamic imports, top-level await, and how modules enable tree-shaking." --- -## Overview +Why does Node.js have two different module systems? Why can bundlers remove unused code from ES Modules but not from CommonJS? And why do some imports need curly braces while others don't? -**ES Modules** (ESM) is JavaScript's official module system, introduced in ES6. It uses `import` and `export` statements to share code between files, enabling better code organization, encapsulation, and tree-shaking for smaller bundle sizes. ES Modules are now the standard for both browser and Node.js development. +ES Modules (ESM) is JavaScript's official module system, standardized in ES2015. It's the answer to years of competing module formats, and it's designed from the ground up to be statically analyzable, which unlocks optimizations that older systems simply can't match. + +```javascript +// math.js - Exporting functionality +export const PI = 3.14159 +export function square(x) { + return x * x +} + +// app.js - Importing what you need +import { PI, square } from './math.js' + +console.log(square(4)) // 16 +console.log(PI) // 3.14159 +``` + +This guide goes beyond the basics. You'll learn why ESM's design makes it better than CommonJS for tooling and optimization, how live bindings work, and the practical differences between browsers and Node.js. <Info> **What you'll learn in this guide:** -- `import` and `export` syntax -- Named exports vs default exports -- Re-exporting and aggregating modules -- Dynamic imports with `import()` -- Module scope vs global scope -- CommonJS vs ES Modules differences -- Tree-shaking and dead code elimination -- Top-level await -- Import maps and import assertions +- Why ES Modules exist and what problems they solve +- The key differences between ESM and CommonJS (and when each applies) +- How live bindings make ESM exports work differently than CommonJS +- All the export and import syntax variations +- Dynamic imports for code splitting and lazy loading +- Top-level await and when to use it +- Browser vs Node.js: how ESM works in each environment +- Import maps for bare module specifiers in browsers +- How ESM enables tree-shaking and smaller bundles </Info> <Warning> -**Prerequisites:** This guide builds on [IIFE, Modules and Namespaces](/concepts/iife-modules) which covers the history and evolution of JavaScript module patterns. +**Prerequisite:** This guide assumes you're familiar with basic module concepts. If terms like "named exports" or "default exports" are new to you, start with our [IIFE, Modules & Namespaces](/concepts/iife-modules) guide first. +</Warning> + +--- + +## Why ES Modules Matter + +For most of JavaScript's history, there was no built-in way to split code into reusable pieces. The language simply didn't have modules. Developers created workarounds: IIFEs to avoid polluting the global scope, the Module Pattern for encapsulation, and eventually third-party systems like CommonJS (for Node.js) and AMD (for browsers). + +These solutions worked, but they were all invented outside the language itself. Each had tradeoffs, and none could be fully optimized by JavaScript engines or build tools. + +ES Modules changed that. Introduced in ES2015 (ES6), ESM is part of the language specification. This means: + +- **Browsers can load modules natively** without bundlers (though bundlers still help with optimization) +- **Tools can analyze your code statically** because imports and exports are declarative +- **Unused code can be eliminated** (tree-shaking) because the module graph is known at build time +- **The syntax is standardized** across all JavaScript environments + +Today, ESM is supported in all modern browsers and Node.js. It's the module system you should use for new projects. + +--- + +## The Shipping Container Analogy + +Think of ES Modules like the standardized shipping container that revolutionized global trade. + +Before shipping containers, cargo was loaded piece by piece. Every ship, truck, and warehouse had different ways of handling goods. It was slow, error-prone, and impossible to optimize at scale. + +Shipping containers changed everything. A standard size meant cranes, ships, and trucks could all handle cargo the same way. You could plan logistics before the ship even arrived because you knew exactly what you were dealing with. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ COMMONJS vs ES MODULES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ COMMONJS (Dynamic Loading) ES MODULES (Static Analysis) │ +│ ─────────────────────────── ──────────────────────────── │ +│ │ +│ ┌──────────────────────┐ ┌──────────────────────┐ │ +│ │ require('./math') │ │ import { add } │ │ +│ │ │ │ from './math.js' │ │ +│ │ Resolved at │ │ │ │ +│ │ RUNTIME │ │ Known at │ │ +│ │ │ │ BUILD TIME │ │ +│ │ Could be anything: │ │ │ │ +│ │ require(userInput) │ │ Tools can: │ │ +│ │ require(condition │ │ • See all imports │ │ +│ │ ? 'a' : 'b') │ │ • Remove dead code │ │ +│ │ │ │ • Optimize bundles │ │ +│ └──────────────────────┘ └──────────────────────┘ │ +│ │ +│ Like loose cargo: Like shipping containers: │ +│ flexible but hard standardized and │ +│ to optimize optimizable │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +ESM's static structure is like those shipping containers. Because imports and exports are declarative (not computed at runtime), tools can "see" your entire module graph before running any code. This visibility enables optimizations that are simply impossible with dynamic systems like CommonJS. + +--- + +## ESM vs CommonJS: The Complete Comparison + +If you've worked with Node.js, you've used CommonJS. It's been Node's module system since the beginning. But ESM and CommonJS work differently at a core level. + +| Aspect | ES Modules | CommonJS | +|--------|------------|----------| +| **Syntax** | `import` / `export` | `require()` / `module.exports` | +| **Loading** | Asynchronous | Synchronous | +| **Analysis** | Static (build time) | Dynamic (runtime) | +| **Exports** | Live bindings (references) | Value copies | +| **Strict mode** | Always enabled | Optional | +| **Top-level `this`** | `undefined` | `module.exports` | +| **File extensions** | Required in browsers | Optional in Node | +| **Tree-shaking** | Yes | No | + +### Syntax Side-by-Side + +```javascript +// ───────────────────────────────────────────── +// COMMONJS (Node.js traditional) +// ───────────────────────────────────────────── + +// Exporting +const PI = 3.14159 +function square(x) { return x * x } + +module.exports = { PI, square } +// or: exports.PI = PI + +// Importing +const { PI, square } = require('./math') +const math = require('./math') // whole module + + +// ───────────────────────────────────────────── +// ES MODULES (modern standard) +// ───────────────────────────────────────────── + +// Exporting +export const PI = 3.14159 +export function square(x) { return x * x } + +// Importing +import { PI, square } from './math.js' +import * as math from './math.js' // namespace import +``` + +### Static vs Dynamic: Why It Matters + +CommonJS imports are function calls that happen at runtime. You can put them anywhere, compute the path dynamically, and even conditionally require different modules: + +```javascript +// CommonJS - Dynamic (works but prevents optimization) +const moduleName = condition ? 'moduleA' : 'moduleB' +const mod = require(`./${moduleName}`) + +if (needsFeature) { + const feature = require('./heavy-feature') +} +``` + +ESM imports must be at the top level with string literals. This seems restrictive, but it's a feature, not a bug: + +```javascript +// ES Modules - Static (enables optimization) +import { feature } from './heavy-feature.js' // must be top-level +import { helper } from './utils.js' // path must be a string + +// ❌ These are syntax errors in ESM: +// import { x } from condition ? 'a.js' : 'b.js' +// if (condition) { import { y } from './module.js' } +``` + +Because ESM imports are static, bundlers can build a complete picture of your dependencies before running any code. This enables dead code elimination, bundle splitting, and other optimizations. + +<Tip> +**Need dynamic loading in ESM?** Use `import()` for dynamic imports (covered later in this guide). You get the best of both worlds: static analysis for your main code, dynamic loading when you actually need it. +</Tip> + +### Async vs Sync Loading + +CommonJS loads modules synchronously. When Node.js hits a `require()`, it blocks until the file is read and executed. This works fine on a server with fast disk access. + +ESM loads modules asynchronously. The browser fetches module files over the network, which can't block the main thread. This async nature is why: + +- ESM works natively in browsers +- Top-level `await` is possible in ESM +- The loading behavior is more predictable + +--- + +## Live Bindings: Why ESM Exports Are Different + +Here's a difference that trips people up. When you import from a CommonJS module, you get a **copy** of the exported value. When you import from an ES Module, you get a **live binding**: a reference to the original variable. + +```javascript +// ───────────────────────────────────────────── +// counter.cjs (CommonJS) +// ───────────────────────────────────────────── +let count = 0 +function increment() { count++ } +function getCount() { return count } + +module.exports = { count, increment, getCount } + + +// ───────────────────────────────────────────── +// main.cjs (CommonJS consumer) +// ───────────────────────────────────────────── +const { count, increment, getCount } = require('./counter.cjs') + +console.log(count) // 0 +increment() +console.log(count) // 0 (still! it's a copy) +console.log(getCount()) // 1 (function reads the real value) +``` + +```javascript +// ───────────────────────────────────────────── +// counter.mjs (ES Module) +// ───────────────────────────────────────────── +export let count = 0 +export function increment() { count++ } + + +// ───────────────────────────────────────────── +// main.mjs (ESM consumer) +// ───────────────────────────────────────────── +import { count, increment } from './counter.mjs' + +console.log(count) // 0 +increment() +console.log(count) // 1 (live binding reflects the change!) +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ LIVE BINDINGS EXPLAINED │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ COMMONJS (Value Copy) ES MODULES (Live Binding) │ +│ ───────────────────── ──────────────────────── │ +│ │ +│ counter.js: counter.js: │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ count: 1 │ │ count: 1 │ ◄───────┐ │ +│ └─────────────┘ └─────────────┘ │ │ +│ │ ▲ │ │ +│ │ copy at │ reference │ │ +│ │ require time │ always │ │ +│ ▼ │ current │ │ +│ main.js: main.js: │ │ +│ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ count: 0 │ (stale!) │ count ──────┼─────────┘ │ +│ └─────────────┘ └─────────────┘ │ +│ │ +│ The imported value is The import IS the │ +│ frozen at require time original variable │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Why Live Bindings Matter + +Live bindings have practical implications: + +1. **Singleton state works correctly** — If a module exports state, all importers see the same state +2. **Circular dependencies are safer** — Because bindings are live, you can have modules that depend on each other (though you should still avoid this when possible) +3. **You can't reassign imports** — `count = 5` throws an error because you don't own that binding + +```javascript +import { count } from './counter.js' + +count = 10 // ❌ TypeError: imported bindings are read-only + // Even though 'count' is 'let' in the source, you can't reassign it here +``` + +<Note> +Imported bindings are always read-only to the importer. Only the module that exports a variable can modify it. This prevents confusing "action at a distance" bugs. +</Note> + +--- + +## Export Syntax Deep Dive + +ES Modules give you several ways to export functionality. Here's the complete picture. + +### Named Exports + +The most common pattern. You can export inline or group exports at the bottom: + +```javascript +// Inline named exports +export const PI = 3.14159 +export function calculateArea(radius) { + return PI * radius * radius +} +export class Circle { + constructor(radius) { + this.radius = radius + } +} + +// Or group them at the bottom (same result) +const PI = 3.14159 +function calculateArea(radius) { + return PI * radius * radius +} +class Circle { + constructor(radius) { + this.radius = radius + } +} + +export { PI, calculateArea, Circle } +``` + +### Renaming Exports + +Use `as` to export under a different name: + +```javascript +function internalHelper() { /* ... */ } + +export { internalHelper as helper } +// Consumers import as: import { helper } from './module.js' +``` + +### Default Exports + +Each module can have one default export. It represents the module's "main" thing: + +```javascript +// A class as default export +export default class Logger { + log(message) { + console.log(`[LOG] ${message}`) + } +} + +// Or a function +export default function formatDate(date) { + return date.toISOString() +} + +// Or a value (note: no variable declaration with default) +export default { + name: 'Config', + version: '1.0.0' +} +``` + +### Mixing Named and Default Exports + +You can have both, though use this sparingly: + +```javascript +// React does this: default for the main API, named for utilities +export default function React() { /* ... */ } +export function useState() { /* ... */ } +export function useEffect() { /* ... */ } + +// Consumer can import both: +import React, { useState, useEffect } from 'react' +``` + +### Re-Exporting (Barrel Files) + +Re-exports let you aggregate multiple modules into one entry point. This is common in libraries: + +```javascript +// utils/index.js (barrel file) +export { formatDate, parseDate } from './date.js' +export { formatCurrency } from './currency.js' +export { default as Logger } from './logger.js' + +// Re-export everything from a module +export * from './math.js' + +// Re-export with rename +export { helper as utilHelper } from './helpers.js' +``` + +Now consumers can import from one place: + +```javascript +import { formatDate, formatCurrency, Logger } from './utils/index.js' +``` + +<Warning> +**Barrel file gotcha:** Re-exporting everything with `export *` can hurt tree-shaking. The bundler may include code you don't use. Prefer explicit re-exports for better optimization. +</Warning> + +--- + +## Import Syntax Deep Dive + +Every export style has a corresponding import style. + +### Named Imports + +Import specific exports by name (must match exactly): + +```javascript +import { PI, calculateArea } from './math.js' +import { formatDate } from './date.js' +``` + +### Renaming Imports + +Use `as` when names conflict or you want something clearer: + +```javascript +import { formatDate as formatDateISO } from './date.js' +import { formatDate as formatDateUS } from './date-us.js' +``` + +### Default Imports + +No curly braces. You choose the name: + +```javascript +// The module exports: export default class Logger { } +import Logger from './logger.js' // common convention: match the export +import MyLogger from './logger.js' // but any name works +import L from './logger.js' // even short names +``` + +### Namespace Imports + +Import everything as a single object: + +```javascript +import * as math from './math.js' + +console.log(math.PI) // 3.14159 +console.log(math.calculateArea(5)) // 78.54 +console.log(math.default) // the default export, if any +``` + +### Combined Imports + +Mixing default and named in one statement: + +```javascript +// Module exports both default and named +import React, { useState, useEffect } from 'react' +import lodash, { debounce, throttle } from 'lodash' +``` + +### Side-Effect Imports + +Import a module just for its side effects (no bindings): + +```javascript +import './polyfills.js' // runs the file, imports nothing +import './analytics.js' // sets up tracking +import './styles.css' // with bundler support +``` + +### Module Specifiers + +The string after `from` is called the module specifier: + +```javascript +// Relative paths (start with ./ or ../) +import { x } from './utils.js' +import { y } from '../shared/helpers.js' + +// Absolute paths (less common) +import { z } from '/lib/utils.js' + +// Bare specifiers (no path prefix) +import { useState } from 'react' // needs bundler or import map +import lodash from 'lodash' +``` + +<Tip> +**Bare specifiers** like `'react'` don't work in browsers by default — browsers don't know where to find `'react'`. You need either a bundler or an import map (covered later). +</Tip> + +--- + +## Module Characteristics + +ES Modules have built-in behaviors that differ from regular scripts. + +### Automatic Strict Mode + +Every ES Module runs in strict mode automatically. No `"use strict"` needed: + +```javascript +// In a module, this throws an error: +undeclaredVariable = 'oops' // ReferenceError: undeclaredVariable is not defined + +// These also fail: +delete Object.prototype // TypeError +function f(a, a) {} // SyntaxError: duplicate parameter +``` + +### Module Scope + +Variables in a module are local to that module, not global: + +```javascript +// module.js +const privateValue = 'secret' // not on window/global +var alsoPrivate = 'hidden' // var doesn't leak to global either + +// Only exports are accessible from outside +export const publicValue = 'visible' +``` + +### Singleton Behavior + +A module's code runs exactly once, no matter how many times you import it: + +```javascript +// counter.js +console.log('Module initialized!') // logs once +export let count = 0 + +// a.js +import { count } from './counter.js' // "Module initialized!" + +// b.js +import { count } from './counter.js' // nothing logged (already ran) +``` + +This makes modules natural singletons. All importers share the same instance. + +### `this` is `undefined` + +At the top level of a module, `this` is `undefined` (not `window` or `global`): + +```javascript +// script.js (regular script) +console.log(this) // window (in browser) + +// module.js (ES Module) +console.log(this) // undefined +``` + +### Import Hoisting + +Imports are hoisted to the top of the module. You can reference imported values before the import statement in code order (though you shouldn't): + +```javascript +// This works (but don't write code like this) +console.log(helper()) // imports are hoisted + +import { helper } from './utils.js' +``` + +### Deferred Execution in Browsers + +Module scripts are deferred by default. They don't block HTML parsing and execute after the document is parsed: + +```html +<!-- Blocks parsing until loaded and executed --> +<script src="blocking.js"></script> + +<!-- Deferred automatically (like adding defer attribute) --> +<script type="module" src="module.js"></script> +``` + +--- + +## Dynamic Imports + +Static imports must be at the top level, but sometimes you need to load modules dynamically. That's what `import()` is for. + +### The `import()` Expression + +[`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) looks like a function call, but it's special syntax. It returns a Promise that resolves to the module's namespace object: + +```javascript +// Load a module dynamically +const module = await import('./math.js') +console.log(module.PI) // 3.14159 +console.log(module.default) // the default export, if any + +// Or with .then() +import('./math.js').then(module => { + console.log(module.PI) +}) +``` + +### Accessing Exports + +With dynamic imports, you get a module namespace object: + +```javascript +// Named exports are properties +const { formatDate, parseDate } = await import('./date.js') + +// Default export is on the 'default' property +const { default: Logger } = await import('./logger.js') +// or +const loggerModule = await import('./logger.js') +const Logger = loggerModule.default +``` + +### Real-World Use Cases + +**Route-based code splitting:** + +```javascript +// Load page components only when navigating +async function loadPage(pageName) { + const pages = { + home: () => import('./pages/Home.js'), + about: () => import('./pages/About.js'), + contact: () => import('./pages/Contact.js') + } + + const pageModule = await pages[pageName]() + return pageModule.default +} +``` + +**Conditional feature loading:** + +```javascript +// Only load heavy charting library if user needs it +async function showChart(data) { + const { Chart } = await import('chart.js') + const chart = new Chart(canvas, { /* ... */ }) +} +``` + +**Lazy loading based on feature detection:** + +```javascript +let crypto + +if (typeof window !== 'undefined' && window.crypto) { + crypto = window.crypto +} else { + // Only load polyfill in environments that need it + const module = await import('crypto-polyfill') + crypto = module.default +} +``` + +**Loading based on user preference:** + +```javascript +async function loadTheme(themeName) { + // Path is computed at runtime - not possible with static imports + const theme = await import(`./themes/${themeName}.js`) + applyTheme(theme.default) +} +``` + +<Note> +`import()` works in regular scripts too, not just modules. This is useful for adding ESM libraries to legacy codebases. +</Note> + +--- + +## Top-Level Await + +ES Modules support `await` at the top level, outside of any function. This is useful for setup that requires async operations. + +```javascript +// config.js +const response = await fetch('/api/config') +export const config = await response.json() + +// database.js +import { MongoClient } from 'mongodb' +const client = new MongoClient(uri) +await client.connect() +export const db = client.db('myapp') +``` + +### How It Affects Module Loading + +When a module uses top-level await, it blocks modules that depend on it: + +```javascript +// slow.js +await new Promise(r => setTimeout(r, 2000)) // 2 second delay +export const value = 42 + +// app.js +import { value } from './slow.js' // waits for slow.js to finish +console.log(value) // logs after 2 seconds +``` + +Modules that don't depend on `slow.js` can still load in parallel. + +### When to Use (and When Not To) + +**Good uses:** + +```javascript +// Loading configuration at startup +export const config = await loadConfig() + +// Database connection that's needed before anything else +export const db = await connectToDatabase() + +// One-time initialization +await initializeAnalytics() +``` + +**Avoid:** + +```javascript +// ❌ Don't do slow operations that could be lazy +const heavyData = await fetch('/api/huge-dataset') // blocks everything + +// ✓ Better: export a function that fetches when needed +export async function getHeavyData() { + return fetch('/api/huge-dataset') +} +``` + +<Warning> +Top-level await can create waterfall loading. If module A awaits and module B depends on A, then module C depends on B, everything loads sequentially. Use it judiciously. </Warning> +--- + +## Browser vs Node.js: ESM Differences + +ES Modules work in both browsers and Node.js, but there are differences in how you enable and use them. + +### Enabling ESM + +| Environment | How to Enable | +|-------------|---------------| +| **Browser** | `<script type="module" src="app.js"></script>` | +| **Node.js** | Use `.mjs` extension, or set `"type": "module"` in package.json | + +**Browser:** + +```html +<!-- The type="module" attribute enables ESM --> +<script type="module" src="./app.js"></script> + +<!-- Inline module --> +<script type="module"> + import { greet } from './utils.js' + greet('World') +</script> +``` + +**Node.js:** + +```javascript +// Option 1: Use .mjs extension +// math.mjs +export const add = (a, b) => a + b + +// Option 2: Set type in package.json +// package.json: { "type": "module" } +// Then .js files are treated as ESM +``` + +### File Extensions + +| Environment | Extension Required? | +|-------------|---------------------| +| **Browser** | Yes — must include `.js` or full URL | +| **Node.js** | Yes for ESM (can omit for CommonJS) | + +```javascript +// Browser - extensions required +import { helper } from './utils.js' // ✓ +import { helper } from './utils' // ❌ 404 error + +// Node.js ESM - extensions required +import { helper } from './utils.js' // ✓ +import { helper } from './utils' // ❌ ERR_MODULE_NOT_FOUND +``` + +### Bare Specifiers + +```javascript +import lodash from 'lodash' // "bare specifier" - no path prefix +``` + +| Environment | Bare Specifier Support | +|-------------|------------------------| +| **Browser** | No (needs import map or bundler) | +| **Node.js** | Yes (looks in node_modules) | + +### `import.meta` + +Both environments provide [`import.meta`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta), but with different properties: + +```javascript +// Browser +console.log(import.meta.url) // "https://example.com/js/app.js" + +// Node.js +console.log(import.meta.url) // "file:///path/to/app.js" +console.log(import.meta.dirname) // "/path/to" (Node v20.11.0+) +console.log(import.meta.filename) // "/path/to/app.js" (Node v20.11.0+) +``` + +### CORS in Browsers + +When loading modules from different origins, browsers enforce CORS: + +```html +<!-- Same-origin: works fine --> +<script type="module" src="/js/app.js"></script> + +<!-- Cross-origin: server must send CORS headers --> +<script type="module" src="https://other-site.com/module.js"></script> +<!-- Requires: Access-Control-Allow-Origin header --> +``` + +### Summary Table + +| Feature | Browser | Node.js | +|---------|---------|---------| +| Enable via | `type="module"` | `.mjs` or `"type": "module"` | +| File extensions | Required | Required for ESM | +| Bare specifiers | Import map needed | Works (node_modules) | +| Top-level await | Yes | Yes | +| `import.meta.url` | Full URL | `file://` path | +| CORS | Enforced | N/A | +| Runs in strict mode | Yes | Yes | + +--- + +## Import Maps + +[Import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) solve a browser problem: how do you use bare specifiers like `'lodash'` without a bundler? + +### The Problem + +This works in Node.js because Node looks in `node_modules`: + +```javascript +import confetti from 'canvas-confetti' // Node: finds it in node_modules +``` + +In browsers, this fails — the browser doesn't know where `'canvas-confetti'` lives. + +### The Solution: Import Maps + +An import map tells the browser where to find modules: + +```html +<script type="importmap"> +{ + "imports": { + "canvas-confetti": "https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.module.mjs", + "lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js" + } +} +</script> + +<script type="module"> + // Now bare specifiers work! + import confetti from 'canvas-confetti' + import { debounce } from 'lodash' + + confetti() +</script> +``` + +### Path Prefixes + +Map entire package paths: + +```html +<script type="importmap"> +{ + "imports": { + "lodash/": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/" + } +} +</script> + +<script type="module"> + // The trailing slash enables path mapping + import debounce from 'lodash/debounce.js' + import throttle from 'lodash/throttle.js' +</script> +``` + +### Browser Support + +Import maps are supported in all modern browsers (Chrome 89+, Safari 16.4+, Firefox 108+). For older browsers, you'll need a polyfill or bundler. + +<Tip> +Import maps are great for simple projects, demos, and learning. For production apps with many dependencies, bundlers like Vite still provide better optimization and developer experience. +</Tip> + +--- + +## Tree-Shaking and Bundlers + +One of ESM's biggest advantages is enabling tree-shaking, which bundlers use to eliminate dead code. + +### What is Tree-Shaking? + +Tree-shaking removes unused exports from your final bundle: + +```javascript +// math.js +export function add(a, b) { return a + b } +export function subtract(a, b) { return a - b } +export function multiply(a, b) { return a * b } +export function divide(a, b) { return a / b } + +// app.js +import { add } from './math.js' +console.log(add(2, 3)) +``` + +A tree-shaking bundler sees that only `add` is used, so `subtract`, `multiply`, and `divide` are removed from the bundle. + +### Why ESM Enables This + +CommonJS can't be reliably tree-shaken because imports are dynamic: + +```javascript +// CommonJS - bundler can't know which exports are used +const math = require('./math') +const operation = userInput === 'add' ? math.add : math.subtract +``` + +ESM imports are static declarations, so the bundler knows exactly what's imported: + +```javascript +// ESM - bundler knows only 'add' is used +import { add } from './math.js' +``` + +### Modern Bundlers + +Even with native ESM support in browsers, bundlers remain valuable for: + +- **Tree-shaking** — Remove unused code +- **Code splitting** — Break your app into smaller chunks +- **Minification** — Shrink code for production +- **Transpilation** — Support older browsers +- **Asset handling** — Import CSS, images, JSON + +Popular options: +- **Vite** — Fast development, Rollup-based production builds +- **esbuild** — Extremely fast, great for libraries +- **Rollup** — Best tree-shaking, ideal for libraries +- **Webpack** — Most features, larger projects + +<Note> +For small projects or learning, you can use native ESM in browsers without a bundler. For production apps, bundlers still provide significant benefits. +</Note> + +--- + +## Common Mistakes + +### Mistake #1: Named vs Default Import Confusion + +This is the most common ESM mistake. The syntax looks similar but means different things: + +```javascript +// ───────────────────────────────────────────── +// The module exports this: +export default function Logger() {} +export function format() {} + +// ───────────────────────────────────────────── + +// ❌ WRONG - trying to import default as named +import { Logger } from './logger.js' +// Error: The module doesn't have a named export called 'Logger' + +// ✓ CORRECT - no braces for default +import Logger from './logger.js' + +// ✓ CORRECT - braces for named exports +import { format } from './logger.js' + +// ✓ CORRECT - both together +import Logger, { format } from './logger.js' +``` + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE CURLY BRACE RULE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ export default X → import X from '...' (no braces) │ +│ export { Y } → import { Y } from '...' (braces) │ +│ export { Z as W } → import { W } from '...' (braces) │ +│ │ +│ Default = main thing, you name it │ +│ Named = specific items, names must match │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Mistake #2: Circular Dependencies + +When module A imports module B, and module B imports module A, you can get `undefined` values: + +```javascript +// a.js +import { b } from './b.js' +export const a = 'A' +console.log('In a.js, b is:', b) + +// b.js +import { a } from './a.js' +export const b = 'B' +console.log('In b.js, a is:', a) + +// Running a.js throws: +// ReferenceError: Cannot access 'a' before initialization +// (a.js hasn't finished executing when b.js tries to access 'a') +``` + +**Fix:** Restructure to avoid circular deps, or use functions that defer access until runtime: + +```javascript +// Better: export functions that read values at call time +export function getA() { return a } +``` + +### Mistake #3: Missing File Extensions in Browsers + +```javascript +// ❌ WRONG in browsers +import { helper } from './utils' // 404 error + +// ✓ CORRECT +import { helper } from './utils.js' +``` + +### Mistake #4: Mixing CommonJS and ESM in Node.js + +You can't use `require()` in an ESM file or `import` in a CommonJS file without extra steps: + +```javascript +// ❌ In an ESM file (.mjs or type: module) +const fs = require('fs') // ReferenceError: require is not defined + +// ✓ CORRECT in ESM +import fs from 'fs' +import { readFile } from 'fs/promises' + +// ✓ If you really need require in ESM +import { createRequire } from 'module' +const require = createRequire(import.meta.url) +const legacyModule = require('some-commonjs-package') +``` + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **ESM is JavaScript's official module system** — It's standardized, works in browsers natively, and is the future of JavaScript modules. + +2. **Static structure enables optimization** — Because imports are declarations, not function calls, tools can analyze your code and remove unused exports (tree-shaking). + +3. **Live bindings, not copies** — ESM exports are references to the original variable. Changes in the source module are reflected in importers. CommonJS exports are value copies. + +4. **Use curly braces for named imports, no braces for default** — `import { named }` vs `import defaultExport`. Mixing these up is the #1 beginner mistake. + +5. **Dynamic imports for code splitting** — Use `import()` when you need to load modules conditionally or lazily. It returns a Promise. + +6. **ESM is always strict mode** — No need for `"use strict"`. Variables don't leak to global scope. + +7. **Modules execute once** — No matter how many files import a module, its top-level code runs exactly once. Modules are singletons. + +8. **File extensions are required** — In browsers and Node.js ESM, you must include `.js`. No automatic extension resolution. + +9. **Import maps solve bare specifiers in browsers** — Without a bundler, use import maps to tell browsers where to find packages like `'lodash'`. + +10. **Bundlers still matter** — Even with native ESM support, bundlers provide tree-shaking, minification, and code splitting that improve production performance. +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="What's the fundamental difference between ESM and CommonJS that enables tree-shaking?"> + **Answer:** + + ESM imports are **static** — they must be at the top level with string literals. This means bundlers can analyze the entire dependency graph at build time without running any code. + + CommonJS uses **dynamic** `require()` calls that execute at runtime. The module path can be computed (`require(variable)`), used conditionally, or placed anywhere in code. Bundlers can't know what's actually imported until the code runs. + + ```javascript + // ESM - bundler sees exactly what's imported + import { add } from './math.js' // static, analyzable + + // CommonJS - bundler can't be certain + const op = condition ? 'add' : 'subtract' + const math = require('./math') + math[op](1, 2) // which function is used? Unknown until runtime + ``` + </Accordion> + + <Accordion title="What are 'live bindings' and how do they differ from CommonJS exports?"> + **Answer:** + + In ESM, imported bindings are **live references** to the exported variables. If the source module changes the value, importers see the new value. + + In CommonJS, `module.exports` provides **value copies** at the time of `require()`. Later changes in the source don't affect what was imported. + + ```javascript + // ESM: live binding + // counter.mjs + export let count = 0 + export function increment() { count++ } + + // main.mjs + import { count, increment } from './counter.mjs' + console.log(count) // 0 + increment() + console.log(count) // 1 (live!) + + // CommonJS: copy + // counter.cjs + let count = 0 + module.exports = { count, increment: () => count++ } + + // main.cjs + const { count, increment } = require('./counter.cjs') + console.log(count) // 0 + increment() + console.log(count) // 0 (still - it's a copy) + ``` + </Accordion> + + <Accordion title="When would you use dynamic imports over static imports?"> + **Answer:** + + Use `import()` when you need to: + + 1. **Load modules conditionally** — Based on user action, feature flags, or environment + 2. **Code split** — Load heavy components only when needed (route-based splitting) + 3. **Compute the module path** — The path is determined at runtime + 4. **Load modules in non-module scripts** — `import()` works even in regular scripts + + ```javascript + // Route-based code splitting + async function loadPage(route) { + const page = await import(`./pages/${route}.js`) + return page.default + } + + // Conditional loading + if (userWantsCharts) { + const { Chart } = await import('chart.js') + } + ``` + + Static imports are better when you always need the module — they're faster to analyze and optimize. + </Accordion> + + <Accordion title="Why do browsers require file extensions in imports, but Node.js CommonJS doesn't?"> + **Answer:** + + **Browsers** make HTTP requests for imports. Without an extension, the browser doesn't know what URL to request. It can't try multiple extensions (`.js`, `.mjs`, `/index.js`) because each would be a separate network request. + + **Node.js CommonJS** runs on the local file system where checking multiple file variations is fast. It tries: exact path → `.js` → `.json` → `.node` → `/index.js`, etc. + + **Node.js ESM** chose to require extensions for consistency with browsers and to avoid the ambiguity of the CommonJS resolution algorithm. + + ```javascript + // Browser - must include extension + import { x } from './utils.js' // ✓ + import { x } from './utils' // ❌ 404 + + // Node CommonJS - extension optional + const x = require('./utils') // ✓ finds utils.js + + // Node ESM - extension required + import { x } from './utils.js' // ✓ + import { x } from './utils' // ❌ ERR_MODULE_NOT_FOUND + ``` + </Accordion> + + <Accordion title="What is an import map and when would you use one?"> + **Answer:** + + An import map is a JSON object that tells browsers how to resolve bare module specifiers (like `'lodash'`). It maps package names to URLs. + + ```html + <script type="importmap"> + { + "imports": { + "lodash": "https://cdn.jsdelivr.net/npm/lodash-es/lodash.js", + "lodash/": "https://cdn.jsdelivr.net/npm/lodash-es/" + } + } + </script> + + <script type="module"> + import { debounce } from 'lodash' // works now! + </script> + ``` + + **Use import maps when:** + - Building simple apps without a bundler + - Creating demos or examples + - Learning/prototyping + - You want CDN-based dependencies + + For production apps with many dependencies, bundlers usually provide better optimization. + </Accordion> + + <Accordion title="What happens if two modules import each other (circular dependency)?"> + **Answer:** + + ESM handles circular dependencies, but you can get errors for values that haven't been initialized yet. + + ```javascript + // a.js + import { b } from './b.js' + export const a = 'A' + console.log(b) // 'B' (b.js already ran) + + // b.js + import { a } from './a.js' + export const b = 'B' + console.log(a) // ReferenceError! (a.js hasn't finished) + ``` + + When b.js runs, a.js is still in the middle of executing (it imported b.js), so accessing `a` throws a `ReferenceError: Cannot access 'a' before initialization` because `const` declarations have a temporal dead zone (TDZ). + + **Solutions:** + 1. Restructure to avoid circular dependencies + 2. Move shared code to a third module + 3. Use functions that access values later (not at module load time) + + ```javascript + // Works: function accesses 'a' when called, not when defined + export function getA() { return a } + ``` + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="IIFE, Modules & Namespaces" icon="box" href="/concepts/iife-modules"> + The history of JavaScript modules and foundational patterns + </Card> + <Card title="async/await" icon="clock" href="/concepts/async-await"> + Used with dynamic imports and top-level await + </Card> + <Card title="Scope and Closures" icon="lock" href="/concepts/scope-and-closures"> + How module scope isolates variables + </Card> + <Card title="Design Patterns" icon="shapes" href="/concepts/design-patterns"> + Module pattern and other encapsulation patterns + </Card> +</CardGroup> + +--- + ## Reference <CardGroup cols={2}> - <Card title="JavaScript Modules — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"> - MDN guide to JavaScript modules + <Card title="JavaScript Modules Guide — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"> + Comprehensive guide to using modules in JavaScript + </Card> + <Card title="import statement — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"> + Complete reference for static import syntax + </Card> + <Card title="export statement — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export"> + Complete reference for export syntax </Card> - <Card title="import — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"> - MDN documentation for import statement + <Card title="import() operator — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import"> + Dynamic import syntax and behavior </Card> - <Card title="export — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export"> - MDN documentation for export statement + <Card title="import.meta — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta"> + Module metadata including URL and Node.js properties </Card> - <Card title="Dynamic import() — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import"> - MDN documentation for dynamic imports + <Card title="Import maps — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap"> + Browser support for bare module specifiers </Card> </CardGroup> +--- + ## Articles <CardGroup cols={2}> <Card title="ES Modules: A Cartoon Deep-Dive" icon="newspaper" href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/"> - By Lin Clark — Mozilla Hacks + Lin Clark's illustrated guide explains how ES Modules work under the hood. The best visual explanation of module loading, linking, and evaluation you'll find. </Card> - <Card title="JavaScript Modules" icon="newspaper" href="https://javascript.info/modules-intro"> - By JavaScript.info + <Card title="JavaScript Modules" icon="newspaper" href="https://javascript.info/modules"> + The javascript.info series covers modules comprehensively. Includes interactive examples and exercises to test your understanding. + </Card> + <Card title="Node.js ES Modules Documentation" icon="newspaper" href="https://nodejs.org/api/esm.html"> + The official Node.js documentation for ES Modules. Covers enabling ESM, interoperability with CommonJS, import.meta, and the resolution algorithm. + </Card> + <Card title="ES6 Modules in Depth" icon="newspaper" href="https://ponyfoo.com/articles/es6-modules-in-depth"> + Nicolás Bevacqua's deep dive into module syntax and semantics. Great for understanding the design decisions behind ESM. </Card> </CardGroup> -- [ES Modules in Node.js — Node.js Documentation](https://nodejs.org/api/esm.html) -- [Understanding ES6 Modules — SitePoint](https://www.sitepoint.com/understanding-es6-modules/) -- [ES6 Modules in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-modules-in-depth) -- [Dynamic imports — JavaScript.info](https://javascript.info/modules-dynamic-imports) -- [CommonJS vs ES Modules — LogRocket](https://blog.logrocket.com/commonjs-vs-es-modules-node-js/) -- [Tree Shaking — webpack](https://webpack.js.org/guides/tree-shaking/) +--- ## Videos <CardGroup cols={2}> <Card title="JavaScript ES6 Modules" icon="video" href="https://www.youtube.com/watch?v=cRHQNNcYf6s"> - By Web Dev Simplified + Web Dev Simplified breaks down import/export syntax with clear examples. Perfect for solidifying your understanding of the basics. </Card> <Card title="ES Modules in 100 Seconds" icon="video" href="https://www.youtube.com/watch?v=qgRUr-YUk1Q"> - By Fireship + Fireship's rapid-fire overview of ES Modules. Great for a quick refresher or introduction to the key concepts. + </Card> + <Card title="JavaScript Modules Past & Present" icon="video" href="https://www.youtube.com/watch?v=GQ96b_u7rGc"> + Historical context on how JavaScript modules evolved from IIFEs to CommonJS to ESM. Helps you understand why ESM is designed the way it is. </Card> </CardGroup> - -- [JavaScript Modules in 100 Seconds — Fireship](https://www.youtube.com/watch?v=qgRUr-YUk1Q) -- [ES6 Modules — Traversy Media](https://www.youtube.com/watch?v=_3oSWwapPKQ) -- [Import, Export, and Require in JavaScript — The Coding Train](https://www.youtube.com/watch?v=jN4IM5tp1SE) diff --git a/tests/advanced-topics/es-modules/es-modules.test.js b/tests/advanced-topics/es-modules/es-modules.test.js new file mode 100644 index 00000000..d62bfac8 --- /dev/null +++ b/tests/advanced-topics/es-modules/es-modules.test.js @@ -0,0 +1,1207 @@ +import { describe, it, expect, vi } from 'vitest' + +describe('ES Modules', () => { + // =========================================== + // Part 1: Live Bindings + // =========================================== + + describe('Part 1: Live Bindings', () => { + describe('ESM Live Bindings vs CommonJS Value Copies', () => { + it('should demonstrate CommonJS-style value copy behavior', () => { + // CommonJS exports copies of primitive values at require time + // Simulating: module.exports = { count, increment, getCount } + + function createCommonJSModule() { + let count = 0 + function increment() { count++ } + function getCount() { return count } + + // CommonJS exports a snapshot (copy) of the value + return { count, increment, getCount } + } + + const { count, increment, getCount } = createCommonJSModule() + + expect(count).toBe(0) + increment() + expect(count).toBe(0) // Still 0! It's a copy from export time + expect(getCount()).toBe(1) // Function reads the real internal value + }) + + it('should demonstrate ESM-style live binding behavior', () => { + // ESM exports live references - changes are visible to importers + // Simulating with an object that acts as a module namespace + + function createESMModule() { + const moduleNamespace = { + count: 0, + increment() { + moduleNamespace.count++ + } + } + return moduleNamespace + } + + const mod = createESMModule() + + expect(mod.count).toBe(0) + mod.increment() + expect(mod.count).toBe(1) // Live binding reflects the change! + mod.increment() + expect(mod.count).toBe(2) // Still updating + }) + + it('should show live bindings work with objects', () => { + // Even with objects, ESM bindings are live references + const moduleState = { + user: null, + setUser(u) { moduleState.user = u }, + getUser() { return moduleState.user } + } + + expect(moduleState.user).toBe(null) + + moduleState.setUser({ name: 'Alice' }) + expect(moduleState.user).toEqual({ name: 'Alice' }) // Live! + + moduleState.setUser({ name: 'Bob' }) + expect(moduleState.user).toEqual({ name: 'Bob' }) // Updated! + }) + + it('should demonstrate singleton state via live bindings', () => { + // All importers share the same module state + const sharedModule = (() => { + let state = { count: 0 } + return { + getState: () => state, + increment: () => { state.count++ } + } + })() + + // Simulate two different "importers" + const importer1 = sharedModule + const importer2 = sharedModule + + importer1.increment() + expect(importer1.getState().count).toBe(1) + expect(importer2.getState().count).toBe(1) // Same state! + + importer2.increment() + expect(importer1.getState().count).toBe(2) // Both see the update + expect(importer2.getState().count).toBe(2) + }) + }) + + describe('Why Live Bindings Matter', () => { + it('should enable proper state management across module boundaries', () => { + // Auth module that multiple parts of an app might import + const authModule = (() => { + let currentUser = null + let isAuthenticated = false + + return { + get currentUser() { return currentUser }, + get isAuthenticated() { return isAuthenticated }, + login(user) { + currentUser = user + isAuthenticated = true + }, + logout() { + currentUser = null + isAuthenticated = false + } + } + })() + + // Header component checks auth + expect(authModule.isAuthenticated).toBe(false) + + // Login form logs in + authModule.login({ name: 'Alice', email: 'alice@test.com' }) + + // Header immediately sees the change (live binding) + expect(authModule.isAuthenticated).toBe(true) + expect(authModule.currentUser.name).toBe('Alice') + + // Logout button logs out + authModule.logout() + + // All components see the change + expect(authModule.isAuthenticated).toBe(false) + expect(authModule.currentUser).toBe(null) + }) + }) + }) + + // =========================================== + // Part 2: Read-Only Imports + // =========================================== + + describe('Part 2: Read-Only Imports', () => { + describe('Imported bindings cannot be reassigned', () => { + it('should demonstrate that imports are read-only (simulated with Object.defineProperty)', () => { + // ESM imports are read-only - you can't reassign them + // We simulate this with a frozen/non-writable property + + const moduleExports = {} + Object.defineProperty(moduleExports, 'count', { + value: 0, + writable: false, + enumerable: true + }) + + expect(moduleExports.count).toBe(0) + + // Attempting to reassign throws in strict mode + expect(() => { + 'use strict' + moduleExports.count = 10 + }).toThrow(TypeError) + }) + + it('should show that const-like behavior applies to all imports', () => { + // Even if the source uses `let`, importers can't reassign + const createModule = () => { + let value = 'original' // let in source module + return { + get value() { return value }, + setValue(v) { value = v } // only module can change it + } + } + + const mod = createModule() + + // Importer can read + expect(mod.value).toBe('original') + + // Importer can call methods that modify (module modifies itself) + mod.setValue('updated') + expect(mod.value).toBe('updated') + + // But direct assignment to the binding would fail in real ESM + // import { value } from './mod.js' + // value = 'hack' // TypeError: Assignment to constant variable + }) + + it('should allow modification of imported object properties', () => { + // You can't reassign the import, but you CAN modify object properties + const configModule = { + config: { + theme: 'light', + debug: false + } + } + + // Can't do: config = newObject (would throw) + // But CAN do: config.theme = 'dark' + + configModule.config.theme = 'dark' + expect(configModule.config.theme).toBe('dark') + + configModule.config.debug = true + expect(configModule.config.debug).toBe(true) + }) + }) + }) + + // =========================================== + // Part 3: Circular Dependencies and TDZ + // =========================================== + + describe('Part 3: Circular Dependencies and TDZ', () => { + describe('Temporal Dead Zone (TDZ) with const/let', () => { + it('should throw ReferenceError when accessing const before initialization', () => { + expect(() => { + // This simulates what happens in a circular dependency + // when module B tries to access a const from module A + // before A has finished executing + + const accessBeforeInit = () => { + console.log(value) // Accessing before declaration + const value = 'initialized' + } + accessBeforeInit() + }).toThrow(ReferenceError) + }) + + it('should throw ReferenceError when accessing let before initialization', () => { + expect(() => { + const accessBeforeInit = () => { + console.log(value) // TDZ - ReferenceError + let value = 'initialized' + } + accessBeforeInit() + }).toThrow(ReferenceError) + }) + + it('should NOT throw with var (hoisted with undefined)', () => { + // var is hoisted and initialized to undefined + // This is why old circular dependency examples showed 'undefined' + let result + + const accessVarBeforeInit = () => { + result = value // undefined, not an error + var value = 'initialized' + } + + accessVarBeforeInit() + expect(result).toBe(undefined) // var is hoisted as undefined + }) + }) + + describe('Circular Dependency Patterns', () => { + it('should demonstrate safe circular dependency with deferred access', () => { + // Safe pattern: export functions that access values at call time + + const moduleA = { + value: null, + getValue: () => moduleA.value, + init: () => { moduleA.value = 'A initialized' } + } + + const moduleB = { + value: null, + getValue: () => moduleB.value, + getAValue: () => moduleA.getValue(), // Deferred access + init: () => { moduleB.value = 'B initialized' } + } + + // Simulate circular initialization + // B tries to access A.value before A.init() runs + expect(moduleB.getAValue()).toBe(null) // A not initialized yet + + moduleA.init() + expect(moduleB.getAValue()).toBe('A initialized') // Now it works + + moduleB.init() + expect(moduleB.getValue()).toBe('B initialized') + }) + + it('should show how to restructure to avoid circular deps', () => { + // Instead of A importing B and B importing A, + // create a shared module C that both import + + const sharedModule = { + sharedConfig: { apiUrl: 'https://api.example.com' }, + sharedUtil: (x) => x.toUpperCase() + } + + const moduleA = { + config: sharedModule.sharedConfig, + formatName: (name) => sharedModule.sharedUtil(name) + } + + const moduleB = { + config: sharedModule.sharedConfig, + formatTitle: (title) => sharedModule.sharedUtil(title) + } + + // No circular dependency - both depend on shared module + expect(moduleA.formatName('alice')).toBe('ALICE') + expect(moduleB.formatTitle('hello')).toBe('HELLO') + expect(moduleA.config).toBe(moduleB.config) // Same reference + }) + }) + }) + + // =========================================== + // Part 4: Module Singleton Behavior + // =========================================== + + describe('Part 4: Module Singleton Behavior', () => { + describe('Module code executes exactly once', () => { + it('should only run initialization code once', () => { + let initCount = 0 + + // Simulating a module that runs initialization code + const createSingletonModule = (() => { + initCount++ // This runs once when module loads + + return { + getValue: () => 'module value', + getInitCount: () => initCount + } + })() + + // Multiple "imports" all get the same instance + const import1 = createSingletonModule + const import2 = createSingletonModule + const import3 = createSingletonModule + + expect(initCount).toBe(1) // Only ran once + expect(import1).toBe(import2) + expect(import2).toBe(import3) + }) + + it('should share state across all importers', () => { + const cacheModule = (() => { + const cache = new Map() + console.log('Cache module initialized') // Runs once + + return { + set: (key, value) => cache.set(key, value), + get: (key) => cache.get(key), + size: () => cache.size + } + })() + + // Different "files" using the cache + // file1.js + cacheModule.set('user', { id: 1 }) + + // file2.js - sees the same cache + expect(cacheModule.get('user')).toEqual({ id: 1 }) + + // file3.js - also same cache + cacheModule.set('token', 'abc123') + + expect(cacheModule.size()).toBe(2) + }) + + it('should maintain singleton even with different import styles', () => { + // Whether you use named imports, default import, or namespace import, + // you get the same module instance + + const mathModule = (() => { + const moduleId = Math.random() // Generated once + + return { + moduleId, + PI: 3.14159, + add: (a, b) => a + b, + default: function Calculator() { + this.result = 0 + } + } + })() + + // import { add, PI } from './math.js' + const { add, PI } = mathModule + + // import * as math from './math.js' + const math = mathModule + + // import Calculator from './math.js' + const Calculator = mathModule.default + + // All reference the same module + expect(math.PI).toBe(PI) + expect(math.add).toBe(add) + expect(math.moduleId).toBe(mathModule.moduleId) + }) + }) + }) + + // =========================================== + // Part 5: Dynamic Imports + // =========================================== + + describe('Part 5: Dynamic Imports', () => { + describe('import() returns a Promise', () => { + it('should resolve to module namespace object', async () => { + // Simulating dynamic import behavior + const mockModule = { + namedExport: 'named value', + anotherExport: 42, + default: function DefaultExport() { return 'default' } + } + + const dynamicImport = () => Promise.resolve(mockModule) + + const module = await dynamicImport() + + expect(module.namedExport).toBe('named value') + expect(module.anotherExport).toBe(42) + expect(module.default()).toBe('default') + }) + + it('should allow destructuring named exports', async () => { + const mockDateModule = { + formatDate: (d) => d.toISOString(), + parseDate: (s) => new Date(s) + } + + const dynamicImport = () => Promise.resolve(mockDateModule) + + // Destructure directly from await + const { formatDate, parseDate } = await dynamicImport() + + expect(typeof formatDate).toBe('function') + expect(typeof parseDate).toBe('function') + }) + + it('should access default export via .default property', async () => { + const mockModule = { + default: class Logger { + log(msg) { return `[LOG] ${msg}` } + } + } + + const dynamicImport = () => Promise.resolve(mockModule) + + // Method 1: Destructure with rename + const { default: Logger } = await dynamicImport() + const logger1 = new Logger() + expect(logger1.log('test')).toBe('[LOG] test') + + // Method 2: Access .default property + const module = await dynamicImport() + const Logger2 = module.default + const logger2 = new Logger2() + expect(logger2.log('hello')).toBe('[LOG] hello') + }) + }) + + describe('Dynamic Import Use Cases', () => { + it('should enable conditional module loading', async () => { + const modules = { + light: { theme: 'light', bg: '#fff', text: '#000' }, + dark: { theme: 'dark', bg: '#000', text: '#fff' } + } + + async function loadTheme(themeName) { + // Simulating: const theme = await import(`./themes/${themeName}.js`) + return Promise.resolve(modules[themeName]) + } + + const lightTheme = await loadTheme('light') + expect(lightTheme.bg).toBe('#fff') + + const darkTheme = await loadTheme('dark') + expect(darkTheme.bg).toBe('#000') + }) + + it('should enable route-based code splitting', async () => { + const pageModules = { + home: { default: () => 'Home Page Content' }, + about: { default: () => 'About Page Content' }, + contact: { default: () => 'Contact Page Content' } + } + + async function loadPage(pageName) { + // Simulating route-based dynamic import + const pageModule = await Promise.resolve(pageModules[pageName]) + return pageModule.default + } + + const HomePage = await loadPage('home') + expect(HomePage()).toBe('Home Page Content') + + const AboutPage = await loadPage('about') + expect(AboutPage()).toBe('About Page Content') + }) + + it('should enable lazy loading of heavy features', async () => { + let chartLibraryLoaded = false + + const heavyChartLibrary = { + Chart: class { + constructor(data) { + chartLibraryLoaded = true + this.data = data + } + render() { + return `Chart with ${this.data.length} points` + } + } + } + + async function showChart(data) { + // Only load chart library when actually needed + const { Chart } = await Promise.resolve(heavyChartLibrary) + const chart = new Chart(data) + return chart.render() + } + + expect(chartLibraryLoaded).toBe(false) // Not loaded yet + + const result = await showChart([1, 2, 3, 4, 5]) + + expect(chartLibraryLoaded).toBe(true) // Now loaded + expect(result).toBe('Chart with 5 points') + }) + + it('should work with Promise.all for parallel loading', async () => { + const modules = { + header: { render: () => '<header>Header</header>' }, + footer: { render: () => '<footer>Footer</footer>' }, + sidebar: { render: () => '<aside>Sidebar</aside>' } + } + + async function loadComponents() { + const [header, footer, sidebar] = await Promise.all([ + Promise.resolve(modules.header), + Promise.resolve(modules.footer), + Promise.resolve(modules.sidebar) + ]) + + return { header, footer, sidebar } + } + + const components = await loadComponents() + + expect(components.header.render()).toBe('<header>Header</header>') + expect(components.footer.render()).toBe('<footer>Footer</footer>') + expect(components.sidebar.render()).toBe('<aside>Sidebar</aside>') + }) + }) + + describe('Error Handling with Dynamic Imports', () => { + it('should handle module not found errors', async () => { + const loadModule = (name) => { + if (name === 'nonexistent') { + return Promise.reject(new Error('Module not found')) + } + return Promise.resolve({ value: 'found' }) + } + + // Successful load + const mod = await loadModule('existing') + expect(mod.value).toBe('found') + + // Failed load + await expect(loadModule('nonexistent')).rejects.toThrow('Module not found') + }) + + it('should use try-catch for error handling', async () => { + const loadModule = () => Promise.reject(new Error('Network error')) + + let errorHandled = false + let fallbackUsed = false + + try { + await loadModule() + } catch (error) { + errorHandled = true + // Use fallback + fallbackUsed = true + } + + expect(errorHandled).toBe(true) + expect(fallbackUsed).toBe(true) + }) + }) + }) + + // =========================================== + // Part 6: Export and Import Syntax Variations + // =========================================== + + describe('Part 6: Export and Import Syntax Variations', () => { + describe('Named Exports', () => { + it('should support inline named exports', () => { + // export const PI = 3.14159 + // export function square(x) { return x * x } + // export class Circle { } + + const moduleExports = {} + + moduleExports.PI = 3.14159 + moduleExports.square = function(x) { return x * x } + moduleExports.Circle = class { + constructor(radius) { this.radius = radius } + area() { return moduleExports.PI * this.radius ** 2 } + } + + expect(moduleExports.PI).toBe(3.14159) + expect(moduleExports.square(4)).toBe(16) + + const circle = new moduleExports.Circle(5) + expect(circle.area()).toBeCloseTo(78.54, 1) + }) + + it('should support grouped exports at bottom', () => { + // const PI = 3.14159 + // function square(x) { return x * x } + // export { PI, square } + + const PI = 3.14159 + function square(x) { return x * x } + + const exports = { PI, square } + + expect(exports.PI).toBe(3.14159) + expect(exports.square(5)).toBe(25) + }) + + it('should support renaming exports with as', () => { + // function internalHelper() { } + // export { internalHelper as helper } + + function internalHelper() { return 'helped' } + function _privateUtil() { return 'util' } + + const exports = { + helper: internalHelper, + publicUtil: _privateUtil + } + + expect(exports.helper()).toBe('helped') + expect(exports.publicUtil()).toBe('util') + expect(exports.internalHelper).toBe(undefined) // Not exported under original name + }) + }) + + describe('Default Exports', () => { + it('should support default export of function', () => { + // export default function greet(name) { } + + function greet(name) { return `Hello, ${name}!` } + const moduleExports = { default: greet } + + // import greet from './greet.js' + const importedGreet = moduleExports.default + expect(importedGreet('World')).toBe('Hello, World!') + }) + + it('should support default export of class', () => { + // export default class User { } + + class User { + constructor(name) { this.name = name } + greet() { return `Hi, I'm ${this.name}` } + } + + const moduleExports = { default: User } + + // import User from './user.js' + const ImportedUser = moduleExports.default + const user = new ImportedUser('Alice') + expect(user.greet()).toBe("Hi, I'm Alice") + }) + + it('should support default export of object/value', () => { + // export default { name: 'Config', version: '1.0' } + + const moduleExports = { + default: { + name: 'Config', + version: '1.0.0', + debug: false + } + } + + // import config from './config.js' + const config = moduleExports.default + expect(config.name).toBe('Config') + expect(config.version).toBe('1.0.0') + }) + }) + + describe('Mixed Named and Default Exports', () => { + it('should support both default and named exports', () => { + // export default function React() { } + // export function useState() { } + // export function useEffect() { } + + function React() { return 'React' } + function useState(initial) { return [initial, () => {}] } + function useEffect(fn) { fn() } + + const moduleExports = { + default: React, + useState, + useEffect + } + + // import React, { useState, useEffect } from 'react' + const ImportedReact = moduleExports.default + const { useState: importedUseState, useEffect: importedUseEffect } = moduleExports + + expect(ImportedReact()).toBe('React') + expect(importedUseState(0)).toEqual([0, expect.any(Function)]) + }) + }) + + describe('Import Variations', () => { + it('should support named imports with exact names', () => { + // import { PI, square } from './math.js' + + const mathModule = { + PI: 3.14159, + square: (x) => x * x, + cube: (x) => x * x * x + } + + const { PI, square } = mathModule + + expect(PI).toBe(3.14159) + expect(square(3)).toBe(9) + }) + + it('should support renaming imports with as', () => { + // import { formatDate as formatDateISO } from './date.js' + + const dateModule = { + formatDate: (d) => d.toISOString() + } + + const dateUSModule = { + formatDate: (d) => d.toLocaleDateString('en-US') + } + + const { formatDate: formatDateISO } = dateModule + const { formatDate: formatDateUS } = dateUSModule + + const date = new Date('2024-01-15') + expect(formatDateISO(date)).toContain('2024-01-15') + expect(typeof formatDateUS(date)).toBe('string') + }) + + it('should support namespace imports (import * as)', () => { + // import * as math from './math.js' + + const mathModule = { + PI: 3.14159, + E: 2.71828, + add: (a, b) => a + b, + multiply: (a, b) => a * b, + default: { name: 'Math Utils' } + } + + // Namespace import gets all exports as properties + const math = mathModule + + expect(math.PI).toBe(3.14159) + expect(math.E).toBe(2.71828) + expect(math.add(2, 3)).toBe(5) + expect(math.multiply(4, 5)).toBe(20) + expect(math.default.name).toBe('Math Utils') // default is also accessible + }) + + it('should support side-effect only imports', () => { + // import './polyfills.js' + // import './analytics.js' + + let polyfillsLoaded = false + let analyticsInitialized = false + + // Simulating side-effect modules + const loadPolyfills = () => { polyfillsLoaded = true } + const initAnalytics = () => { analyticsInitialized = true } + + loadPolyfills() + initAnalytics() + + expect(polyfillsLoaded).toBe(true) + expect(analyticsInitialized).toBe(true) + }) + }) + + describe('Re-exports (Barrel Files)', () => { + it('should support re-exporting named exports', () => { + // date.js + const dateModule = { + formatDate: (d) => d.toISOString(), + parseDate: (s) => new Date(s) + } + + // currency.js + const currencyModule = { + formatCurrency: (n) => `$${n.toFixed(2)}` + } + + // utils/index.js (barrel file) + // export { formatDate, parseDate } from './date.js' + // export { formatCurrency } from './currency.js' + const utilsBarrel = { + ...dateModule, + ...currencyModule + } + + // Consumer imports from barrel + const { formatDate, formatCurrency } = utilsBarrel + + expect(formatCurrency(19.99)).toBe('$19.99') + expect(typeof formatDate(new Date())).toBe('string') + }) + + it('should support re-exporting default as named', () => { + // logger.js + // export default class Logger { } + const loggerModule = { + default: class Logger { + log(msg) { return msg } + } + } + + // utils/index.js + // export { default as Logger } from './logger.js' + const utilsBarrel = { + Logger: loggerModule.default + } + + const { Logger } = utilsBarrel + const logger = new Logger() + expect(logger.log('test')).toBe('test') + }) + + it('should support re-exporting all (export *)', () => { + // math.js exports multiple functions + const mathModule = { + add: (a, b) => a + b, + subtract: (a, b) => a - b, + multiply: (a, b) => a * b + } + + // utils/index.js + // export * from './math.js' + const utilsBarrel = { ...mathModule } + + expect(utilsBarrel.add(1, 2)).toBe(3) + expect(utilsBarrel.subtract(5, 3)).toBe(2) + expect(utilsBarrel.multiply(4, 5)).toBe(20) + }) + }) + }) + + // =========================================== + // Part 7: Module Characteristics + // =========================================== + + describe('Part 7: Module Characteristics', () => { + describe('Automatic Strict Mode', () => { + it('should demonstrate strict mode behaviors', () => { + // ES Modules are always in strict mode + + // Assigning to undeclared variable throws + expect(() => { + 'use strict' + undeclaredVar = 'oops' + }).toThrow(ReferenceError) + }) + + it('should prevent duplicate parameters in strict mode', () => { + // In strict mode, duplicate parameter names are syntax errors + // This would be caught at parse time in a real module: + // function f(a, a) { } // SyntaxError + + // We can test that strict mode is enforced + expect(() => { + 'use strict' + eval('function f(a, a) {}') + }).toThrow(SyntaxError) + }) + + it('should make this undefined in functions called without context', () => { + 'use strict' + + function getThis() { + return this + } + + expect(getThis()).toBe(undefined) + }) + }) + + describe('Module Scope (not global)', () => { + it('should keep module variables private by default', () => { + // In a module, top-level variables are scoped to the module + const createModule = () => { + const privateValue = 'secret' + const publicValue = 'visible' + + return { + publicValue, + getPrivate: () => privateValue + } + } + + const mod = createModule() + + expect(mod.publicValue).toBe('visible') + expect(mod.getPrivate()).toBe('secret') + expect(mod.privateValue).toBe(undefined) // Not exposed + }) + + it('should not leak var to global scope in modules', () => { + // In regular scripts, var leaks to window + // In modules, var is module-scoped + + const createModule = () => { + var moduleVar = 'module scoped' + return { getVar: () => moduleVar } + } + + const mod = createModule() + expect(mod.getVar()).toBe('module scoped') + expect(typeof moduleVar).toBe('undefined') // Not in outer scope + }) + }) + + describe('Top-level this is undefined', () => { + it('should have undefined this at module top level', () => { + // In ES Modules, top-level this is undefined + // (not window or global) + + // Regular function in strict mode has undefined this when called without context + function getThisInStrictMode() { + 'use strict' + return this + } + + // Called without context, this is undefined (like module top-level) + expect(getThisInStrictMode()).toBe(undefined) + + // Arrow functions capture this from enclosing scope + // In a real ES module, this would be undefined at the top level + const arrowThis = (() => this)() + + // Note: In test environment, the outer `this` may not be undefined + // but in a real ES module file, top-level `this` IS undefined + // This test demonstrates the concept via strict mode function + }) + }) + + describe('Import Hoisting', () => { + it('should demonstrate that imports are hoisted', () => { + // In ES Modules, import declarations are hoisted + // The imported bindings are available throughout the module + + // This would work in a real module: + // console.log(helper()) // Works! Imports are hoisted + // import { helper } from './utils.js' + + // We simulate by showing the concept + const moduleCode = () => { + // Imports are processed first, before any code runs + const imports = { helper: () => 'helped' } + + // Then code runs, with imports already available + const result = imports.helper() // Can use before "import line" + return result + } + + expect(moduleCode()).toBe('helped') + }) + }) + }) + + // =========================================== + // Part 8: Common Mistakes + // =========================================== + + describe('Part 8: Common Mistakes', () => { + describe('Mistake #1: Named vs Default Import Confusion', () => { + it('should demonstrate the difference between named and default imports', () => { + const moduleWithBoth = { + default: function Logger() { return 'default' }, + format: () => 'named format' + } + + // CORRECT: No braces for default + // import Logger from './logger.js' + const Logger = moduleWithBoth.default + + // CORRECT: Braces for named + // import { format } from './logger.js' + const { format } = moduleWithBoth + + expect(Logger()).toBe('default') + expect(format()).toBe('named format') + + // WRONG would be: + // import { Logger } from './logger.js' // Error: no named export 'Logger' + expect(moduleWithBoth.Logger).toBe(undefined) // Not a named export! + }) + + it('should show the curly brace rule', () => { + /* + export default X → import X from '...' (no braces) + export { Y } → import { Y } from '...' (braces) + export { Z as W } → import { W } from '...' (braces) + */ + + const modA = { default: 'X value' } + const modB = { Y: 'Y value' } + const modC = { W: 'Z exported as W' } + + const X = modA.default // No braces + const { Y } = modB // Braces + const { W } = modC // Braces (renamed) + + expect(X).toBe('X value') + expect(Y).toBe('Y value') + expect(W).toBe('Z exported as W') + }) + }) + + describe('Mistake #2: Missing File Extensions', () => { + it('should demonstrate that extensions are required', () => { + // In browsers and Node.js ESM, file extensions are required + + const validPaths = [ + './utils.js', // Correct + './components/Button.js', // Correct + '../helpers.mjs', // Correct + ] + + const invalidPaths = [ + './utils', // Missing extension - 404 in browser + './components/Button', // Missing extension - ERR_MODULE_NOT_FOUND + ] + + // All valid paths have extensions + validPaths.forEach(path => { + expect(path).toMatch(/\.(js|mjs|cjs)$/) + }) + + // Invalid paths lack extensions + invalidPaths.forEach(path => { + expect(path).not.toMatch(/\.(js|mjs|cjs)$/) + }) + }) + }) + + describe('Mistake #3: Using require in ESM', () => { + it('should show that require is not available in ESM', () => { + // In ESM files, require() is not defined + // This would throw: ReferenceError: require is not defined + + const esmEnvironment = { + require: undefined, // Not available + import: () => Promise.resolve({}), // Use this instead + importMeta: { url: 'file:///path/to/module.js' } + } + + expect(esmEnvironment.require).toBe(undefined) + expect(typeof esmEnvironment.import).toBe('function') + }) + + it('should show createRequire workaround', () => { + // If you need require in ESM (for CommonJS packages): + // import { createRequire } from 'module' + // const require = createRequire(import.meta.url) + + // Simulating createRequire + const createRequire = (url) => { + return (moduleName) => { + // This would actually load CommonJS modules + return { loaded: moduleName, from: url } + } + } + + const require = createRequire('file:///app/main.js') + const legacyModule = require('some-commonjs-package') + + expect(legacyModule.loaded).toBe('some-commonjs-package') + }) + }) + }) + + // =========================================== + // Part 9: Test Your Knowledge (from docs) + // =========================================== + + describe('Part 9: Test Your Knowledge', () => { + describe('Q1: Static vs Dynamic - Why tree-shaking works', () => { + it('should show ESM imports are statically analyzable', () => { + // ESM imports are declarations, not function calls + // Bundlers can see exactly what's imported without running code + + const moduleExports = { + add: (a, b) => a + b, + subtract: (a, b) => a - b, + multiply: (a, b) => a * b, + divide: (a, b) => a / b + } + + // Static import - bundler knows only 'add' is used + const { add } = moduleExports + + // The other functions can be tree-shaken out + const usedExports = ['add'] + const unusedExports = ['subtract', 'multiply', 'divide'] + + expect(usedExports).toContain('add') + expect(unusedExports).not.toContain('add') + }) + }) + + describe('Q2: Live bindings vs copies', () => { + it('should demonstrate the key difference', () => { + // ESM: live binding (reference) + const esmModule = { count: 0, increment() { this.count++ } } + + expect(esmModule.count).toBe(0) + esmModule.increment() + expect(esmModule.count).toBe(1) // Live - sees the change + + // CommonJS simulation: value copy + let cjsCount = 0 + const cjsExport = { + count: cjsCount, // Copy at export time + increment() { cjsCount++ } + } + + expect(cjsExport.count).toBe(0) + cjsExport.increment() + expect(cjsExport.count).toBe(0) // Still 0 - it's a copy + }) + }) + + describe('Q3: When to use dynamic imports', () => { + it('should use dynamic imports for conditional loading', async () => { + const features = { + charts: { render: () => 'chart' }, + maps: { render: () => 'map' } + } + + async function loadFeature(name) { + // Only loads when called, not at module load time + return Promise.resolve(features[name]) + } + + // Feature loaded on demand + const charts = await loadFeature('charts') + expect(charts.render()).toBe('chart') + }) + }) + + describe('Q4: Why extensions are required', () => { + it('should explain browser vs Node resolution', () => { + // Browsers make HTTP requests - can't try multiple extensions + // Node ESM matches browser behavior for consistency + + const browserRequest = (path) => { + // Browser requests exactly what you ask for + // Check for common JS extensions + const hasExtension = /\.(js|mjs|cjs|json)$/.test(path) + if (!hasExtension) { + return { status: 404, error: 'Not Found' } + } + return { status: 200, content: 'module code' } + } + + expect(browserRequest('./utils').status).toBe(404) + expect(browserRequest('./utils.js').status).toBe(200) + expect(browserRequest('./module.mjs').status).toBe(200) + }) + }) + + describe('Q5: What happens with circular dependencies', () => { + it('should show TDZ error with const/let', () => { + // With const/let, accessing before init throws ReferenceError + expect(() => { + const fn = () => { + console.log(x) // TDZ + const x = 1 + } + fn() + }).toThrow(ReferenceError) + }) + + it('should show deferred access pattern works', () => { + const moduleA = { value: null } + const moduleB = { + getValue: () => moduleA.value // Deferred - reads at call time + } + + expect(moduleB.getValue()).toBe(null) // A not initialized + + moduleA.value = 'initialized' + expect(moduleB.getValue()).toBe('initialized') // Works now + }) + }) + }) +}) From af3c2a1d1ba2d60224670407921198a3bc49b8ae Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 17:05:07 -0300 Subject: [PATCH 120/128] feat: add comprehensive Data Structures concept page with tests - Cover built-in structures: Array, Object, Map, Set, WeakMap, WeakSet - Implement Stack, Queue, Linked List, BST, and Graph with BFS/DFS - Include ASCII diagrams for visual explanations - Add time complexity tables and trade-off comparisons - Cover common interview questions (Two Sum, Valid Parentheses, etc.) - Add 59 tests covering all data structures and edge cases - Fix indexOf comment accuracy after unshift operation - Clarify Object property order behavior in ES2015+ --- docs/concepts/data-structures.mdx | 1269 ++++++++++++++++- .../data-structures/data-structures.test.js | 1054 ++++++++++++++ 2 files changed, 2294 insertions(+), 29 deletions(-) create mode 100644 tests/advanced-topics/data-structures/data-structures.test.js diff --git a/docs/concepts/data-structures.mdx b/docs/concepts/data-structures.mdx index 0aff8db1..33d2147d 100644 --- a/docs/concepts/data-structures.mdx +++ b/docs/concepts/data-structures.mdx @@ -1,47 +1,1258 @@ --- -title: "Data Structures: Organizing Data in JavaScript" -sidebarTitle: "Data Structures: Organizing Data" -description: "Learn data structures in JavaScript — arrays, objects, stacks, queues, linked lists, trees, and graphs. Understand how to organize and access data efficiently." +title: "Data Structures: Organizing and Storing Data in JavaScript" +sidebarTitle: "Data Structures: Organizing and Storing Data" +description: "Learn JavaScript data structures from built-in Arrays, Objects, Maps, and Sets to implementing Stacks, Queues, and Linked Lists. Understand when to use each structure." --- -## Overview +Why does finding an item in an array take longer as it grows? Why can you look up an object property instantly regardless of how many properties it has? The answer lies in **data structures**. -Data structures are ways of organizing and storing data so that it can be accessed and modified efficiently. JavaScript provides built-in data structures like Arrays and Objects, as well as ES6 additions like Map and Set. Understanding data structures is crucial for writing efficient algorithms. +```javascript +// Array: searching gets slower as the array grows +const users = ['alice', 'bob', 'charlie', /* ...thousands more */] +users.includes('zara') // Has to check every element - O(n) -## Articles +// Object: lookup is instant regardless of size +const userMap = { alice: 1, bob: 2, charlie: 3, /* ...thousands more */ } +userMap['zara'] // Direct access - O(1) +``` + +A **data structure** is a way of organizing data so it can be used efficiently. The right structure makes your code faster and cleaner. The wrong one can make simple operations painfully slow. + +<Info> +**What you'll learn in this guide:** +- JavaScript's built-in structures: Array, Object, Map, Set, WeakMap, WeakSet +- When to use each built-in structure +- How to implement: Stack, Queue, Linked List, Binary Search Tree +- Choosing the right data structure for the job +- Common interview questions and patterns +</Info> + +<Warning> +**Prerequisites:** This guide shows time complexity (like O(1) and O(n)) for operations. If you're not familiar with Big O notation, check out our [Algorithms & Big O guide](/concepts/algorithms-big-o) first. We also use [classes](/concepts/factories-classes) for implementations. +</Warning> + +--- + +## What Are Data Structures? + +Think of data structures like different ways to organize a library. You could: + +- **Stack books on a table** — Easy to add/remove from the top, but finding a specific book means digging through the pile +- **Line them up on a shelf** — Easy to browse in order, but adding a book in the middle means shifting everything +- **Organize by category with an index** — Finding any book is fast, but you need to maintain the index + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DATA STRUCTURE TRADE-OFFS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ARRAY OBJECT/MAP LINKED LIST │ +│ ┌─┬─┬─┬─┬─┐ ┌────────────┐ ┌───┐ ┌───┐ │ +│ │0│1│2│3│4│ │ key: value │ │ A │──►│ B │──► │ +│ └─┴─┴─┴─┴─┘ │ key: value │ └───┘ └───┘ │ +│ └────────────┘ │ +│ ✓ Fast index access ✓ Fast key lookup ✓ Fast insert/delete │ +│ ✓ Ordered ✓ Flexible keys ✗ Slow search │ +│ ✗ Slow insert in middle ✗ No order (Object) ✗ No index access │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +Every data structure has trade-offs. Your job is to pick the one that makes your most frequent operations fast. + +--- + +## JavaScript's Built-in Data Structures + +JavaScript gives you several data structures out of the box. Let's look at each one. + +### Arrays + +An **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** is an ordered collection of values, accessed by numeric index. It's the most common data structure in JavaScript. + +```javascript +const fruits = ['apple', 'banana', 'cherry'] + +// Access by index - O(1) +fruits[0] // 'apple' + +// Add to end - O(1) +fruits.push('date') // ['apple', 'banana', 'cherry', 'date'] + +// Remove from end - O(1) +fruits.pop() // 'date' + +// Add to beginning - O(n) - shifts all elements! +fruits.unshift('apricot') // ['apricot', 'apple', 'banana', 'cherry'] + +// Search - O(n) +fruits.indexOf('banana') // 3 +fruits.includes('mango') // false +``` + +**Time Complexity:** + +| Operation | Method | Complexity | Why | +|-----------|--------|------------|-----| +| Access by index | `arr[i]` | O(1) | Direct memory access | +| Add/remove at end | `push()`, `pop()` | O(1) | No shifting needed | +| Add/remove at start | `unshift()`, `shift()` | O(n) | Must shift all elements | +| Search | `indexOf()`, `includes()` | O(n) | Must check each element | +| Insert in middle | `splice()` | O(n) | Must shift elements after | + +**When to use Arrays:** +- You need ordered data +- You access elements by position +- You mostly add/remove from the end +- You need to iterate over all elements + +--- + +### Objects + +An **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** stores key-value pairs where keys are strings or Symbols. It's JavaScript's fundamental way to group related data. + +```javascript +const user = { + name: 'Alice', + age: 30, + email: 'alice@example.com' +} + +// Access - O(1) +user.name // 'Alice' +user['age'] // 30 + +// Add/Update - O(1) +user.role = 'admin' + +// Delete - O(1) +delete user.email + +// Check if key exists - O(1) +'name' in user // true +user.hasOwnProperty('name') // true +``` + +**Limitations of Objects:** +- Keys are converted to strings (numbers become "1", "2", etc.) +- Objects have a prototype chain (inherited properties) +- No built-in `.size` property +- Property order is preserved in ES2015+, but with specific rules: integer keys are sorted numerically first, then string keys appear in insertion order + +**When to use Objects:** +- Storing entity data (user profiles, settings) +- When keys are known strings +- Configuration objects +- JSON data + +--- + +### Map + +A **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** is like an Object but with superpowers: keys can be *any* type, it maintains insertion order, and has a `.size` property. + +```javascript +const map = new Map() + +// Keys can be ANY type +map.set('string', 'works') +map.set(123, 'number key') +map.set({ id: 1 }, 'object key') +map.set(true, 'boolean key') + +// Access - O(1) +map.get('string') // 'works' +map.get(123) // 'number key' + +// Size is built-in +map.size // 4 + +// Check existence - O(1) +map.has('string') // true + +// Delete - O(1) +map.delete(123) + +// Iteration (maintains insertion order) +for (const [key, value] of map) { + console.log(key, value) +} +``` + +**Map vs Object:** + +| Feature | Map | Object | +|---------|-----|--------| +| Key types | Any | String or Symbol | +| Order | Guaranteed insertion order | Preserved (integer keys sorted first) | +| Size | `map.size` | `Object.keys(obj).length` | +| Iteration | Directly iterable | Need `Object.keys()` | +| Performance | Better for frequent add/delete | Better for static data | +| Prototype | None | Has prototype chain | + +**When to use Map:** +- Keys aren't strings (objects, functions, etc.) +- You need to know the size frequently +- You add/delete keys often +- Order matters + +```javascript +// Common use: counting occurrences +function countWords(text) { + const words = text.toLowerCase().split(/\s+/) + const counts = new Map() + + for (const word of words) { + counts.set(word, (counts.get(word) || 0) + 1) + } + + return counts +} + +countWords('the cat and the dog') +// Map { 'the' => 2, 'cat' => 1, 'and' => 1, 'dog' => 1 } +``` + +--- + +### Set + +A **[Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)** stores unique values. Duplicates are automatically ignored. + +```javascript +const set = new Set() + +// Add values - O(1) +set.add(1) +set.add(2) +set.add(2) // Ignored - already exists +set.add('hello') + +set.size // 3 (not 4!) + +// Check existence - O(1) +set.has(2) // true + +// Delete - O(1) +set.delete(1) + +// Iteration +for (const value of set) { + console.log(value) +} +``` + +**The classic use case: removing duplicates** + +```javascript +const numbers = [1, 2, 2, 3, 3, 3, 4] +const unique = [...new Set(numbers)] // [1, 2, 3, 4] +``` + +**Set Operations (ES2024+):** + +<Note> +These methods are part of ES2024 and are supported in all modern browsers as of late 2024. Check [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#browser_compatibility) if you need to support older browsers. +</Note> + +```javascript +const a = new Set([1, 2, 3]) +const b = new Set([2, 3, 4]) + +// Union: elements in either set +a.union(b) // Set {1, 2, 3, 4} + +// Intersection: elements in both sets +a.intersection(b) // Set {2, 3} + +// Difference: elements in a but not in b +a.difference(b) // Set {1} + +// Symmetric difference: elements in either but not both +a.symmetricDifference(b) // Set {1, 4} + +// Subset check +new Set([1, 2]).isSubsetOf(a) // true +``` + +**When to use Set:** +- You need unique values +- You check "does this exist?" frequently +- Removing duplicates from arrays +- Tracking visited items + +--- + +### WeakMap and WeakSet + +<Accordion title="WeakMap and WeakSet (Advanced)"> + +**[WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)** and **[WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)** are special versions where keys (WeakMap) or values (WeakSet) are held "weakly." This means they don't prevent garbage collection. + +**WeakMap:** +- Keys must be objects (or non-registered symbols) +- If the key object has no other references, it gets garbage collected +- Not iterable (no `.keys()`, `.values()`, `.forEach()`) +- No `.size` property + +```javascript +const privateData = new WeakMap() + +class User { + constructor(name, password) { + this.name = name + // Store private data that can't be accessed externally + privateData.set(this, { password }) + } + + checkPassword(input) { + return privateData.get(this).password === input + } +} + +const user = new User('Alice', 'secret123') +user.name // 'Alice' +user.password // undefined - it's private! +user.checkPassword('secret123') // true + +// When 'user' is garbage collected, the private data is too +``` + +**WeakSet:** +- Values must be objects +- Useful for tracking which objects have been processed + +```javascript +const processed = new WeakSet() + +function processOnce(obj) { + if (processed.has(obj)) { + return // Already processed + } + + processed.add(obj) + // Do expensive processing... +} +``` + +**When to use Weak versions:** +- Caching computed data for objects +- Storing private instance data +- Tracking objects without preventing garbage collection + +</Accordion> + +--- + +## Implementing Common Data Structures + +JavaScript doesn't have built-in Stack, Queue, or Linked List classes, but they're easy to implement and important to understand. + +### Stack (LIFO) + +A **Stack** follows Last-In-First-Out: the last item added is the first removed. Think of a stack of plates. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ STACK (LIFO) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ push(4) pop() │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌───┐ ┌───┐ │ +│ │ 4 │ ◄─ top │ │ │ +│ ├───┤ ├───┤ │ +│ │ 3 │ │ 3 │ ◄─ top │ +│ ├───┤ ├───┤ │ +│ │ 2 │ │ 2 │ │ +│ ├───┤ ├───┤ │ +│ │ 1 │ │ 1 │ │ +│ └───┘ └───┘ │ +│ │ +│ "Last in, first out" - like a stack of plates │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**Real-world uses:** +- Browser history (back button) +- Undo/redo functionality +- Function call stack +- Expression evaluation (parentheses matching) + +**Implementation:** + +```javascript +class Stack { + constructor() { + this.items = [] + } + + push(item) { + this.items.push(item) + } + + pop() { + return this.items.pop() + } + + peek() { + return this.items[this.items.length - 1] + } + + isEmpty() { + return this.items.length === 0 + } + + size() { + return this.items.length + } +} + +// Usage +const stack = new Stack() +stack.push(1) +stack.push(2) +stack.push(3) +stack.peek() // 3 (look at top without removing) +stack.pop() // 3 +stack.pop() // 2 +stack.size() // 1 +``` + +**Time Complexity:** All operations are O(1). + +--- + +### Queue (FIFO) + +A **Queue** follows First-In-First-Out: the first item added is the first removed. Think of a line at a store. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ QUEUE (FIFO) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ enqueue(4) dequeue() │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌───┬───┬───┬───┐ ┌───┬───┬───┐ │ +│ │ 4 │ 3 │ 2 │ 1 │ ───────────────────────► │ 4 │ 3 │ 2 │ │ +│ └───┴───┴───┴───┘ └───┴───┴───┘ │ +│ back front back front │ +│ │ +│ "First in, first out" - like a line at a store │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**Real-world uses:** +- Task scheduling +- Print queue +- BFS graph traversal +- Message queues + +**Implementation:** + +```javascript +class Queue { + constructor() { + this.items = [] + } + + enqueue(item) { + this.items.push(item) + } + + dequeue() { + return this.items.shift() // Note: O(n) with arrays! + } + + front() { + return this.items[0] + } + + isEmpty() { + return this.items.length === 0 + } + + size() { + return this.items.length + } +} + +// Usage +const queue = new Queue() +queue.enqueue('first') +queue.enqueue('second') +queue.enqueue('third') +queue.dequeue() // 'first' +queue.front() // 'second' +``` + +<Warning> +**Performance note:** Using `shift()` on an array is O(n) because all remaining elements must be re-indexed. For performance-critical code, use a linked list implementation or an object with head/tail pointers. +</Warning> + +--- + +### Linked List + +A **Linked List** is a chain of nodes where each node points to the next. Unlike arrays, elements aren't stored in contiguous memory. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ LINKED LIST │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ head tail │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ value: 1 │ │ value: 2 │ │ value: 3 │ │ value: 4 │ │ +│ │ next: ───────► │ next: ───────► │ next: ───────► │ next: null│ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ Nodes can be anywhere in memory - connected by references │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**Linked List vs Array:** + +| Operation | Array | Linked List | +|-----------|-------|-------------| +| Access by index | O(1) | O(n) | +| Insert at beginning | O(n) | O(1) | +| Insert at end | O(1) | O(1) with tail pointer | +| Insert in middle | O(n) | O(1) if you have the node | +| Search | O(n) | O(n) | + +**Implementation:** + +```javascript +class Node { + constructor(value) { + this.value = value + this.next = null + } +} + +class LinkedList { + constructor() { + this.head = null + this.size = 0 + } + + // Add to beginning - O(1) + prepend(value) { + const node = new Node(value) + node.next = this.head + this.head = node + this.size++ + } + + // Add to end - O(n) + append(value) { + const node = new Node(value) + + if (!this.head) { + this.head = node + } else { + let current = this.head + while (current.next) { + current = current.next + } + current.next = node + } + this.size++ + } + + // Find a value - O(n) + find(value) { + let current = this.head + while (current) { + if (current.value === value) { + return current + } + current = current.next + } + return null + } + + // Convert to array for easy viewing + toArray() { + const result = [] + let current = this.head + while (current) { + result.push(current.value) + current = current.next + } + return result + } +} + +// Usage +const list = new LinkedList() +list.prepend(1) +list.append(2) +list.append(3) +list.prepend(0) +list.toArray() // [0, 1, 2, 3] +list.find(2) // Node { value: 2, next: Node } +``` + +**When to use Linked Lists:** +- Frequent insertions/deletions at the beginning +- You don't need random access by index +- Implementing queues (for O(1) dequeue) + +--- + +### Binary Search Tree + +A **Binary Search Tree (BST)** is a hierarchical structure where each node has at most two children. The left child is smaller, the right child is larger. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ BINARY SEARCH TREE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────┐ │ +│ │ 10 │ ◄─ root │ +│ └────┘ │ +│ / \ │ +│ ┌────┐ ┌────┐ │ +│ │ 5 │ │ 15 │ │ +│ └────┘ └────┘ │ +│ / \ \ │ +│ ┌────┐ ┌────┐ ┌────┐ │ +│ │ 3 │ │ 7 │ │ 20 │ │ +│ └────┘ └────┘ └────┘ │ +│ │ +│ Rule: left child < parent < right child │ +│ This makes searching fast: just go left or right! │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**Time Complexity:** + +| Operation | Average | Worst (unbalanced) | +|-----------|---------|-------------------| +| Search | O(log n) | O(n) | +| Insert | O(log n) | O(n) | +| Delete | O(log n) | O(n) | + +**Implementation:** + +```javascript +class TreeNode { + constructor(value) { + this.value = value + this.left = null + this.right = null + } +} + +class BinarySearchTree { + constructor() { + this.root = null + } + + insert(value) { + const node = new TreeNode(value) + + if (!this.root) { + this.root = node + return + } + + let current = this.root + while (true) { + if (value < current.value) { + // Go left + if (!current.left) { + current.left = node + return + } + current = current.left + } else { + // Go right + if (!current.right) { + current.right = node + return + } + current = current.right + } + } + } + + search(value) { + let current = this.root + + while (current) { + if (value === current.value) { + return current + } + current = value < current.value ? current.left : current.right + } + + return null + } + + // In-order traversal: left, root, right (gives sorted order) + inOrder(node = this.root, result = []) { + if (node) { + this.inOrder(node.left, result) + result.push(node.value) + this.inOrder(node.right, result) + } + return result + } +} + +// Usage +const bst = new BinarySearchTree() +bst.insert(10) +bst.insert(5) +bst.insert(15) +bst.insert(3) +bst.insert(7) +bst.insert(20) + +bst.search(7) // TreeNode { value: 7, ... } +bst.search(100) // null +bst.inOrder() // [3, 5, 7, 10, 15, 20] - sorted! +``` + +**When to use BST:** +- You need fast search, insert, and delete (O(log n) average) +- Data needs to stay sorted +- Implementing autocomplete, spell checkers + +--- + +### Graph + +<Accordion title="Graph (Brief Overview)"> + +A **Graph** consists of nodes (vertices) connected by edges. Think social networks (people connected by friendships) or maps (cities connected by roads). + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ GRAPH │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ A ─────── B │ +│ /│\ │ │ +│ / │ \ │ │ +│ / │ \ │ │ +│ C │ D ────┘ │ +│ \ │ / │ +│ \ │ / │ +│ \│/ │ +│ E │ +│ │ +│ Adjacency List representation: │ +│ A: [B, C, D, E] │ +│ B: [A, D] │ +│ C: [A, E] │ +│ ... │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**Basic Implementation (Adjacency List):** + +```javascript +class Graph { + constructor() { + this.adjacencyList = new Map() + } + + addVertex(vertex) { + if (!this.adjacencyList.has(vertex)) { + this.adjacencyList.set(vertex, []) + } + } + + addEdge(v1, v2) { + this.adjacencyList.get(v1).push(v2) + this.adjacencyList.get(v2).push(v1) // For undirected graph + } + + // Breadth-First Search - uses Queue (FIFO) + bfs(start) { + const visited = new Set() + const queue = [start] + const result = [] + + while (queue.length) { + const vertex = queue.shift() + if (visited.has(vertex)) continue + + visited.add(vertex) + result.push(vertex) + + for (const neighbor of this.adjacencyList.get(vertex)) { + if (!visited.has(neighbor)) { + queue.push(neighbor) + } + } + } + + return result + } + + // Depth-First Search - uses Stack (LIFO) via recursion + dfs(start, visited = new Set(), result = []) { + if (visited.has(start)) return result + + visited.add(start) + result.push(start) + + for (const neighbor of this.adjacencyList.get(start)) { + this.dfs(neighbor, visited, result) + } + + return result + } +} + +// Usage +const graph = new Graph() +graph.addVertex('A') +graph.addVertex('B') +graph.addVertex('C') +graph.addEdge('A', 'B') +graph.addEdge('A', 'C') +graph.addEdge('B', 'C') +graph.bfs('A') // ['A', 'B', 'C'] - level by level +graph.dfs('A') // ['A', 'B', 'C'] - goes deep first +``` + +**Real-world uses:** +- Social networks (friend connections) +- Maps and navigation (shortest path) +- Recommendation systems +- Dependency resolution (package managers) + +</Accordion> + +--- + +## Choosing the Right Data Structure + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ WHICH DATA STRUCTURE SHOULD I USE? │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Need ordered data with index access? │ +│ └──► ARRAY │ +│ │ +│ Need key-value pairs with string keys? │ +│ └──► OBJECT (static data) or MAP (dynamic) │ +│ │ +│ Need key-value with any type as key? │ +│ └──► MAP │ +│ │ +│ Need unique values only? │ +│ └──► SET │ +│ │ +│ Need LIFO (last in, first out)? │ +│ └──► STACK │ +│ │ +│ Need FIFO (first in, first out)? │ +│ └──► QUEUE │ +│ │ +│ Need fast insert/delete at beginning? │ +│ └──► LINKED LIST │ +│ │ +│ Need fast search + sorted data? │ +│ └──► BINARY SEARCH TREE │ +│ │ +│ Modeling relationships/connections? │ +│ └──► GRAPH │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +| Use Case | Best Structure | Why | +|----------|----------------|-----| +| Todo list | Array | Ordered, index access | +| User settings | Object | String keys, static | +| Word frequency counter | Map | Easy increment, any key | +| Tag system | Set | Unique values | +| Browser back button | Stack | LIFO | +| Task scheduler | Queue | FIFO | +| Playlist with prev/next | Linked List (doubly) | O(1) traversal | +| Dictionary/autocomplete | Trie | Fast prefix search | +| Social network | Graph | Connections | + +--- + +## Common Interview Questions + +Interview questions often test your understanding of data structures. Here are patterns you'll encounter: + +<AccordionGroup> + <Accordion title="Array: Two Sum"> + **Problem:** Find two numbers in an array that add up to a target. + + **Approach:** Use a Map to store numbers you've seen. For each number, check if `target - number` exists in the Map. + + ```javascript + function twoSum(nums, target) { + const seen = new Map() + + for (let i = 0; i < nums.length; i++) { + const complement = target - nums[i] + + if (seen.has(complement)) { + return [seen.get(complement), i] + } + + seen.set(nums[i], i) + } + + return [] + } + + twoSum([2, 7, 11, 15], 9) // [0, 1] + ``` + + **Why Map?** O(1) lookup turns O(n²) brute force into O(n). + </Accordion> + + <Accordion title="Stack: Valid Parentheses"> + **Problem:** Check if a string of brackets is valid: `()[]{}`. + + **Approach:** Push opening brackets onto stack. When you see a closing bracket, pop and check if it matches. + + ```javascript + function isValid(s) { + const stack = [] + const pairs = { ')': '(', ']': '[', '}': '{' } + + for (const char of s) { + if (char in pairs) { + // Closing bracket - check if it matches + if (stack.pop() !== pairs[char]) { + return false + } + } else { + // Opening bracket - push to stack + stack.push(char) + } + } + + return stack.length === 0 + } + + isValid('([{}])') // true + isValid('([)]') // false + ``` + </Accordion> + + <Accordion title="Linked List: Reverse"> + **Problem:** Reverse a linked list. + + **Approach:** Keep track of previous, current, and next. Reverse pointers as you go. + + ```javascript + function reverseList(head) { + let prev = null + let current = head + + while (current) { + const next = current.next // Save next + current.next = prev // Reverse pointer + prev = current // Move prev forward + current = next // Move current forward + } + + return prev // New head + } + ``` + + **Key insight:** You need three pointers to avoid losing references. + </Accordion> + + <Accordion title="Linked List: Detect Cycle"> + **Problem:** Determine if a linked list has a cycle. + + **Approach:** Floyd's Tortoise and Hare - use two pointers, one fast (2 steps) and one slow (1 step). If they meet, there's a cycle. + + ```javascript + function hasCycle(head) { + let slow = head + let fast = head + + while (fast && fast.next) { + slow = slow.next + fast = fast.next.next + + if (slow === fast) { + return true // They met - cycle exists + } + } + + return false // Fast reached end - no cycle + } + ``` + + **Why this works:** In a cycle, the fast pointer will eventually "lap" the slow pointer. + </Accordion> + + <Accordion title="Tree: Maximum Depth"> + **Problem:** Find the maximum depth of a binary tree. + + **Approach:** Recursively find the depth of left and right subtrees, take the max. + + ```javascript + function maxDepth(root) { + if (!root) return 0 + + const leftDepth = maxDepth(root.left) + const rightDepth = maxDepth(root.right) + + return Math.max(leftDepth, rightDepth) + 1 + } + ``` + + **Base case:** Empty tree has depth 0. + </Accordion> + + <Accordion title="Queue: Implement with Two Stacks"> + **Problem:** Implement a queue using only stacks. + + **Approach:** Use two stacks. Push to stack1. For dequeue, if stack2 is empty, pour all of stack1 into stack2 (reversing order), then pop from stack2. + + ```javascript + class QueueFromStacks { + constructor() { + this.stack1 = [] // For enqueue + this.stack2 = [] // For dequeue + } + + enqueue(item) { + this.stack1.push(item) + } + + dequeue() { + if (this.stack2.length === 0) { + // Pour stack1 into stack2 + while (this.stack1.length) { + this.stack2.push(this.stack1.pop()) + } + } + return this.stack2.pop() + } + } + ``` + + **Amortized O(1):** Each element is moved at most twice. + </Accordion> +</AccordionGroup> + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Arrays** are great for ordered data with index access. Push/pop are O(1), but shift/unshift are O(n). + +2. **Objects** store string-keyed data. Use them for static configuration and entity data. + +3. **Map** is the better choice when keys aren't strings, you need `.size`, or you add/delete frequently. + +4. **Set** stores unique values. The `[...new Set(arr)]` trick removes duplicates instantly. + +5. **Stack (LIFO)** is perfect for undo/redo, parsing expressions, and DFS traversal. + +6. **Queue (FIFO)** is ideal for task scheduling and BFS traversal. Use a linked list for O(1) dequeue. + +7. **Linked Lists** excel at insertions/deletions but lack random access. Use when you frequently modify the beginning. + +8. **Binary Search Trees** give O(log n) search/insert/delete on average. They keep data sorted. + +9. **Choose based on your most frequent operation.** What makes one structure fast makes another slow. + +10. **Interview tip:** When you need O(1) lookup, think Map or Set. When you need to track order of operations, think Stack or Queue. +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: When would you use a Map instead of an Object?"> + **Answer:** + + Use Map when: + - Keys are not strings (objects, numbers, etc.) + - You need to know the size frequently (`.size` vs `Object.keys().length`) + - You add/delete keys often (Map is optimized for this) + - You need guaranteed insertion order + - You want to avoid prototype chain issues + + Use Object when: + - Keys are known strings + - You're working with JSON data + - You need object destructuring or spread syntax + </Accordion> + + <Accordion title="Question 2: Why is array shift() O(n) but pop() O(1)?"> + **Answer:** + + `pop()` removes from the end. No other elements need to move. + + `shift()` removes from the beginning. Every remaining element must be re-indexed: + - Element at index 1 moves to 0 + - Element at index 2 moves to 1 + - ...and so on + + This is why Queue implementations with arrays have O(n) dequeue. For O(1), use a linked list or object with head/tail pointers. + </Accordion> + + <Accordion title="Question 3: What's the difference between a Stack and a Queue?"> + **Answer:** + + **Stack (LIFO):** Last In, First Out + - Like a stack of plates - you take from the top + - `push()` and `pop()` operate on the same end + - Use for: undo/redo, back button, recursion + + **Queue (FIFO):** First In, First Out + - Like a line at a store - first person in line is served first + - `enqueue()` adds to back, `dequeue()` removes from front + - Use for: task scheduling, BFS, print queues + </Accordion> + + <Accordion title="Question 4: When would a Linked List be better than an Array?"> + **Answer:** + + Linked List wins when: + - You frequently insert/delete at the beginning (O(1) vs O(n)) + - You don't need random access by index + - You're implementing a queue (O(1) dequeue) + - Memory is fragmented (nodes can be anywhere) + + Array wins when: + - You need index-based access + - You iterate sequentially often + - You mostly add/remove from the end + - You need `.length`, `.map()`, `.filter()`, etc. + </Accordion> + + <Accordion title="Question 5: What makes Binary Search Trees fast?"> + **Answer:** + + BSTs use the rule: left < parent < right. This means: + + - To find a value, compare with root + - If smaller, go left; if larger, go right + - Each comparison eliminates half the remaining nodes + + This gives O(log n) search, insert, and delete (on average). + + **Catch:** If you insert sorted data, the tree becomes a linked list (all nodes on one side), and operations become O(n). Self-balancing trees (AVL, Red-Black) solve this. + </Accordion> + + <Accordion title="Question 6: How would you remove duplicates from an array?"> + **Answer:** + + The cleanest way is with Set: + + ```javascript + const unique = [...new Set(array)] + ``` + + This works because: + 1. `new Set(array)` creates a Set (which only keeps unique values) + 2. `[...set]` spreads the Set back into an array + + Time complexity: O(n) - each element is processed once. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="Data Structures in JavaScript" icon="newspaper" href="https://medium.com/siliconwat/data-structures-in-javascript-1b9aed0ea17c"> - By Thon Ly + <Card title="Algorithms & Big O" icon="gauge-high" href="/concepts/algorithms-big-o"> + Understanding time complexity helps you choose the right data structure + </Card> + <Card title="Factories & Classes" icon="industry" href="/concepts/factories-classes"> + The class syntax used to implement data structures </Card> - <Card title="Algorithms and Data Structures in JavaScript" icon="newspaper" href="https://itnext.io/algorithms-and-data-structures-in-javascript-a71548f902cb"> - By Oleksii Trekhleb + <Card title="Higher-Order Functions" icon="function" href="/concepts/higher-order-functions"> + Array methods like map, filter, and reduce + </Card> + <Card title="Recursion" icon="arrow-rotate-left" href="/concepts/recursion"> + Essential for tree and graph traversal algorithms </Card> </CardGroup> -- [Data Structures: Objects and Arrays ― Chris Nwamba](https://scotch.io/courses/10-need-to-know-javascript-concepts/data-structures-objects-and-arrays) -- [Data structures in JavaScript — Benoit Vallon](http://blog.benoitvallon.com/data-structures-in-javascript/data-structures-in-javascript/) -- [Playing with Data Structures in Javascript — Anish K.](https://blog.cloudboost.io/playing-with-data-structures-in-javascript-stack-a55ebe50f29d) -- [The Little Guide of Queue in JavaScript — Germán Cutraro](https://hackernoon.com/the-little-guide-of-queue-in-javascript-4f67e79260d9) -- [All algorithms writing with JavaScript in the book 'Algorithms Fourth Edition'](https://github.com/barretlee/algorithms) -- [Collection of classic computer science paradigms in JavaScript](https://github.com/nzakas/computer-science-in-javascript) -- [All the things you didn't know you wanted to know about data structures](https://github.com/jamiebuilds/itsy-bitsy-data-structures) -- [JavaScript Data Structures: 40 Part Series — miku86](https://dev.to/miku86/series/3259) -- [Data Structures: Understanding Graphs — Rachel Hawa](https://medium.com/javascript-in-plain-english/data-structures-understanding-graphs-82509d35e6b5) -- [Data Structures Two Ways: Linked List (Pt 1) — Freddie Duffield](https://dev.to/freddieduffield/data-structures-two-ways-linked-list-2n61) -- [Data Structures Two Ways: Linked List (Pt 2) — Freddie Duffield](https://dev.to/freddieduffield/data-structures-two-ways-linked-list-pt2-2i60) -- [Graph Data Structures Explained in JavaScript — Adrian Mejia](https://dev.to/amejiarosario/graph-data-structures-for-beginners-5edn) +--- -## Videos +## Reference <CardGroup cols={2}> - <Card title="Algorithms In Javascript | Ace Your Interview" icon="video" href="https://www.youtube.com/watch?v=H_EBPZgiAas&list=PLDmvslp_VR0zYUSth_8O69p4_cmvZEgLa"> - By Eduonix Learning Solutions + <Card title="Array — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array"> + Complete reference for JavaScript arrays </Card> - <Card title="Data Structures and Algorithms in JavaScript" icon="video" href="https://www.youtube.com/watch?v=Gj5qBheGOEo&list=PLWKjhJtqVAbkso-IbgiiP48n-O-JQA9PJ"> - By freeCodeCamp + <Card title="Object — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object"> + Documentation for Object methods and properties + </Card> + <Card title="Map — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"> + Guide to the Map collection type + </Card> + <Card title="Set — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set"> + Documentation for Set and its new ES2024 methods + </Card> + <Card title="WeakMap — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"> + When to use WeakMap for memory management + </Card> + <Card title="Data Structures Guide — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Data_structures"> + MDN's overview of JavaScript data types and structures </Card> </CardGroup> -- [Learning JavaScript Data Structures and Algorithms: Sorting — Packt Video](https://www.youtube.com/watch?v=Ymh_AurrMbA) -- [JavaScript Data Structures: Getting Started — Academind](https://www.youtube.com/watch?v=41GSinwoMYA&ab_channel=Academind) +## Articles + +<CardGroup cols={2}> + <Card title="Map and Set — JavaScript.info" icon="newspaper" href="https://javascript.info/map-set"> + The clearest explanation of Map and Set with interactive examples. Covers WeakMap and WeakSet too. + </Card> + <Card title="JavaScript Algorithms and Data Structures" icon="newspaper" href="https://github.com/trekhleb/javascript-algorithms"> + Oleksii Trekhleb's legendary GitHub repo with implementations of every data structure and algorithm in JavaScript. Over 180k stars for good reason. + </Card> + <Card title="Data Structures in JavaScript" icon="newspaper" href="https://www.freecodecamp.org/news/data-structures-in-javascript-with-examples/"> + freeCodeCamp's practical guide covering arrays through graphs with real-world examples you can follow along with. + </Card> + <Card title="Itsy Bitsy Data Structures" icon="newspaper" href="https://github.com/jamiebuilds/itsy-bitsy-data-structures"> + Jamie Kyle's annotated source code explaining data structures in ~200 lines. Perfect if you learn by reading well-commented code. + </Card> +</CardGroup> + +## Videos + +<CardGroup cols={2}> + <Card title="Data Structures and Algorithms in JavaScript" icon="video" href="https://www.youtube.com/watch?v=Gj5qBheGOEo&list=PLWKjhJtqVAbkso-IbgiiP48n-O-JQA9PJ"> + freeCodeCamp's complete 8-hour course covering everything from Big O to graph algorithms. Great for interview prep. + </Card> + <Card title="JavaScript Data Structures: Getting Started" icon="video" href="https://www.youtube.com/watch?v=41GSinwoMYA"> + Academind's beginner-friendly introduction focusing on when and why to use each structure, not just how. + </Card> + <Card title="Data Structures Easy to Advanced" icon="video" href="https://www.youtube.com/watch?v=RBSGKlAvoiM"> + William Fiset's comprehensive course with animations that make complex structures like trees and graphs click. + </Card> +</CardGroup> diff --git a/tests/advanced-topics/data-structures/data-structures.test.js b/tests/advanced-topics/data-structures/data-structures.test.js new file mode 100644 index 00000000..bfa89fee --- /dev/null +++ b/tests/advanced-topics/data-structures/data-structures.test.js @@ -0,0 +1,1054 @@ +import { describe, it, expect } from 'vitest' + +describe('Data Structures', () => { + describe('Arrays', () => { + it('should access elements by index in O(1)', () => { + const arr = ['a', 'b', 'c', 'd', 'e'] + + expect(arr[0]).toBe('a') + expect(arr[2]).toBe('c') + expect(arr[4]).toBe('e') + }) + + it('should add and remove from end with push/pop in O(1)', () => { + const arr = [1, 2, 3] + + arr.push(4) + expect(arr).toEqual([1, 2, 3, 4]) + + const popped = arr.pop() + expect(popped).toBe(4) + expect(arr).toEqual([1, 2, 3]) + }) + + it('should add and remove from beginning with unshift/shift (O(n))', () => { + const arr = [1, 2, 3] + + arr.unshift(0) + expect(arr).toEqual([0, 1, 2, 3]) + + const shifted = arr.shift() + expect(shifted).toBe(0) + expect(arr).toEqual([1, 2, 3]) + }) + + it('should search with indexOf and includes in O(n)', () => { + const arr = ['apple', 'banana', 'cherry'] + + expect(arr.indexOf('banana')).toBe(1) + expect(arr.indexOf('mango')).toBe(-1) + expect(arr.includes('cherry')).toBe(true) + expect(arr.includes('grape')).toBe(false) + }) + + it('should insert in middle with splice in O(n)', () => { + const arr = [1, 2, 4, 5] + + // Insert 3 at index 2 + arr.splice(2, 0, 3) + expect(arr).toEqual([1, 2, 3, 4, 5]) + + // Remove element at index 2 + arr.splice(2, 1) + expect(arr).toEqual([1, 2, 4, 5]) + }) + }) + + describe('Objects', () => { + it('should access, add, and delete properties in O(1)', () => { + const user = { name: 'Alice', age: 30 } + + // Access + expect(user.name).toBe('Alice') + expect(user['age']).toBe(30) + + // Add + user.email = 'alice@example.com' + expect(user.email).toBe('alice@example.com') + + // Delete + delete user.email + expect(user.email).toBe(undefined) + }) + + it('should check for key existence', () => { + const user = { name: 'Alice' } + + expect('name' in user).toBe(true) + expect('age' in user).toBe(false) + expect(user.hasOwnProperty('name')).toBe(true) + }) + + it('should convert numeric keys to strings', () => { + const obj = {} + obj[1] = 'one' + obj['1'] = 'one as string' + + // Both are the same key! + expect(Object.keys(obj)).toEqual(['1']) + expect(obj[1]).toBe('one as string') + expect(obj['1']).toBe('one as string') + }) + }) + + describe('Map', () => { + it('should use any value type as key', () => { + const map = new Map() + + const objKey = { id: 1 } + const funcKey = () => {} + + map.set('string', 'string key') + map.set(123, 'number key') + map.set(objKey, 'object key') + map.set(funcKey, 'function key') + map.set(true, 'boolean key') + + expect(map.get('string')).toBe('string key') + expect(map.get(123)).toBe('number key') + expect(map.get(objKey)).toBe('object key') + expect(map.get(funcKey)).toBe('function key') + expect(map.get(true)).toBe('boolean key') + }) + + it('should have a size property', () => { + const map = new Map() + map.set('a', 1) + map.set('b', 2) + map.set('c', 3) + + expect(map.size).toBe(3) + }) + + it('should check existence with has()', () => { + const map = new Map([['key', 'value']]) + + expect(map.has('key')).toBe(true) + expect(map.has('nonexistent')).toBe(false) + }) + + it('should delete entries', () => { + const map = new Map([['a', 1], ['b', 2]]) + + map.delete('a') + expect(map.has('a')).toBe(false) + expect(map.size).toBe(1) + }) + + it('should maintain insertion order', () => { + const map = new Map() + map.set('first', 1) + map.set('second', 2) + map.set('third', 3) + + const keys = [...map.keys()] + expect(keys).toEqual(['first', 'second', 'third']) + }) + + it('should iterate with for...of', () => { + const map = new Map([['a', 1], ['b', 2]]) + const entries = [] + + for (const [key, value] of map) { + entries.push([key, value]) + } + + expect(entries).toEqual([['a', 1], ['b', 2]]) + }) + + it('should be useful for counting occurrences', () => { + function countWords(text) { + const words = text.toLowerCase().split(/\s+/) + const counts = new Map() + + for (const word of words) { + counts.set(word, (counts.get(word) || 0) + 1) + } + + return counts + } + + const result = countWords('the cat and the dog') + expect(result.get('the')).toBe(2) + expect(result.get('cat')).toBe(1) + expect(result.get('and')).toBe(1) + }) + }) + + describe('Set', () => { + it('should store only unique values', () => { + const set = new Set() + + set.add(1) + set.add(2) + set.add(2) // Duplicate - ignored + set.add(3) + set.add(3) // Duplicate - ignored + + expect(set.size).toBe(3) + expect([...set]).toEqual([1, 2, 3]) + }) + + it('should check existence with has()', () => { + const set = new Set([1, 2, 3]) + + expect(set.has(2)).toBe(true) + expect(set.has(5)).toBe(false) + }) + + it('should remove duplicates from array', () => { + const numbers = [1, 2, 2, 3, 3, 3, 4] + const unique = [...new Set(numbers)] + + expect(unique).toEqual([1, 2, 3, 4]) + }) + + it('should delete values', () => { + const set = new Set([1, 2, 3]) + + set.delete(2) + expect(set.has(2)).toBe(false) + expect(set.size).toBe(2) + }) + + it('should iterate in insertion order', () => { + const set = new Set() + set.add('first') + set.add('second') + set.add('third') + + expect([...set]).toEqual(['first', 'second', 'third']) + }) + + it('should perform set operations (ES2024+)', () => { + const a = new Set([1, 2, 3]) + const b = new Set([2, 3, 4]) + + // Skip if ES2024 Set methods not available + if (typeof a.union !== 'function') { + // Manual implementation for older environments + const union = new Set([...a, ...b]) + expect([...union].sort()).toEqual([1, 2, 3, 4]) + + const intersection = new Set([...a].filter(x => b.has(x))) + expect([...intersection].sort()).toEqual([2, 3]) + + const difference = new Set([...a].filter(x => !b.has(x))) + expect([...difference]).toEqual([1]) + + return + } + + // Union: elements in either set + expect([...a.union(b)].sort()).toEqual([1, 2, 3, 4]) + + // Intersection: elements in both sets + expect([...a.intersection(b)].sort()).toEqual([2, 3]) + + // Difference: elements in a but not in b + expect([...a.difference(b)]).toEqual([1]) + + // Symmetric difference: elements in either but not both + expect([...a.symmetricDifference(b)].sort()).toEqual([1, 4]) + }) + + it('should check subset relationships (ES2024+)', () => { + const small = new Set([1, 2]) + const large = new Set([1, 2, 3, 4]) + + // Skip if ES2024 Set methods not available + if (typeof small.isSubsetOf !== 'function') { + // Manual implementation for older environments + const isSubset = [...small].every(x => large.has(x)) + expect(isSubset).toBe(true) + + const isSuperset = [...small].every(x => large.has(x)) + expect(isSuperset).toBe(true) + + const largeIsSubsetOfSmall = [...large].every(x => small.has(x)) + expect(largeIsSubsetOfSmall).toBe(false) + + return + } + + expect(small.isSubsetOf(large)).toBe(true) + expect(large.isSupersetOf(small)).toBe(true) + expect(large.isSubsetOf(small)).toBe(false) + }) + }) + + describe('WeakMap', () => { + it('should only accept objects as keys', () => { + const weakMap = new WeakMap() + const obj = { id: 1 } + + weakMap.set(obj, 'value') + expect(weakMap.get(obj)).toBe('value') + + // Cannot use primitives as keys + expect(() => weakMap.set('string', 'value')).toThrow(TypeError) + }) + + it('should support get, set, has, delete operations', () => { + const weakMap = new WeakMap() + const obj = { id: 1 } + + weakMap.set(obj, 'data') + expect(weakMap.has(obj)).toBe(true) + expect(weakMap.get(obj)).toBe('data') + + weakMap.delete(obj) + expect(weakMap.has(obj)).toBe(false) + }) + + it('should be useful for private data pattern', () => { + const privateData = new WeakMap() + + class User { + constructor(name, password) { + this.name = name + privateData.set(this, { password }) + } + + checkPassword(input) { + return privateData.get(this).password === input + } + } + + const user = new User('Alice', 'secret123') + expect(user.name).toBe('Alice') + expect(user.password).toBe(undefined) // Not accessible + expect(user.checkPassword('secret123')).toBe(true) + expect(user.checkPassword('wrong')).toBe(false) + }) + }) + + describe('WeakSet', () => { + it('should only accept objects as values', () => { + const weakSet = new WeakSet() + const obj = { id: 1 } + + weakSet.add(obj) + expect(weakSet.has(obj)).toBe(true) + + // Cannot use primitives + expect(() => weakSet.add('string')).toThrow(TypeError) + }) + + it('should be useful for tracking processed objects', () => { + const processed = new WeakSet() + + function processOnce(obj) { + if (processed.has(obj)) { + return 'already processed' + } + processed.add(obj) + return 'processed' + } + + const obj = { data: 'test' } + expect(processOnce(obj)).toBe('processed') + expect(processOnce(obj)).toBe('already processed') + }) + }) + + describe('Stack Implementation', () => { + class Stack { + constructor() { + this.items = [] + } + + push(item) { + this.items.push(item) + } + + pop() { + return this.items.pop() + } + + peek() { + return this.items[this.items.length - 1] + } + + isEmpty() { + return this.items.length === 0 + } + + size() { + return this.items.length + } + } + + it('should follow LIFO (Last In, First Out)', () => { + const stack = new Stack() + + stack.push(1) + stack.push(2) + stack.push(3) + + expect(stack.pop()).toBe(3) // Last in + expect(stack.pop()).toBe(2) + expect(stack.pop()).toBe(1) // First in + }) + + it('should peek without removing', () => { + const stack = new Stack() + stack.push('a') + stack.push('b') + + expect(stack.peek()).toBe('b') + expect(stack.size()).toBe(2) // Still 2 items + }) + + it('should report isEmpty correctly', () => { + const stack = new Stack() + + expect(stack.isEmpty()).toBe(true) + + stack.push(1) + expect(stack.isEmpty()).toBe(false) + + stack.pop() + expect(stack.isEmpty()).toBe(true) + }) + + it('should handle pop and peek on empty stack', () => { + const stack = new Stack() + + expect(stack.pop()).toBe(undefined) + expect(stack.peek()).toBe(undefined) + expect(stack.size()).toBe(0) + }) + + it('should solve valid parentheses problem', () => { + function isValid(s) { + const stack = [] + const pairs = { ')': '(', ']': '[', '}': '{' } + + for (const char of s) { + if (char in pairs) { + if (stack.pop() !== pairs[char]) { + return false + } + } else { + stack.push(char) + } + } + + return stack.length === 0 + } + + expect(isValid('()')).toBe(true) + expect(isValid('()[]{}')).toBe(true) + expect(isValid('([{}])')).toBe(true) + expect(isValid('(]')).toBe(false) + expect(isValid('([)]')).toBe(false) + expect(isValid('(((')).toBe(false) + }) + }) + + describe('Queue Implementation', () => { + class Queue { + constructor() { + this.items = [] + } + + enqueue(item) { + this.items.push(item) + } + + dequeue() { + return this.items.shift() + } + + front() { + return this.items[0] + } + + isEmpty() { + return this.items.length === 0 + } + + size() { + return this.items.length + } + } + + it('should follow FIFO (First In, First Out)', () => { + const queue = new Queue() + + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(3) + + expect(queue.dequeue()).toBe(1) // First in + expect(queue.dequeue()).toBe(2) + expect(queue.dequeue()).toBe(3) // Last in + }) + + it('should peek at front without removing', () => { + const queue = new Queue() + queue.enqueue('first') + queue.enqueue('second') + + expect(queue.front()).toBe('first') + expect(queue.size()).toBe(2) // Still 2 items + }) + + it('should report isEmpty correctly', () => { + const queue = new Queue() + + expect(queue.isEmpty()).toBe(true) + + queue.enqueue(1) + expect(queue.isEmpty()).toBe(false) + + queue.dequeue() + expect(queue.isEmpty()).toBe(true) + }) + + it('should handle dequeue and front on empty queue', () => { + const queue = new Queue() + + expect(queue.dequeue()).toBe(undefined) + expect(queue.front()).toBe(undefined) + expect(queue.size()).toBe(0) + }) + }) + + describe('Linked List Implementation', () => { + class Node { + constructor(value) { + this.value = value + this.next = null + } + } + + class LinkedList { + constructor() { + this.head = null + this.size = 0 + } + + prepend(value) { + const node = new Node(value) + node.next = this.head + this.head = node + this.size++ + } + + append(value) { + const node = new Node(value) + + if (!this.head) { + this.head = node + } else { + let current = this.head + while (current.next) { + current = current.next + } + current.next = node + } + this.size++ + } + + find(value) { + let current = this.head + while (current) { + if (current.value === value) { + return current + } + current = current.next + } + return null + } + + toArray() { + const result = [] + let current = this.head + while (current) { + result.push(current.value) + current = current.next + } + return result + } + } + + it('should prepend elements in O(1)', () => { + const list = new LinkedList() + + list.prepend(3) + list.prepend(2) + list.prepend(1) + + expect(list.toArray()).toEqual([1, 2, 3]) + }) + + it('should append elements', () => { + const list = new LinkedList() + + list.append(1) + list.append(2) + list.append(3) + + expect(list.toArray()).toEqual([1, 2, 3]) + }) + + it('should find elements', () => { + const list = new LinkedList() + list.append(1) + list.append(2) + list.append(3) + + const found = list.find(2) + expect(found.value).toBe(2) + expect(found.next.value).toBe(3) + + expect(list.find(5)).toBe(null) + }) + + it('should track size correctly', () => { + const list = new LinkedList() + + expect(list.size).toBe(0) + + list.append(1) + list.append(2) + list.prepend(0) + + expect(list.size).toBe(3) + }) + + it('should handle operations on empty list', () => { + const list = new LinkedList() + + expect(list.head).toBe(null) + expect(list.find(1)).toBe(null) + expect(list.toArray()).toEqual([]) + }) + + it('should reverse a linked list', () => { + function reverseList(head) { + let prev = null + let current = head + + while (current) { + const next = current.next + current.next = prev + prev = current + current = next + } + + return prev + } + + const list = new LinkedList() + list.append(1) + list.append(2) + list.append(3) + + list.head = reverseList(list.head) + expect(list.toArray()).toEqual([3, 2, 1]) + }) + }) + + describe('Binary Search Tree Implementation', () => { + class TreeNode { + constructor(value) { + this.value = value + this.left = null + this.right = null + } + } + + class BinarySearchTree { + constructor() { + this.root = null + } + + insert(value) { + const node = new TreeNode(value) + + if (!this.root) { + this.root = node + return + } + + let current = this.root + while (true) { + if (value < current.value) { + if (!current.left) { + current.left = node + return + } + current = current.left + } else { + if (!current.right) { + current.right = node + return + } + current = current.right + } + } + } + + search(value) { + let current = this.root + + while (current) { + if (value === current.value) { + return current + } + current = value < current.value ? current.left : current.right + } + + return null + } + + inOrder(node = this.root, result = []) { + if (node) { + this.inOrder(node.left, result) + result.push(node.value) + this.inOrder(node.right, result) + } + return result + } + } + + it('should insert values following BST property', () => { + const bst = new BinarySearchTree() + bst.insert(10) + bst.insert(5) + bst.insert(15) + + expect(bst.root.value).toBe(10) + expect(bst.root.left.value).toBe(5) + expect(bst.root.right.value).toBe(15) + }) + + it('should search for values', () => { + const bst = new BinarySearchTree() + bst.insert(10) + bst.insert(5) + bst.insert(15) + bst.insert(3) + bst.insert(7) + + expect(bst.search(7).value).toBe(7) + expect(bst.search(15).value).toBe(15) + expect(bst.search(100)).toBe(null) + }) + + it('should return sorted values with in-order traversal', () => { + const bst = new BinarySearchTree() + bst.insert(10) + bst.insert(5) + bst.insert(15) + bst.insert(3) + bst.insert(7) + bst.insert(20) + + expect(bst.inOrder()).toEqual([3, 5, 7, 10, 15, 20]) + }) + + it('should find max depth of tree', () => { + function maxDepth(root) { + if (!root) return 0 + + const leftDepth = maxDepth(root.left) + const rightDepth = maxDepth(root.right) + + return Math.max(leftDepth, rightDepth) + 1 + } + + const bst = new BinarySearchTree() + bst.insert(10) + bst.insert(5) + bst.insert(15) + bst.insert(3) + + expect(maxDepth(bst.root)).toBe(3) + }) + + it('should handle empty tree operations', () => { + const bst = new BinarySearchTree() + + expect(bst.root).toBe(null) + expect(bst.search(10)).toBe(null) + expect(bst.inOrder()).toEqual([]) + }) + + it('should handle duplicate values (goes to right subtree)', () => { + const bst = new BinarySearchTree() + bst.insert(10) + bst.insert(10) // Duplicate + bst.insert(10) // Another duplicate + + // Duplicates go to the right (based on our implementation: else branch) + expect(bst.root.value).toBe(10) + expect(bst.root.right.value).toBe(10) + expect(bst.root.right.right.value).toBe(10) + expect(bst.inOrder()).toEqual([10, 10, 10]) + }) + }) + + describe('Graph Implementation', () => { + class Graph { + constructor() { + this.adjacencyList = new Map() + } + + addVertex(vertex) { + if (!this.adjacencyList.has(vertex)) { + this.adjacencyList.set(vertex, []) + } + } + + addEdge(v1, v2) { + this.adjacencyList.get(v1).push(v2) + this.adjacencyList.get(v2).push(v1) + } + + bfs(start) { + const visited = new Set() + const queue = [start] + const result = [] + + while (queue.length) { + const vertex = queue.shift() + if (visited.has(vertex)) continue + + visited.add(vertex) + result.push(vertex) + + for (const neighbor of this.adjacencyList.get(vertex)) { + if (!visited.has(neighbor)) { + queue.push(neighbor) + } + } + } + + return result + } + + dfs(start, visited = new Set(), result = []) { + if (visited.has(start)) return result + + visited.add(start) + result.push(start) + + for (const neighbor of this.adjacencyList.get(start)) { + this.dfs(neighbor, visited, result) + } + + return result + } + } + + it('should add vertices and edges', () => { + const graph = new Graph() + graph.addVertex('A') + graph.addVertex('B') + graph.addVertex('C') + graph.addEdge('A', 'B') + graph.addEdge('A', 'C') + + expect(graph.adjacencyList.get('A')).toContain('B') + expect(graph.adjacencyList.get('A')).toContain('C') + expect(graph.adjacencyList.get('B')).toContain('A') + }) + + it('should perform breadth-first search', () => { + const graph = new Graph() + graph.addVertex('A') + graph.addVertex('B') + graph.addVertex('C') + graph.addVertex('D') + graph.addEdge('A', 'B') + graph.addEdge('A', 'C') + graph.addEdge('B', 'D') + + const result = graph.bfs('A') + + // BFS visits level by level + expect(result[0]).toBe('A') + expect(result.includes('B')).toBe(true) + expect(result.includes('C')).toBe(true) + expect(result.includes('D')).toBe(true) + }) + + it('should perform depth-first search', () => { + const graph = new Graph() + graph.addVertex('A') + graph.addVertex('B') + graph.addVertex('C') + graph.addVertex('D') + graph.addEdge('A', 'B') + graph.addEdge('A', 'C') + graph.addEdge('B', 'D') + + const result = graph.dfs('A') + + // DFS goes deep before wide + expect(result[0]).toBe('A') + expect(result.length).toBe(4) + }) + }) + + describe('Common Interview Patterns', () => { + it('Two Sum - using Map for O(n) lookup', () => { + function twoSum(nums, target) { + const seen = new Map() + + for (let i = 0; i < nums.length; i++) { + const complement = target - nums[i] + + if (seen.has(complement)) { + return [seen.get(complement), i] + } + + seen.set(nums[i], i) + } + + return [] + } + + expect(twoSum([2, 7, 11, 15], 9)).toEqual([0, 1]) + expect(twoSum([3, 2, 4], 6)).toEqual([1, 2]) + expect(twoSum([3, 3], 6)).toEqual([0, 1]) + }) + + it('Detect cycle in linked list - Floyd\'s algorithm', () => { + function hasCycle(head) { + let slow = head + let fast = head + + while (fast && fast.next) { + slow = slow.next + fast = fast.next.next + + if (slow === fast) { + return true + } + } + + return false + } + + // Create a list with cycle + const node1 = { val: 1, next: null } + const node2 = { val: 2, next: null } + const node3 = { val: 3, next: null } + node1.next = node2 + node2.next = node3 + node3.next = node1 // Cycle back to node1 + + expect(hasCycle(node1)).toBe(true) + + // List without cycle + const a = { val: 1, next: null } + const b = { val: 2, next: null } + a.next = b + + expect(hasCycle(a)).toBe(false) + }) + + it('Queue using two stacks', () => { + class QueueFromStacks { + constructor() { + this.stack1 = [] + this.stack2 = [] + } + + enqueue(item) { + this.stack1.push(item) + } + + dequeue() { + if (this.stack2.length === 0) { + while (this.stack1.length) { + this.stack2.push(this.stack1.pop()) + } + } + return this.stack2.pop() + } + } + + const queue = new QueueFromStacks() + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(3) + + expect(queue.dequeue()).toBe(1) // FIFO + expect(queue.dequeue()).toBe(2) + + queue.enqueue(4) + expect(queue.dequeue()).toBe(3) + expect(queue.dequeue()).toBe(4) + }) + }) + + describe('Choosing the Right Data Structure', () => { + it('should use Array for ordered data with index access', () => { + const todos = ['Buy milk', 'Walk dog', 'Write code'] + + // O(1) access by index + expect(todos[1]).toBe('Walk dog') + + // Easy to iterate + expect(todos.map(t => t.toUpperCase())).toEqual([ + 'BUY MILK', 'WALK DOG', 'WRITE CODE' + ]) + }) + + it('should use Set for unique values and fast lookup', () => { + const visited = new Set() + + // Track unique visitors + visited.add('user1') + visited.add('user2') + visited.add('user1') // Duplicate ignored + + expect(visited.size).toBe(2) + expect(visited.has('user1')).toBe(true) // O(1) lookup + }) + + it('should use Map for non-string keys or frequent updates', () => { + // Using objects as keys + const cache = new Map() + const request1 = { url: '/api/users', method: 'GET' } + const request2 = { url: '/api/posts', method: 'GET' } + + cache.set(request1, { data: ['user1', 'user2'] }) + cache.set(request2, { data: ['post1', 'post2'] }) + + expect(cache.get(request1).data).toEqual(['user1', 'user2']) + }) + + it('should use Stack for undo/redo or backtracking', () => { + const history = [] + + // Record actions + history.push('action1') + history.push('action2') + history.push('action3') + + // Undo - pop most recent + const undone = history.pop() + expect(undone).toBe('action3') + }) + + it('should use Queue for task scheduling', () => { + const taskQueue = [] + + // Add tasks + taskQueue.push('task1') + taskQueue.push('task2') + taskQueue.push('task3') + + // Process in order + expect(taskQueue.shift()).toBe('task1') // First added + expect(taskQueue.shift()).toBe('task2') + }) + }) +}) From 8d633920458f2e71c8869061fcc01df5cf09ee74 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 17:05:40 -0300 Subject: [PATCH 121/128] feat: add comprehensive Algorithms & Big O concept page with tests - Complete rewrite of algorithms-big-o.mdx (~2500 words) - Cover Big O notation fundamentals with accurate technical definitions - Include searching algorithms (linear, binary search) - Include sorting algorithms (bubble sort with early termination, merge sort) - Add common interview patterns (two pointers, sliding window, frequency counter) - Add JavaScript built-in methods complexity reference tables - Include 70 comprehensive tests for all algorithm implementations - Add Key Takeaways and Test Your Knowledge sections - Fact-checked all complexity claims against authoritative sources --- docs/concepts/algorithms-big-o.mdx | 684 ++++++++++++++++-- .../algorithms-big-o/algorithms-big-o.test.js | 523 +++++++++++++ 2 files changed, 1146 insertions(+), 61 deletions(-) create mode 100644 tests/advanced-topics/algorithms-big-o/algorithms-big-o.test.js diff --git a/docs/concepts/algorithms-big-o.mdx b/docs/concepts/algorithms-big-o.mdx index 64221752..db6665c9 100644 --- a/docs/concepts/algorithms-big-o.mdx +++ b/docs/concepts/algorithms-big-o.mdx @@ -1,98 +1,660 @@ --- -title: "Algorithms & Big O: Problem Solving & Performance in JavaScript" -sidebarTitle: "Algorithms & Big O: Problem Solving & Performance" -description: "Learn algorithms and Big O notation in JavaScript — understand time/space complexity, common algorithms, and how to analyze and optimize your code's performance." +title: "Algorithms & Big O: Measuring Code Performance in JavaScript" +sidebarTitle: "Algorithms & Big O: Measuring Code Performance" +description: "Learn Big O notation and algorithms in JavaScript. Understand time complexity, implement searching and sorting algorithms, and recognize common interview patterns." --- -## Overview +Why does one solution pass all tests instantly while another times out? Why do interviewers care so much about "time complexity"? Consider these two functions that both find if an array contains duplicates: -**Algorithms** are step-by-step procedures for solving problems, while **Big O notation** is how we measure their efficiency. Understanding both is essential for writing performant code, passing technical interviews, and making informed decisions about data processing approaches. +```javascript +// Approach A: Nested loops +function hasDuplicatesA(arr) { + for (let i = 0; i < arr.length; i++) { + for (let j = i + 1; j < arr.length; j++) { + if (arr[i] === arr[j]) return true + } + } + return false +} + +// Approach B: Using a Set +function hasDuplicatesB(arr) { + return new Set(arr).size !== arr.length +} +``` + +Both work correctly. But with 100,000 elements, Approach A takes several seconds while Approach B finishes in milliseconds. The difference? **[Big O notation](https://en.wikipedia.org/wiki/Big_O_notation)**, which tells us how code performance scales with input size. <Info> **What you'll learn in this guide:** -- Big O notation and complexity analysis -- Time complexity vs space complexity -- Common complexities: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ) -- Sorting algorithms (bubble, merge, quick sort) -- Searching algorithms (linear, binary search) -- Common algorithm patterns (sliding window, two pointers, etc.) +- What Big O notation actually measures +- The common complexities: O(1), O(log n), O(n), O(n log n), O(n²) - How to analyze your own code's complexity +- JavaScript built-in methods and their complexity +- Implementing binary search and merge sort +- Common interview patterns: two pointers, sliding window, frequency counter </Info> +<Warning> +**Prerequisite:** This guide assumes you're familiar with [data structures](/concepts/data-structures) like arrays, objects, Maps, and Sets. You should also be comfortable with [recursion](/concepts/recursion) for the sorting algorithms section. +</Warning> + +--- + +## What is Big O Notation? + +**Big O notation** describes how an algorithm's runtime or space requirements grow as input size increases. It provides an upper bound on growth rate and ignores constants, giving us a way to compare algorithms regardless of hardware. + +### The Package Delivery Analogy + +Imagine you need to deliver packages to houses on a street: + +- **O(1)**: You have the exact address. Go directly there. Whether there are 10 or 10,000 houses, it takes the same time. +- **O(n)**: You check each house until you find the right one. More houses = proportionally more time. +- **O(n²)**: For each house, you compare it with every other house. 10 houses = 100 comparisons. 100 houses = 10,000 comparisons. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ HOW ALGORITHMS SCALE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Operations │ +│ ▲ │ +│ │ O(n²) │ +│ 1M │ •••• │ +│ │ ••• │ +│ │ ••• │ +│ │ ••• │ +│ 100K │ •••• O(n log n) │ +│ │ ••• ════════════ │ +│ │ •••• ═════ │ +│ │ •••• ═════ │ +│ 10K │ ••••• ═════ O(n) │ +│ │ •••• ═════ ────────────── │ +│ │ •••• ═════ ─────── │ +│ │ •••• ═════ ─────── O(log n) │ +│ 1K │•••• ═════ ────── ............ │ +│ │═════ ─────── ........ │ +│ │ ────── .......... O(1) │ +│ 100 │──── .......... ══════════════ │ +│ └──────────────────────────────────────────────────────────► │ +│ 100 1K 10K 100K 1M Input (n) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## The Big O Complexity Scale + +Here are the most common complexities you'll encounter, from fastest to slowest: + +| Complexity | Name | Example | 1,000 items | 1,000,000 items | +|------------|------|---------|-------------|-----------------| +| O(1) | Constant | Array access | 1 op | 1 op | +| O(log n) | Logarithmic | Binary search | ~10 ops | ~20 ops | +| O(n) | Linear | Simple loop | 1,000 ops | 1,000,000 ops | +| O(n log n) | Linearithmic | Merge sort | ~10,000 ops | ~20,000,000 ops | +| O(n²) | Quadratic | Nested loops | 1,000,000 ops | 1,000,000,000,000 ops | + +<AccordionGroup> + <Accordion title="O(1) - Constant Time"> + The operation takes the same time regardless of input size. + + ```javascript + // Array access by index + const arr = [1, 2, 3, 4, 5] + const element = arr[2] // O(1) - instant, no matter array size + + // Object/Map lookup + const user = { name: 'Alice', age: 30 } + const name = user.name // O(1) + + const map = new Map() + map.set('key', 'value') + map.get('key') // O(1) + + // Array push and pop (end operations) + arr.push(6) // O(1) + arr.pop() // O(1) + ``` + </Accordion> + + <Accordion title="O(log n) - Logarithmic Time"> + Time increases slowly as input grows. Each step eliminates half the remaining data. This is the "sweet spot" for searching sorted data. + + ```javascript + // Binary search - covered in detail below + // With 1,000,000 elements, only ~20 comparisons needed! + + // Think of it like a phone book: + // Open middle → wrong half eliminated → repeat + ``` + </Accordion> + + <Accordion title="O(n) - Linear Time"> + Time grows proportionally with input. If you double the input, you double the time. + + ```javascript + // Finding maximum value + function findMax(arr) { + let max = arr[0] + for (let i = 1; i < arr.length; i++) { // Visits each element once + if (arr[i] > max) max = arr[i] + } + return max + } + + // Most array methods are O(n) + arr.indexOf(5) // O(n) - may check every element + arr.includes(5) // O(n) + arr.find(x => x > 3) // O(n) + arr.filter(x => x > 3) // O(n) + arr.map(x => x * 2) // O(n) + ``` + </Accordion> + + <Accordion title="O(n log n) - Linearithmic Time"> + Common for efficient sorting algorithms. Faster than O(n²), but slower than O(n). + + ```javascript + // JavaScript's built-in sort is O(n log n) + const sorted = [...arr].sort((a, b) => a - b) + + // Merge sort and quick sort (average case) are also O(n log n) + ``` + </Accordion> + + <Accordion title="O(n²) - Quadratic Time"> + Time grows with the square of input. Nested loops over the same data are the typical culprit. **Avoid for large datasets.** + + ```javascript + // Checking all pairs + function findPairs(arr) { + const pairs = [] + for (let i = 0; i < arr.length; i++) { // O(n) + for (let j = i + 1; j < arr.length; j++) { // O(n) for each i + pairs.push([arr[i], arr[j]]) + } + } + return pairs // Total: O(n) × O(n) = O(n²) + } + + // Bubble sort - O(n²), mostly used for teaching + ``` + </Accordion> +</AccordionGroup> + +--- + +## How to Analyze Your Code + +Follow these steps to determine your code's complexity: + +<Steps> + <Step title="Identify the input size"> + What variable represents n? Usually it's array length or a number parameter. + </Step> + + <Step title="Count the loops"> + - One loop over n elements = O(n) + - Nested loops = multiply: O(n) × O(n) = O(n²) + - Loop that halves each time = O(log n) + </Step> + + <Step title="Drop constants and lower terms"> + - O(2n) → O(n) + - O(n² + n) → O(n²) + - O(500) → O(1) + </Step> +</Steps> + +```javascript +// Example analysis +function example(arr) { + console.log(arr[0]) // O(1) + + for (let i = 0; i < arr.length; i++) { // O(n) + console.log(arr[i]) + } + + for (let i = 0; i < arr.length; i++) { // O(n) + for (let j = 0; j < arr.length; j++) { // × O(n) + console.log(arr[i], arr[j]) + } + } +} +// Total: O(1) + O(n) + O(n²) = O(n²) +// The n² dominates, so we say this function is O(n²) +``` + +--- + +## JavaScript Built-in Methods Complexity + +Knowing these helps you make better decisions: + +### Array Methods + +| Method | Complexity | Why | +|--------|------------|-----| +| `push()`, `pop()` | O(1) | End operations, no shifting | +| `shift()`, `unshift()` | O(n) | Must re-index all elements | +| `splice()` | O(n) | May shift elements | +| `slice()` | O(n) | Creates copy of portion | +| `indexOf()`, `includes()` | O(n) | Linear search | +| `find()`, `findIndex()` | O(n) | Linear search | +| `map()`, `filter()`, `forEach()` | O(n) | Visits each element | +| `sort()` | O(n log n) | Implementation varies by browser | + +### Object, Map, and Set + +| Operation | Object | Map | Set | +|-----------|--------|-----|-----| +| Get/Set/Has | O(1) | O(1) | O(1) | +| Delete | O(1) | O(1) | O(1) | +| Keys/Values | O(n) | O(n) | O(n) | + +<Tip> +**Use Set.has() instead of Array.includes()** when checking membership repeatedly. Set lookups are O(1) while array searches are O(n). +</Tip> + +--- + +## Searching Algorithms + +### Linear Search - O(n) + +Check each element one by one. Simple but slow for large arrays. + +```javascript +function linearSearch(arr, target) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === target) return i + } + return -1 +} + +linearSearch([3, 7, 1, 9, 4], 9) // Returns 3 +``` + +### Binary Search - O(log n) + +Divide and conquer on a **sorted** array. Eliminates half the remaining elements each step. + +```javascript +function binarySearch(arr, target) { + let left = 0 + let right = arr.length - 1 + + while (left <= right) { + const mid = Math.floor((left + right) / 2) + + if (arr[mid] === target) return mid + if (arr[mid] < target) left = mid + 1 + else right = mid - 1 + } + + return -1 +} + +binarySearch([1, 3, 5, 7, 9, 11, 13], 9) // Returns 4 +``` + +<Warning> +**Binary search requires a sorted array.** If your data isn't sorted, you'll need to sort it first O(n log n) or use linear search. +</Warning> + +--- + +## Sorting Algorithms + +### Quick Reference + +| Algorithm | Best | Average | Worst | Space | Use When | +|-----------|------|---------|-------|-------|----------| +| Bubble Sort | O(n)* | O(n²) | O(n²) | O(1) | Never in production | +| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Need guaranteed performance | +| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n)** | General purpose | +| JS `sort()` | O(n log n) | O(n log n) | O(n log n) | O(n) | Most cases | + +*Bubble sort achieves O(n) best case only with early termination optimization (when no swaps needed). +**Quick sort space is O(log n) average case, O(n) worst case due to recursion stack depth. + +### Bubble Sort - O(n²) + +Repeatedly swaps adjacent elements if they're in wrong order. Educational, but too slow for real use. + +```javascript +function bubbleSort(arr) { + const result = [...arr] + const n = result.length + + for (let i = 0; i < n; i++) { + let swapped = false + + for (let j = 0; j < n - i - 1; j++) { + if (result[j] > result[j + 1]) { + [result[j], result[j + 1]] = [result[j + 1], result[j]] + swapped = true + } + } + + // If no swaps occurred, array is sorted + if (!swapped) break + } + + return result +} +``` + +### Merge Sort - O(n log n) + +Divide array in half, sort each half, merge them back. Consistent performance with guaranteed O(n log n). + +```javascript +function mergeSort(arr) { + if (arr.length <= 1) return arr + + const mid = Math.floor(arr.length / 2) + const left = mergeSort(arr.slice(0, mid)) + const right = mergeSort(arr.slice(mid)) + + return merge(left, right) +} + +function merge(left, right) { + const result = [] + let i = 0 + let j = 0 + + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i]) + i++ + } else { + result.push(right[j]) + j++ + } + } + + return result.concat(left.slice(i)).concat(right.slice(j)) +} + +mergeSort([38, 27, 43, 3, 9, 82, 10]) // [3, 9, 10, 27, 38, 43, 82] +``` + +<Note> +**In practice, use JavaScript's built-in `sort()`**. Modern browsers typically use Timsort (V8) or similar O(n log n) algorithms optimized for real-world data. Implement your own sorts for learning or when you have specific requirements. +</Note> + +--- + +## Common Interview Patterns + +These patterns solve many algorithm problems efficiently. + +### Two Pointers - O(n) + +Use two pointers moving toward each other or in the same direction. Great for sorted arrays and finding pairs. + +```javascript +// Find pair that sums to target in sorted array +function twoSum(arr, target) { + let left = 0 + let right = arr.length - 1 + + while (left < right) { + const sum = arr[left] + arr[right] + + if (sum === target) return [left, right] + if (sum < target) left++ + else right-- + } + + return null +} + +twoSum([1, 3, 5, 7, 9], 12) // [1, 4] (3 + 9 = 12) +``` + +### Sliding Window - O(n) + +Maintain a "window" that slides through the array. Perfect for subarray problems. + +```javascript +// Maximum sum of k consecutive elements +function maxSumSubarray(arr, k) { + if (arr.length < k) return null + + // Calculate first window + let windowSum = 0 + for (let i = 0; i < k; i++) { + windowSum += arr[i] + } + + let maxSum = windowSum + + // Slide the window: remove left element, add right element + for (let i = k; i < arr.length; i++) { + windowSum = windowSum - arr[i - k] + arr[i] + maxSum = Math.max(maxSum, windowSum) + } + + return maxSum +} + +maxSumSubarray([2, 1, 5, 1, 3, 2], 3) // 9 (5 + 1 + 3) +``` + +### Frequency Counter - O(n) + +Use an object or Map to count occurrences. Avoids nested loops when comparing collections. + +```javascript +// Check if two strings are anagrams +function isAnagram(str1, str2) { + if (str1.length !== str2.length) return false + + const freq = {} + + // Count characters in first string + for (const char of str1) { + freq[char] = (freq[char] || 0) + 1 + } + + // Subtract counts for second string + for (const char of str2) { + if (!freq[char]) return false + freq[char]-- + } + + return true +} + +isAnagram('listen', 'silent') // true +isAnagram('hello', 'world') // false +``` + +--- + +## Key Takeaways + <Info> -Common Big O complexities from fastest to slowest: O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n²) quadratic, O(2ⁿ) exponential. +**The key things to remember:** + +1. **Big O measures scalability**, not absolute speed. It tells you how performance changes as input grows. + +2. **O(1) and O(log n) are fast**. O(n) is acceptable. O(n²) gets slow quickly. Avoid O(2^n) for any significant input. + +3. **Nested loops multiply complexity**. Two nested loops over n = O(n²). Three = O(n³). + +4. **Drop constants and lower terms**. O(2n + 100) simplifies to O(n). + +5. **Array end operations are O(1)**, beginning operations are O(n). Prefer `push`/`pop` over `shift`/`unshift`. + +6. **Use Set for O(1) lookups** instead of `Array.includes()` for repeated membership checks. + +7. **Binary search is O(log n)** but requires sorted data. Worth sorting first if you'll search multiple times. + +8. **JavaScript's sort() is O(n log n)** in all modern browsers. Use it unless you have specific requirements. + +9. **Learn the patterns**: Two pointers, sliding window, and frequency counter solve most interview problems. + +10. **Space complexity matters too**. Creating a new array of size n uses O(n) space. </Info> -## Reference +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the time complexity of this code?"> + ```javascript + function mystery(arr) { + for (let i = 0; i < arr.length; i++) { + for (let j = 0; j < 10; j++) { + console.log(arr[i]) + } + } + } + ``` + + **Answer:** O(n) + + The outer loop runs n times, but the inner loop always runs exactly 10 times (constant). So it's O(n × 10) = O(10n) = O(n). The constant 10 is dropped. + </Accordion> + + <Accordion title="Question 2: Why is binary search O(log n)?"> + **Answer:** Because each comparison eliminates half the remaining elements. + + With 1,000 elements: 1000 → 500 → 250 → 125 → 62 → 31 → 15 → 7 → 3 → 1 + + That's about 10 steps. log₂(1000) ≈ 10. With 1,000,000 elements, it only takes ~20 steps. + </Accordion> + + <Accordion title="Question 3: Array has 1 million elements. Which is faster: indexOf() once, or converting to Set then using has()?"> + **Answer:** It depends on how many lookups you need. + + - **One lookup**: `indexOf()` is faster. O(n) vs O(n) for Set creation + O(1) for lookup. + - **Many lookups**: Convert to Set first. O(n) creation + O(1) per lookup beats O(n) per lookup. + + Rule of thumb: If you'll search more than once, use a Set. + </Accordion> + + <Accordion title="Question 4: What's wrong with using bubble sort?"> + **Answer:** It's O(n²), making it impractical for large datasets. + + With 10,000 elements: ~100,000,000 operations. JavaScript's built-in sort() at O(n log n) would take ~130,000 operations for the same data. + + Bubble sort is useful for learning but should never be used in production code. + </Accordion> + + <Accordion title="Question 5: How would you find if an array has duplicates in O(n) time?"> + **Answer:** Use a Set to track seen elements: + + ```javascript + function hasDuplicates(arr) { + const seen = new Set() + for (const item of arr) { + if (seen.has(item)) return true // O(1) lookup + seen.add(item) // O(1) insert + } + return false + } + ``` + + This is O(n) time and O(n) space. The naive nested loop approach would be O(n²) time but O(1) space. + </Accordion> + + <Accordion title="Question 6: What pattern would you use to find the longest substring without repeating characters?"> + **Answer:** **Sliding window** with a Set or Map. + + ```javascript + function longestUniqueSubstring(s) { + const seen = new Set() + let maxLen = 0 + let left = 0 + + for (let right = 0; right < s.length; right++) { + while (seen.has(s[right])) { + seen.delete(s[left]) + left++ + } + seen.add(s[right]) + maxLen = Math.max(maxLen, right - left + 1) + } + + return maxLen + } + ``` + + Time: O(n), Space: O(min(n, alphabet size)) + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="Data Structures and Algorithms using ES6" icon="newspaper" href="https://github.com/Crizstian/data-structure-and-algorithms-with-ES6"> - GitHub Repository + <Card title="Data Structures" icon="database" href="/concepts/data-structures"> + Understanding arrays, objects, Maps, and Sets is essential for choosing the right tool + </Card> + <Card title="Recursion" icon="repeat" href="/concepts/recursion"> + Many algorithms like merge sort and binary search can be implemented recursively </Card> - <Card title="Algorithms and data structures implemented in JavaScript" icon="newspaper" href="https://github.com/trekhleb/javascript-algorithms"> - By Oleksii Trekhleb + <Card title="Higher-Order Functions" icon="layer-group" href="/concepts/higher-order-functions"> + Map, filter, and reduce are built on these concepts + </Card> + <Card title="Map, Reduce, Filter" icon="filter" href="/concepts/map-reduce-filter"> + Understanding the complexity of these common operations </Card> </CardGroup> -## Articles +--- -### Big O Notation +## Reference <CardGroup cols={2}> - <Card title="Big O Notation in Javascript" icon="newspaper" href="https://medium.com/cesars-tech-insights/big-o-notation-javascript-25c79f50b19b"> - By César Antón Dorantes + <Card title="Array — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array"> + Complete reference for array methods and their behavior + </Card> + <Card title="Map — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"> + Hash-based key-value storage with fast lookups </Card> - <Card title="Time Complexity/Big O Notation" icon="newspaper" href="https://medium.com/javascript-scene/time-complexity-big-o-notation-1a4310c3ee4b"> - By Tim Roberts + <Card title="Set — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set"> + O(1) operations for membership testing </Card> </CardGroup> -- [Big O in JavaScript — Gabriela Medina](https://medium.com/@gmedina229/big-o-in-javascript-36ff67766051) -- [Big O Search Algorithms in JavaScript — Bradley Braithwaite](https://www.bradoncode.com/blog/2012/04/big-o-algorithm-examples-in-javascript.html) -- [Algorithms in plain English: time complexity and Big-O Notation — Michael Olorunnisola](https://medium.freecodecamp.org/time-is-complex-but-priceless-f0abd015063c) -- [An Introduction to Big O Notation — Joseph Trettevik](https://dev.to/lofiandcode/an-introduction-to-big-o-notation-210o) - -### Algorithms - -- [JS: Interview Algorithm](http://www.thatjsdude.com/interview/js1.html) -- [Algorithms in JavaScript — Thon Ly](https://medium.com/siliconwat/algorithms-in-javascript-b0bed68f4038) -- [JavaScript Objects, Square Brackets and Algorithms — Dmitri Grabov](https://medium.freecodecamp.org/javascript-objects-square-brackets-and-algorithms-e9a2916dc158) -- [Atwood's Law applied to CS101 - Classic algorithms and data structures implemented in JavaScript](https://github.com/felipernb/algorithms.js) -- [Data Structures and Algorithms library in JavaScript](https://github.com/yangshun/lago) -- [Collection of computer science algorithms and data structures written in JavaScript](https://github.com/idosela/algorithms-in-javascript) -- [Algorithms and Data Structures in JavaScript — Oleksii Trekhleb](https://dev.to/trekhleb/algorithms-and-data-structures-in-javascript-49i3) - -## Videos - -### Big O Notation +## Articles <CardGroup cols={2}> - <Card title="JavaScript: Intro to Big O Notation and Function Runtime" icon="video" href="https://www.youtube.com/watch?v=HgA5VOFan5E"> - By Eric Traub + <Card title="Big O Cheat Sheet" icon="newspaper" href="https://www.bigocheatsheet.com/"> + Visual reference for time and space complexity of common algorithms and data structures. Bookmark this one. + </Card> + <Card title="JavaScript Algorithms and Data Structures" icon="newspaper" href="https://github.com/trekhleb/javascript-algorithms"> + Comprehensive GitHub repo with 190k+ stars. Every algorithm implemented in JavaScript with explanations. </Card> - <Card title="Learn Big O Notation In 12 Minutes" icon="video" href="https://www.youtube.com/watch?v=itn09C2ZB9Y"> - By Web Dev Simplified + <Card title="Time Complexity of JavaScript Array Methods" icon="newspaper" href="https://dev.to/lukocastillo/time-complexity-big-0-for-javascript-array-methods-and-examples-mlg"> + Detailed breakdown of every array method's complexity with examples and explanations. + </Card> + <Card title="Algorithms in Plain English" icon="newspaper" href="https://www.freecodecamp.org/news/time-is-complex-but-priceless-f0abd015063c/"> + FreeCodeCamp's beginner-friendly guide to Big O with real-world analogies. </Card> </CardGroup> -- [Essential Big O for JavaScript Developers — Dave Smith](https://www.youtube.com/watch?v=KatlvCFHPRo) -- [Big O Notation - Time Complexity Analysis — WebTunings](https://www.youtube.com/watch?v=ALl86xJiTD8) -- [JavaScript Algorithms: Big-O Notation - Codevolution](https://www.youtube.com/watch?v=3yUuo7TqMW8) -- [JavaScript Algorithms Crash Course: Learn Algorithms & "Big O" from the Ground Up! - Academind](https://www.youtube.com/watch?v=JgWm6sQwS_I) -- [Big O Notation - Data Structures and Algorithms in Javascript - RoadSideCoder](https://www.youtube.com/watch?v=LaexPVi1VRE) - -### Algorithms +## Videos <CardGroup cols={2}> - <Card title="JavaScript Algorithms" icon="video" href="https://www.youtube.com/playlist?list=PLC3y8-rFHvwiRYB4-HHKHblh3_bQNJTMa"> - By Codevolution + <Card title="Big O Notation in 12 Minutes" icon="video" href="https://www.youtube.com/watch?v=itn09C2ZB9Y"> + Web Dev Simplified's concise explanation. Perfect if you want the essentials without filler. + </Card> + <Card title="JavaScript Algorithms Playlist" icon="video" href="https://www.youtube.com/playlist?list=PLC3y8-rFHvwiRYB4-HHKHblh3_bQNJTMa"> + Codevolution's complete series covering sorting, searching, and common patterns step by step. </Card> - <Card title="Dynamic Programming - Learn to Solve Algorithmic Problems" icon="video" href="https://www.youtube.com/watch?v=oBt53YbR9Kk&t=1021s"> - By FreeCodeCamp + <Card title="Data Structures and Algorithms in JavaScript" icon="video" href="https://www.youtube.com/watch?v=Gj5qBheGOEo&list=PLWKjhJtqVAbkso-IbgiiP48n-O-JQA9PJ"> + FreeCodeCamp's comprehensive 8-hour course. Great for deep learning when you have the time. </Card> </CardGroup> - -- [Data Structures and Algorithms in Javascript | DSA with JS - RoadsideCoder](https://www.youtube.com/playlist?list=PLKhlp2qtUcSZtJefDThsXcsAbRBCSTgW4) -- [Javascript Algorithms + Data Structures - KodingKevin](https://www.youtube.com/playlist?list=PLn2ipk-jqgZiAHiA70hOxAj8RMUeqYNK3) -- [JavaScript Data Structures: Getting Started - Academind](https://www.youtube.com/watch?v=41GSinwoMYA) -- [Algorithms and Data Structures - The Coding Train (Daniel Shiffman)](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH) diff --git a/tests/advanced-topics/algorithms-big-o/algorithms-big-o.test.js b/tests/advanced-topics/algorithms-big-o/algorithms-big-o.test.js new file mode 100644 index 00000000..94a934de --- /dev/null +++ b/tests/advanced-topics/algorithms-big-o/algorithms-big-o.test.js @@ -0,0 +1,523 @@ +import { describe, it, expect } from 'vitest' + +// ============================================ +// SEARCHING ALGORITHMS +// ============================================ + +// Linear Search - O(n) +function linearSearch(arr, target) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === target) return i + } + return -1 +} + +// Binary Search - O(log n) +function binarySearch(arr, target) { + let left = 0 + let right = arr.length - 1 + + while (left <= right) { + const mid = Math.floor((left + right) / 2) + + if (arr[mid] === target) return mid + if (arr[mid] < target) left = mid + 1 + else right = mid - 1 + } + + return -1 +} + +// ============================================ +// SORTING ALGORITHMS +// ============================================ + +// Bubble Sort - O(n²) average/worst, O(n) best with early termination +function bubbleSort(arr) { + const result = [...arr] + const n = result.length + + for (let i = 0; i < n; i++) { + let swapped = false + + for (let j = 0; j < n - i - 1; j++) { + if (result[j] > result[j + 1]) { + [result[j], result[j + 1]] = [result[j + 1], result[j]] + swapped = true + } + } + + // If no swaps occurred, array is sorted + if (!swapped) break + } + + return result +} + +// Merge Sort - O(n log n) +function mergeSort(arr) { + if (arr.length <= 1) return arr + + const mid = Math.floor(arr.length / 2) + const left = mergeSort(arr.slice(0, mid)) + const right = mergeSort(arr.slice(mid)) + + return merge(left, right) +} + +function merge(left, right) { + const result = [] + let i = 0 + let j = 0 + + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i]) + i++ + } else { + result.push(right[j]) + j++ + } + } + + return result.concat(left.slice(i)).concat(right.slice(j)) +} + +// ============================================ +// INTERVIEW PATTERNS +// ============================================ + +// Two Pointers - Find pair that sums to target +function twoSum(arr, target) { + let left = 0 + let right = arr.length - 1 + + while (left < right) { + const sum = arr[left] + arr[right] + + if (sum === target) return [left, right] + if (sum < target) left++ + else right-- + } + + return null +} + +// Sliding Window - Maximum sum of k consecutive elements +function maxSumSubarray(arr, k) { + if (arr.length < k) return null + + let windowSum = 0 + for (let i = 0; i < k; i++) { + windowSum += arr[i] + } + + let maxSum = windowSum + + for (let i = k; i < arr.length; i++) { + windowSum = windowSum - arr[i - k] + arr[i] + maxSum = Math.max(maxSum, windowSum) + } + + return maxSum +} + +// Frequency Counter - Check anagrams +function isAnagram(str1, str2) { + if (str1.length !== str2.length) return false + + const freq = {} + + for (const char of str1) { + freq[char] = (freq[char] || 0) + 1 + } + + for (const char of str2) { + if (!freq[char]) return false + freq[char]-- + } + + return true +} + +// Has Duplicates - O(n) with Set +function hasDuplicates(arr) { + const seen = new Set() + for (const item of arr) { + if (seen.has(item)) return true + seen.add(item) + } + return false +} + +// Longest Unique Substring - Sliding Window +function longestUniqueSubstring(s) { + const seen = new Set() + let maxLen = 0 + let left = 0 + + for (let right = 0; right < s.length; right++) { + while (seen.has(s[right])) { + seen.delete(s[left]) + left++ + } + seen.add(s[right]) + maxLen = Math.max(maxLen, right - left + 1) + } + + return maxLen +} + +// ============================================ +// TESTS +// ============================================ + +describe('Algorithms & Big O', () => { + describe('Searching Algorithms', () => { + describe('Linear Search', () => { + it('should find element at beginning', () => { + expect(linearSearch([1, 2, 3, 4, 5], 1)).toBe(0) + }) + + it('should find element at end', () => { + expect(linearSearch([1, 2, 3, 4, 5], 5)).toBe(4) + }) + + it('should find element in middle', () => { + expect(linearSearch([3, 7, 1, 9, 4], 9)).toBe(3) + }) + + it('should return -1 when element not found', () => { + expect(linearSearch([1, 2, 3, 4, 5], 10)).toBe(-1) + }) + + it('should handle empty array', () => { + expect(linearSearch([], 1)).toBe(-1) + }) + + it('should find first occurrence of duplicates', () => { + expect(linearSearch([1, 2, 3, 2, 5], 2)).toBe(1) + }) + }) + + describe('Binary Search', () => { + it('should find element in sorted array', () => { + expect(binarySearch([1, 3, 5, 7, 9, 11, 13], 9)).toBe(4) + }) + + it('should find first element', () => { + expect(binarySearch([1, 3, 5, 7, 9], 1)).toBe(0) + }) + + it('should find last element', () => { + expect(binarySearch([1, 3, 5, 7, 9], 9)).toBe(4) + }) + + it('should return -1 when element not found', () => { + expect(binarySearch([1, 3, 5, 7, 9], 6)).toBe(-1) + }) + + it('should handle single element array - found', () => { + expect(binarySearch([5], 5)).toBe(0) + }) + + it('should handle single element array - not found', () => { + expect(binarySearch([5], 3)).toBe(-1) + }) + + it('should handle empty array', () => { + expect(binarySearch([], 5)).toBe(-1) + }) + + it('should work with large sorted array', () => { + const arr = Array.from({ length: 1000 }, (_, i) => i * 2) // [0, 2, 4, ..., 1998] + expect(binarySearch(arr, 500)).toBe(250) + expect(binarySearch(arr, 501)).toBe(-1) + }) + }) + }) + + describe('Sorting Algorithms', () => { + describe('Bubble Sort', () => { + it('should sort array in ascending order', () => { + expect(bubbleSort([5, 3, 8, 4, 2])).toEqual([2, 3, 4, 5, 8]) + }) + + it('should handle already sorted array', () => { + expect(bubbleSort([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]) + }) + + it('should handle reverse sorted array', () => { + expect(bubbleSort([5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5]) + }) + + it('should handle array with duplicates', () => { + expect(bubbleSort([3, 1, 4, 1, 5, 9, 2, 6])).toEqual([1, 1, 2, 3, 4, 5, 6, 9]) + }) + + it('should handle single element', () => { + expect(bubbleSort([42])).toEqual([42]) + }) + + it('should handle empty array', () => { + expect(bubbleSort([])).toEqual([]) + }) + + it('should not mutate original array', () => { + const original = [3, 1, 4, 1, 5] + bubbleSort(original) + expect(original).toEqual([3, 1, 4, 1, 5]) + }) + + it('should handle negative numbers', () => { + expect(bubbleSort([-3, -1, -4, -1, -5])).toEqual([-5, -4, -3, -1, -1]) + }) + + it('should terminate early on already sorted array (O(n) best case)', () => { + // This test verifies the early termination optimization works + // On an already sorted array, only one pass is needed + const sorted = [1, 2, 3, 4, 5] + expect(bubbleSort(sorted)).toEqual([1, 2, 3, 4, 5]) + }) + }) + + describe('Merge Sort', () => { + it('should sort array in ascending order', () => { + expect(mergeSort([38, 27, 43, 3, 9, 82, 10])).toEqual([3, 9, 10, 27, 38, 43, 82]) + }) + + it('should handle already sorted array', () => { + expect(mergeSort([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]) + }) + + it('should handle reverse sorted array', () => { + expect(mergeSort([5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5]) + }) + + it('should handle array with duplicates', () => { + expect(mergeSort([3, 1, 4, 1, 5, 9, 2, 6])).toEqual([1, 1, 2, 3, 4, 5, 6, 9]) + }) + + it('should handle single element', () => { + expect(mergeSort([42])).toEqual([42]) + }) + + it('should handle empty array', () => { + expect(mergeSort([])).toEqual([]) + }) + + it('should handle negative numbers', () => { + expect(mergeSort([-3, 1, -4, 1, -5, 9])).toEqual([-5, -4, -3, 1, 1, 9]) + }) + + it('should maintain stability for equal elements', () => { + // Merge sort is stable - equal elements maintain relative order + const result = mergeSort([3, 1, 2, 1]) + expect(result).toEqual([1, 1, 2, 3]) + }) + }) + }) + + describe('Interview Patterns', () => { + describe('Two Pointers - Two Sum', () => { + it('should find pair that sums to target', () => { + expect(twoSum([1, 3, 5, 7, 9], 12)).toEqual([1, 4]) // 3 + 9 = 12 + }) + + it('should find pair at extremes', () => { + expect(twoSum([1, 2, 3, 4, 5], 6)).toEqual([0, 4]) // 1 + 5 = 6 + }) + + it('should find adjacent pair', () => { + expect(twoSum([1, 2, 3, 4, 5], 9)).toEqual([3, 4]) // 4 + 5 = 9 + }) + + it('should return null when no pair exists', () => { + expect(twoSum([1, 2, 3, 4, 5], 100)).toBe(null) + }) + + it('should handle minimum size array', () => { + expect(twoSum([3, 7], 10)).toEqual([0, 1]) + }) + + it('should return null for single element', () => { + expect(twoSum([5], 10)).toBe(null) + }) + }) + + describe('Sliding Window - Max Sum Subarray', () => { + it('should find maximum sum of k consecutive elements', () => { + expect(maxSumSubarray([2, 1, 5, 1, 3, 2], 3)).toBe(9) // 5 + 1 + 3 + }) + + it('should handle window at beginning', () => { + expect(maxSumSubarray([9, 1, 1, 1, 1, 1], 2)).toBe(10) // 9 + 1 + }) + + it('should handle window at end', () => { + expect(maxSumSubarray([1, 1, 1, 1, 9, 8], 2)).toBe(17) // 9 + 8 + }) + + it('should handle k equal to array length', () => { + expect(maxSumSubarray([1, 2, 3], 3)).toBe(6) + }) + + it('should return null if array shorter than k', () => { + expect(maxSumSubarray([1, 2], 3)).toBe(null) + }) + + it('should handle negative numbers', () => { + expect(maxSumSubarray([-1, -2, 5, 6, -3], 2)).toBe(11) // 5 + 6 + }) + + it('should handle all negative numbers', () => { + expect(maxSumSubarray([-5, -3, -8, -2], 2)).toBe(-8) // -5 + -3 = -8 is max + }) + }) + + describe('Frequency Counter - Anagram Check', () => { + it('should return true for valid anagrams', () => { + expect(isAnagram('listen', 'silent')).toBe(true) + }) + + it('should return true for same string', () => { + expect(isAnagram('hello', 'hello')).toBe(true) + }) + + it('should return false for different lengths', () => { + expect(isAnagram('hello', 'helloo')).toBe(false) + }) + + it('should return false for non-anagrams', () => { + expect(isAnagram('hello', 'world')).toBe(false) + }) + + it('should handle empty strings', () => { + expect(isAnagram('', '')).toBe(true) + }) + + it('should be case sensitive', () => { + expect(isAnagram('Listen', 'Silent')).toBe(false) + }) + + it('should handle repeated characters', () => { + expect(isAnagram('aab', 'baa')).toBe(true) + expect(isAnagram('aab', 'bba')).toBe(false) + }) + + it('should handle single characters', () => { + expect(isAnagram('a', 'a')).toBe(true) + expect(isAnagram('a', 'b')).toBe(false) + }) + }) + + describe('Has Duplicates', () => { + it('should return true when duplicates exist', () => { + expect(hasDuplicates([1, 2, 3, 2, 5])).toBe(true) + }) + + it('should return false when no duplicates', () => { + expect(hasDuplicates([1, 2, 3, 4, 5])).toBe(false) + }) + + it('should handle empty array', () => { + expect(hasDuplicates([])).toBe(false) + }) + + it('should handle single element', () => { + expect(hasDuplicates([1])).toBe(false) + }) + + it('should detect duplicates at beginning', () => { + expect(hasDuplicates([1, 1, 2, 3, 4])).toBe(true) + }) + + it('should detect duplicates at end', () => { + expect(hasDuplicates([1, 2, 3, 4, 4])).toBe(true) + }) + + it('should work with strings', () => { + expect(hasDuplicates(['a', 'b', 'c', 'a'])).toBe(true) + expect(hasDuplicates(['a', 'b', 'c', 'd'])).toBe(false) + }) + }) + + describe('Longest Unique Substring', () => { + it('should find longest substring without repeating characters', () => { + expect(longestUniqueSubstring('abcabcbb')).toBe(3) // "abc" + }) + + it('should handle all same characters', () => { + expect(longestUniqueSubstring('bbbbb')).toBe(1) + }) + + it('should handle unique characters at end', () => { + expect(longestUniqueSubstring('pwwkew')).toBe(3) // "wke" + }) + + it('should handle empty string', () => { + expect(longestUniqueSubstring('')).toBe(0) + }) + + it('should handle single character', () => { + expect(longestUniqueSubstring('a')).toBe(1) + }) + + it('should handle all unique characters', () => { + expect(longestUniqueSubstring('abcdef')).toBe(6) + }) + + it('should handle repeating pattern', () => { + expect(longestUniqueSubstring('abab')).toBe(2) + }) + }) + }) + + describe('Big O Concepts', () => { + describe('Array operations complexity', () => { + it('should demonstrate O(1) array access', () => { + const arr = [1, 2, 3, 4, 5] + // Direct index access is O(1) + expect(arr[0]).toBe(1) + expect(arr[4]).toBe(5) + expect(arr[2]).toBe(3) + }) + + it('should demonstrate O(1) push and pop', () => { + const arr = [1, 2, 3] + arr.push(4) // O(1) + expect(arr).toEqual([1, 2, 3, 4]) + const popped = arr.pop() // O(1) + expect(popped).toBe(4) + expect(arr).toEqual([1, 2, 3]) + }) + + it('should demonstrate O(n) shift and unshift', () => { + const arr = [1, 2, 3] + // These are O(n) because they require re-indexing all elements + arr.unshift(0) + expect(arr).toEqual([0, 1, 2, 3]) + const shifted = arr.shift() + expect(shifted).toBe(0) + expect(arr).toEqual([1, 2, 3]) + }) + }) + + describe('Set vs Array for lookups', () => { + it('should demonstrate Set.has() is faster than Array.includes() for repeated lookups', () => { + const arr = Array.from({ length: 1000 }, (_, i) => i) + const set = new Set(arr) + + // Both find the element, but Set.has() is O(1) vs Array.includes() O(n) + expect(arr.includes(500)).toBe(true) + expect(set.has(500)).toBe(true) + + expect(arr.includes(999)).toBe(true) + expect(set.has(999)).toBe(true) + + expect(arr.includes(1001)).toBe(false) + expect(set.has(1001)).toBe(false) + }) + }) + }) +}) From 77a6c6d9deb1c04ea5e7bb0b6ad4779355408021 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 17:07:07 -0300 Subject: [PATCH 122/128] feat: add comprehensive Design Patterns concept page with tests - Cover 6 practical JS patterns: Module, Singleton, Factory, Observer, Proxy, Decorator - Add Note clarifying Module is JS-specific, not a GoF pattern - Include ASCII diagrams, real-world analogies, and code examples - Add common mistakes section and pattern selection guide - Include 23 passing tests covering all patterns - Add Key Takeaways, Test Your Knowledge Q&A, and curated resources --- docs/concepts/design-patterns.mdx | 1131 ++++++++++++++++- .../design-patterns/design-patterns.test.js | 600 +++++++++ 2 files changed, 1698 insertions(+), 33 deletions(-) create mode 100644 tests/advanced-topics/design-patterns/design-patterns.test.js diff --git a/docs/concepts/design-patterns.mdx b/docs/concepts/design-patterns.mdx index f19bff56..c6cf3578 100644 --- a/docs/concepts/design-patterns.mdx +++ b/docs/concepts/design-patterns.mdx @@ -1,57 +1,1122 @@ --- -title: "Design Patterns: Proven Solutions in JavaScript" -sidebarTitle: "Design Patterns: Proven Solutions" -description: "Learn JavaScript design patterns — Singleton, Factory, Observer, Module, and more. Master proven solutions to common programming problems for maintainable code." +title: "Design Patterns: Reusable Solutions to Common Problems in JavaScript" +sidebarTitle: "Design Patterns: Reusable Solutions" +description: "Learn JavaScript design patterns like Module, Singleton, Observer, Factory, Proxy, and Decorator. Understand when to use each pattern and avoid common pitfalls." --- -## Overview +Ever find yourself solving the same problem over and over? What if experienced developers already figured out the best solutions to these recurring challenges? -**Design patterns** are reusable solutions to commonly occurring problems in software design. They represent best practices evolved over time by experienced developers. Understanding design patterns helps you write more maintainable, scalable, and efficient code. +```javascript +// The Observer pattern — notify multiple listeners when something happens +const newsletter = { + subscribers: [], + + subscribe(callback) { + this.subscribers.push(callback) + }, + + publish(article) { + this.subscribers.forEach(callback => callback(article)) + } +} -## Books +// Anyone can subscribe +newsletter.subscribe(article => console.log(`New article: ${article}`)) +newsletter.subscribe(article => console.log(`Saving "${article}" for later`)) + +// When we publish, all subscribers get notified +newsletter.publish("Design Patterns in JavaScript") +// "New article: Design Patterns in JavaScript" +// "Saving "Design Patterns in JavaScript" for later" +``` + +**Design patterns** are proven solutions to common problems in software design. They're not code you copy-paste. They're templates, blueprints, or recipes that you adapt to solve specific problems in your own code. Learning patterns gives you a vocabulary to discuss solutions with other developers and helps you recognize when a well-known solution fits your problem. + +<Info> +**What you'll learn in this guide:** +- What design patterns are and why they matter +- The Module pattern for organizing code with private state +- The Singleton pattern (and why it's often unnecessary in JavaScript) +- The Factory pattern for creating objects dynamically +- The Observer pattern for event-driven programming +- The Proxy pattern for controlling object access +- The Decorator pattern for adding behavior without modification +- How to choose the right pattern for your problem +</Info> + +<Warning> +**Prerequisites:** This guide assumes you understand [Factories and Classes](/concepts/factories-classes) and [IIFE, Modules & Namespaces](/concepts/iife-modules). Design patterns build on these object-oriented and modular programming concepts. +</Warning> + +--- + +## The Toolkit Analogy + +Think of design patterns like specialized tools in a toolkit. A general-purpose hammer works for many tasks, but sometimes you need a specific tool: a Phillips screwdriver for certain screws, a wrench for bolts, or pliers for gripping. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DESIGN PATTERNS TOOLKIT │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ CREATIONAL STRUCTURAL BEHAVIORAL │ +│ ─────────── ────────── ────────── │ +│ How objects How objects How objects │ +│ are created are composed communicate │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Singleton │ │ Proxy │ │ Observer │ │ +│ │ Factory │ │ Decorator │ │ │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ +│ Use when you need Use when you need Use when objects │ +│ to control object to wrap or extend need to react to │ +│ creation objects changes in others │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ MODULE (JS-specific) — Encapsulates code with private state │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +You don't use every tool for every job. Similarly, you don't use every pattern in every project. The skill is recognizing when a pattern fits your problem. + +--- + +## What Are Design Patterns? + +Design patterns are typical solutions to commonly occurring problems in software design. The term was popularized by the "Gang of Four" (GoF) in their 1994 book *Design Patterns: Elements of Reusable Object-Oriented Software*. They catalogued 23 patterns that developers kept reinventing. + +### Why JavaScript Is Different + +The original GoF patterns were written for languages like C++ and Smalltalk. JavaScript is different: + +| Feature | Impact on Patterns | +|---------|-------------------| +| **First-class functions** | Many patterns simplify to just passing functions around | +| **Prototypal inheritance** | No need for complex class hierarchies | +| **ES Modules** | Built-in module system replaces manual Module pattern | +| **Dynamic typing** | No need for interface abstractions | +| **Closures** | Natural way to create private state | + +This means some classical patterns are overkill in JavaScript, while others become more elegant. We'll focus on the patterns that are genuinely useful in modern JavaScript. + +### The Three Categories + +The original GoF patterns are grouped into three categories: + +1. **Creational Patterns** — Control how objects are created + - Singleton, Factory Method, Abstract Factory, Builder, Prototype + +2. **Structural Patterns** — Control how objects are composed + - Proxy, Decorator, Adapter, Facade, Bridge, Composite, Flyweight + +3. **Behavioral Patterns** — Control how objects communicate + - Observer, Strategy, Command, Mediator, Iterator, State, and others + +<Note> +**JavaScript-specific patterns:** The **Module pattern** isn't one of the original 23 GoF patterns. It's a JavaScript idiom that emerged to solve JavaScript-specific problems (like the lack of built-in modules before ES6). We include it here because it's essential for JavaScript developers. +</Note> + +We'll cover six patterns that are particularly useful in JavaScript: **Module** (JS-specific), **Singleton**, **Factory**, **Observer**, **Proxy**, and **Decorator**. + +--- + +## The Module Pattern + +The **Module pattern** encapsulates code into reusable units with private and public parts. Before ES6 modules existed, developers used IIFEs (Immediately Invoked Function Expressions) to create this pattern. Today, JavaScript has built-in [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) that provide this naturally. + +### ES6 Modules: The Modern Approach + +Each file is its own module. Variables are private unless you [`export`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) them: + +```javascript +// counter.js — A module with private state +let count = 0 // Private — not exported, not accessible outside + +export function increment() { + count++ + return count +} + +export function decrement() { + count-- + return count +} + +export function getCount() { + return count +} + +// main.js — Using the module +import { increment, getCount } from './counter.js' + +increment() +increment() +console.log(getCount()) // 2 + +// Trying to access private state +// console.log(count) // ReferenceError: count is not defined +``` + +### The Classic IIFE Module Pattern + +Before ES6, developers used closures to create modules: + +```javascript +// The revealing module pattern using IIFE +const Counter = (function() { + // Private variables and functions + let count = 0 + + function logChange(action) { + console.log(`Counter ${action}: ${count}`) + } + + // Public API — "revealed" by returning an object + return { + increment() { + count++ + logChange('incremented') + return count + }, + decrement() { + count-- + logChange('decremented') + return count + }, + getCount() { + return count + } + } +})() + +Counter.increment() // "Counter incremented: 1" +Counter.increment() // "Counter incremented: 2" +console.log(Counter.getCount()) // 2 + +// Private members are truly private +console.log(Counter.count) // undefined +console.log(Counter.logChange) // undefined +``` + +### When to Use the Module Pattern + +<AccordionGroup> + <Accordion title="Organizing related functionality"> + Group related functions and data together. A `UserService` module might contain `login()`, `logout()`, `getCurrentUser()`, and private token storage. + </Accordion> + + <Accordion title="Hiding implementation details"> + Expose only what consumers need. Internal helper functions, validation logic, and caching mechanisms stay private. + </Accordion> + + <Accordion title="Avoiding global namespace pollution"> + Instead of 50 global functions, you have one module export. This prevents naming collisions with other code. + </Accordion> +</AccordionGroup> + +<Tip> +**Modern JavaScript:** Use ES6 modules (`import`/`export`) for new projects. The IIFE pattern is mainly for legacy code or environments without module support. See [IIFE, Modules & Namespaces](/concepts/iife-modules) for a deeper dive. +</Tip> + +--- + +## The Singleton Pattern + +The **Singleton pattern** ensures a class has only one instance and provides a global access point to that instance. According to [Refactoring Guru](https://refactoring.guru/design-patterns/singleton), it solves two problems: guaranteeing a single instance and providing global access to it. + +### JavaScript Implementation + +```javascript +// Singleton using Object.freeze — immutable configuration +const Config = { + apiUrl: 'https://api.example.com', + timeout: 5000, + debug: false +} + +Object.freeze(Config) // Prevent all modifications + +// Usage anywhere in your app +console.log(Config.apiUrl) // "https://api.example.com" + +// Attempting to modify throws an error in strict mode (silently fails otherwise) +Config.apiUrl = 'https://evil.com' +console.log(Config.apiUrl) // Still "https://api.example.com" + +Config.debug = true +console.log(Config.debug) // Still false — frozen objects are immutable +``` + +### Class-Based Singleton + +```javascript +let instance = null + +class Database { + constructor() { + if (instance) { + return instance // Return existing instance + } + + this.connection = null + instance = this + } + + connect(url) { + if (!this.connection) { + this.connection = `Connected to ${url}` + console.log(this.connection) + } + return this.connection + } +} + +const db1 = new Database() +const db2 = new Database() + +console.log(db1 === db2) // true — Same instance! + +db1.connect('mongodb://localhost') // "Connected to mongodb://localhost" +db2.connect('mongodb://other') // Returns same connection, doesn't reconnect +``` + +### Why Singleton Is Often an Anti-Pattern in JavaScript + +Here's the thing: **Singletons are often unnecessary in JavaScript**. Here's why: + +```javascript +// ES Modules are already singletons! +// config.js +export const config = { + apiUrl: 'https://api.example.com', + timeout: 5000 +} + +// main.js +import { config } from './config.js' + +// other.js +import { config } from './config.js' + +// Both files get the SAME object — modules are cached! +``` + +<Warning> +**Problems with Singletons:** + +1. **Testing difficulties** — Tests share the same instance, making isolation hard +2. **Hidden dependencies** — Code that uses a Singleton has an implicit dependency +3. **Tight coupling** — Components become coupled to a specific implementation +4. **ES Modules already do this** — Module exports are cached; you get the same object every time + +**Better alternatives:** Dependency injection, React Context, or simply exporting an object from a module. +</Warning> + +### When Singletons Make Sense + +Despite the caveats, Singletons can be appropriate for: +- **Logging services** — One logger instance for the entire app +- **Configuration objects** — App-wide settings that shouldn't change +- **Connection pools** — Managing a single pool of database connections <CardGroup cols={2}> - <Card title="Learning JavaScript Design Patterns" icon="book" href="https://addyosmani.com/resources/essentialjsdesignpatterns/book/"> - By Addy Osmani + <Card title="Singleton Pattern — Refactoring Guru" icon="book" href="https://refactoring.guru/design-patterns/singleton"> + Detailed explanation with pros, cons, and implementation in multiple languages </Card> - <Card title="Pro JavaScript Design Patterns" icon="book" href="https://pepa.holla.cz/wp-content/uploads/2016/08/Pro-JavaScript-Design-Patterns.pdf"> - By Ross Harmes and Dustin Diaz +</CardGroup> + +--- + +## The Factory Pattern + +The **Factory pattern** creates objects without exposing the creation logic. Instead of using `new` directly, you call a factory function that returns the appropriate object. This centralizes object creation and makes it easy to change how objects are created without updating every call site. + +```javascript +// Factory function — creates different user types +function createUser(type, name) { + const baseUser = { + name, + createdAt: new Date(), + greet() { + return `Hi, I'm ${this.name}` + } + } + + switch (type) { + case 'admin': + return { + ...baseUser, + role: 'admin', + permissions: ['read', 'write', 'delete', 'manage-users'], + promote(user) { + console.log(`${this.name} promoted ${user.name}`) + } + } + + case 'editor': + return { + ...baseUser, + role: 'editor', + permissions: ['read', 'write'] + } + + case 'viewer': + default: + return { + ...baseUser, + role: 'viewer', + permissions: ['read'] + } + } +} + +// Usage — no need to know the internal structure +const admin = createUser('admin', 'Alice') +const editor = createUser('editor', 'Bob') +const viewer = createUser('viewer', 'Charlie') + +console.log(admin.permissions) // ['read', 'write', 'delete', 'manage-users'] +console.log(editor.permissions) // ['read', 'write'] +console.log(viewer.greet()) // "Hi, I'm Charlie" +``` + +### When to Use the Factory Pattern + +- **Creating objects with complex setup** — Encapsulate the complexity +- **Creating different types based on input** — Switch logic in one place +- **Decoupling creation from usage** — Callers don't need to know implementation details + +<Info> +**Want to go deeper?** The Factory pattern is covered extensively in [Factories and Classes](/concepts/factories-classes), including factory functions vs classes, the `new` keyword, and when to use each approach. +</Info> + +<CardGroup cols={2}> + <Card title="Factory Method — Refactoring Guru" icon="book" href="https://refactoring.guru/design-patterns/factory-method"> + Complete guide to the Factory Method pattern with diagrams and examples </Card> </CardGroup> -## Articles +--- + +## The Observer Pattern + +The **Observer pattern** defines a subscription mechanism that notifies multiple objects about events. According to [Refactoring Guru](https://refactoring.guru/design-patterns/observer), it lets you "define a subscription mechanism to notify multiple objects about any events that happen to the object they're observing." + +This pattern is everywhere: DOM events, React state updates, Redux subscriptions, Node.js EventEmitter, and RxJS observables all use variations of Observer. + +### Building an Observable + +```javascript +class Observable { + constructor() { + this.observers = [] + } + + subscribe(fn) { + this.observers.push(fn) + // Return an unsubscribe function + return () => { + this.observers = this.observers.filter(observer => observer !== fn) + } + } + + notify(data) { + this.observers.forEach(observer => observer(data)) + } +} + +// Usage: A stock price tracker +const stockPrice = new Observable() + +// Subscriber 1: Log to console +const unsubscribeLogger = stockPrice.subscribe(price => { + console.log(`Stock price updated: $${price}`) +}) + +// Subscriber 2: Check for alerts +stockPrice.subscribe(price => { + if (price > 150) { + console.log('ALERT: Price above $150!') + } +}) + +// Subscriber 3: Update UI (simulated) +stockPrice.subscribe(price => { + console.log(`Updating chart with price: $${price}`) +}) + +// When price changes, all subscribers are notified +stockPrice.notify(145) +// "Stock price updated: $145" +// "Updating chart with price: $145" + +stockPrice.notify(155) +// "Stock price updated: $155" +// "ALERT: Price above $150!" +// "Updating chart with price: $155" + +// Unsubscribe the logger +unsubscribeLogger() + +stockPrice.notify(160) +// No log message, but alert and chart still update +``` + +### The Magazine Subscription Analogy + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ THE OBSERVER PATTERN │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ PUBLISHER (Observable) SUBSCRIBERS (Observers) │ +│ ────────────────────── ─────────────────────── │ +│ │ +│ ┌─────────────────────┐ ┌─────────────┐ │ +│ │ │ ──────────► │ Reader #1 │ │ +│ │ Magazine │ └─────────────┘ │ +│ │ Publisher │ ┌─────────────┐ │ +│ │ │ ──────────► │ Reader #2 │ │ +│ │ • subscribers[] │ └─────────────┘ │ +│ │ • subscribe() │ ┌─────────────┐ │ +│ │ • unsubscribe() │ ──────────► │ Reader #3 │ │ +│ │ • notify() │ └─────────────┘ │ +│ │ │ │ +│ └─────────────────────┘ │ +│ │ +│ When a new issue publishes, all subscribers receive it automatically. │ +│ Readers can subscribe or unsubscribe at any time. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Real-World Example: Form Validation + +```javascript +// Observable form field +class FormField { + constructor(initialValue = '') { + this.value = initialValue + this.observers = [] + } + + subscribe(fn) { + this.observers.push(fn) + return () => { + this.observers = this.observers.filter(o => o !== fn) + } + } + + setValue(newValue) { + this.value = newValue + this.observers.forEach(fn => fn(newValue)) + } +} + +// Usage +const emailField = new FormField('') + +// Validator subscriber +emailField.subscribe(value => { + const isValid = value.includes('@') + console.log(isValid ? 'Valid email' : 'Invalid email') +}) + +// Character counter subscriber +emailField.subscribe(value => { + console.log(`Characters: ${value.length}`) +}) + +emailField.setValue('test') +// "Invalid email" +// "Characters: 4" + +emailField.setValue('test@example.com') +// "Valid email" +// "Characters: 16" +``` <CardGroup cols={2}> - <Card title="JavaScript Design Patterns – Explained with Examples" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-design-patterns-explained/"> - By Germán Cocca + <Card title="Observer Pattern — Refactoring Guru" icon="book" href="https://refactoring.guru/design-patterns/observer"> + Complete explanation with UML diagrams and pseudocode </Card> - <Card title="4 JavaScript Design Patterns You Should Know" icon="newspaper" href="https://scotch.io/bar-talk/4-javascript-design-patterns-you-should-know"> - By Devan Patel +</CardGroup> + +--- + +## The Proxy Pattern + +The **[Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) pattern** provides a surrogate or placeholder for another object to control access to it. In JavaScript, the ES6 `Proxy` object lets you intercept and redefine fundamental operations like property access, assignment, and function calls. + +### Basic Proxy Example + +```javascript +const user = { + name: 'Alice', + age: 25, + email: 'alice@example.com' +} + +const userProxy = new Proxy(user, { + // Intercept property reads + get(target, property) { + console.log(`Accessing property: ${property}`) + return target[property] + }, + + // Intercept property writes + set(target, property, value) { + console.log(`Setting ${property} to ${value}`) + + // Validation: age must be a non-negative number + if (property === 'age') { + if (typeof value !== 'number' || value < 0) { + throw new Error('Age must be a non-negative number') + } + } + + // Validation: email must contain @ + if (property === 'email') { + if (!value.includes('@')) { + throw new Error('Invalid email format') + } + } + + target[property] = value + return true + } +}) + +// All access goes through the proxy +console.log(userProxy.name) +// "Accessing property: name" +// "Alice" + +userProxy.age = 26 +// "Setting age to 26" + +userProxy.age = -5 +// Error: Age must be a non-negative number + +userProxy.email = 'invalid' +// Error: Invalid email format +``` + +### Practical Use Case: Lazy Loading + +```javascript +// Expensive object that we don't want to create until needed +function createExpensiveResource() { + console.log('Creating expensive resource...') + return { + data: 'Loaded data from database', + process() { + return `Processing: ${this.data}` + } + } +} + +// Proxy that delays creation until first use +function createLazyResource() { + let resource = null + + return new Proxy({}, { + get(target, property) { + // Create resource on first access + if (!resource) { + resource = createExpensiveResource() + } + + const value = resource[property] + // If it's a method, bind it to the resource + return typeof value === 'function' ? value.bind(resource) : value + } + }) +} + +const lazyResource = createLazyResource() +console.log('Proxy created, resource not loaded yet') + +// Resource is only created when we actually use it +console.log(lazyResource.data) +// "Creating expensive resource..." +// "Loaded data from database" + +console.log(lazyResource.process()) +// "Processing: Loaded data from database" +``` + +### When to Use the Proxy Pattern + +| Use Case | Example | +|----------|---------| +| **Validation** | Validate data before setting properties | +| **Logging/Debugging** | Log all property accesses for debugging | +| **Lazy initialization** | Delay expensive object creation | +| **Access control** | Restrict access to certain properties | +| **Caching** | Cache expensive computations | + +<CardGroup cols={2}> + <Card title="Proxy — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"> + Complete API reference for JavaScript's Proxy object + </Card> + <Card title="Proxy Pattern — Refactoring Guru" icon="book" href="https://refactoring.guru/design-patterns/proxy"> + Pattern explanation with diagrams and use cases + </Card> +</CardGroup> + +--- + +## The Decorator Pattern + +The **Decorator pattern** attaches new behaviors to objects by wrapping them in objects that contain these behaviors. According to [Refactoring Guru](https://refactoring.guru/design-patterns/decorator), it lets you "attach new behaviors to objects by placing these objects inside special wrapper objects." + +In JavaScript, decorators are often implemented as functions that take an object and return an enhanced version. + +### Adding Abilities to Objects + +```javascript +// Base object +const createCharacter = (name) => ({ + name, + health: 100, + describe() { + return `${this.name} (${this.health} HP)` + } +}) + +// Decorator: Add flying ability +const withFlying = (character) => ({ + ...character, + fly() { + return `${character.name} soars through the sky!` + }, + describe() { + return `${character.describe()} [Can fly]` + } +}) + +// Decorator: Add swimming ability +const withSwimming = (character) => ({ + ...character, + swim() { + return `${character.name} dives into the water!` + }, + describe() { + return `${character.describe()} [Can swim]` + } +}) + +// Decorator: Add armor +const withArmor = (character, armorPoints) => ({ + ...character, + armor: armorPoints, + takeDamage(amount) { + const reducedDamage = Math.max(0, amount - armorPoints) + character.health -= reducedDamage + return `${character.name} takes ${reducedDamage} damage (${armorPoints} blocked)` + }, + describe() { + return `${character.describe()} [Armor: ${armorPoints}]` + } +}) + +// Compose decorators to build characters +const duck = withSwimming(withFlying(createCharacter('Duck'))) +console.log(duck.describe()) // "Duck (100 HP) [Can fly] [Can swim]" +console.log(duck.fly()) // "Duck soars through the sky!" +console.log(duck.swim()) // "Duck dives into the water!" + +const knight = withArmor(createCharacter('Knight'), 20) +console.log(knight.describe()) // "Knight (100 HP) [Armor: 20]" +console.log(knight.takeDamage(50)) // "Knight takes 30 damage (20 blocked)" +``` + +### Function Decorators + +Decorators also work great with functions: + +```javascript +// Decorator: Log function calls +const withLogging = (fn, fnName) => { + return function(...args) { + console.log(`Calling ${fnName} with:`, args) + const result = fn.apply(this, args) + console.log(`${fnName} returned:`, result) + return result + } +} + +// Decorator: Memoize (cache) results +const withMemoization = (fn) => { + const cache = new Map() + + return function(...args) { + const key = JSON.stringify(args) + + if (cache.has(key)) { + console.log('Cache hit!') + return cache.get(key) + } + + const result = fn.apply(this, args) + cache.set(key, result) + return result + } +} + +// Original function +function fibonacci(n) { + if (n <= 1) return n + return fibonacci(n - 1) + fibonacci(n - 2) +} + +// Decorated version with logging +const loggedAdd = withLogging((a, b) => a + b, 'add') +loggedAdd(2, 3) +// "Calling add with: [2, 3]" +// "add returned: 5" + +// Decorated fibonacci with memoization +const memoizedFib = withMemoization(function fib(n) { + if (n <= 1) return n + return memoizedFib(n - 1) + memoizedFib(n - 2) +}) + +console.log(memoizedFib(10)) // 55 +console.log(memoizedFib(10)) // "Cache hit!" — 55 +``` + +### When to Use the Decorator Pattern + +- **Adding features without modifying original code** — Open/Closed Principle +- **Composing behaviors dynamically** — Mix and match capabilities +- **Cross-cutting concerns** — Logging, caching, validation, timing + +<CardGroup cols={2}> + <Card title="Decorator Pattern — Refactoring Guru" icon="book" href="https://refactoring.guru/design-patterns/decorator"> + Full explanation with structure diagrams and applicability guidelines + </Card> +</CardGroup> + +--- + +## Common Mistakes with Design Patterns + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DESIGN PATTERN MISTAKES │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MISTAKE #1: PATTERN OVERUSE │ +│ ─────────────────────────── │ +│ Using patterns where simple code would work better. │ +│ A plain function is often better than a Factory class. │ +│ │ +│ MISTAKE #2: WRONG PATTERN CHOICE │ +│ ───────────────────────────── │ +│ Using Singleton when you just need a module export. │ +│ Using Observer when a simple callback would suffice. │ +│ │ +│ MISTAKE #3: IGNORING JAVASCRIPT IDIOMS │ +│ ──────────────────────────────────── │ +│ JavaScript has closures, first-class functions, and ES modules. │ +│ Many classical patterns simplify dramatically in JavaScript. │ +│ │ +│ MISTAKE #4: PREMATURE ABSTRACTION │ +│ ──────────────────────────────── │ +│ Adding patterns before you have a real problem to solve. │ +│ "You Ain't Gonna Need It" (YAGNI) applies to patterns too. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### The "Golden Hammer" Anti-Pattern + +When you learn a new pattern, resist the urge to use it everywhere: + +```javascript +// ❌ OVERKILL: Factory for simple objects +class UserFactory { + createUser(name) { + return new User(name) + } +} +const factory = new UserFactory() +const user = factory.createUser('Alice') + +// ✓ SIMPLE: Just create the object +const user = { name: 'Alice' } +// or +const user = new User('Alice') +``` + +<Warning> +**Ask yourself these questions before using a pattern:** + +1. **Do I have a real problem?** Don't solve problems you don't have yet. +2. **Is there a simpler solution?** A plain function or object might be enough. +3. **Does JavaScript already solve this?** ES modules, Promises, and iterators are built-in patterns. +4. **Will my team understand it?** Patterns only help if everyone knows them. +</Warning> + +--- + +## Choosing the Right Pattern + +| Problem | Pattern | Alternative | +|---------|---------|-------------| +| Need to organize code with private state | **Module** | ES6 module exports | +| Need exactly one instance | **Singleton** | Just export an object from a module | +| Need to create objects dynamically | **Factory** | Plain function returning objects | +| Need to notify multiple listeners of changes | **Observer** | EventEmitter, callbacks, or a library | +| Need to control or validate object access | **Proxy** | Getter/setter methods | +| Need to add behavior without modification | **Decorator** | Higher-order functions, composition | + +<Tip> +**Rule of Thumb:** Start with the simplest solution that works. Introduce patterns when you hit a real problem they solve, not before. +</Tip> + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Design patterns are templates, not code** — Adapt them to your specific problem; don't force-fit them + +2. **JavaScript simplifies many patterns** — First-class functions, closures, and ES modules reduce boilerplate + +3. **Module pattern organizes code** — Use ES modules for new projects; understand IIFE pattern for legacy code + +4. **Singleton is often unnecessary** — ES module exports are already cached; use sparingly if at all + +5. **Factory centralizes object creation** — Great for creating different types based on input + +6. **Observer enables event-driven code** — The foundation of DOM events, React state, and reactive programming + +7. **Proxy intercepts object operations** — Use for validation, logging, lazy loading, and access control + +8. **Decorator adds behavior through wrapping** — Compose features without modifying original code + +9. **Avoid pattern overuse** — Simple code beats clever patterns; apply the YAGNI principle + +10. **Learn to recognize patterns in the wild** — DOM events use Observer, Promises use a form of Observer, middleware uses Decorator +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's the main purpose of the Module pattern?"> + **Answer:** + + The Module pattern encapsulates code into reusable units with **private and public parts**. It allows you to: + - Hide implementation details (private variables and functions) + - Expose only a public API + - Avoid polluting the global namespace + + In modern JavaScript, ES6 modules (`import`/`export`) provide this naturally. Variables in a module are private unless exported. + + ```javascript + // privateHelper is not exported — it's private + function privateHelper() { /* ... */ } + + // Only publicFunction is accessible to importers + export function publicFunction() { + privateHelper() + } + ``` + </Accordion> + + <Accordion title="Question 2: Why is Singleton often considered an anti-pattern in JavaScript?"> + **Answer:** + + Singleton is often unnecessary in JavaScript because: + + 1. **ES modules are already singletons** — When you export an object, all importers get the same instance + 2. **Testing difficulties** — Tests share state, making isolation hard + 3. **Hidden dependencies** — Code using Singletons has implicit dependencies + 4. **JavaScript can create objects directly** — No need for the class-based workarounds other languages require + + ```javascript + // ES module — already a singleton! + export const config = { apiUrl: '...' } + + // Every import gets the same object + import { config } from './config.js' // Same instance everywhere + ``` + </Accordion> + + <Accordion title="Question 3: What are the three parts of an Observer pattern implementation?"> + **Answer:** + + The Observer pattern has three key parts: + + 1. **Subscriber list** — An array to store observer functions + 2. **Subscribe method** — Adds a function to the list (often returns an unsubscribe function) + 3. **Notify method** — Calls all subscribed functions with data + + ```javascript + class Observable { + constructor() { + this.observers = [] // 1. Subscriber list + } + + subscribe(fn) { // 2. Subscribe method + this.observers.push(fn) + return () => { // Returns unsubscribe + this.observers = this.observers.filter(o => o !== fn) + } + } + + notify(data) { // 3. Notify method + this.observers.forEach(fn => fn(data)) + } + } + ``` + </Accordion> + + <Accordion title="Question 4: How does the Proxy pattern differ from the Decorator pattern?"> + **Answer:** + + Both wrap objects, but they have different purposes: + + **Proxy Pattern:** + - **Controls access** to an object + - Intercepts operations like get, set, delete + - The proxy typically has the same interface as the target + - Use for: validation, logging, lazy loading, access control + + **Decorator Pattern:** + - **Adds new behavior** to an object + - Wraps the object and extends its capabilities + - May add new methods or modify existing ones + - Use for: composing features, cross-cutting concerns + + ```javascript + // Proxy — same interface, controlled access + const proxy = new Proxy(obj, { get(t, p) { /* intercept */ } }) + + // Decorator — enhanced interface, new behavior + const enhanced = withLogging(withCache(obj)) + ``` + </Accordion> + + <Accordion title="Question 5: When should you use the Factory pattern?"> + **Answer:** + + Use the Factory pattern when: + + 1. **Object creation is complex** — Encapsulate setup logic in one place + 2. **You need different types based on input** — Switch logic centralized in the factory + 3. **You want to decouple creation from usage** — Callers don't need to know implementation + 4. **You might change how objects are created** — Update the factory, not every call site + + ```javascript + // Factory — creation logic in one place + function createNotification(type, message) { + switch (type) { + case 'error': return { type, message, color: 'red' } + case 'success': return { type, message, color: 'green' } + default: return { type: 'info', message, color: 'blue' } + } + } + + // Easy to use — no need to know the structure + const notification = createNotification('error', 'Something went wrong') + ``` + </Accordion> + + <Accordion title="Question 6: What's the 'Golden Hammer' anti-pattern?"> + **Answer:** + + The "Golden Hammer" anti-pattern is the tendency to use a familiar tool (or pattern) for every problem, even when it's not appropriate. + + **Signs you're doing this:** + - Using Singleton for everything that "should be global" + - Creating Factory classes for simple object literals + - Using Observer when a callback would suffice + - Adding patterns before you have a real problem + + **How to avoid it:** + - Start with the simplest solution + - Add patterns only when you hit a real problem they solve + - Ask: "Would a plain function/object work here?" + - Remember: Code clarity beats clever patterns + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts + +<CardGroup cols={2}> + <Card title="Factories and Classes" icon="industry" href="/concepts/factories-classes"> + Deep dive into object creation with factory functions and ES6 classes + </Card> + <Card title="IIFE, Modules & Namespaces" icon="box" href="/concepts/iife-modules"> + How JavaScript evolved from IIFEs to modern ES modules + </Card> + <Card title="Higher-Order Functions" icon="function" href="/concepts/higher-order-functions"> + Functions that work with functions — the foundation of many patterns + </Card> + <Card title="Scope and Closures" icon="lock" href="/concepts/scope-and-closures"> + How closures enable private state in the Module pattern </Card> </CardGroup> -- [JavaScript Design Patterns – Beginner's Guide to Mobile Web Development — Soumyajit Pathak](https://medium.com/beginners-guide-to-mobile-web-development/javascript-design-patterns-25f0faaaa15) -- [JavaScript Design Patterns — Akash Pal](https://medium.com/front-end-hacking/javascript-design-patterns-ed9d4c144c81) -- [JavaScript Design Patterns: Understanding Design Patterns in JavaScript - Sukhjinder Arora](https://blog.bitsrc.io/understanding-design-patterns-in-javascript-13345223f2dd) -- [All the 23 (GoF) design patterns implemented in Javascript — Felipe Beline](https://github.com/fbeline/Design-Patterns-JS) -- [The Power of the Module Pattern in JavaScript — jsmanifest](https://medium.com/better-programming/the-power-of-the-module-pattern-in-javascript-3c73f7cd10e8) -- [Design Patterns for Developers using JavaScript pt. I — Oliver Mensah](https://dev.to/omensah/design-patterns-for-developers-using-javascript----part-one--b3e) -- [Design Patterns for Developers using JavaScript pt. II — Oliver Mensah](https://dev.to/omensah/design-patterns-for-developers-using-javascript---part-two--3p39) -- [Design patterns in modern JavaScript development](https://levelup.gitconnected.com/design-patterns-in-modern-javascript-development-ec84d8be06ca) -- [JavaScript Design Pattern — Module Pattern - Factory Pattern — Moon](https://medium.com/javascript-in-plain-english/javascript-design-pattern-module-pattern-555737eccecd) -- [Design Patterns: Null Object - Carlos Caballero](https://medium.com/better-programming/design-patterns-null-object-5ee839e37892) -- [Strategy Pattern - Francesco Ciulla](https://dev.to/francescoxx/strategy-pattern-5oh) -- [Adapter Pattern - Francesco Ciulla](https://dev.to/francescoxx/adapter-pattern-5bjk) -- [The Power of Composite Pattern in JavaScript - jsmanifest](https://dev.to/jsmanifest/the-power-of-composite-pattern-in-javascript-2732) -- [JavaScript Patterns Workshop — Lydia Hallie](https://javascriptpatterns.vercel.app/patterns) +--- + +## Reference + +<CardGroup cols={2}> + <Card title="Proxy — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"> + Complete API reference for JavaScript's built-in Proxy object + </Card> + <Card title="JavaScript Modules — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"> + Official guide to ES6 modules with import and export + </Card> + <Card title="Design Patterns Catalog — Refactoring Guru" icon="compass" href="https://refactoring.guru/design-patterns/catalog"> + Complete catalog of classic design patterns with examples + </Card> + <Card title="Reflect — MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect"> + The Reflect object used with Proxy for default behavior + </Card> +</CardGroup> + +## Articles + +<CardGroup cols={2}> + <Card title="JavaScript Design Patterns — patterns.dev" icon="newspaper" href="https://www.patterns.dev/vanilla/module-pattern"> + Lydia Hallie and Addy Osmani's modern guide with animated visualizations. Each pattern gets its own interactive explanation showing exactly how data flows. + </Card> + <Card title="JavaScript Design Patterns — freeCodeCamp" icon="newspaper" href="https://www.freecodecamp.org/news/javascript-design-patterns-explained/"> + Beginner-friendly walkthrough of essential patterns with practical code examples. Great starting point if you're new to design patterns. + </Card> + <Card title="Learning JavaScript Design Patterns" icon="newspaper" href="https://www.patterns.dev/book/"> + Addy Osmani's free online book, updated for modern JavaScript. The definitive resource covering patterns, anti-patterns, and real-world applications. + </Card> + <Card title="Mixins — javascript.info" icon="newspaper" href="https://javascript.info/mixins"> + Authoritative guide on adding behaviors to classes without inheritance. Shows the EventMixin pattern that's used throughout JavaScript libraries. + </Card> +</CardGroup> ## Videos <CardGroup cols={2}> - <Card title="JavaScript Design Patterns" icon="video" href="https://www.udacity.com/course/javascript-design-patterns--ud989"> - By Udacity + <Card title="10 Design Patterns Explained in 10 Minutes" icon="video" href="https://www.youtube.com/watch?v=tv-_1er1mWI"> + Fireship's fast-paced overview covering the essential patterns every developer should know. Perfect for a quick refresher or introduction. + </Card> + <Card title="JavaScript Design Patterns — Udacity" icon="video" href="https://www.udacity.com/course/javascript-design-patterns--ud989"> + Free comprehensive course covering MVC, MVP, and organizing large JavaScript applications. Great for understanding patterns in context. </Card> - <Card title="JavaScript Patterns for 2017" icon="video" href="https://www.youtube.com/watch?v=hO7mzO83N1Q"> - By Scott Allen + <Card title="Factory Functions in JavaScript" icon="video" href="https://www.youtube.com/watch?v=ImwrezYhw4w"> + Fun Fun Function's engaging explanation of factories vs classes. MPJ's conversational style makes complex concepts approachable. </Card> </CardGroup> diff --git a/tests/advanced-topics/design-patterns/design-patterns.test.js b/tests/advanced-topics/design-patterns/design-patterns.test.js new file mode 100644 index 00000000..24e1b023 --- /dev/null +++ b/tests/advanced-topics/design-patterns/design-patterns.test.js @@ -0,0 +1,600 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +describe('Design Patterns', () => { + describe('Module Pattern', () => { + it('should encapsulate private state using closures', () => { + // IIFE-based module pattern + const Counter = (function () { + let count = 0 // Private variable + + return { + increment() { + count++ + return count + }, + decrement() { + count-- + return count + }, + getCount() { + return count + } + } + })() + + expect(Counter.getCount()).toBe(0) + expect(Counter.increment()).toBe(1) + expect(Counter.increment()).toBe(2) + expect(Counter.decrement()).toBe(1) + + // Private variable is not accessible + expect(Counter.count).toBeUndefined() + }) + + it('should only expose public methods', () => { + const Module = (function () { + // Private function + function privateHelper(value) { + return value * 2 + } + + // Public API + return { + publicMethod(value) { + return privateHelper(value) + 10 + } + } + })() + + expect(Module.publicMethod(5)).toBe(20) // (5 * 2) + 10 + expect(Module.privateHelper).toBeUndefined() + }) + }) + + describe('Singleton Pattern', () => { + it('should return the same instance when created multiple times', () => { + let instance = null + + class Singleton { + constructor() { + if (instance) { + return instance + } + this.timestamp = Date.now() + instance = this + } + } + + const instance1 = new Singleton() + const instance2 = new Singleton() + + expect(instance1).toBe(instance2) + expect(instance1.timestamp).toBe(instance2.timestamp) + }) + + it('should prevent modification with Object.freeze', () => { + const Config = { + apiUrl: 'https://api.example.com', + timeout: 5000 + } + + Object.freeze(Config) + + // In strict mode (which Vitest uses), this throws an error + expect(() => { + Config.apiUrl = 'https://evil.com' + }).toThrow(TypeError) + + expect(() => { + Config.newProperty = 'test' + }).toThrow(TypeError) + + // Original values remain unchanged + expect(Config.apiUrl).toBe('https://api.example.com') + expect(Config.newProperty).toBeUndefined() + }) + + it('should demonstrate that ES modules behave like singletons', () => { + // Simulating ES module behavior + const createModule = () => { + const cache = new Map() + + return function getModule(name) { + if (!cache.has(name)) { + cache.set(name, { name, timestamp: Date.now() }) + } + return cache.get(name) + } + } + + const requireModule = createModule() + + const module1 = requireModule('config') + const module2 = requireModule('config') + + expect(module1).toBe(module2) + }) + }) + + describe('Factory Pattern', () => { + it('should create objects without using the new keyword', () => { + function createUser(name, role) { + return { + name, + role, + greet() { + return `Hi, I'm ${this.name}` + } + } + } + + const user = createUser('Alice', 'admin') + + expect(user.name).toBe('Alice') + expect(user.role).toBe('admin') + expect(user.greet()).toBe("Hi, I'm Alice") + }) + + it('should return different object instances', () => { + function createProduct(name) { + return { name, id: Math.random() } + } + + const product1 = createProduct('Widget') + const product2 = createProduct('Widget') + + expect(product1).not.toBe(product2) + expect(product1.id).not.toBe(product2.id) + }) + + it('should create different types based on input', () => { + function createNotification(type, message) { + const base = { message, timestamp: Date.now() } + + switch (type) { + case 'error': + return { ...base, type: 'error', color: 'red', icon: '❌' } + case 'success': + return { ...base, type: 'success', color: 'green', icon: '✓' } + case 'warning': + return { ...base, type: 'warning', color: 'yellow', icon: '⚠' } + default: + return { ...base, type: 'info', color: 'blue', icon: 'ℹ' } + } + } + + const error = createNotification('error', 'Failed!') + const success = createNotification('success', 'Done!') + const info = createNotification('unknown', 'Info') + + expect(error.color).toBe('red') + expect(success.color).toBe('green') + expect(info.type).toBe('info') + }) + }) + + describe('Observer Pattern', () => { + let observable + + beforeEach(() => { + observable = { + observers: [], + + subscribe(fn) { + this.observers.push(fn) + return () => { + this.observers = this.observers.filter((o) => o !== fn) + } + }, + + notify(data) { + this.observers.forEach((fn) => fn(data)) + } + } + }) + + it('should allow subscribing to events', () => { + const callback = vi.fn() + + observable.subscribe(callback) + observable.notify('test data') + + expect(callback).toHaveBeenCalledWith('test data') + expect(callback).toHaveBeenCalledTimes(1) + }) + + it('should notify all subscribers', () => { + const callback1 = vi.fn() + const callback2 = vi.fn() + const callback3 = vi.fn() + + observable.subscribe(callback1) + observable.subscribe(callback2) + observable.subscribe(callback3) + + observable.notify('event data') + + expect(callback1).toHaveBeenCalledWith('event data') + expect(callback2).toHaveBeenCalledWith('event data') + expect(callback3).toHaveBeenCalledWith('event data') + }) + + it('should allow unsubscribing', () => { + const callback = vi.fn() + + const unsubscribe = observable.subscribe(callback) + + observable.notify('first') + expect(callback).toHaveBeenCalledTimes(1) + + unsubscribe() + + observable.notify('second') + expect(callback).toHaveBeenCalledTimes(1) // Still 1, not called again + }) + + it('should handle multiple subscriptions and unsubscriptions', () => { + const callback1 = vi.fn() + const callback2 = vi.fn() + + const unsub1 = observable.subscribe(callback1) + observable.subscribe(callback2) + + observable.notify('test') + expect(callback1).toHaveBeenCalledTimes(1) + expect(callback2).toHaveBeenCalledTimes(1) + + unsub1() + + observable.notify('test2') + expect(callback1).toHaveBeenCalledTimes(1) // Not called again + expect(callback2).toHaveBeenCalledTimes(2) // Called again + }) + }) + + describe('Proxy Pattern', () => { + it('should intercept property access (get)', () => { + const target = { name: 'Alice', age: 25 } + const accessLog = [] + + const proxy = new Proxy(target, { + get(obj, prop) { + accessLog.push(prop) + return obj[prop] + } + }) + + expect(proxy.name).toBe('Alice') + expect(proxy.age).toBe(25) + expect(accessLog).toEqual(['name', 'age']) + }) + + it('should intercept property assignment (set)', () => { + const target = { count: 0 } + const setLog = [] + + const proxy = new Proxy(target, { + set(obj, prop, value) { + setLog.push({ prop, value }) + obj[prop] = value + return true + } + }) + + proxy.count = 5 + proxy.newProp = 'hello' + + expect(target.count).toBe(5) + expect(target.newProp).toBe('hello') + expect(setLog).toEqual([ + { prop: 'count', value: 5 }, + { prop: 'newProp', value: 'hello' } + ]) + }) + + it('should validate values on set', () => { + const user = { name: 'Alice', age: 25 } + + const validatedUser = new Proxy(user, { + set(obj, prop, value) { + if (prop === 'age') { + if (typeof value !== 'number') { + throw new TypeError('Age must be a number') + } + if (value < 0 || value > 150) { + throw new RangeError('Age must be between 0 and 150') + } + } + obj[prop] = value + return true + } + }) + + // Valid assignment + validatedUser.age = 30 + expect(validatedUser.age).toBe(30) + + // Invalid assignments + expect(() => { + validatedUser.age = 'thirty' + }).toThrow(TypeError) + + expect(() => { + validatedUser.age = -5 + }).toThrow(RangeError) + + expect(() => { + validatedUser.age = 200 + }).toThrow(RangeError) + }) + + it('should provide default values for missing properties', () => { + const target = { name: 'Alice' } + + const withDefaults = new Proxy(target, { + get(obj, prop) { + if (prop in obj) { + return obj[prop] + } + return `Default value for ${prop}` + } + }) + + expect(withDefaults.name).toBe('Alice') + expect(withDefaults.missing).toBe('Default value for missing') + }) + }) + + describe('Decorator Pattern', () => { + it('should add new methods to objects', () => { + const createBird = (name) => ({ + name, + chirp() { + return `${this.name} says chirp!` + } + }) + + const withFlying = (bird) => ({ + ...bird, + fly() { + return `${bird.name} is flying!` + } + }) + + const sparrow = withFlying(createBird('Sparrow')) + + expect(sparrow.chirp()).toBe('Sparrow says chirp!') + expect(sparrow.fly()).toBe('Sparrow is flying!') + }) + + it('should preserve original object properties', () => { + const original = { + name: 'Widget', + price: 100, + getInfo() { + return `${this.name}: $${this.price}` + } + } + + const withDiscount = (product, discountPercent) => ({ + ...product, + discount: discountPercent, + getDiscountedPrice() { + return product.price * (1 - discountPercent / 100) + } + }) + + const discounted = withDiscount(original, 20) + + expect(discounted.name).toBe('Widget') + expect(discounted.price).toBe(100) + expect(discounted.discount).toBe(20) + expect(discounted.getDiscountedPrice()).toBe(80) + }) + + it('should allow composing multiple decorators', () => { + const createCharacter = (name) => ({ + name, + abilities: [], + describe() { + return `${this.name} can: ${this.abilities.join(', ') || 'nothing yet'}` + } + }) + + const withSwimming = (character) => ({ + ...character, + abilities: [...character.abilities, 'swim'], + swim() { + return `${character.name} swims!` + } + }) + + const withFlying = (character) => ({ + ...character, + abilities: [...character.abilities, 'fly'], + fly() { + return `${character.name} flies!` + } + }) + + const withFireBreathing = (character) => ({ + ...character, + abilities: [...character.abilities, 'breathe fire'], + breatheFire() { + return `${character.name} breathes fire!` + } + }) + + // Compose decorators + const dragon = withFireBreathing(withFlying(createCharacter('Dragon'))) + + expect(dragon.abilities).toEqual(['fly', 'breathe fire']) + expect(dragon.fly()).toBe('Dragon flies!') + expect(dragon.breatheFire()).toBe('Dragon breathes fire!') + + // Different composition + const duck = withSwimming(withFlying(createCharacter('Duck'))) + + expect(duck.abilities).toEqual(['fly', 'swim']) + expect(duck.fly()).toBe('Duck flies!') + expect(duck.swim()).toBe('Duck swims!') + }) + + it('should work with function decorators', () => { + // Logging decorator + const withLogging = (fn) => { + return function (...args) { + const result = fn.apply(this, args) + return result + } + } + + // Timing decorator + const withTiming = (fn) => { + return function (...args) { + const start = Date.now() + const result = fn.apply(this, args) + const end = Date.now() + return { result, duration: end - start } + } + } + + const add = (a, b) => a + b + const timedAdd = withTiming(withLogging(add)) + + const output = timedAdd(2, 3) + + expect(output.result).toBe(5) + expect(typeof output.duration).toBe('number') + expect(output.duration).toBeGreaterThanOrEqual(0) + }) + + it('should implement memoization decorator', () => { + const withMemoization = (fn) => { + const cache = new Map() + + return function (...args) { + const key = JSON.stringify(args) + + if (cache.has(key)) { + return { value: cache.get(key), cached: true } + } + + const result = fn.apply(this, args) + cache.set(key, result) + return { value: result, cached: false } + } + } + + let callCount = 0 + const expensiveOperation = (n) => { + callCount++ + return n * n + } + + const memoized = withMemoization(expensiveOperation) + + const result1 = memoized(5) + expect(result1).toEqual({ value: 25, cached: false }) + expect(callCount).toBe(1) + + const result2 = memoized(5) + expect(result2).toEqual({ value: 25, cached: true }) + expect(callCount).toBe(1) // Not called again + + const result3 = memoized(10) + expect(result3).toEqual({ value: 100, cached: false }) + expect(callCount).toBe(2) // Called for new argument + }) + }) + + describe('Pattern Integration', () => { + it('should combine Observer and Singleton for a global event bus', () => { + // Singleton event bus using module pattern + const EventBus = (function () { + const events = new Map() + + return Object.freeze({ + on(event, callback) { + if (!events.has(event)) { + events.set(event, []) + } + events.get(event).push(callback) + }, + + off(event, callback) { + if (events.has(event)) { + const callbacks = events.get(event).filter((cb) => cb !== callback) + events.set(event, callbacks) + } + }, + + emit(event, data) { + if (events.has(event)) { + events.get(event).forEach((callback) => callback(data)) + } + } + }) + })() + + const handler1 = vi.fn() + const handler2 = vi.fn() + + EventBus.on('user:login', handler1) + EventBus.on('user:login', handler2) + + EventBus.emit('user:login', { userId: 123 }) + + expect(handler1).toHaveBeenCalledWith({ userId: 123 }) + expect(handler2).toHaveBeenCalledWith({ userId: 123 }) + + EventBus.off('user:login', handler1) + EventBus.emit('user:login', { userId: 456 }) + + expect(handler1).toHaveBeenCalledTimes(1) + expect(handler2).toHaveBeenCalledTimes(2) + }) + + it('should combine Factory and Decorator patterns', () => { + // Factory for creating base enemies + const createEnemy = (type) => { + const enemies = { + goblin: { name: 'Goblin', health: 50, damage: 10 }, + orc: { name: 'Orc', health: 100, damage: 20 }, + troll: { name: 'Troll', health: 200, damage: 30 } + } + return { ...enemies[type] } + } + + // Decorators for enemy modifiers + const withArmor = (enemy, armor) => ({ + ...enemy, + armor, + takeDamage(amount) { + return Math.max(0, amount - armor) + } + }) + + const withPoison = (enemy) => ({ + ...enemy, + poisonDamage: 5, + attack() { + return `${enemy.name} attacks for ${enemy.damage} + ${this.poisonDamage} poison!` + } + }) + + // Create decorated enemies + const armoredOrc = withArmor(createEnemy('orc'), 15) + const poisonGoblin = withPoison(createEnemy('goblin')) + const armoredPoisonTroll = withPoison(withArmor(createEnemy('troll'), 25)) + + expect(armoredOrc.armor).toBe(15) + expect(armoredOrc.takeDamage(30)).toBe(15) + + expect(poisonGoblin.attack()).toBe('Goblin attacks for 10 + 5 poison!') + + expect(armoredPoisonTroll.armor).toBe(25) + expect(armoredPoisonTroll.attack()).toBe('Troll attacks for 30 + 5 poison!') + }) + }) +}) From 4d6adbfc36393fc412b5a9c99069f46b09ad1f5d Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 17:11:39 -0300 Subject: [PATCH 123/128] feat: add comprehensive Clean Code concept page - Add complete guide covering clean code principles for JavaScript - Include sections on meaningful naming, small functions, DRY, side effects - Add early returns, guard clauses, and comments best practices - Add SOLID principles with code examples for all 5 principles - Include newspaper analogy with ASCII diagram - Add Key Takeaways, Test Your Knowledge Q&A section - Add curated articles and videos (Ryan McDermott, Fireship, Uncle Bob) - Reference Robert C. Martin's Clean Code book as foundational text - Link to related concepts: pure-functions, modern-js-syntax, error-handling --- docs/concepts/clean-code.mdx | 901 +++++++++++++++++++++++++++++++++-- 1 file changed, 872 insertions(+), 29 deletions(-) diff --git a/docs/concepts/clean-code.mdx b/docs/concepts/clean-code.mdx index 7d29d4bf..45f5d336 100644 --- a/docs/concepts/clean-code.mdx +++ b/docs/concepts/clean-code.mdx @@ -1,57 +1,900 @@ --- title: "Clean Code: Writing Readable JavaScript" sidebarTitle: "Clean Code: Writing Readable JavaScript" -description: "Learn clean code principles for JavaScript — meaningful names, small functions, DRY, SOLID, and best practices. Write code that's easy to read, understand, and maintain." +description: "Learn clean code principles for JavaScript. Covers meaningful naming, small functions, DRY, avoiding side effects, and best practices to write maintainable code." --- -## Overview +Why do some codebases feel like a maze while others read like a well-written story? What makes code easy to change versus code that makes you want to rewrite everything from scratch? -**Clean code** is code that is easy to understand, easy to change, and easy to maintain. It follows consistent conventions, uses meaningful names, keeps functions small and focused, and avoids unnecessary complexity. Writing clean code is a skill that improves with practice and attention to detail. +```javascript +// Which would you rather debug at 2am? + +// Version A +function p(a, b) { + let x = 0 + for (let i = 0; i < a.length; i++) { + if (a[i].s === 1) x += a[i].p * b + } + return x +} + +// Version B +function calculateActiveProductsTotal(products, taxRate) { + let total = 0 + for (const product of products) { + if (product.status === PRODUCT_STATUS.ACTIVE) { + total += product.price * taxRate + } + } + return total +} +``` + +**Clean code** is code that's easy to read, easy to understand, and easy to change. The principles behind clean code were popularized by Robert C. Martin's book *[Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)*, and Ryan McDermott adapted these principles specifically for JavaScript in his [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) repository (94k+ GitHub stars). Both are essential reading for any JavaScript developer. <Info> -Clean code is not just about making code work—it's about making code that other developers (including your future self) can easily read, understand, and modify. +**What you'll learn in this guide:** +- What makes code "clean" and why it matters +- Naming conventions that make code self-documenting +- How to write small, focused functions that do one thing +- The DRY principle and when to apply it +- How to avoid side effects and write predictable code +- Using early returns to reduce nesting +- When to write comments (and when not to) +- SOLID principles applied to JavaScript </Info> -## Articles +--- + +## The Newspaper Analogy + +Think of your code like a newspaper article. A reader should understand the gist from the headline, get more details from the first paragraph, and find supporting information as they read further. Your code should work the same way: high-level functions at the top, implementation details below. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CODE LIKE A NEWSPAPER │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ // HEADLINE: What does this module do? │ +│ export function processUserOrder(userId, orderId) { │ +│ const user = getUser(userId) │ +│ const order = getOrder(orderId) │ +│ validateOrder(user, order) │ +│ return chargeAndShip(user, order) │ +│ } │ +│ │ +│ // DETAILS: How does it do it? │ +│ function getUser(userId) { ... } │ +│ function getOrder(orderId) { ... } │ +│ function validateOrder(user, order) { ... } │ +│ function chargeAndShip(user, order) { ... } │ +│ │ +│ Read top-to-bottom. The "what" comes before the "how". │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Meaningful Naming + +Names are everywhere in code: variables, functions, classes, files. Good names make comments unnecessary. Bad names make simple code confusing. + +### Use Pronounceable, Searchable Names + +```javascript +// ❌ What does this even mean? +const yyyymmdstr = moment().format('YYYY/MM/DD') +const d = new Date() +const t = d.getTime() + +// ✓ Crystal clear +const currentDate = moment().format('YYYY/MM/DD') +const now = new Date() +const timestamp = now.getTime() +``` + +### Use the Same Word for the Same Concept + +Pick one word per concept and stick with it. If you fetch users with `getUser()`, don't also have `fetchClient()` and `retrieveCustomer()`. + +```javascript +// ❌ Inconsistent - which one do I use? +getUserInfo() +fetchClientData() +retrieveCustomerRecord() + +// ✓ Consistent vocabulary +getUser() +getClient() +getCustomer() +``` + +### Avoid Mental Mapping + +Single-letter variables force readers to remember what `a`, `x`, or `l` mean. Be explicit. + +```javascript +// ❌ What is 'l'? A number? A location? A letter? +locations.forEach(l => { + doStuff() + // ... 50 lines later + dispatch(l) // Wait, what was 'l' again? +}) + +// ✓ No guessing required +locations.forEach(location => { + doStuff() + dispatch(location) +}) +``` + +### Don't Add Unnecessary Context + +If your class is called `Car`, you don't need `carMake`, `carModel`, `carColor`. The context is already there. + +```javascript +// ❌ Redundant prefixes +const Car = { + carMake: 'Honda', + carModel: 'Accord', + carColor: 'Blue' +} + +// ✓ Context is already clear +const Car = { + make: 'Honda', + model: 'Accord', + color: 'Blue' +} +``` + +--- + +## Functions Should Do One Thing + +This is the single most important rule in clean code. When functions do one thing, they're easier to name, easier to test, and easier to reuse. + +### Keep Functions Small and Focused + +```javascript +// ❌ This function does too many things +function emailClients(clients) { + clients.forEach(client => { + const clientRecord = database.lookup(client) + if (clientRecord.isActive()) { + email(client) + } + }) +} + +// ✓ Each function has one job +function emailActiveClients(clients) { + clients + .filter(isActiveClient) + .forEach(email) +} + +function isActiveClient(client) { + const clientRecord = database.lookup(client) + return clientRecord.isActive() +} +``` + +### Limit Function Parameters + +Two or fewer parameters is ideal. If you need more, use an object with destructuring. This also makes the call site self-documenting. + +```javascript +// ❌ What do these arguments mean? +createMenu('Settings', 'User preferences', 'Save', true) + +// ✓ Self-documenting with destructuring +createMenu({ + title: 'Settings', + body: 'User preferences', + buttonText: 'Save', + cancellable: true +}) + +function createMenu({ title, body, buttonText, cancellable = false }) { + // ... +} +``` + +### Don't Use Boolean Flags + +A boolean parameter is a sign that the function does more than one thing. Split it into two functions instead. + +```javascript +// ❌ Boolean flag = function does two things +function createFile(name, isTemp) { + if (isTemp) { + fs.create(`./temp/${name}`) + } else { + fs.create(name) + } +} + +// ✓ Two focused functions +function createFile(name) { + fs.create(name) +} + +function createTempFile(name) { + createFile(`./temp/${name}`) +} +``` + +--- + +## Avoid Magic Numbers and Strings + +Magic values are unexplained numbers or strings scattered through your code. They make code hard to understand and hard to change. + +```javascript +// ❌ What is 86400000? Why 18? +setTimeout(blastOff, 86400000) + +if (user.age > 18) { + allowAccess() +} + +if (status === 1) { + // ... +} + +// ✓ Named constants are searchable and self-documenting +const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000 +const MINIMUM_LEGAL_AGE = 18 +const STATUS = { + ACTIVE: 1, + INACTIVE: 0 +} + +setTimeout(blastOff, MILLISECONDS_PER_DAY) + +if (user.age > MINIMUM_LEGAL_AGE) { + allowAccess() +} + +if (status === STATUS.ACTIVE) { + // ... +} +``` + +<Tip> +**Pro tip:** ESLint's `no-magic-numbers` rule can automatically flag magic numbers in your code. +</Tip> + +--- + +## DRY: Don't Repeat Yourself + +Duplicate code means multiple places to update when logic changes. But be careful: a bad abstraction is worse than duplication. Only abstract when you see a clear pattern. + +```javascript +// ❌ Duplicate logic +function showDeveloperList(developers) { + developers.forEach(dev => { + const salary = dev.calculateSalary() + const experience = dev.getExperience() + const githubLink = dev.getGithubLink() + render({ salary, experience, githubLink }) + }) +} + +function showManagerList(managers) { + managers.forEach(mgr => { + const salary = mgr.calculateSalary() + const experience = mgr.getExperience() + const portfolio = mgr.getPortfolio() + render({ salary, experience, portfolio }) + }) +} + +// ✓ Unified with type-specific handling +function showEmployeeList(employees) { + employees.forEach(employee => { + const baseData = { + salary: employee.calculateSalary(), + experience: employee.getExperience() + } + + const extraData = employee.type === 'developer' + ? { githubLink: employee.getGithubLink() } + : { portfolio: employee.getPortfolio() } + + render({ ...baseData, ...extraData }) + }) +} +``` + +--- + +## Avoid Side Effects + +A function has a side effect when it does something other than take inputs and return outputs: modifying a global variable, writing to a file, or mutating an input parameter. Side effects make code unpredictable and hard to test. For a deeper dive, see our [Pure Functions](/concepts/pure-functions) guide. + +```javascript +// ❌ Mutates the original array - side effect! +function addItemToCart(cart, item) { + cart.push({ item, date: Date.now() }) +} + +// ✓ Returns a new array - no side effects +function addItemToCart(cart, item) { + return [...cart, { item, date: Date.now() }] +} +``` + +```javascript +// ❌ Modifies global state +let name = 'Ryan McDermott' + +function splitName() { + name = name.split(' ') // Mutates global! +} + +// ✓ Pure function - no globals modified +function splitName(name) { + return name.split(' ') +} + +const fullName = 'Ryan McDermott' +const nameParts = splitName(fullName) +``` + +--- + +## Early Returns and Guard Clauses + +Deeply nested code is hard to follow. Use early returns to handle edge cases first, then write the main logic without extra indentation. + +```javascript +// ❌ Deeply nested - hard to follow +function getPayAmount(employee) { + let result + if (employee.isSeparated) { + result = { amount: 0, reason: 'separated' } + } else { + if (employee.isRetired) { + result = { amount: 0, reason: 'retired' } + } else { + // ... complex salary calculation + result = { amount: salary, reason: 'employed' } + } + } + return result +} + +// ✓ Guard clauses - flat and readable +function getPayAmount(employee) { + if (employee.isSeparated) { + return { amount: 0, reason: 'separated' } + } + + if (employee.isRetired) { + return { amount: 0, reason: 'retired' } + } + + // Main logic at the top level + const salary = calculateSalary(employee) + return { amount: salary, reason: 'employed' } +} +``` + +The same applies to loops. Use `continue` to skip iterations instead of nesting: + +```javascript +// ❌ Unnecessary nesting +for (const user of users) { + if (user.isActive) { + if (user.hasPermission) { + processUser(user) + } + } +} + +// ✓ Flat and scannable +for (const user of users) { + if (!user.isActive) continue + if (!user.hasPermission) continue + + processUser(user) +} +``` + +--- + +## Comments: Less is More + +Good code mostly documents itself. Comments should explain *why*, not *what*. If you need a comment to explain what code does, consider rewriting the code to be clearer. + +### Don't State the Obvious + +```javascript +// ❌ These comments add nothing +function hashIt(data) { + // The hash + let hash = 0 + + // Length of string + const length = data.length + + // Loop through every character + for (let i = 0; i < length; i++) { + // Get character code + const char = data.charCodeAt(i) + // Make the hash + hash = (hash << 5) - hash + char + // Convert to 32-bit integer + hash &= hash + } + return hash +} + +// ✓ Only comment what's not obvious +function hashIt(data) { + let hash = 0 + const length = data.length + + for (let i = 0; i < length; i++) { + const char = data.charCodeAt(i) + hash = (hash << 5) - hash + char + hash &= hash // Convert to 32-bit integer + } + return hash +} +``` + +### Don't Leave Commented-Out Code + +That's what version control is for. Delete it. If you need it later, check the git history. + +```javascript +// ❌ Dead code cluttering the file +doStuff() +// doOtherStuff() +// doSomeMoreStuff() +// doSoMuchStuff() + +// ✓ Clean +doStuff() +``` + +### Don't Write Journal Comments + +Git log exists for a reason. + +```javascript +// ❌ This is what git history is for +/** + * 2016-12-20: Removed monads (RM) + * 2016-10-01: Added special monads (JP) + * 2016-02-03: Removed type-checking (LI) + */ +function combine(a, b) { + return a + b +} + +// ✓ Just the code +function combine(a, b) { + return a + b +} +``` + +--- + +## SOLID Principles in JavaScript + +SOLID is a set of five principles that help you write maintainable, flexible code. Here's how they apply to JavaScript: + +<AccordionGroup> + <Accordion title="Single Responsibility Principle (SRP)"> + A class or module should have only one reason to change. + + ```javascript + // ❌ UserSettings handles both settings AND authentication + class UserSettings { + constructor(user) { + this.user = user + } + + changeSettings(settings) { + if (this.verifyCredentials()) { + // update settings + } + } + + verifyCredentials() { + // authentication logic + } + } + + // ✓ Separate responsibilities + class UserAuth { + constructor(user) { + this.user = user + } + + verifyCredentials() { + // authentication logic + } + } + + class UserSettings { + constructor(user, auth) { + this.user = user + this.auth = auth + } + + changeSettings(settings) { + if (this.auth.verifyCredentials()) { + // update settings + } + } + } + ``` + </Accordion> + + <Accordion title="Open/Closed Principle (OCP)"> + Code should be open for extension but closed for modification. Add new features by adding new code, not changing existing code. + + ```javascript + // ❌ Must modify this function for every new shape + function getArea(shape) { + if (shape.type === 'circle') { + return Math.PI * shape.radius ** 2 + } else if (shape.type === 'rectangle') { + return shape.width * shape.height + } + // Add another if for every new shape... + } + + // ✓ Extend by adding new classes + class Shape { + getArea() { + throw new Error('getArea must be implemented') + } + } + + class Circle extends Shape { + constructor(radius) { + super() + this.radius = radius + } + + getArea() { + return Math.PI * this.radius ** 2 + } + } + + class Rectangle extends Shape { + constructor(width, height) { + super() + this.width = width + this.height = height + } + + getArea() { + return this.width * this.height + } + } + ``` + </Accordion> + + <Accordion title="Liskov Substitution Principle (LSP)"> + Child classes should be usable wherever parent classes are expected without breaking the code. + + ```javascript + // ❌ Square breaks when used where Rectangle is expected + class Rectangle { + constructor() { + this.width = 0 + this.height = 0 + } + + setWidth(width) { + this.width = width + } + + setHeight(height) { + this.height = height + } + + getArea() { + return this.width * this.height + } + } + + class Square extends Rectangle { + setWidth(width) { + this.width = width + this.height = width // Breaks LSP! + } + + setHeight(height) { + this.width = height + this.height = height + } + } + + // This fails for Square - expects 20, gets 25 + function calculateAreas(rectangles) { + rectangles.forEach(rect => { + rect.setWidth(4) + rect.setHeight(5) + console.log(rect.getArea()) // Square returns 25, not 20! + }) + } + + // ✓ Better: separate classes, no inheritance relationship + class Rectangle { + constructor(width, height) { + this.width = width + this.height = height + } + + getArea() { + return this.width * this.height + } + } + + class Square { + constructor(side) { + this.side = side + } + + getArea() { + return this.side * this.side + } + } + ``` + </Accordion> + + <Accordion title="Interface Segregation Principle (ISP)"> + Don't force clients to depend on methods they don't use. In JavaScript, use optional configuration objects instead of requiring many parameters. + + ```javascript + // ❌ Forcing clients to provide options they don't need + class DOMTraverser { + constructor(settings) { + this.settings = settings + this.rootNode = settings.rootNode + this.settings.animationModule.setup() // Required even if not needed! + } + } + + const traverser = new DOMTraverser({ + rootNode: document.body, + animationModule: { setup() {} } // Must provide even if not animating + }) + + // ✓ Make features optional + class DOMTraverser { + constructor(settings) { + this.settings = settings + this.rootNode = settings.rootNode + + if (settings.animationModule) { + settings.animationModule.setup() + } + } + } + + const traverser = new DOMTraverser({ + rootNode: document.body + // animationModule is optional now + }) + ``` + </Accordion> + + <Accordion title="Dependency Inversion Principle (DIP)"> + Depend on abstractions, not concrete implementations. Inject dependencies rather than instantiating them inside your classes. + + ```javascript + // ❌ Tightly coupled to InventoryRequester + class InventoryTracker { + constructor(items) { + this.items = items + this.requester = new InventoryRequester() // Hard dependency + } + } + + // ✓ Dependency injection + class InventoryTracker { + constructor(items, requester) { + this.items = items + this.requester = requester // Injected - can be any requester + } + } + ``` + </Accordion> +</AccordionGroup> + +--- + +## Write Testable Code + +Functions that do one thing with no side effects are easy to test. If a function is hard to test, it's often a sign that it's doing too much or has hidden dependencies. Clean code and testable code go hand in hand. + +--- + +## Key Takeaways + +<Info> +**The key things to remember:** + +1. **Names matter** — Use meaningful, pronounceable, searchable names. Good names eliminate the need for comments. + +2. **Functions should do one thing** — This is the most important rule. Small, focused functions are easier to name, test, and reuse. + +3. **Limit function parameters** — Two or fewer is ideal. Use object destructuring for more. + +4. **Eliminate magic numbers** — Use named constants that explain what values mean. + +5. **DRY, but don't over-abstract** — Remove duplication, but a bad abstraction is worse than duplication. + +6. **Avoid side effects** — Prefer pure functions that don't mutate inputs or global state. + +7. **Use early returns** — Guard clauses reduce nesting and make code easier to follow. + +8. **Comments explain why, not what** — If you need to explain what code does, rewrite the code. + +9. **Delete dead code** — Commented-out code and unused functions clutter your codebase. Git remembers. + +10. **Use tools** — ESLint catches issues, Prettier handles formatting. Don't argue about style. +</Info> + +--- + +## Test Your Knowledge + +<AccordionGroup> + <Accordion title="Question 1: What's wrong with this function name?"> + ```javascript + function process(data) { + // ... + } + ``` + + **Answer:** + + The name `process` is too vague. It doesn't tell you what kind of processing happens or what kind of data is expected. Better names would be `validateUserInput`, `parseJsonResponse`, or `calculateOrderTotal`, depending on what the function actually does. + </Accordion> + + <Accordion title="Question 2: Why is this function problematic?"> + ```javascript + function createUser(name, email, age, isAdmin, sendWelcomeEmail) { + // ... + } + ``` + + **Answer:** + + Too many parameters (5). It's hard to remember the order, and the boolean flags (`isAdmin`, `sendWelcomeEmail`) suggest the function might be doing multiple things. Refactor to use an options object: + + ```javascript + function createUser({ name, email, age, isAdmin = false }) { + // ... + } + + function sendWelcomeEmail(user) { + // Separate function for separate concern + } + ``` + </Accordion> + + <Accordion title="Question 3: When should you write a comment?"> + **Answer:** + + Write comments when you need to explain *why* something is done a certain way, especially for: + - Business logic that isn't obvious from the code + - Workarounds for bugs or edge cases + - Legal or licensing requirements + - Complex algorithms where the approach isn't self-evident + + Don't write comments that explain *what* the code does. If the code needs explanation, rewrite it to be clearer. + </Accordion> + + <Accordion title="Question 4: What's a 'magic number' and why is it bad?"> + **Answer:** + + A magic number is an unexplained numeric literal in code, like `86400000` or `18`. They're bad because: + - You can't search for what they mean + - They don't explain their purpose + - If the value needs to change, you have to find every occurrence + + Replace with named constants: `MILLISECONDS_PER_DAY` or `MINIMUM_LEGAL_AGE`. + </Accordion> + + <Accordion title="Question 5: How would you refactor this nested code?"> + ```javascript + function processUser(user) { + if (user) { + if (user.isActive) { + if (user.hasPermission) { + return doSomething(user) + } + } + } + return null + } + ``` + + **Answer:** + + Use guard clauses (early returns) to flatten the nesting: + + ```javascript + function processUser(user) { + if (!user) return null + if (!user.isActive) return null + if (!user.hasPermission) return null + + return doSomething(user) + } + ``` + + Each guard clause handles one edge case, and the main logic sits at the top level without indentation. + </Accordion> +</AccordionGroup> + +--- + +## Related Concepts <CardGroup cols={2}> - <Card title="Clean Code Explained – A Practical Introduction to Clean Coding for Beginners" icon="newspaper" href="https://www.freecodecamp.org/news/clean-coding-for-beginners/"> - By freeCodeCamp + <Card title="Pure Functions" icon="flask" href="/concepts/pure-functions"> + Deep dive into functions without side effects and why they make code predictable + </Card> + <Card title="Modern JS Syntax" icon="wand-magic-sparkles" href="/concepts/modern-js-syntax"> + ES6+ features like destructuring and arrow functions that enable cleaner code </Card> - <Card title="Clean Code concepts adapted for JavaScript" icon="newspaper" href="https://github.com/ryanmcdermott/clean-code-javascript"> - By Ryan McDermott + <Card title="Error Handling" icon="triangle-exclamation" href="/concepts/error-handling"> + How to handle errors cleanly without swallowing exceptions or cluttering code + </Card> + <Card title="Design Patterns" icon="sitemap" href="/concepts/design-patterns"> + Reusable solutions that embody clean code principles at a higher level </Card> </CardGroup> -- [Function parameters in JavaScript Clean Code — Kevin Peters](https://medium.com/@kevin_peters/function-parameters-in-javascript-clean-code-4caac109159b) -- [Keeping your code clean — Samuel James](https://codeburst.io/keeping-your-code-clean-d30bcffd1a10) -- [Best Practices for Using Modern JavaScript Syntax — M. David Green](https://www.sitepoint.com/modern-javascript-best-practices/) -- [best practices for cross node/web development - Jimmy Wärting](https://github.com/aspect-build/aspect-cli/issues/305) -- [Writing Clean Code - Dylan Paulus](https://dev.to/ganderzz/on-writing-clean-code-57cm) -- [Writing Clean Code and The Practice of Programming - Nityesh Agarwal](https://dev.to/nityeshaga/writing-clean-code-and-the-practice-of-programming-actionable-advice-for-beginners-5f0k) -- [Clean code, dirty code, human code - Daniel Irvine](https://dev.to/d_ir/clean-code-dirty-code-human-code-6nm) -- [Practical Ways to Write Better JavaScript - Ryland G](https://dev.to/taillogs/practical-ways-to-write-better-javascript-26d4) -- [The Must-Know Clean Code Principles - Kesk on Medium](https://medium.com/swlh/the-must-know-clean-code-principles-1371a14a2e75) -- [How to use destructuring in JavaScript to write cleaner, more powerful code - freecodecamp](https://www.freecodecamp.org/news/how-to-use-destructuring-in-javascript-to-write-cleaner-more-powerful-code-9d1b38794050/) -- [Write Clean Code Using JavaScript Object Destructuring - Asel Siriwardena](https://betterprogramming.pub/write-clean-code-using-javascript-object-destructuring-3551302130e7) +--- ## Books -<Card title="Clean Code: A Handbook of Agile Software Craftsmanship" icon="book" href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/"> - By Robert C. Martin +<Card title="Clean Code: A Handbook of Agile Software Craftsmanship" icon="book" href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882"> + The foundational text by Robert C. Martin that started the clean code movement. While examples are in Java, the principles apply to any language. A must-read for every developer. </Card> +## Articles + +<CardGroup cols={2}> + <Card title="Clean Code JavaScript" icon="newspaper" href="https://github.com/ryanmcdermott/clean-code-javascript"> + The definitive JavaScript adaptation of Clean Code principles with 94k+ GitHub stars. Every example is practical and immediately applicable to your code. + </Card> + <Card title="Clean Coding for Beginners" icon="newspaper" href="https://www.freecodecamp.org/news/clean-coding-for-beginners/"> + freeCodeCamp's beginner-friendly introduction covering the "why" behind each clean code principle. Great starting point if you're new to these concepts. + </Card> + <Card title="Coding Style" icon="newspaper" href="https://javascript.info/coding-style"> + javascript.info's practical guide to syntax, formatting, and style. Includes a visual cheat sheet you can reference while coding. + </Card> + <Card title="Ninja Code" icon="newspaper" href="https://javascript.info/ninja-code"> + A satirical guide showing what NOT to do. The humor makes the anti-patterns memorable, and you'll recognize some of these mistakes in real codebases. + </Card> +</CardGroup> + ## Videos <CardGroup cols={2}> <Card title="JavaScript Pro Tips - Code This, NOT That" icon="video" href="https://www.youtube.com/watch?v=Mus_vwhTCq0"> - By Fireship + Fireship's fast-paced video showing modern patterns that replace outdated approaches. Great examples of before/after refactoring. </Card> - <Card title="Clean Code playlist - Beau teaches" icon="video" href="https://www.youtube.com/watch?v=b9c5GmmS7ks&list=PLWKjhJtqVAbkK24EaPurzMq0-kw5U9pJh&index=1"> - By freeCodeCamp + <Card title="Clean Code Playlist" icon="video" href="https://www.youtube.com/watch?v=b9c5GmmS7ks&list=PLWKjhJtqVAbkK24EaPurzMq0-kw5U9pJh"> + freeCodeCamp's multi-part series covering each clean code principle in depth with live coding. Perfect for visual learners. + </Card> + <Card title="Clean Code - Uncle Bob" icon="video" href="https://www.youtube.com/watch?v=7EmboKQH8lM"> + Robert C. Martin himself explaining clean code fundamentals. Hearing it from the source gives you the philosophy behind the principles. </Card> </CardGroup> - -- [JavaScript Best Practices and Coding Conventions - Write Clean Code](https://youtu.be/RMN_bkZ1KM0?si=Ssg3cNZ_DB7CIwKQ) -- [JavaScript Clean Code](https://youtu.be/vPXzVNmCPg4?si=QR1k4E6Zx5H4mfcs) -- [Tips On Learning How To Code](https://www.youtube.com/watch?v=0wHyoBPc6zs) From 60644d4e6e742e059050567f06c08204eeaa6fd7 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 19:08:45 -0300 Subject: [PATCH 124/128] feat: restructure Getting Started section with SEO optimization - Create new getting-started/ folder with 4 pages: - about.mdx: Project origin, original 33 concepts, what we changed - how-to-learn.mdx: Page structure, resource types, learning tips - prerequisites.mdx: Tools setup (browser, code editor, Node.js) - learning-paths.mdx: Beginner, intermediate, interview prep paths - Refactor index.mdx as Welcome page: - Add SEO-optimized title and description - Add intro paragraph with keyword placement - Add Web Platform category card (7 categories total) - Update links to new getting-started pages - Delete old introduction.mdx (content moved to new pages) - Update docs.json navigation for new structure - SEO improvements across all pages: - Titles under 60 characters with hooks - Descriptions 140-160 characters with keywords - Question-format H2s for featured snippets --- docs/docs.json | 5 +- docs/getting-started/about.mdx | 173 +++++++++++++++++ docs/getting-started/how-to-learn.mdx | 172 +++++++++++++++++ docs/getting-started/learning-paths.mdx | 239 ++++++++++++++++++++++++ docs/getting-started/prerequisites.mdx | 170 +++++++++++++++++ docs/index.mdx | 109 +++++------ docs/introduction.mdx | 154 --------------- 7 files changed, 814 insertions(+), 208 deletions(-) create mode 100644 docs/getting-started/about.mdx create mode 100644 docs/getting-started/how-to-learn.mdx create mode 100644 docs/getting-started/learning-paths.mdx create mode 100644 docs/getting-started/prerequisites.mdx delete mode 100644 docs/introduction.mdx diff --git a/docs/docs.json b/docs/docs.json index bb3be7e1..272b598a 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -27,7 +27,10 @@ "icon": "rocket", "pages": [ "index", - "introduction" + "getting-started/about", + "getting-started/how-to-learn", + "getting-started/prerequisites", + "getting-started/learning-paths" ] }, { diff --git a/docs/getting-started/about.mdx b/docs/getting-started/about.mdx new file mode 100644 index 00000000..9bc59603 --- /dev/null +++ b/docs/getting-started/about.mdx @@ -0,0 +1,173 @@ +--- +title: "About 33 JavaScript Concepts: Origin and Goals" +sidebarTitle: "What is This Project?" +description: "Discover the story behind 33 JavaScript Concepts. Learn what topics are covered, who this guide is for, and how it helps you become a better developer." +--- + +## The Origin Story + +In 2017, Stephen Curtis wrote an article titled ["33 Fundamentals Every JavaScript Developer Should Know"](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1). It outlined the core concepts that separate developers who *use* JavaScript from developers who truly *understand* it. + +[Leonardo Maldonado](https://github.com/leonardomso) took this idea and built something bigger: a curated collection of the best resources for each concept. What started as a personal learning project became one of the most popular JavaScript repositories on GitHub. + +<Tip> +**Recognition:** GitHub featured this project as one of the [top open source projects of 2018](https://github.blog/news-insights/octoverse/new-open-source-projects/#top-projects-of-2018). +</Tip> + +--- + +## Who Is This For? + +This guide is for anyone who wants to learn JavaScript, regardless of your current level. + +| If you are... | This guide will help you... | +|---------------|---------------------------| +| **A complete beginner** | Build a solid foundation from the ground up | +| **Self-taught** | Fill gaps in your knowledge | +| **Preparing for interviews** | Understand concepts interviewers commonly ask about | +| **An experienced developer** | Deepen your understanding of how JavaScript works | + +There are no prerequisites. If you've never written a line of code, you can start here. + +--- + +## The Original 33 Concepts + +These are the original 33 concepts that inspired this project. We've since reorganized and expanded some topics, but this is the foundation: + +<AccordionGroup> + <Accordion title="Fundamentals (Concepts 1-6)"> + 1. **Call Stack** - How JavaScript tracks function execution + 2. **Primitive Types** - String, Number, Boolean, Null, Undefined, Symbol, BigInt + 3. **Value Types vs Reference Types** - How data is stored and passed + 4. **Type Coercion** - Implicit and explicit type conversion + 5. **Equality Operators** - == vs === and how comparisons work + 6. **Scope and Closures** - Where variables are accessible and how functions remember their environment + </Accordion> + + <Accordion title="Functions & Execution (Concepts 7-10)"> + 7. **Expression vs Statement** - Understanding the difference + 8. **IIFE, Modules, and Namespaces** - Code organization patterns + 9. **Message Queue and Event Loop** - JavaScript's concurrency model + 10. **Timers** - setTimeout, setInterval, and requestAnimationFrame + </Accordion> + + <Accordion title="JavaScript Engines (Concepts 11-13)"> + 11. **JavaScript Engines** - V8, SpiderMonkey, and how JS runs + 12. **Bitwise Operators** - Low-level operations and typed arrays + 13. **DOM and Layout Trees** - How browsers render pages + </Accordion> + + <Accordion title="Object-Oriented JavaScript (Concepts 14-18)"> + 14. **Factories and Classes** - Object creation patterns + 15. **this, call, apply, and bind** - Context and function binding + 16. **new, Constructor, instanceof** - Object instantiation + 17. **Prototype Inheritance** - JavaScript's inheritance model + 18. **Object.create and Object.assign** - Object manipulation methods + </Accordion> + + <Accordion title="Functional Programming (Concepts 19-23)"> + 19. **map, reduce, filter** - Array transformation methods + 20. **Pure Functions and Side Effects** - Functional programming basics + 21. **Closures** - Functions that remember their scope + 22. **Higher-Order Functions** - Functions that operate on functions + 23. **Recursion** - Functions that call themselves + </Accordion> + + <Accordion title="Async JavaScript (Concepts 24-26)"> + 24. **Collections and Generators** - Iterables and lazy evaluation + 25. **Promises** - Handling asynchronous operations + 26. **async/await** - Modern async syntax + </Accordion> + + <Accordion title="Advanced Topics (Concepts 27-33)"> + 27. **Data Structures** - Arrays, Objects, Maps, Sets, and more + 28. **Big O Notation** - Algorithm complexity analysis + 29. **Algorithms** - Common algorithms in JavaScript + 30. **Inheritance and Polymorphism** - OOP principles + 31. **Design Patterns** - Proven solutions to common problems + 32. **Currying and Composition** - Advanced functional techniques + 33. **Clean Code** - Writing maintainable JavaScript + </Accordion> +</AccordionGroup> + +--- + +## What We've Changed + +JavaScript and web development have evolved since the original list was created. We've updated this guide to better reflect what modern developers need to know. + +### Concepts We Added + +| Concept | Why We Added It | +|---------|-----------------| +| **Callbacks** | Essential for understanding async JavaScript before diving into Promises | +| **HTTP and Fetch** | Every web developer needs to know how to make network requests | +| **Web Workers** | Important for performance and running code off the main thread | +| **Error Handling** | Critical for building reliable applications | +| **Regular Expressions** | A fundamental tool for text processing and validation | +| **Modern JS Syntax** | Destructuring, spread operator, and other ES6+ features are now standard | +| **ES Modules** | The official module system for JavaScript | + +### Concepts We Removed or Merged + +| Original Concept | What Happened | +|------------------|---------------| +| **Expression vs Statement** | Covered within other concept pages where relevant | +| **Timers** | Merged into the Event Loop concept | +| **Bitwise Operators** | Rarely used in day-to-day JavaScript development | +| **new, Constructor, instanceof** | Merged into Factories and Classes | +| **Object.create and Object.assign** | Merged into Object Creation and Prototypes | + +<Info> +The goal isn't to have exactly 33 concepts. It's to give you the knowledge you need to truly understand JavaScript. +</Info> + +--- + +## What Makes This Guide Different? + +### Learn the Concept, Then Go Deeper + +Each concept page teaches you the topic directly with clear explanations and practical code examples. Once you understand the fundamentals, you'll find a curated list of articles, videos, and books to explore further. + +### Curated Resources + +Every resource is hand-picked from across the web. Instead of one perspective, you get the best explanations from multiple teachers and sources. + +### Community-Driven + +Hundreds of developers have contributed to this project. Resources are continuously reviewed, updated, and improved by the community. + +### Multiple Formats + +Everyone learns differently. Each concept includes: + +<CardGroup cols={3}> + <Card title="Articles" icon="newspaper"> + In-depth written explanations + </Card> + <Card title="Videos" icon="video"> + Visual explanations and talks + </Card> + <Card title="Books" icon="book"> + Comprehensive deep-dives + </Card> +</CardGroup> + +### Available in 40+ Languages + +Thanks to our community of translators, this guide is accessible to developers worldwide. Check out the [translations page](/translations) to find your language. + +--- + +## Ready to Continue? + +<CardGroup cols={2}> + <Card title="How to Learn" icon="book-open" href="/getting-started/how-to-learn"> + Learn how to use this guide effectively + </Card> + <Card title="Prerequisites" icon="wrench" href="/getting-started/prerequisites"> + Set up your learning environment + </Card> +</CardGroup> diff --git a/docs/getting-started/how-to-learn.mdx b/docs/getting-started/how-to-learn.mdx new file mode 100644 index 00000000..4196899b --- /dev/null +++ b/docs/getting-started/how-to-learn.mdx @@ -0,0 +1,172 @@ +--- +title: "How to Learn JavaScript Effectively with This Guide" +sidebarTitle: "How to Learn" +description: "Learn how to study JavaScript effectively. Tips for practicing code, understanding concepts, and getting the most from each lesson in this guide." +--- + +## How Each Concept Page Works + +Every concept page in this guide follows a consistent structure to help you learn effectively: + +<Steps> + <Step title="Overview"> + Each page starts with a clear explanation of the concept. We break down what it is, why it matters, and how it works in JavaScript. + </Step> + <Step title="Code Examples"> + You'll find practical code examples that demonstrate the concept. Run these in your browser's console or code editor to see them in action. + </Step> + <Step title="Common Mistakes"> + We highlight the mistakes developers commonly make so you can avoid them. + </Step> + <Step title="Key Takeaways"> + A summary of the most important points to remember. + </Step> + <Step title="Curated Resources"> + Hand-picked articles, videos, and book recommendations for deeper learning. + </Step> +</Steps> + +--- + +## Types of Resources + +Each concept includes multiple types of learning materials. Choose what works best for your learning style: + +<CardGroup cols={3}> + <Card title="Articles" icon="newspaper"> + **Best for:** Deep understanding + + Written tutorials and explanations you can read at your own pace. Great for reference. + </Card> + <Card title="Videos" icon="video"> + **Best for:** Visual learners + + Watch concepts explained visually. Many include animations and live coding. + </Card> + <Card title="Books" icon="book"> + **Best for:** Comprehensive learning + + In-depth coverage for when you want to go deep on a topic. + </Card> +</CardGroup> + +<Tip> +**Mix it up.** If an article doesn't click, try watching a video on the same topic. Different explanations work for different people. +</Tip> + +--- + +## Tips for Effective Learning + +### 1. Don't Just Read - Practice + +Reading about JavaScript isn't enough. You need to write code. + +```javascript +// Don't just read this example - type it yourself +const numbers = [1, 2, 3, 4, 5] +const doubled = numbers.map(num => num * 2) +console.log(doubled) // [2, 4, 6, 8, 10] +``` + +Open your browser's console (press F12) or use a code editor and actually run the examples. Modify them. Break them. See what happens. + +### 2. Take Your Time + +This isn't a race. Some concepts will click immediately. Others might take days or weeks to fully understand. That's normal. + +| Concept Type | Typical Time to Understand | +|--------------|---------------------------| +| Basic syntax | Hours | +| Core concepts (scope, closures) | Days to weeks | +| Advanced patterns | Weeks to months | + +### 3. Follow the Order (Especially for Beginners) + +The concepts build on each other. If you're new to JavaScript, start from the beginning: + +1. **Primitive Types** - What are the basic building blocks? +2. **Value vs Reference Types** - How is data stored? +3. **Scope and Closures** - Where can you access variables? +4. **Call Stack** - How does JavaScript execute code? + +Jumping ahead might leave gaps in your understanding. + +### 4. Revisit Concepts + +You won't master a concept in one sitting. Plan to revisit: + +<Steps> + <Step title="First Pass"> + Read the overview and try the basic examples + </Step> + <Step title="Second Pass (1 week later)"> + Explore the curated resources. Watch a video or read an article. + </Step> + <Step title="Third Pass (1 month later)"> + Review and apply the concept in a real project + </Step> +</Steps> + +### 5. Explain It to Someone Else + +The best way to know if you understand something is to explain it. Try: + +- Writing a blog post about a concept you learned +- Explaining it to a friend or colleague +- Answering questions on Stack Overflow or Reddit + +If you can't explain it simply, you don't understand it well enough yet. + +--- + +## How Much Time Should You Spend? + +There's no "right" answer, but here are some guidelines: + +| Your Goal | Suggested Pace | +|-----------|---------------| +| Casual learning | 1 concept per week | +| Active study | 2-3 concepts per week | +| Interview prep | 1 concept per day (review mode) | + +<Tip> +**Quality over quantity.** It's better to deeply understand 5 concepts than to skim through all 33. +</Tip> + +--- + +## Using the Browser Console + +The fastest way to practice is with your browser's built-in console: + +<Steps> + <Step title="Open DevTools"> + Press **F12** (or **Cmd+Option+J** on Mac) in any browser + </Step> + <Step title="Go to Console Tab"> + Click the "Console" tab + </Step> + <Step title="Type JavaScript"> + Type any JavaScript code and press Enter to run it + </Step> +</Steps> + +```javascript +// Try this in your console right now +const greeting = "Hello, JavaScript!" +console.log(greeting) +``` + +--- + +## Ready to Set Up? + +<CardGroup cols={2}> + <Card title="Prerequisites" icon="wrench" href="/getting-started/prerequisites"> + Get the tools you need to start learning + </Card> + <Card title="Learning Paths" icon="map" href="/getting-started/learning-paths"> + Find the right path for your experience level + </Card> +</CardGroup> diff --git a/docs/getting-started/learning-paths.mdx b/docs/getting-started/learning-paths.mdx new file mode 100644 index 00000000..f0185258 --- /dev/null +++ b/docs/getting-started/learning-paths.mdx @@ -0,0 +1,239 @@ +--- +title: "JavaScript Learning Paths: Beginner to Advanced" +sidebarTitle: "Learning Paths" +description: "Find the right JavaScript learning path for your level. Structured guides for beginners, intermediate developers, and technical interview preparation." +--- + +## Choose Your Path + +Not everyone starts from the same place. Choose a learning path that matches your experience and goals. + +<Tabs> + <Tab title="Beginner"> + **For:** Complete beginners or those new to JavaScript + + **Time:** 4-8 weeks at a comfortable pace + + Start here if you're new to programming or just starting with JavaScript. + </Tab> + <Tab title="Intermediate"> + **For:** Developers with some JavaScript experience + + **Time:** 2-4 weeks + + Choose this if you can write basic JavaScript but want to understand it more deeply. + </Tab> + <Tab title="Interview Prep"> + **For:** Preparing for technical interviews + + **Time:** 1-2 weeks (review mode) + + Focus on concepts commonly asked in JavaScript interviews. + </Tab> +</Tabs> + +--- + +## Beginner Path + +If you're new to JavaScript, follow this order. Each concept builds on the previous ones. + +<Steps> + <Step title="Week 1-2: The Fundamentals"> + Start with the building blocks of JavaScript. + + 1. [Primitive Types](/concepts/primitive-types) - What types of data exist in JavaScript? + 2. [Value vs Reference Types](/concepts/value-reference-types) - How is data stored and copied? + 3. [Type Coercion](/concepts/type-coercion) - How JavaScript converts between types + 4. [Equality Operators](/concepts/equality-operators) - The difference between == and === + </Step> + + <Step title="Week 3-4: Scope and Functions"> + Understand how JavaScript organizes and executes code. + + 5. [Scope and Closures](/concepts/scope-and-closures) - Where variables are accessible + 6. [Call Stack](/concepts/call-stack) - How JavaScript tracks function calls + 7. [Event Loop](/concepts/event-loop) - How async code works + </Step> + + <Step title="Week 5-6: Working with Data"> + Learn to transform and manipulate data. + + 8. [Higher-Order Functions](/concepts/higher-order-functions) - Functions that work with functions + 9. [map, reduce, filter](/concepts/map-reduce-filter) - Essential array methods + 10. [Pure Functions](/concepts/pure-functions) - Writing predictable code + </Step> + + <Step title="Week 7-8: Async JavaScript"> + Handle operations that take time. + + 11. [Callbacks](/concepts/callbacks) - The original async pattern + 12. [Promises](/concepts/promises) - Modern async handling + 13. [async/await](/concepts/async-await) - Clean async syntax + </Step> +</Steps> + +<Info> +**Take your time.** There's no rush. If a concept doesn't click, spend more time on it before moving on. Revisit the resources, try different explanations, and practice with code. +</Info> + +--- + +## Intermediate Path + +You know JavaScript basics. Now deepen your understanding with these concepts: + +<Steps> + <Step title="How JavaScript Works"> + Understand what's happening under the hood. + + 1. [Call Stack](/concepts/call-stack) - How function execution is tracked + 2. [Event Loop](/concepts/event-loop) - The concurrency model + 3. [JavaScript Engines](/concepts/javascript-engines) - V8 and how code runs + </Step> + + <Step title="Object-Oriented JavaScript"> + Master objects and prototypes. + + 4. [this, call, apply, bind](/concepts/this-call-apply-bind) - Context binding + 5. [Object Creation and Prototypes](/concepts/object-creation-prototypes) - The prototype chain + 6. [Factories and Classes](/concepts/factories-classes) - Object creation patterns + 7. [Inheritance and Polymorphism](/concepts/inheritance-polymorphism) - OOP in JavaScript + </Step> + + <Step title="Functional Programming"> + Write cleaner, more predictable code. + + 8. [Pure Functions](/concepts/pure-functions) - Side-effect free functions + 9. [Higher-Order Functions](/concepts/higher-order-functions) - Functions as values + 10. [Currying and Composition](/concepts/currying-composition) - Advanced patterns + 11. [Recursion](/concepts/recursion) - Functions that call themselves + </Step> + + <Step title="Advanced Patterns"> + Level up your code quality. + + 12. [Design Patterns](/concepts/design-patterns) - Proven solutions + 13. [Error Handling](/concepts/error-handling) - Graceful failure + 14. [Clean Code](/concepts/clean-code) - Writing maintainable code + </Step> +</Steps> + +--- + +## Interview Prep Path + +Technical interviews often focus on these concepts. Make sure you can explain them clearly and write code examples. + +### Must-Know Concepts + +These come up in almost every JavaScript interview: + +| Concept | Why It's Asked | Key Things to Know | +|---------|---------------|-------------------| +| [Closures](/concepts/scope-and-closures) | Tests fundamental understanding | How inner functions access outer variables | +| [this keyword](/concepts/this-call-apply-bind) | Common source of bugs | The four binding rules | +| [Promises](/concepts/promises) | Essential for async code | Chaining, error handling, Promise.all | +| [Event Loop](/concepts/event-loop) | Shows deep understanding | Call stack, task queue, microtasks | +| [Prototypes](/concepts/object-creation-prototypes) | JavaScript's inheritance | Prototype chain, Object.create | + +### Common Interview Questions by Topic + +<AccordionGroup> + <Accordion title="Scope and Closures"> + - What is a closure? Give an example. + - What's the difference between `var`, `let`, and `const`? + - Explain lexical scope. + - What is hoisting? + + **Study:** [Scope and Closures](/concepts/scope-and-closures) + </Accordion> + + <Accordion title="this Keyword"> + - What are the rules for `this` binding? + - What's the difference between `call`, `apply`, and `bind`? + - How does `this` work in arrow functions? + - What's the output of [tricky this code]? + + **Study:** [this, call, apply, bind](/concepts/this-call-apply-bind) + </Accordion> + + <Accordion title="Async JavaScript"> + - What's the difference between callbacks, promises, and async/await? + - How does the event loop work? + - What are microtasks vs macrotasks? + - How do you handle errors in async code? + + **Study:** [Promises](/concepts/promises), [async/await](/concepts/async-await), [Event Loop](/concepts/event-loop) + </Accordion> + + <Accordion title="Objects and Prototypes"> + - How does prototypal inheritance work? + - What's the difference between classical and prototypal inheritance? + - Explain `Object.create()`. + - What's the prototype chain? + + **Study:** [Object Creation and Prototypes](/concepts/object-creation-prototypes) + </Accordion> + + <Accordion title="Data Structures and Algorithms"> + - Implement common array methods (map, filter, reduce). + - What's the time complexity of [operation]? + - When would you use a Map vs an Object? + + **Study:** [Data Structures](/concepts/data-structures), [Algorithms and Big O](/concepts/algorithms-big-o) + </Accordion> +</AccordionGroup> + +<Tip> +**Practice explaining out loud.** In interviews, you need to articulate your thinking. Practice explaining each concept as if you're teaching someone else. +</Tip> + +--- + +## Topic-Based Paths + +Want to focus on a specific area? Here are paths organized by topic: + +### Async Mastery + +1. [Callbacks](/concepts/callbacks) +2. [Promises](/concepts/promises) +3. [async/await](/concepts/async-await) +4. [Event Loop](/concepts/event-loop) +5. [Generators and Iterators](/concepts/generators-iterators) + +### Object-Oriented JavaScript + +1. [Factories and Classes](/concepts/factories-classes) +2. [this, call, apply, bind](/concepts/this-call-apply-bind) +3. [Object Creation and Prototypes](/concepts/object-creation-prototypes) +4. [Inheritance and Polymorphism](/concepts/inheritance-polymorphism) + +### Functional Programming + +1. [Pure Functions](/concepts/pure-functions) +2. [Higher-Order Functions](/concepts/higher-order-functions) +3. [map, reduce, filter](/concepts/map-reduce-filter) +4. [Recursion](/concepts/recursion) +5. [Currying and Composition](/concepts/currying-composition) + +### Web Development + +1. [DOM](/concepts/dom) +2. [HTTP and Fetch](/concepts/http-fetch) +3. [Web Workers](/concepts/web-workers) +4. [ES Modules](/concepts/es-modules) + +--- + +## Start Learning + +<CardGroup cols={2}> + <Card title="Primitive Types" icon="play" href="/concepts/primitive-types"> + Begin with the first concept + </Card> + <Card title="All Concepts" icon="list" href="/getting-started/about"> + See the full list of 33 concepts + </Card> +</CardGroup> diff --git a/docs/getting-started/prerequisites.mdx b/docs/getting-started/prerequisites.mdx new file mode 100644 index 00000000..e0af518a --- /dev/null +++ b/docs/getting-started/prerequisites.mdx @@ -0,0 +1,170 @@ +--- +title: "JavaScript Setup: Tools You Need to Start Learning" +sidebarTitle: "Prerequisites" +description: "Set up your JavaScript learning environment in minutes. All you need is a browser and optionally a code editor. Perfect for complete beginners." +--- + +## What Do You Need to Learn JavaScript? + +This guide is designed for everyone, including complete beginners. You don't need to know any programming language before starting. + +All you need are a few free tools that you probably already have. + +--- + +## Required: A Web Browser + +JavaScript runs in every web browser. You can use any modern browser: + +| Browser | DevTools Shortcut | +|---------|------------------| +| **Chrome** (recommended) | F12 or Cmd+Option+J (Mac) | +| **Firefox** | F12 or Cmd+Option+I (Mac) | +| **Safari** | Cmd+Option+C (enable in Preferences first) | +| **Edge** | F12 | + +<Tip> +**We recommend Chrome** for learning. It has excellent developer tools and most tutorials use it for screenshots and examples. +</Tip> + +### Using the Browser Console + +The browser console is where you'll run JavaScript code. Here's how to open it: + +<Steps> + <Step title="Open any webpage"> + Even a blank tab works + </Step> + <Step title="Open Developer Tools"> + Press **F12** (Windows/Linux) or **Cmd+Option+J** (Mac) + </Step> + <Step title="Click the Console tab"> + This is your JavaScript playground + </Step> + <Step title="Type code and press Enter"> + Try typing `console.log("Hello!")` and press Enter + </Step> +</Steps> + +```javascript +// Type this in your console right now +console.log("Hello, JavaScript!") +// Output: Hello, JavaScript! + +// Try some math +2 + 2 +// Output: 4 + +// Create a variable +const name = "Your Name" +console.log(name) +// Output: Your Name +``` + +That's it. You're ready to learn JavaScript. + +--- + +## Recommended: A Code Editor + +While you can learn a lot in the browser console, a code editor makes writing longer code much easier. + +### Free Options + +<CardGroup cols={2}> + <Card title="VS Code" icon="code" href="https://code.visualstudio.com/"> + **Most popular choice.** Free, powerful, with excellent JavaScript support. Works on Windows, Mac, and Linux. + </Card> + <Card title="Sublime Text" icon="code" href="https://www.sublimetext.com/"> + **Fast and lightweight.** Free to evaluate, works on all platforms. + </Card> +</CardGroup> + +### Online Editors (No Installation) + +If you don't want to install anything, these online editors work great: + +<CardGroup cols={2}> + <Card title="CodePen" icon="codepen" href="https://codepen.io/"> + Great for quick experiments. See your code run instantly. + </Card> + <Card title="JSFiddle" icon="js" href="https://jsfiddle.net/"> + Simple and clean. Good for testing snippets. + </Card> + <Card title="StackBlitz" icon="bolt" href="https://stackblitz.com/"> + Full development environment in your browser. + </Card> + <Card title="CodeSandbox" icon="box" href="https://codesandbox.io/"> + Perfect for larger projects and frameworks. + </Card> +</CardGroup> + +--- + +## Optional: Node.js + +[Node.js](https://nodejs.org/) lets you run JavaScript outside the browser, on your computer's command line. + +**You don't need Node.js to learn from this guide.** Everything can be done in the browser. + +However, if you want to: +- Run JavaScript files from your terminal +- Use JavaScript for backend development later +- Follow along with some advanced tutorials + +Then install the **LTS (Long Term Support)** version from [nodejs.org](https://nodejs.org/). + +### Checking if Node.js is Installed + +Open your terminal (Command Prompt on Windows, Terminal on Mac/Linux) and type: + +```bash +node --version +``` + +If you see a version number like `v20.10.0`, you're good to go. + +--- + +## Your First JavaScript Code + +Let's make sure everything works. Open your browser console and type: + +```javascript +// Variables +const message = "I'm learning JavaScript!" +console.log(message) + +// A simple function +function greet(name) { + return "Hello, " + name + "!" +} + +console.log(greet("World")) +// Output: Hello, World! +``` + +If you see the output, congratulations! You're ready to start learning. + +--- + +## Summary + +| Tool | Required? | Purpose | +|------|-----------|---------| +| Web Browser | Yes | Run JavaScript, use DevTools console | +| Code Editor | Recommended | Write and save longer code | +| Node.js | Optional | Run JavaScript outside the browser | + +--- + +## Next Steps + +<CardGroup cols={2}> + <Card title="Learning Paths" icon="map" href="/getting-started/learning-paths"> + Find the right learning path for your goals + </Card> + <Card title="Start with Primitives" icon="play" href="/concepts/primitive-types"> + Jump into the first concept + </Card> +</CardGroup> diff --git a/docs/index.mdx b/docs/index.mdx index 1c742c95..03ddb476 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,6 +1,7 @@ --- -title: "33 JavaScript Concepts" -description: "33 concepts every JavaScript developer should know" +title: "33 JavaScript Concepts Every Developer Should Know" +sidebarTitle: "Welcome" +description: "Learn the 33 essential JavaScript concepts every developer needs. Free guide with explanations, code examples, and curated resources for all skill levels." --- <div className="not-prose"> @@ -10,7 +11,7 @@ description: "33 concepts every JavaScript developer should know" Every JavaScript developer should know these fundamental concepts </p> <div className="flex justify-center gap-4 mb-8"> - <a href="/introduction" className="px-6 py-3 bg-primary text-black font-semibold rounded-lg hover:opacity-90 transition"> + <a href="/getting-started/about" className="px-6 py-3 bg-primary text-black font-semibold rounded-lg hover:opacity-90 transition"> Get Started </a> <a href="https://github.com/leonardomso/33-js-concepts" className="px-6 py-3 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"> @@ -20,76 +21,78 @@ description: "33 concepts every JavaScript developer should know" </div> </div> -## About This Project +--- -This repository was created with the intention of helping developers master their concepts in JavaScript. It is not a requirement, but a guide for future studies. +JavaScript is the language of the web. Whether you're just starting out or have years of experience, understanding these 33 core concepts will make you a stronger developer. This guide breaks down each topic with clear explanations, practical code examples, and hand-picked resources to help you learn. -<Info> -**Recognized by GitHub** as one of the [top open source projects of 2018](https://blog.github.com/2018-12-13-new-open-source-projects/)! -</Info> +--- -Based on an article written by [Stephen Curtis](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1), this project provides curated resources including articles, videos, and books for each of the 33 essential JavaScript concepts. +## Why These 33 Concepts? -## Explore the Concepts +This isn't just another tutorial. It's a roadmap to truly understanding how JavaScript works under the hood. Whether you're building websites, mobile apps, or servers, these concepts are the foundation. <CardGroup cols={2}> - <Card title="Fundamentals" icon="cube" href="/concepts/call-stack"> - Call Stack, Primitive Types, Value vs Reference, Type Coercion, Equality Operators, and Scope + <Card title="For Beginners" icon="seedling"> + No prior JavaScript knowledge required. Start from the fundamentals and build a solid foundation. </Card> - <Card title="Functions & Execution" icon="code" href="/concepts/expression-statement"> - Expressions vs Statements, IIFE & Modules, Event Loop, and Timers + <Card title="For Experienced Devs" icon="rocket"> + Fill in the gaps and deepen your understanding of concepts you use every day. </Card> - <Card title="Under the Hood" icon="gear" href="/concepts/javascript-engines"> - JavaScript Engines, Bitwise Operators, and the DOM + <Card title="For Interview Prep" icon="briefcase"> + These concepts are commonly asked in technical interviews. Be ready to explain them. </Card> - <Card title="Object-Oriented JS" icon="sitemap" href="/concepts/factories-classes"> - Classes, this keyword, Prototypes, and Object methods + <Card title="For Everyone" icon="globe"> + Available in 40+ languages thanks to our community of contributors. + </Card> +</CardGroup> + +--- + +## What You'll Learn + +<CardGroup cols={2}> + <Card title="Fundamentals" icon="cube" href="/concepts/primitive-types"> + Types, Scope, Closures, Call Stack, and how JavaScript really works + </Card> + <Card title="Functions & Execution" icon="code" href="/concepts/event-loop"> + Event Loop, IIFE, Modules, and how code gets executed </Card> - <Card title="Functional Programming" icon="filter" href="/concepts/map-reduce-filter"> - map/reduce/filter, Pure Functions, Closures, Higher-Order Functions, and Recursion + <Card title="Web Platform" icon="browser" href="/concepts/dom"> + DOM manipulation, HTTP requests with Fetch, and Web Workers + </Card> + <Card title="Object-Oriented JS" icon="sitemap" href="/concepts/factories-classes"> + Classes, Prototypes, `this` keyword, and inheritance </Card> <Card title="Async JavaScript" icon="clock" href="/concepts/promises"> - Generators, Promises, and async/await + Callbacks, Promises, async/await, and handling asynchronous code </Card> - <Card title="Advanced Topics" icon="graduation-cap" href="/concepts/data-structures"> - Data Structures, Big O, Algorithms, Design Patterns, and Clean Code + <Card title="Functional Programming" icon="filter" href="/concepts/higher-order-functions"> + Pure functions, Higher-order functions, map/reduce/filter, and recursion </Card> - <Card title="Community" icon="users" href="/translations"> - Available in 40+ languages thanks to our amazing community + <Card title="Advanced Topics" icon="graduation-cap" href="/concepts/data-structures"> + Data structures, Algorithms, Design patterns, and clean code </Card> </CardGroup> -## Why Learn These Concepts? - -Understanding these 33 concepts will help you: - -- **Write better code** - Understand how JavaScript works under the hood -- **Debug faster** - Know why your code behaves the way it does -- **Ace interviews** - These concepts are commonly asked in technical interviews -- **Build scalable apps** - Apply best practices and design patterns +--- -## Get Started +## A Community Effort -<Steps> - <Step title="Read the Introduction"> - Start with the [Introduction](/introduction) to understand how to use this guide effectively. - </Step> - <Step title="Begin with Fundamentals"> - Start with the [Call Stack](/concepts/call-stack) and work your way through the fundamentals. - </Step> - <Step title="Practice and Apply"> - Read the articles, watch the videos, and practice implementing these concepts in your own projects. - </Step> -</Steps> +<Tip> +**Recognized by GitHub** as one of the [top open source projects of 2018](https://github.blog/news-insights/octoverse/new-open-source-projects/#top-projects-of-2018)! +</Tip> -## Contributing +This project was created by [Leonardo Maldonado](https://github.com/leonardomso) and has grown through contributions from hundreds of developers worldwide. It has been translated into over 40 languages, making JavaScript education accessible to everyone. -This project thrives on community contributions. Feel free to: +--- -- Submit a PR adding new resources -- Create translations in your native language -- Report issues or suggest improvements +## Ready to Begin? -<Card title="Contributing Guidelines" icon="book" href="/contributing"> - Learn how you can contribute to this project -</Card> +<CardGroup cols={2}> + <Card title="What is This Project?" icon="circle-info" href="/getting-started/about"> + Learn about the project's origin and what makes it different + </Card> + <Card title="Start Learning" icon="play" href="/concepts/primitive-types"> + Jump straight into the first concept + </Card> +</CardGroup> diff --git a/docs/introduction.mdx b/docs/introduction.mdx deleted file mode 100644 index 88aa18be..00000000 --- a/docs/introduction.mdx +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: "Introduction" -description: "How to use this guide to master JavaScript concepts" ---- - -## Welcome - -Welcome to **33 JavaScript Concepts** - a comprehensive guide to mastering the fundamental and advanced concepts that every JavaScript developer should know. - -This project was created by [Leonardo Maldonado](https://github.com/leonardomso) and has been contributed to by hundreds of developers worldwide. It has been translated into over 40 languages and was recognized by GitHub as one of the top open source projects of 2018. - -## How to Use This Guide - -<Steps> - <Step title="Start from the Beginning"> - The concepts are ordered from fundamental to advanced. If you're new to JavaScript, start with the **Fundamentals** section and work your way through. - </Step> - <Step title="Read the Overview"> - Each concept page starts with an overview explaining the core idea based on the ECMAScript specification. - </Step> - <Step title="Explore the Resources"> - Each concept includes curated articles, videos, and book recommendations. Choose the format that works best for your learning style. - </Step> - <Step title="Practice"> - Reading alone isn't enough. Practice implementing these concepts in your own projects or coding exercises. - </Step> -</Steps> - -## The 33 Concepts - -Here's a complete overview of all the concepts covered in this guide: - -<AccordionGroup> - <Accordion title="Fundamentals (1-6)"> - 1. **Call Stack** - How JavaScript tracks function execution - 2. **Primitive Types** - String, Number, Boolean, Null, Undefined, Symbol, BigInt - 3. **Value Types vs Reference Types** - How data is stored and passed - 4. **Type Coercion** - Implicit and explicit type conversion - 5. **Equality Operators** - == vs === vs typeof - 6. **Scope** - Function, Block, and Lexical scope - </Accordion> - - <Accordion title="Functions & Execution (7-10)"> - 7. **Expression vs Statement** - Understanding the difference - 8. **IIFE, Modules, and Namespaces** - Code organization patterns - 9. **Message Queue and Event Loop** - JavaScript's concurrency model - 10. **Timers** - setTimeout, setInterval, and requestAnimationFrame - </Accordion> - - <Accordion title="Under the Hood (11-13)"> - 11. **JavaScript Engines** - V8, SpiderMonkey, and how JS runs - 12. **Bitwise Operators** - Low-level operations and typed arrays - 13. **DOM and Layout Trees** - How browsers render pages - </Accordion> - - <Accordion title="Object-Oriented JavaScript (14-18)"> - 14. **Factories and Classes** - Object creation patterns - 15. **this, call, apply, and bind** - Context and function binding - 16. **new, Constructor, instanceof** - Object instantiation - 17. **Prototype Inheritance** - JavaScript's inheritance model - 18. **Object.create and Object.assign** - Object manipulation methods - </Accordion> - - <Accordion title="Functional Programming (19-23)"> - 19. **map, reduce, filter** - Array transformation methods - 20. **Pure Functions and Side Effects** - Functional programming basics - 21. **Closures** - Functions that remember their scope - 22. **Higher-Order Functions** - Functions that operate on functions - 23. **Recursion** - Functions that call themselves - </Accordion> - - <Accordion title="Async JavaScript (24-26)"> - 24. **Collections and Generators** - Iterables and lazy evaluation - 25. **Promises** - Handling asynchronous operations - 26. **async/await** - Modern async syntax - </Accordion> - - <Accordion title="Advanced Topics (27-33)"> - 27. **Data Structures** - Arrays, Objects, Maps, Sets, and more - 28. **Big O Notation** - Algorithm complexity analysis - 29. **Algorithms** - Common algorithms in JavaScript - 30. **Inheritance and Polymorphism** - OOP principles - 31. **Design Patterns** - Proven solutions to common problems - 32. **Currying and Composition** - Advanced functional techniques - 33. **Clean Code** - Writing maintainable JavaScript - </Accordion> -</AccordionGroup> - -## Learning Path Recommendations - -<Tabs> - <Tab title="Beginners"> - If you're new to JavaScript, focus on these concepts first: - - 1. Call Stack - 2. Primitive Types - 3. Value vs Reference Types - 4. Scope - 5. Closures - 6. Promises - 7. async/await - - Take your time with each concept before moving on. - </Tab> - - <Tab title="Intermediate"> - If you have some JavaScript experience, prioritize: - - 1. Event Loop - 2. this, call, apply, bind - 3. Prototype Inheritance - 4. Higher-Order Functions - 5. Design Patterns - 6. Big O Notation - - These concepts will deepen your understanding significantly. - </Tab> - - <Tab title="Interview Prep"> - For technical interviews, make sure you understand: - - 1. Call Stack & Event Loop - 2. Closures - 3. Promises & async/await - 4. this keyword - 5. Prototype Chain - 6. Big O Notation - 7. Common Algorithms - - Be prepared to explain these concepts and write code examples. - </Tab> -</Tabs> - -## Resource Types - -Each concept page includes multiple types of resources: - -<CardGroup cols={3}> - <Card title="Articles" icon="newspaper"> - In-depth written explanations and tutorials - </Card> - <Card title="Videos" icon="video"> - Visual explanations and conference talks - </Card> - <Card title="Books" icon="book"> - Comprehensive coverage for deep learning - </Card> -</CardGroup> - -## Ready to Start? - -<Card title="Begin with Call Stack" icon="arrow-right" href="/concepts/call-stack"> - Start your journey with the first concept: understanding how JavaScript's call stack works. -</Card> From 1921a8f3522266ddd2226dd98e53339c943c2a24 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 19:21:31 -0300 Subject: [PATCH 125/128] refactor: streamline README and move translations to separate file - Reduce README from 1293 to 170 lines (~87% smaller) - Organize concepts into 7 categories with descriptions - Link all concepts to 33jsconcepts.com documentation site - Remove inline articles/videos (now available on docs site) - Create TRANSLATIONS.md with all 42 community translations - Keep original header image and GitHub recognition badge --- README.md | 1308 ++++------------------------------------------- TRANSLATIONS.md | 54 ++ 2 files changed, 147 insertions(+), 1215 deletions(-) create mode 100644 TRANSLATIONS.md diff --git a/README.md b/README.md index 924a02f7..95541bbc 100644 --- a/README.md +++ b/README.md @@ -1,1292 +1,170 @@ <h1 align="center"> <br> - <a href="https://github.com/leonardomso/33"><img src="github-image.png" alt="33 Concepts Every JS Developer Should Know"></a> + <a href="https://github.com/leonardomso/33-js-concepts"><img src="github-image.png" alt="33 Concepts Every JS Developer Should Know"></a> <br> <br> <strong>33 Concepts Every JavaScript Developer Should Know</strong> <br><br> </h1> -<div align="center"> - <p> - <a href="#introduction">Introduction</a> • - <a href="#community">Community</a> • - <a href="#table-of-contents">Table of Contents</a> • - <a href="#license">License</a> - </p> -</div> +<p align="center"> + <a href="https://33jsconcepts.com">Read the Full Guide</a> • + <a href="#concepts">Concepts</a> • + <a href="TRANSLATIONS.md">Translations</a> • + <a href="CONTRIBUTING.md">Contributing</a> +</p> <div align="center"> - <strong>🚀 Considered by GitHub as one of the <a href="https://blog.github.com/2018-12-13-new-open-source-projects/">top open source projects of 2018!</a></strong> + <strong>Recognized by GitHub as one of the <a href="https://github.blog/news-insights/octoverse/new-open-source-projects/#top-projects-of-2018">top open source projects of 2018!</a></strong> </div> -## Introduction - -This repository was created with the intention of helping developers master their concepts in JavaScript. It is not a requirement, but a guide for future studies. It is based on an article written by Stephen Curtis and you can read it [here](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1). - -## Community - -Feel free to submit a PR by adding a link to your own recaps or reviews. If you want to translate the repo into your native language, please feel free to do so. - -All the translations for this repo will be listed below: - -- [اَلْعَرَبِيَّةُ‎ (Arabic)](https://github.com/amrsekilly/33-js-concepts) — Amr Elsekilly -- [Български (Bulgarian)](https://github.com/thewebmasterp/33-js-concepts) - thewebmasterp -- [汉语 (Chinese)](https://github.com/stephentian/33-js-concepts) — Re Tian -- [Português do Brasil (Brazilian Portuguese)](https://github.com/tiagoboeing/33-js-concepts) — Tiago Boeing -- [한국어 (Korean)](https://github.com/yjs03057/33-js-concepts.git) — Suin Lee -- [Español (Spanish)](https://github.com/adonismendozaperez/33-js-conceptos) — Adonis Mendoza -- [Türkçe (Turkish)](https://github.com/ilker0/33-js-concepts) — İlker Demir -- [русский язык (Russian)](https://github.com/gumennii/33-js-concepts) — Mihail Gumennii -- [Tiếng Việt (Vietnamese)](https://github.com/nguyentranchung/33-js-concepts) — Nguyễn Trần Chung -- [Polski (Polish)](https://github.com/lip3k/33-js-concepts) — Dawid Lipinski -- [فارسی (Persian)](https://github.com/majidalavizadeh/33-js-concepts) — Majid Alavizadeh -- [Bahasa Indonesia (Indonesian)](https://github.com/rijdz/33-js-concepts) — Rijdzuan Sampoerna -- [Français (French)](https://github.com/robinmetral/33-concepts-js) — Robin Métral -- [हिन्दी (Hindi)](https://github.com/vikaschauhan/33-js-concepts) — Vikas Chauhan -- [Ελληνικά (Greek)](https://github.com/DimitrisZx/33-js-concepts) — Dimitris Zarachanis -- [日本語 (Japanese)](https://github.com/oimo23/33-js-concepts) — oimo23 -- [Deutsch (German)](https://github.com/burhannn/33-js-concepts) — burhannn -- [украї́нська мо́ва (Ukrainian)](https://github.com/AndrewSavetchuk/33-js-concepts-ukrainian-translation) — Andrew Savetchuk -- [සිංහල (Sinhala)](https://github.com/ududsha/33-js-concepts) — Udaya Shamendra -- [Italiano (Italian)](https://github.com/Donearm/33-js-concepts) — Gianluca Fiore -- [Latviešu (Latvian)](https://github.com/ANormalStick/33-js-concepts) - Jānis Īvāns -- [Afaan Oromoo (Oromo)](https://github.com/Amandagne/33-js-concepts) - Amanuel Dagnachew -- [ภาษาไทย (Thai)](https://github.com/ninearif/33-js-concepts) — Arif Waram -- [Català (Catalan)](https://github.com/marioestradaf/33-js-concepts) — Mario Estrada -- [Svenska (Swedish)](https://github.com/FenixHongell/33-js-concepts/) — Fenix Hongell -- [ខ្មែរ (Khmer)](https://github.com/Chhunneng/33-js-concepts) — Chrea Chanchhunneng -- [አማርኛ (Ethiopian)](https://github.com/hmhard/33-js-concepts) - Miniyahil Kebede(ምንያህል ከበደ) -- [Беларуская мова (Belarussian)](https://github.com/Yafimau/33-js-concepts) — Dzianis Yafimau -- [O'zbekcha (Uzbek)](https://github.com/smnv-shokh/33-js-concepts) — Shokhrukh Usmonov -- [Urdu (اردو)](https://github.com/sudoyasir/33-js-concepts) — Yasir Nawaz -- [हिन्दी (Hindi)](https://github.com/milostivyy/33-js-concepts) — Mahima Chauhan -- [বাংলা (Bengali)](https://github.com/Jisan-mia/33-js-concepts) — Jisan Mia -- [ગુજરાતી (Gujarati)](https://github.com/VatsalBhuva11/33-js-concepts) — Vatsal Bhuva -- [سنڌي (Sindhi)](https://github.com/Sunny-unik/33-js-concepts) — Sunny Gandhwani -- [भोजपुरी (Bhojpuri)](https://github.com/debnath003/33-js-concepts) — Pronay Debnath -- [ਪੰਜਾਬੀ (Punjabi)](https://github.com/Harshdev098/33-js-concepts) — Harsh Dev Pathak -- [Latin (Latin)](https://github.com/Harshdev098/33-js-concepts) — Harsh Dev Pathak -- [മലയാളം (Malayalam)](https://github.com/Stark-Akshay/33-js-concepts) — Akshay Manoj -- [Yorùbá (Yoruba)](https://github.com/ayobaj/33-js-concepts) - Ayomide Bajulaye -- [עברית‎ (Hebrew)](https://github.com/rafyzg/33-js-concepts) — Refael Yzgea -- [Nederlands (Dutch)](https://github.com/dlvisser/33-js-concepts) — Dave Visser -- [தமிழ் (Tamil)] (https://github.com/UdayaKrishnanM/33-js-concepts) - Udaya Krishnan M - -<hr> - -## <img align="center" width="35" height="35" src="https://media4.giphy.com/media/3hoLIVAJYkz6T0Ichp/giphy.gif"> <a id="table-of-contents">Table of Contents</a> - -1. [**Call Stack**](#1-call-stack) -2. [**Primitive Types**](#2-primitive-types) -3. [**Value Types and Reference Types**](#3-value-types-and-reference-types) -4. [**Implicit, Explicit, Nominal, Structuring and Duck Typing**](#4-implicit-explicit-nominal-structuring-and-duck-typing) -5. [**== vs === vs typeof**](#5--vs--vs-typeof) -6. [**Function Scope, Block Scope and Lexical Scope**](#6-function-scope-block-scope-and-lexical-scope) -7. [**Expression vs Statement**](#7-expression-vs-statement) -8. [**IIFE, Modules and Namespaces**](#8-iife-modules-and-namespaces) -9. [**Message Queue and Event Loop**](#9-message-queue-and-event-loop) -10. [**setTimeout, setInterval and requestAnimationFrame**](#10-settimeout-setinterval-and-requestanimationframe) -11. [**JavaScript Engines**](#11-javascript-engines) -12. [**Bitwise Operators, Type Arrays and Array Buffers**](#12-bitwise-operators-type-arrays-and-array-buffers) -13. [**DOM and Layout Trees**](#13-dom-and-layout-trees) -14. [**Factories and Classes**](#14-factories-and-classes) -15. [**this, call, apply and bind**](#15-this-call-apply-and-bind) -16. [**new, Constructor, instanceof and Instances**](#16-new-constructor-instanceof-and-instances) -17. [**Prototype Inheritance and Prototype Chain**](#17-prototype-inheritance-and-prototype-chain) -18. [**Object.create and Object.assign**](#18-objectcreate-and-objectassign) -19. [**map, reduce, filter**](#19-map-reduce-filter) -20. [**Pure Functions, Side Effects, State Mutation and Event Propagation**](#20-pure-functions-side-effects-state-mutation-and-event-propagation) -21. [**Closures**](#21-closures) -22. [**High Order Functions**](#22-high-order-functions) -23. [**Recursion**](#23-recursion) -24. [**Collections and Generators**](#24-collections-and-generators) -25. [**Promises**](#25-promises) -26. [**async/await**](#26-asyncawait) -27. [**Data Structures**](#27-data-structures) -28. [**Expensive Operation and Big O Notation**](#28-expensive-operation-and-big-o-notation) -29. [**Algorithms**](#29-algorithms) -30. [**Inheritance, Polymorphism and Code Reuse**](#30-inheritance-polymorphism-and-code-reuse) -31. [**Design Patterns**](#31-design-patterns) -32. [**Partial Applications, Currying, Compose and Pipe**](#32-partial-applications-currying-compose-and-pipe) -33. [**Clean Code**](#33-clean-code) - -<hr> - -## 1. Call Stack - -<p>The call stack is a mechanism that the JavaScript interpreter uses to keep track of function execution within a program. In JavaScript, functions are executed in the order they are called. The call stack follows the Last In, First Out (LIFO) principle, meaning that the last function pushed onto the stack is the first one to be executed.</p> - -<p>According to the ECMAScript specification, the call stack is defined as part of the execution context. Whenever a function is called, a new execution context is created and placed at the top of the stack. Once the function completes, its execution context is removed from the stack, and control returns to the previous context. This helps manage synchronous code execution, as each function call must complete before the next one can begin.</p> - -### Reference - -- [Call Stack — MDN](https://developer.mozilla.org/en-US/docs/Glossary/Call_stack) - -### <img align="center" width="30" height="30" src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Understanding Javascript Call Stack, Event Loops — Gaurav Pandvia](https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec) -- [Understanding the JavaScript Call Stack — Charles Freeborn](https://medium.freecodecamp.org/understanding-the-javascript-call-stack-861e41ae61d4) -- [Javascript: What Is The Execution Context? What Is The Call Stack? — Valentino Gagliardi](https://medium.com/@valentinog/javascript-what-is-the-execution-context-what-is-the-call-stack-bd23c78f10d1) -- [What is the JS Event Loop and Call Stack? — Jess Telford](https://gist.github.com/jesstelford/9a35d20a2aa044df8bf241e00d7bc2d0) -- [Understanding Execution Context and Execution Stack in Javascript — Sukhjinder Arora](https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0) -- [How JavaScript Works Under The Hood: An Overview of JavaScript Engine, Heap and, Call Stack — Bipin Rajbhar](https://dev.to/bipinrajbhar/how-javascript-works-under-the-hood-an-overview-of-javascript-engine-heap-and-call-stack-1j5o) -- [The JS Call stack Explained in 9 minutes](https://www.youtube.com/watch?v=W8AeMrVtFLY) - Colt Steel (YouTube) - -### <img align="center" width="30" height="30" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Javascript: the Call Stack explained — Coding Blocks India](https://www.youtube.com/watch?v=w6QGEiQceOM) -- [The JS Call Stack Explained In 9 Minutes — Colt Steele](https://www.youtube.com/watch?v=W8AeMrVtFLY) -- [What is the Call Stack? — Eric Traub](https://www.youtube.com/watch?v=w7QWQlkLY_s) -- [The Call Stack — Kevin Drumm](https://www.youtube.com/watch?v=Q2sFmqvpBe0) -- [Understanding JavaScript Execution — Codesmith](https://www.youtube.com/watch?v=Z6a1cLyq7Ac&list=PLWrQZnG8l0E4kd1T_nyuVoxQUaYEWFgcD) -- [What the heck is the event loop anyway? — Philip Roberts](https://www.youtube.com/watch?v=8aGhZQkoFbQ) -- [How JavaScript Code is executed? ❤️& Call Stack — Akshay Saini](https://www.youtube.com/watch?v=iLWTnMzWtj4&list=PLlasXeu85E9cQ32gLCvAvr9vNaUccPVNP) -- [Call Stacks - CS50](https://www.youtube.com/watch?v=aCPkszeKRa4) -- [Learn the JavaScript Call Stack - codecupdev](https://www.youtube.com/watch?v=HXqXPGS96rw) -- [JavaScript Functions and the Call Stack | How does the Call stack work - Chidre'sTechTutorials](https://www.youtube.com/watch?v=P6H-T4cUDR4) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 2. Primitive Types - -<p>According to the ECMAScript specification, JavaScript has six primitive data types: string, number, bigint, boolean, undefined, and symbol. These types are immutable, meaning their values cannot be altered. There is also a special primitive type called null, which represents the intentional absence of any object value.</p> - -<p>Primitive values are directly assigned to a variable, and when you manipulate a primitive type, you're working directly on the value. Unlike objects, primitives do not have properties or methods, but JavaScript automatically wraps primitive values with object counterparts when necessary (e.g., when calling methods on strings).</p> - -### Reference - -- [JavaScript data types and data structures — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Primitive_values) - -### <img align="center" width="30" height="30" src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Primitive and Non-primitive data-types in JavaScript - GeeksforGeeks](https://www.geeksforgeeks.org/primitive-and-non-primitive-data-types-in-javascript) -- [How numbers are encoded in JavaScript — Dr. Axel Rauschmayer](http://2ality.com/2012/04/number-encoding.html) -- [What Every JavaScript Developer Should Know About Floating Point Numbers — Chewxy](https://blog.chewxy.com/2014/02/24/what-every-javascript-developer-should-know-about-floating-point-numbers/) -- [The Secret Life of JavaScript Primitives — Angus Croll](https://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/) -- [Primitive Types — Flow](https://flow.org/en/docs/types/primitives/) -- [(Not) Everything in JavaScript is an Object — Daniel Li](https://dev.to/d4nyll/not-everything-in-javascript-is-an-object) -- [JavaScript data types and data structures — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Primitive_values) -- [Diving Deeper in JavaScripts Objects — Arfat Salman](https://blog.bitsrc.io/diving-deeper-in-javascripts-objects-318b1e13dc12) -- [The differences between Object.freeze() vs Const in JavaScript — Bolaji Ayodeji](https://medium.com/@bolajiayodeji/the-differences-between-object-freeze-vs-const-in-javascript-4eacea534d7c) -- [Object to primitive conversion — JavaScript.Info](https://javascript.info/object-toprimitive) -- [Methods of primitives - Javascript.info](https://javascript.info/primitives-methods) - -### <img align="center" width="30" height="30" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript Reference vs Primitive Types — Academind](https://www.youtube.com/watch?v=9ooYYRLdg_g) -- [JavaScript Primitive Types — Simon Sez IT](https://www.youtube.com/watch?v=HsbWQsSCE5Y) -- [Value Types and Reference Types in JavaScript — Programming with Mosh](https://www.youtube.com/watch?v=e-_mDyqm2oU) -- [JavaScript Primitive Data Types — Avelx](https://www.youtube.com/watch?v=qw3j0A3DIzQ) -- [Everything you never wanted to know about JavaScript numbers — Bartek Szopka](https://www.youtube.com/watch?v=MqHDDtVYJRI) -- [What are variables in Javascript? — JS For Everyone](https://www.youtube.com/watch?v=B4Bbmei_thw) -- [TIPOS DE DATOS PRIMITIVOS en JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=cC65D2q5f8I) -- [Data Type in JavaScript - ScholarHat](https://www.youtube.com/watch?v=aFDvBjVjCh8) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 3. Value Types and Reference Types - -<p>According to the ECMAScript specification, value types are stored directly in the location that the variable accesses. These include types like number, string, boolean, undefined, bigint, symbol, and null. When you assign a value type to a variable, the value itself is stored.</p> - -<p>Reference types, on the other hand, are objects stored in the heap. Variables assigned to reference types actually store references (pointers) to the objects, not the objects themselves. When you assign a reference type to another variable, both variables reference the same object in memory.</p> - -### <img align="center" width="30" height="30" src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Explaining Value vs. Reference in Javascript — Arnav Aggarwal](https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0) -- [Primitive Types & Reference Types in JavaScript — Bran van der Meer](https://gist.github.com/branneman/7fb06d8a74d7e6d4cbcf75c50fec599c) -- [Value Types, Reference Types and Scope in JavaScript — Ben Aston](https://medium.com/@benastontweet/lesson-1b-javascript-fundamentals-380f601ba851) -- [Back to roots: JavaScript Value vs Reference — Miro Koczka](https://medium.com/dailyjs/back-to-roots-javascript-value-vs-reference-8fb69d587a18) -- [Grasp "By Value" and "By Reference" in JavaScript — Léna Faure](https://hackernoon.com/grasp-by-value-and-by-reference-in-javascript-7ed75efa1293) -- [JavaScript Reference and Copy Variables — Vítor Capretz](https://hackernoon.com/javascript-reference-and-copy-variables-b0103074fdf0) -- [JavaScript Primitive vs Reference Values](http://www.javascripttutorial.net/javascript-primitive-vs-reference-values/) -- [JavaScript by Reference vs. by Value — nrabinowitz](https://stackoverflow.com/questions/6605640/javascript-by-reference-vs-by-value) -- [JavaScript Interview Prep: Primitive vs. Reference Types — Mike Cronin](https://dev.to/mostlyfocusedmike/javascript-interview-prep-primitive-vs-reference-types-3o4f) -- [JavaScript map vs. forEach: When to Use Each One - Sajal Soni](https://code.tutsplus.com/tutorials/javascript-map-vs-foreach-when-to-use-each-one--cms-38365) - -### <img align="center" width="30" height="30" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Javascript Pass by Value vs Pass by Reference — techsith](https://www.youtube.com/watch?v=E-dAnFdq8k8) -- [JavaScript Value vs Reference Types — Programming with Mosh](https://www.youtube.com/watch?v=fD0t_DKREbE) -- [VALORES vs REFERENCIAS en JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=AvkyOrWkuQc) -- [JavaScript - Reference vs Primitive Values/ Types - Academind](https://www.youtube.com/watch?v=9ooYYRLdg_g) -- [Value Types and Reference Types in JavaScript - Programming with Mosh](https://www.youtube.com/watch?v=e-_mDyqm2oU) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 4. Implicit, Explicit, Nominal, Structuring and Duck Typing - -<p>The ECMAScript specification defines JavaScript as a dynamically typed language, meaning that types are associated with values rather than variables, and type checking occurs at runtime. There are various ways JavaScript manages types:</p> - -<p><strong>Implicit Typing (or Type Coercion):</strong> This occurs when JavaScript automatically converts one data type to another when required. For instance, JavaScript might convert a string to a number during an arithmetic operation. While this can simplify some code, it can also lead to unexpected results if not handled carefully.</p> - -<p><strong>Explicit Typing:</strong> Unlike implicit typing, explicit typing involves manually converting a value from one type to another using functions like Number(), String(), or Boolean().</p> - -<p><strong>Nominal Typing:</strong> JavaScript doesn't natively support nominal typing, where types are explicitly declared and checked. However, TypeScript, a superset of JavaScript, brings this feature to help catch type errors during development.</p> - -<p><strong>Structural Typing:</strong> In this type system, types are based on the structure or properties of the data. JavaScript is a structurally typed language where objects are compatible if they share the same structure (i.e., the same set of properties and methods).</p> - -<p><strong>Duck Typing:</strong> This is a concept where an object's suitability is determined by the presence of certain properties and methods, rather than by the actual type of the object. JavaScript relies heavily on duck typing, where behavior is inferred from an object's properties rather than its declared type.</p> - -### <img align="center" width="30" height="30" src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [What you need to know about Javascript's Implicit Coercion — Promise Tochi](https://dev.to/promhize/what-you-need-to-know-about-javascripts-implicit-coercion-e23) -- [JavaScript Type Coercion Explained — Alexey Samoshkin](https://medium.freecodecamp.org/js-type-coercion-explained-27ba3d9a2839) -- [Javascript Coercion Explained — Ben Garrison](https://hackernoon.com/javascript-coercion-explained-545c895213d3) -- [What exactly is Type Coercion in Javascript? - Stack Overflow](https://stackoverflow.com/questions/19915688/what-exactly-is-type-coercion-in-javascript) - -### <img align="center" width="30" height="30" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [== ? === ??? ...#@^% - Shirmung Bielefeld](https://www.youtube.com/watch?v=qGyqzN0bjhc&t) -- [Coercion in Javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=b04Q_vyqEG8) -- [JavaScript Questions: What is Coercion? - Steven Hancock](https://www.youtube.com/watch?v=z4-8wMSPJyI) -- [Typing: Static vs Dynamic, Weak vs. Strong - Codexpanse](https://www.youtube.com/watch?v=C5fr0LZLMAs) -- [EL SISTEMA de TIPOS DE JAVASCRIPT - La Cocina del Código](https://www.youtube.com/watch?v=0ei4nb49GKo) -- [Duck Typing in Javascript - Techmaker Studio](https://www.youtube.com/watch?v=oEpgyoMEkrM) -- [Duck Typing in Javascript - Programming with Kartik](https://youtu.be/e4X1KAuk6Bs?si=krZKbsM2i3tmIl2G) - -### Books - -- [You Don't Know JS, 1st Edition: Types & Grammar — Kyle Simpson](https://github.com/getify/You-Dont-Know-JS/tree/1st-ed) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 5. == vs === vs typeof - According to the ECMAScript specification, JavaScript includes both strict (===) and loose (==) equality operators, which behave differently when comparing values. Here's a breakdown: - -== (Loose Equality): This operator performs type coercion before comparing two values. If the values are of different types, JavaScript will attempt to convert one or both values to a common type before comparison, which can lead to unexpected results. - -=== (Strict Equality): This operator compares both the value and the type without any type coercion. If the two values are not of the same type, the comparison will return false. - -typeof Operator: The typeof operator is used to check the data type of a variable. While it's generally reliable, there are certain quirks, like how typeof null returns "object" instead of "null", due to a long-standing behavior in JavaScript's implementation. - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [JavaScript Double Equals vs. Triple Equals — Brandon Morelli](https://codeburst.io/javascript-double-equals-vs-triple-equals-61d4ce5a121a) -- [Should I use === or == equality comparison operator in JavaScript? — Panu Pitkamaki](https://bytearcher.com/articles/equality-comparison-operator-javascript/) -- [Why Use the Triple-Equals Operator in JavaScript? — Louis Lazaris](https://www.impressivewebs.com/why-use-triple-equals-javascipt/) -- [What is the difference between == and === in JavaScript? — Craig Buckler](https://www.oreilly.com/learning/what-is-the-difference-between-and-in-javascript) -- [Why javascript's typeof always return "object"? — Stack Overflow](https://stackoverflow.com/questions/3787901/why-javascripts-typeof-always-return-object) -- [Checking Types in Javascript — Toby Ho](http://tobyho.com/2011/01/28/checking-types-in-javascript/) -- [How to better check data types in JavaScript — Webbjocke](https://webbjocke.com/javascript-check-data-types/) -- [Checking for the Absence of a Value in JavaScript — Tomer Aberbach](https://tomeraberba.ch/html/post/checking-for-the-absence-of-a-value-in-javascript.html) -- [Difference Between == and === in Javascript](https://www.scaler.com/topics/javascript/difference-between-double-equals-and-triple-equals-in-javascript/) -- [Difference between == and === in JavaScript — GeeksforGeeks](https://www.geeksforgeeks.org/difference-between-double-equal-vs-triple-equal-javascript/) -- [=== vs == Comparision in JavaScript — FreeCodeCamp](https://www.freecodecamp.org/news/javascript-triple-equals-sign-vs-double-equals-sign-comparison-operators-explained-with-examples/) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript - The typeof operator — Java Brains](https://www.youtube.com/watch?v=ol_su88I3kw) -- [Javascript typeof operator — DevDelight](https://www.youtube.com/watch?v=qPYhTPt_SbQ) -- [JavaScript "==" VS "===" — Web Dev Simplified](https://www.youtube.com/watch?v=C5ZVC4HHgIg) -- [=== vs == in javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=a0S1iG3TgP0) -- [The typeof operator in JS - CodeVault](https://www.youtube.com/watch?v=NSS5WRcv7yM) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 6. Function Scope, Block Scope and Lexical Scope - The ECMAScript specification outlines three key types of scope: - -Function Scope: Variables declared within a function using var are only accessible within that function. This scope isolates variables from being accessed outside of the function where they are declared. - -Block Scope: Introduced with ES6, variables declared with let and const are block-scoped. This means they are only accessible within the specific block {} in which they are defined, such as inside loops or conditionals. - -Lexical Scope: Refers to how variable access is determined based on the physical location of the variables in the code. Functions are lexically scoped, meaning that they can access variables from their parent scope. - -### Books - -- [You Don't Know JS Yet, 2nd Edition: Scope & Closures — Kyle Simpson](https://github.com/getify/You-Dont-Know-JS/tree/2nd-ed/scope-closures) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [JavaScript Functions — Understanding The Basics — Brandon Morelli](https://codeburst.io/javascript-functions-understanding-the-basics-207dbf42ed99) -- [Var, Let, and Const – What's the Difference?](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) -- [Functions in JavaScript - Deepa Pandey](https://www.scaler.com/topics/javascript/javascript-functions/) -- [Emulating Block Scope in JavaScript — Josh Clanton](http://adripofjavascript.com/blog/drips/emulating-block-scope-in-javascript.html) -- [The Difference Between Function and Block Scope in JavaScript — Joseph Cardillo](https://medium.com/@josephcardillo/the-difference-between-function-and-block-scope-in-javascript-4296b2322abe) -- [Understanding Scope and Context in JavaScript — Ryan Morr](http://ryanmorr.com/understanding-scope-and-context-in-javascript/) -- [JavaScript Scope and Closures — Zell Liew](https://css-tricks.com/javascript-scope-closures/) -- [Understanding Scope in JavaScript — Wissam Abirached](https://developer.telerik.com/topics/web-development/understanding-scope-in-javascript/) -- [Understanding Scope in JavaScript ― Hammad Ahmed](https://scotch.io/tutorials/understanding-scope-in-javascript) -- [When to use a function declaration vs. a function expression ― Amber Wilkie](https://medium.freecodecamp.org/when-to-use-a-function-declarations-vs-a-function-expression-70f15152a0a0) -- [A JavaScript Fundamentals Cheat Sheet: Scope, Context, and "this" ― Alexandra Fren](https://dev.to/alexandrafren/a-javascript-fundamentals-cheat-sheet-scope-context-and-this-28ai) -- [Functions / Function scope ― MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#function_scope) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [What Makes Javascript Weird ... and Awesome pt. 4 — LearnCode.academy](https://www.youtube.com/watch?v=SBwoFkRjZvE) -- [Variable Scope in JavaScript — Kirupa Chinnathambi](https://www.youtube.com/watch?v=dhp57T3p760) -- [JavaScript Block Scope and Function Scope — mmtuts](https://www.youtube.com/watch?v=aK_nuUAdr8E) -- [What the Heck is Lexical Scope? — NWCalvank](https://www.youtube.com/watch?v=GhNA0r10MmA) -- [Variable Scope — Steve Griffith](https://www.youtube.com/watch?v=FyWdrCZZavQ) -- [Javascript Tutorials for Beginners — Mosh Hemadani](https://www.youtube.com/watch?v=W6NZfCO5SIk) -- [JavaScript Block scope vs Function scope - nivek](https://www.youtube.com/watch?v=IaTztAtoNEY) -- [Lexical scoping in javascript - Hitesh Choudhary](https://www.youtube.com/watch?v=qT5S7GgIioE) -- [Modern Scope Handling in JavaScript (ES6 and Beyond) -Prashant Dewangan ](https://www.youtube.com/watch?v=zMseUdOR7z8) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 7. Expression vs Statement -According to the ECMAScript specification, expressions produce a value, and statements are instructions to perform an action, such as variable assignment or control flow. Function declarations are hoisted and can be called before they are defined in the code, while function expressions are not hoisted and must be defined before being invoked. - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [All you need to know about Javascript's Expressions, Statements and Expression Statements — Promise Tochi](https://dev.to/promhize/javascript-in-depth-all-you-need-to-know-about-expressions-statements-and-expression-statements-5k2) -- [Function Expressions vs Function Declarations — Paul Wilkins](https://www.sitepoint.com/function-expressions-vs-declarations/) -- [JavaScript Function — Declaration vs Expression — Ravi Roshan](https://medium.com/@raviroshan.talk/javascript-function-declaration-vs-expression-f5873b8c7b38) -- [Function Declarations vs. Function Expressions — Mandeep Singh](https://medium.com/@mandeep1012/function-declarations-vs-function-expressions-b43646042052) -- [Function Declarations vs. Function Expressions — Anguls Croll](https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/) -- [Expression statement — MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Expression_statement) - - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Expressions vs. Statements in JavaScript — Hexlet](https://www.youtube.com/watch?v=WVyCrI1cHi8) -- [JavaScript - Expression vs. Statement — WebTunings](https://www.youtube.com/watch?v=3jDpNGJkupA) -- [Javascript Function Expression Vs Declaration For Beginners — Dev Material](https://www.youtube.com/watch?v=qz7Nq1tV7Io) -- [The difference between an expression and a statement in JavaScript](https://youtu.be/eWTuFoBYiwg) -- [Expression in javascript | Statement in javascript - Sathelli Srikanth](https://www.youtube.com/watch?v=cVDs3TZ-kXs) - - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 8. IIFE, Modules and Namespaces -With the introduction of ES6 modules, the role of IIFEs in scope isolation has diminished but they still remain relevant. -### Reference - -- [IIFE — MDN](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) -- [Modularity — MDN](https://developer.mozilla.org/en-US/docs/Glossary/modularity) -- [Namespace — MDN](https://developer.mozilla.org/en-US/docs/Glossary/Namespace) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Mastering Immediately-Invoked Function Expressions ― Chandra Gundamaraju](https://medium.com/@vvkchandra/essential-javascript-mastering-immediately-invoked-function-expressions-67791338ddc6) -- [JavaScript Immediately Invoked Function Expression — javascripttutorial.net](https://www.javascripttutorial.net/javascript-immediately-invoked-function-expression-iife/) -- [A 10 minute primer to JavaScript modules, module formats, module loaders and module bundlers ― Jurgen Van de Moere](https://www.jvandemo.com/a-10-minute-primer-to-javascript-modules-module-formats-module-loaders-and-module-bundlers/) -- [Modules ― Exploring JS](http://exploringjs.com/es6/ch_modules.html) -- [Understanding ES6 Modules — Craig Buckler](https://www.sitepoint.com/understanding-es6-modules/) -- [An overview of ES6 Modules in JavaScript — Brent Graham](https://blog.cloud66.com/an-overview-of-es6-modules-in-javascript/) -- [ES6 Modules in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-modules-in-depth) -- [ES6 modules, Node.js and the Michael Jackson Solution — Alberto Gimeno](https://medium.com/dailyjs/es6-modules-node-js-and-the-michael-jackson-solution-828dc244b8b) -- [JavaScript Modules: A Beginner's Guide — Preethi Kasireddy](https://medium.freecodecamp.org/javascript-modules-a-beginner-s-guide-783f7d7a5fcc) -- [Using JavaScript modules on the web — Addy Osmani & Mathias Bynens](https://developers.google.com/web/fundamentals/primers/modules) -- [IIFE: Immediately Invoked Function Expressions — Parwinder](https://dev.to/bhagatparwinder/iife-immediately-invoked-function-expressions-49c5) -- [Javascript Module Bundlers — Vanshu Hassija](https://sassy-butter-197.notion.site/Javascript-bundlers-016932b17b0744e983c2cc0db31e6f02) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Immediately Invoked Function Expression - Beau teaches JavaScript — freeCodeCamp](https://www.youtube.com/watch?v=3cbiZV4H22c) -- [Understanding JavaScript IIFE — Sheo Narayan](https://www.youtube.com/watch?v=I5EntfMeIIQ) -- [JavaScript Modules: ES6 Import and Export — Kyle Robinson](https://www.youtube.com/watch?v=_3oSWwapPKQ) -- [ES6 - Modules — Ryan Christiani](https://www.youtube.com/watch?v=aQr2bV1BPyE) -- [ES6 Modules in the Real World — Sam Thorogood](https://www.youtube.com/watch?v=fIP4pjAqCtQ) -- [ES6 Modules — TempleCoding](https://www.youtube.com/watch?v=5P04OK6KlXA) -- [JavaScript IIFE (Immediately Invoked Function Expressions) — Steve Griffith](https://www.youtube.com/watch?v=Xd7zgPFwVX8&) - -**[⬆ Back to Top](#table-of-contents)** - --- -## 9. Message Queue and Event Loop -The Event Loop is a critical part of JavaScript's concurrency model, ensuring non-blocking behavior by processing tasks in an asynchronous manner. Understanding how it interacts with the Message Queue and Microtasks is key to mastering JavaScript behavior. -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +## About -- [JavaScript Event Loop Explained — Anoop Raveendran](https://medium.com/front-end-hacking/javascript-event-loop-explained-4cd26af121d4) -- [Understanding JS: The Event Loop — Alexander Kondov](https://hackernoon.com/understanding-js-the-event-loop-959beae3ac40) -- [The JavaScript Event Loop — Flavio Copes](https://flaviocopes.com/javascript-event-loop/) -- [Tasks, microtasks, queues and schedules — Jake Archibald](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) -- [Visualising the JavaScript Event Loop with a Pizza Restaurant analogy — Priyansh Jain](https://dev.to/presto412/visualising-the-javascript-event-loop-with-a-pizza-restaurant-analogy-47a8) -- [JavaScript Visualized: Event Loop — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif) -- [Understanding and Optimizing JavaScript's Event Loop — Xiuer Old](https://medium.com/javascript-zone/understanding-and-optimizing-javascripts-event-loop-717ae0095038#:~:text=The%20event%20loop%20is%20the,%2Dblocking%20I%2FO%20operations.) +This repository helps developers master core JavaScript concepts. Each concept includes clear explanations, practical code examples, and curated resources. -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [What the heck is the event loop anyway? | JSConf EU — Philip Roberts](https://www.youtube.com/watch?v=8aGhZQkoFbQ) -- [JavaScript Event Loop — ComScience Simplified](https://www.youtube.com/watch?v=XzXIMZMN9k4) -- [I'm stuck in an Event Loop — Philip Roberts](https://www.youtube.com/watch?v=6MXRNXXgP_0) -- [In The Loop - Jake Archibald | JSConf.Asia 2018](https://www.youtube.com/watch?v=cCOL7MC4Pl0) -- [Desmitificando el Event Loop (Spanish)](https://www.youtube.com/watch?v=Eqq2Rb7LzYE) -- [Callbacks, Sincrono, Assíncrono e Event Loop (PT-BR)](https://www.youtube.com/watch?v=6lbBaM18X3g) -- [JavaScript Event Loop: How it Works and Why it Matters in 5 Minutes - James Q Quick](https://www.youtube.com/watch?v=6lbBaM18X3g) - -**[⬆ Back to Top](#table-of-contents)** +**[Start learning at 33jsconcepts.com →](https://33jsconcepts.com)** --- -## 10. setTimeout, setInterval and requestAnimationFrame +## Concepts -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +### Fundamentals -- [setTimeout and setInterval — JavaScript.Info](https://javascript.info/settimeout-setinterval) -- [Why not to use setInterval — Akanksha Sharma](https://dev.to/akanksha_9560/why-not-to-use-setinterval--2na9) -- [setTimeout VS setInterval — Develoger](https://develoger.com/settimeout-vs-setinterval-cff85142555b) -- [Using requestAnimationFrame — Chris Coyier](https://css-tricks.com/using-requestanimationframe/) -- [Understanding JavaScript's requestAnimationFrame() — JavaScript Kit](http://www.javascriptkit.com/javatutors/requestanimationframe.shtml) -- [Handling time intervals in JavaScript - Amit Merchant](https://www.amitmerchant.com/Handling-Time-Intervals-In-Javascript/) -- [Debounce – How to Delay a Function in JavaScript - Ondrej Polesny](https://www.freecodecamp.org/news/javascript-debounce-example/) +- **[Primitive Types](https://33jsconcepts.com/concepts/primitive-types)** + Learn JavaScript's 7 primitive types: string, number, bigint, boolean, undefined, null, and symbol. Understand immutability, typeof quirks, and autoboxing. -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos +- **[Value vs Reference Types](https://33jsconcepts.com/concepts/value-reference-types)** + Learn how value types and reference types work in JavaScript. Understand how primitives and objects are stored, why copying objects shares references, and how to avoid mutation bugs. -- [Javascript: How setTimeout and setInterval works — Coding Blocks India](https://www.youtube.com/watch?v=6bPKyl8WYWI) -- [TRUST ISSUES with setTimeout() — Akshay Saini ](https://youtu.be/nqsPmuicJJc?si=4FXKlZfqiJUqO2Y4) -- [setTimeout and setInterval in JavaScript — techsith](https://www.youtube.com/watch?v=TbCgGWe8LN8) -- [JavaScript Timers — Steve Griffith](https://www.youtube.com/watch?v=0VVJSvlUgtg) -- [JavaScript setTimeOut and setInterval Explained — Theodore Anderson](https://www.youtube.com/watch?v=mVKfrWCOB60) +- **[Type Coercion](https://33jsconcepts.com/concepts/type-coercion)** + Learn JavaScript type coercion and implicit conversion. Understand how values convert to strings, numbers, and booleans, the 8 falsy values, and how to avoid common coercion bugs. -**[⬆ Back to Top](#table-of-contents)** +- **[Equality Operators](https://33jsconcepts.com/concepts/equality-operators)** + Learn JavaScript equality operators == vs ===, typeof quirks, and Object.is(). Understand type coercion, why NaN !== NaN, and why typeof null returns 'object'. ---- +- **[Scope and Closures](https://33jsconcepts.com/concepts/scope-and-closures)** + Learn JavaScript scope and closures. Understand the three types of scope, var vs let vs const, lexical scoping, the scope chain, and closure patterns for data privacy. -## 11. JavaScript Engines +- **[Call Stack](https://33jsconcepts.com/concepts/call-stack)** + Learn how the JavaScript call stack tracks function execution. Understand stack frames, LIFO ordering, execution contexts, stack overflow errors, and debugging with stack traces. +### Functions & Execution -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +- **[Event Loop](https://33jsconcepts.com/concepts/event-loop)** + Learn how the JavaScript event loop handles async code. Understand the call stack, task queue, microtasks, and why Promises always run before setTimeout(). -- [JavaScript Engines — Jen Looper](http://www.softwaremag.com/javascript-engines/) -- [Understanding How the Chrome V8 Engine Translates JavaScript into Machine Code — DroidHead](https://medium.freecodecamp.org/understanding-the-core-of-nodejs-the-powerful-chrome-v8-engine-79e7eb8af964) -- [Understanding V8's Bytecode — Franziska Hinkelmann](https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775) -- [JavaScript essentials: why you should know how the engine works - Rainer Hahnekamp](https://www.freecodecamp.org/news/javascript-essentials-why-you-should-know-how-the-engine-works-c2cc0d321553) -- [JavaScript engine fundamentals: Shapes and Inline Caches](https://mathiasbynens.be/notes/shapes-ics) -- [JavaScript engine fundamentals: optimizing prototypes](https://mathiasbynens.be/notes/prototypes) -- [How V8 optimizes array operations](https://v8.dev/blog/elements-kinds) -- [JavaScript Internals: JavaScript engine, Run-time environment & setTimeout Web API — Rupesh Mishra](https://blog.bitsrc.io/javascript-internals-javascript-engine-run-time-environment-settimeout-web-api-eeed263b1617) +- **[IIFE, Modules & Namespaces](https://33jsconcepts.com/concepts/iife-modules)** + Learn how to organize JavaScript code with IIFEs, namespaces, and ES6 modules. Understand private scope, exports, dynamic imports, and common module mistakes. -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos +### Web Platform -- [JavaScript Engines: The Good Parts™ — Mathias Bynens & Benedikt Meurer](https://www.youtube.com/watch?v=5nmpokoRaZI) -- [JS Engine EXPOSED 🔥 Google's V8 Architecture 🚀 | Namaste JavaScript Ep. 16 - Akshay Saini](https://www.youtube.com/watch?v=2WJL19wDH68) -- [How JavaScript Code is executed? How Javascript works behind the scenes](https://youtu.be/iLWTnMzWtj4) -- [Understanding the V8 JavaScript Engine - freeCodeCamp Talks](https://www.youtube.com/watch?v=xckH5s3UuX4) -- [JavaScript Under The Hood - JavaScript Engine Overview - Traversy Media](https://www.youtube.com/watch?v=oc6faXVc54E) -- [Arindam Paul - JavaScript VM internals, EventLoop, Async and ScopeChains](https://www.youtube.com/watch?v=QyUFheng6J0) +- **[DOM](https://33jsconcepts.com/concepts/dom)** + Learn how the DOM works in JavaScript. Understand how browsers represent HTML as a tree, select and manipulate elements, traverse nodes, and optimize rendering performance. -**[⬆ Back to Top](#table-of-contents)** +- **[Fetch API](https://33jsconcepts.com/concepts/http-fetch)** + Learn how to make HTTP requests with the JavaScript Fetch API. Understand GET, POST, response handling, JSON parsing, error patterns, and AbortController for cancellation. ---- +- **[Web Workers](https://33jsconcepts.com/concepts/web-workers)** + Learn Web Workers in JavaScript for running code in background threads. Understand postMessage, Dedicated and Shared Workers, and transferable objects. -## 12. Bitwise Operators, Type Arrays and Array Buffers +### Object-Oriented JavaScript -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +- **[Factories and Classes](https://33jsconcepts.com/concepts/factories-classes)** + Learn JavaScript factory functions and ES6 classes. Understand constructors, prototypes, private fields, inheritance, and when to use each pattern. -- [Programming with JS: Bitwise Operations — Alexander Kondov](https://hackernoon.com/programming-with-js-bitwise-operations-393eb0745dc4) -- [Using JavaScript's Bitwise Operators in Real Life — ian m](https://codeburst.io/using-javascript-bitwise-operators-in-real-life-f551a731ff5) -- [JavaScript Bitwise Operators — w3resource](https://www.w3resource.com/javascript/operators/bitwise-operator.php) -- [Bitwise Operators in Javascript — Joe Cha](https://medium.com/bother7-blog/bitwise-operators-in-javascript-65c4c69be0d3) -- [A Comprehensive Primer on Binary Computation and Bitwise Operators in javascript — Paul Brown](https://medium.com/techtrument/a-comprehensive-primer-on-binary-computation-and-bitwise-operators-in-javascript-81acf8341f04) -- [How can I understand Bitwise operation in JavaScript?](https://www.quora.com/How-can-I-understand-Bitwise-operation-in-JavaScript) +- **[this, call, apply, bind](https://33jsconcepts.com/concepts/this-call-apply-bind)** + Learn how JavaScript's 'this' keyword works and how to control context binding. Understand the 5 binding rules, call/apply/bind methods, arrow functions, and common pitfalls. -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos +- **[Object Creation & Prototypes](https://33jsconcepts.com/concepts/object-creation-prototypes)** + Learn JavaScript's prototype chain and object creation. Understand how inheritance works, the new operator's 4 steps, Object.create(), Object.assign(), and prototype methods. -- [JavaScript Bitwise Operators — Programming with Mosh](https://www.youtube.com/watch?v=mesu75PTDC8) -- [Bitwise Operators and WHY we use them — Alex Hyett](https://www.youtube.com/watch?v=igIjGxF2J-w) -- [JS Bitwise Operators and Binary Numbers — Steve Griffith - Prof3ssorSt3v3](https://www.youtube.com/watch?v=RRyxCmLX_ag) -- [Deep Dive into Blobs, Files, and ArrayBuffers — Steve Griffith - Prof3ssorSt3v3](https://www.youtube.com/watch?v=ScZZoHj7mqY) +- **[Inheritance & Polymorphism](https://33jsconcepts.com/concepts/inheritance-polymorphism)** + Learn inheritance and polymorphism in JavaScript — extending classes, prototype chains, method overriding, and code reuse patterns. -**[⬆ Back to Top](#table-of-contents)** +### Async JavaScript ---- +- **[Callbacks](https://33jsconcepts.com/concepts/callbacks)** + Learn JavaScript callbacks, functions passed to other functions to be called later. Understand sync vs async callbacks, error-first patterns, callback hell, and why Promises were invented. -## 13. DOM and Layout Trees +- **[Promises](https://33jsconcepts.com/concepts/promises)** + Learn JavaScript Promises for handling async operations. Understand how to create, chain, and combine Promises, handle errors properly, and avoid common pitfalls. -### Reference +- **[async/await](https://33jsconcepts.com/concepts/async-await)** + Learn async/await in JavaScript. Syntactic sugar over Promises that makes async code readable. Covers error handling with try/catch, parallel execution with Promise.all, and common pitfalls. -- [Document Object Model (DOM) — MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) +- **[Generators & Iterators](https://33jsconcepts.com/concepts/generators-iterators)** + Learn JavaScript generators and iterators. Understand yield, the iteration protocol, lazy evaluation, infinite sequences, and async generators with for await...of. -### Books +### Functional Programming -- [Eloquent JavaScript, 3rd Edition: Ch. 14 - The Document Object Model](https://eloquentjavascript.net/14_dom.html) +- **[Higher-Order Functions](https://33jsconcepts.com/concepts/higher-order-functions)** + Learn higher-order functions in JavaScript. Understand functions that accept or return other functions, create reusable abstractions, and write cleaner code. -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +- **[Pure Functions](https://33jsconcepts.com/concepts/pure-functions)** + Learn pure functions in JavaScript. Understand the two rules of purity, avoid side effects, and write testable, predictable code with immutable patterns. -- [How To Understand and Modify the DOM in JavaScript — Tania Rascia](https://www.digitalocean.com/community/tutorials/introduction-to-the-dom) -- [What's the Document Object Model, and why you should know how to use it — Leonardo Maldonado](https://medium.freecodecamp.org/whats-the-document-object-model-and-why-you-should-know-how-to-use-it-1a2d0bc5429d) -- [JavaScript DOM Tutorial with Example — Guru99](https://www.guru99.com/how-to-use-dom-and-events-in-javascript.html) -- [What is the DOM? — Chris Coyier](https://css-tricks.com/dom/) -- [Traversing the DOM with JavaScript — Zell Liew](https://zellwk.com/blog/dom-traversals/) -- [DOM Tree](https://javascript.info/dom-nodes) -- [How to traverse the DOM in Javascript — Vojislav Grujić](https://medium.com/javascript-in-plain-english/how-to-traverse-the-dom-in-javascript-d6555c335b4e) -- [Render Tree Construction — Ilya Grigorik](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction) -- [What exactly is the DOM?](https://bitsofco.de/what-exactly-is-the-dom/) -- [JavaScript DOM](https://www.javascripttutorial.net/javascript-dom/) -- [Traversing the Dom with Javascript](https://www.youtube.com/watch?v=Pr4LLrmDLLo) - Steve Griffith (YouTube) +- **[map, reduce, filter](https://33jsconcepts.com/concepts/map-reduce-filter)** + Learn map, reduce, and filter in JavaScript. Transform, filter, and combine arrays without mutation. Includes method chaining and common pitfalls. -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos +- **[Recursion](https://33jsconcepts.com/concepts/recursion)** + Learn recursion in JavaScript. Understand base cases, recursive calls, the call stack, and patterns like factorial, tree traversal, and memoization. -- [JavaScript DOM — The Net Ninja](https://www.youtube.com/watch?v=FIORjGvT0kk) -- [JavaScript DOM Crash Course — Traversy Media](https://www.youtube.com/watch?v=0ik6X4DJKCc) -- [JavaScript DOM Manipulation Methods — Web Dev Simplified](https://www.youtube.com/watch?v=y17RuWkWdn8) -- [JavaScript DOM Traversal Methods — Web Dev Simplified](https://www.youtube.com/watch?v=v7rSSy8CaYE) +- **[Currying & Composition](https://33jsconcepts.com/concepts/currying-composition)** + Learn currying and function composition in JavaScript. Build reusable functions from simple pieces using curry, compose, and pipe for cleaner, modular code. -**[⬆ Back to Top](#table-of-contents)** +### Advanced Topics ---- - -## 14. Factories and Classes +- **[JavaScript Engines](https://33jsconcepts.com/concepts/javascript-engines)** + Learn how JavaScript engines work. Understand V8's architecture, parsing, compilation, JIT optimization, hidden classes, inline caching, and garbage collection. -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +- **[Error Handling](https://33jsconcepts.com/concepts/error-handling)** + Learn JavaScript error handling with try/catch/finally. Understand Error types, custom errors, async error patterns, and best practices for robust code. -- [How To Use Classes in JavaScript — Tania Rascia](https://www.digitalocean.com/community/tutorials/understanding-classes-in-javascript) -- [Javascript Classes — Under The Hood — Majid](https://medium.com/tech-tajawal/javascript-classes-under-the-hood-6b26d2667677) -- [Better JavaScript with ES6, Pt. II: A Deep Dive into Classes ― Peleke Sengstacke](https://scotch.io/tutorials/better-javascript-with-es6-pt-ii-a-deep-dive-into-classes) -- [Understand the Factory Design Pattern in Plain JavaScript — Aditya Agarwal](https://medium.com/front-end-hacking/understand-the-factory-design-pattern-in-plain-javascript-20b348c832bd) -- [Factory Functions in JavaScript — Josh Miller](https://atendesigngroup.com/blog/factory-functions-javascript) -- [The Factory Pattern in JS ES6 — SnstsDev](https://medium.com/@SntsDev/the-factory-pattern-in-js-es6-78f0afad17e9) -- [Class vs Factory function: exploring the way forward — Cristi Salcescu](https://medium.freecodecamp.org/class-vs-factory-function-exploring-the-way-forward-73258b6a8d15) -- [How ES6 classes really work and how to build your own — Robert Grosse](https://medium.com/@robertgrosse/how-es6-classes-really-work-and-how-to-build-your-own-fd6085eb326a) -- [Understanding `super` in JavaScript](https://jordankasper.com/understanding-super-in-javascript) -- [An Easy Guide To Understanding Classes In JavaScript](https://dev.to/lawrence_eagles/an-easy-guide-to-understanding-classes-in-javascript-3bcm) +- **[Regular Expressions](https://33jsconcepts.com/concepts/regular-expressions)** + Learn regular expressions in JavaScript. Covers pattern syntax, character classes, quantifiers, flags, capturing groups, and methods like test, match, and replace. -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos +- **[Modern JS Syntax](https://33jsconcepts.com/concepts/modern-js-syntax)** + Learn modern JavaScript ES6+ syntax. Covers destructuring, spread/rest operators, arrow functions, optional chaining, nullish coalescing, and template literals. -- [JavaScript Factory Functions — Programming with Mosh](https://www.youtube.com/watch?v=jpegXpQpb3o) -- [Factory Functions in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=ImwrezYhw4w) -- [Javascript Tutorial Function Factories — Crypto Chan](https://www.youtube.com/watch?v=R7-IwpH80UE) +- **[ES Modules](https://33jsconcepts.com/concepts/es-modules)** + Learn ES Modules in JavaScript. Understand import/export syntax, why ESM beats CommonJS, live bindings, dynamic imports, top-level await, and how modules enable tree-shaking. -**[⬆ Back to Top](#table-of-contents)** - ---- +- **[Data Structures](https://33jsconcepts.com/concepts/data-structures)** + Learn JavaScript data structures from built-in Arrays, Objects, Maps, and Sets to implementing Stacks, Queues, and Linked Lists. Understand when to use each structure. -## 15. this, call, apply and bind +- **[Algorithms & Big O](https://33jsconcepts.com/concepts/algorithms-big-o)** + Learn Big O notation and algorithms in JavaScript. Understand time complexity, implement searching and sorting algorithms, and recognize common interview patterns. -### Reference +- **[Design Patterns](https://33jsconcepts.com/concepts/design-patterns)** + Learn JavaScript design patterns like Module, Singleton, Observer, Factory, Proxy, and Decorator. Understand when to use each pattern and avoid common pitfalls. -- [call() — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) -- [bind() — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind) -- [apply() — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Grokking call(), apply() and bind() methods in JavaScript — Aniket Kudale](https://levelup.gitconnected.com/grokking-call-apply-and-bind-methods-in-javascript-392351a4be8b) -- [JavaScript's Apply, Call, and Bind Methods are Essential for JavaScript Professionals — Richard Bovell](http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/) -- [Javascript: call(), apply() and bind() — Omer Goldberg](https://medium.com/@omergoldberg/javascript-call-apply-and-bind-e5c27301f7bb) -- [The difference between call / apply / bind — Ivan Sifrim](https://medium.com/@ivansifrim/the-differences-between-call-apply-bind-276724bb825b) -- [What the hack is call, apply, bind in JavaScript — Ritik](https://dev.to/ritik_dev_js/what-the-hack-is-call-apply-bind-in-javascript-11ce) -- [Mastering 'this' in JavaScript: Callbacks and bind(), apply(), call() — Michelle Gienow](https://thenewstack.io/mastering-javascript-callbacks-bind-apply-call/) -- [JavaScript's apply, call, and bind explained by hosting a cookout — Kevin Kononenko](https://dev.to/kbk0125/javascripts-apply-call-and-bind-explained-by-hosting-a-cookout-32jo) -- [How AND When to use bind, call, and apply in Javascript — Eigen X](https://www.eigenx.com/blog/https/mediumcom/eigen-x/how-and-when-to-use-bind-call-and-apply-in-javascript-77b6f42898fb) -- [Let me explain to you what is `this`. (Javascript) — Jason Yu](https://dev.to/ycmjason/let-me-explain-to-you-what-is-this-javascript-44ja) -- [Understanding the "this" Keyword in JavaScript — Pavan](https://medium.com/quick-code/understanding-the-this-keyword-in-javascript-cb76d4c7c5e8) -- [How to understand the keyword this and context in JavaScript — Lukas Gisder-Dubé](https://medium.freecodecamp.org/how-to-understand-the-keyword-this-and-context-in-javascript-cd624c6b74b8) -- [What the heck is this in Javascript? — Hridayesh Sharma](https://dev.to/_hridaysharma/what-the-heck-is-this-in-javascript-37n1) -- [This and Bind In Javascript — Brian Barbour](https://dev.to/steelvoltage/this-and-bind-in-javascript-2pam) -- [3 Techniques for Maintaining Your Sanity Using "This" in JavaScript — Carl](https://dev.to/canderson93/3-techniques-for-maintaining-your-sanity-using-this-in-javascript-3idf) -- [Mastering the JavaScript "this" Keyword — Aakash Srivastav](https://dev.to/aakashsr/mastering-the-javascript-this-keyword-4pfa) -- [This binding in JavaScript – 4. New binding — Spyros Argalias](https://dev.to/sargalias/this-binding-in-javascript-4-new-binding-2p1n) -- [A quick intro to 'this' in JavaScript — Natalie Smith](https://dev.to/thatgalnatalie/a-quick-intro-to-this-in-javascript-2mhp) -- [A conversation with the 'this' keyword in Javascript — Karen Efereyan](https://dev.to/developerkaren/a-conversation-with-the-this-keyword-in-javascript-3j6g) -- [What are call(), apply() and bind() in JavaScript — Amitav Mishra](https://jscurious.com/what-are-call-apply-and-bind-in-javascript/) -- [Understanding 'this' binding in JavaScript — Yasemin Cidem](https://yasemincidem.medium.com/understanding-this-binding-in-javascript-86687397c76d) -- [Top 7 tricky questions of 'this' keyword](https://dmitripavlutin.com/javascript-this-interview-questions/) - - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript call, apply and bind — techsith](https://www.youtube.com/watch?v=c0mLRpw-9rI) -- [JavaScript Practical Applications of Call, Apply and Bind functions— techsith](https://www.youtube.com/watch?v=AYVYxezrMWA) -- [JavaScript (call, bind, apply) — curious aatma](https://www.youtube.com/watch?v=Uy0NOXLBraE) -- [Understanding Functions and 'this' In The World of ES2017 — Bryan Hughes](https://www.youtube.com/watch?v=AOSYY1_np_4) -- [bind and this - Object Creation in JavaScript - FunFunFunction](https://www.youtube.com/watch?v=GhbhD1HR5vk) -- [JS Function Methods call(), apply(), and bind() — Steve Griffith](https://www.youtube.com/watch?v=uBdH0iB1VDM) -- [call, apply and bind method in JavaScript](https://www.youtube.com/watch?v=75W8UPQ5l7k&t=261s) -- .[Javascript Interview Questions ( Call, Bind and Apply ) - Polyfills, Output Based, Explicit Binding - Roadside Coder] (https://youtu.be/VkmUOktYDAU?si=SdvLZ8FBmephPxjS) - -**[⬆ Back to Top](#table-of-contents)** +- **[Clean Code](https://33jsconcepts.com/concepts/clean-code)** + Learn clean code principles for JavaScript. Covers meaningful naming, small functions, DRY, avoiding side effects, and best practices to write maintainable code. --- -## 16. new, Constructor, instanceof and Instances +## Translations -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +This project has been translated into 40+ languages by our amazing community! -- [JavaScript For Beginners: the 'new' operator — Brandon Morelli](https://codeburst.io/javascript-for-beginners-the-new-operator-cee35beb669e) -- [Let's demystify JavaScript's 'new' keyword — Cynthia Lee](https://medium.freecodecamp.org/demystifying-javascripts-new-keyword-874df126184c) -- [Constructor, operator "new" — JavaScript.Info](https://javascript.info/constructor-new) -- [Understanding JavaScript Constructors — Faraz Kelhini](https://css-tricks.com/understanding-javascript-constructors/) -- [Use Constructor Functions — Openclassrooms](https://openclassrooms.com/en/courses/3523231-learn-to-code-with-javascript/4379006-use-constructor-functions) -- [Beyond `typeof` and `instanceof`: simplifying dynamic type checks — Dr. Axel Rauschmayer](http://2ality.com/2017/08/type-right.html) -- [Function and Object, instances of each other — Kiro Risk](https://javascriptrefined.io/function-and-object-instances-of-each-other-1e1095d5faac) -- [JavaScript instanceof operator](https://flexiple.com/javascript/instanceof-javascript) - -**[⬆ Back to Top](#table-of-contents)** +**[View all translations →](TRANSLATIONS.md)** --- -## 17. Prototype Inheritance and Prototype Chain - -### Reference - -- [Inheritance and the prototype chain — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles +## Contributing -- [Javascript : Prototype vs Class — Valentin PARSY](https://medium.com/@parsyval/javascript-prototype-vs-class-a7015d5473b) -- [JavaScript engine fundamentals: optimizing prototypes — Mathias Bynens](https://mathiasbynens.be/notes/prototypes) -- [JavaScript Prototype — NC Patro](https://codeburst.io/javascript-prototype-cb29d82b8809) -- [Prototypes in JavaScript — Rupesh Mishra](https://hackernoon.com/prototypes-in-javascript-5bba2990e04b) -- [Prototype in JavaScript: it's quirky, but here's how it works — Pranav Jindal](https://medium.freecodecamp.org/prototype-in-js-busted-5547ec68872) -- [Understanding JavaScript: Prototype and Inheritance — Alexander Kondov](https://hackernoon.com/understanding-javascript-prototype-and-inheritance-d55a9a23bde2) -- [Understanding Classes (ES5) and Prototypal Inheritance in JavaScript — Hridayesh Sharma](https://dev.to/_hridaysharma/understanding-classes-es5-and-prototypal-inheritance-in-javascript-n8d) -- [prototype, **proto** and Prototypal inheritance in JavaScript — Varun Dey](https://dev.to/varundey/prototype-proto-and-prototypal-inheritance-in-javascript-2inl) -- [Prototypal Inheritance — JavaScript.Info](https://javascript.info/prototype-inheritance) -- [How To Work with Prototypes and Inheritance in JavaScript — Tania Rascia](https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript) -- [Master JavaScript Prototypes & Inheritance — Arnav Aggarwal](https://codeburst.io/master-javascript-prototypes-inheritance-d0a9a5a75c4e) -- [JavaScript's Prototypal Inheritance Explained Using CSS — Nash Vail](https://medium.freecodecamp.org/understanding-prototypal-inheritance-in-javascript-with-css-93b2fcda75e4) -- [Prototypal Inheritance in JavaScript — Jannis Redmann](https://gist.github.com/derhuerst/a585c4916b1c361cc6f0) -- [Demystifying ES6 Classes And Prototypal Inheritance ― Neo Ighodaro](https://scotch.io/tutorials/demystifying-es6-classes-and-prototypal-inheritance) -- [Intro To Prototypal Inheritance — Dharani Jayakanthan](https://dev.to/danny/intro-to-prototypal-inheritance---js-9di) -- [Let's Build Prototypal Inheritance in JS — var-che](https://dev.to/varche/let-s-build-prototypal-inheritance-in-js-56mm) -- [Objects, Prototypes and Classes in JavaScript — Atta](https://dev.to/attacomsian/objects-prototypes-and-classes-in-javascript-3i9b) -- [The magical world of JavaScript prototypes — Belén](https://dev.to/ladybenko/the-magical-world-of-javascript-prototypes-1mhg) -- [Understanding Prototypal Inheritance In JavaScript — Lawrence Eagles](https://dev.to/lawrence_eagles/understanding-prototypal-inheritance-in-javascript-4f31#chp-4) -- [Objects and Prototypes in JavaScript — Irena Popova](https://dev.to/irenejpopova/objects-and-prototypes-in-javascript-2eie) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Javascript Prototype Inheritance — Avelx](https://www.youtube.com/watch?v=sOrtAjyk4lQ) -- [JavaScript Prototype Inheritance Explained pt. I — techsith](https://www.youtube.com/watch?v=7oNWNlMrkpc) -- [JavaScript Prototype Inheritance Explained pt. II — techsith](https://www.youtube.com/watch?v=uIlj6_z_wL8) -- [JavaScript Prototype Inheritance Explained — Kyle Robinson](https://www.youtube.com/watch?v=qMO-LTOrJaE) -- [Advanced Javascript - Prototypal Inheritance In 1 Minute](https://www.youtube.com/watch?v=G6l5CHl67HQ) -- [An Overview Of Classical Javascript Classes and Prototypal Inheritance — Pentacode](https://www.youtube.com/watch?v=phwzuiJJPpQ) -- [Object Oriented JavaScript - Prototype — The Net Ninja](https://www.youtube.com/watch?v=4jb4AYEyhRc) -- [Prototype in JavaScript — kudvenkat](https://www.youtube.com/watch?v=2rkEbcptR64) -- [JavaScript Using Prototypes — O'Reilly](https://www.youtube.com/watch?v=oCwCcNvaXAQ) -- [A Beginner's Guide to Javascript's Prototype — Tyler Mcginnis](https://www.youtube.com/watch?v=XskMWBXNbp0) -- [Prototypes in Javascript - p5.js Tutorial — The Coding Train](https://www.youtube.com/watch?v=hS_WqkyUah8) - -### Books - -- [You Don't Know JS, 1st Edition: this & Object Prototypes — Kyle Simpson](https://github.com/getify/You-Dont-Know-JS/tree/1st-ed) -- [The Principles of Object-Oriented JavaScript - Nicholas C. Zakas](https://www.google.com.pk/books/edition/The_Principles_of_Object_Oriented_JavaSc/rorlAwAAQBAJ?hl=en&gbpv=1&pg=PP1&printsec=frontcover) - -**[⬆ Back to Top](#table-of-contents)** +We welcome contributions! See our [Contributing Guidelines](CONTRIBUTING.md) for details. --- -## 18. Object.create and Object.assign - -### Reference - -- [Object.create() — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) -- [Object.assign() — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Object.create in JavaScript — Rupesh Mishra](https://medium.com/@happymishra66/object-create-in-javascript-fa8674df6ed2) -- [Object.create(): the New Way to Create Objects in JavaScript — Rob Gravelle](https://www.htmlgoodies.com/beyond/javascript/object.create-the-new-way-to-create-objects-in-javascript.html) -- [Basic Inheritance with Object.create — Joshua Clanton](http://adripofjavascript.com/blog/drips/basic-inheritance-with-object-create.html) -- [Object.create() In JavaScript — GeeksforGeeks](https://www.geeksforgeeks.org/object-create-javascript/) -- [Understanding the difference between Object.create() and the new operator — Jonathan Voxland](https://medium.com/@jonathanvox01/understanding-the-difference-between-object-create-and-the-new-operator-b2a2f4749358) -- [JavaScript Object Creation: Patterns and Best Practices — Jeff Mott](https://www.sitepoint.com/javascript-object-creation-patterns-best-practises/) -- [Dealing With Objects in JavaScript With Object.assign, Object.keys and hasOwnProperty](https://www.digitalocean.com/community/tutorials/js-dealing-with-objects) -- [Copying Objects in JavaScript ― Orinami Olatunji](https://scotch.io/bar-talk/copying-objects-in-javascript) -- [JavaScript: Object.assign() — Thiago S. Adriano](https://codeburst.io/javascript-object-assign-bc9696dcbb6e) -- [How to deep clone a JavaScript Object — Flavio Copes](https://flaviocopes.com/how-to-clone-javascript-object/) -- [Object.create(): When and Why to Use — VZing](https://dev.to/vzing/object-create-when-and-why-to-use-20m9) +## License -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Object.assign() explained — Aaron Writes Code](https://www.youtube.com/watch?v=aw7NfYhR5rc) -- [Object.assign() Method — techsith](https://www.youtube.com/watch?v=9Ky4X6inpi4) - -**[⬆ Back to Top](#table-of-contents)** +MIT © [Leonardo Maldonado](https://github.com/leonardomso) --- -## 19. map, reduce, filter - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [JavaScript Functional Programming — map, filter and reduce — Bojan Gvozderac](https://medium.com/jsguru/javascript-functional-programming-map-filter-and-reduce-846ff9ba492d) -- [Learn map, filter and reduce in Javascript — João Miguel Cunha](https://medium.com/@joomiguelcunha/learn-map-filter-and-reduce-in-javascript-ea59009593c4) -- [JavaScript's Map, Reduce, and Filter — Dan Martensen](https://danmartensen.svbtle.com/javascripts-map-reduce-and-filter) -- [How to Use Map, Filter, & Reduce in JavaScript — Peleke Sengstacke](https://code.tutsplus.com/tutorials/how-to-use-map-filter-reduce-in-javascript--cms-26209) -- [JavaScript — Learn to Chain Map, Filter, and Reduce — Brandon Morelli](https://codeburst.io/javascript-learn-to-chain-map-filter-and-reduce-acd2d0562cd4) -- [Javascript data structure with map, reduce, filter and ES6 — Deepak Gupta](https://codeburst.io/write-beautiful-javascript-with-%CE%BB-fp-es6-350cd64ab5bf) -- [Understanding map, filter and reduce in Javascript — Luuk Gruijs](https://hackernoon.com/understanding-map-filter-and-reduce-in-javascript-5df1c7eee464) -- [Functional Programming in JS: map, filter, reduce (Pt. 5) — Omer Goldberg](https://hackernoon.com/functional-programming-in-js-map-filter-reduce-pt-5-308a205fdd5f) -- [JavaScript: Map, Filter, Reduce — William S. Vincent](https://wsvincent.com/functional-javascript-map-filter-reduce/) -- [Arrow Functions: Fat and Concise Syntax in JavaScript — Kyle Pennell](https://www.sitepoint.com/es6-arrow-functions-new-fat-concise-syntax-javascript/) -- [JavaScript: Arrow Functions for Beginners — Brandon Morelli](https://codeburst.io/javascript-arrow-functions-for-beginners-926947fc0cdc) -- [When (and why) you should use ES6 arrow functions — and when you shouldn't — Cynthia Lee](https://medium.freecodecamp.org/when-and-why-you-should-use-es6-arrow-functions-and-when-you-shouldnt-3d851d7f0b26) -- [JavaScript — Learn & Understand Arrow Functions — Brandon Morelli](https://codeburst.io/javascript-learn-understand-arrow-functions-fe2083533946) -- [(JavaScript )=> Arrow functions — sigu](https://medium.com/podiihq/javascript-arrow-functions-27d4c3334b83) -- [Javascript.reduce() — Paul Anderson](https://medium.com/@panderson.dev/javascript-reduce-79aab078da23) -- [Why you should replace forEach with map and filter in JavaScript — Roope Hakulinen](https://gofore.com/en/why-you-should-replace-foreach/) -- [Simplify your JavaScript – Use .map(), .reduce(), and .filter() — Etienne Talbot](https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d) -- [JavaScript's Reduce Method Explained By Going On a Diet — Kevin Kononenko](https://blog.codeanalogies.com/2018/07/24/javascripts-reduce-method-explained-by-going-on-a-diet/) -- [Difference between map, filter and reduce in JavaScript — Amirata Khodaparast](https://medium.com/@amiratak88/difference-between-map-filter-and-reduce-in-javascript-822ff79d5160) -- [Map⇄Filter⇄Reduce↻ — ashay mandwarya](https://hackernoon.com/map-filter-reduce-ebbed4be4201) -- [Finding Your Way With .map() — Brandon Wozniewicz](https://medium.freecodecamp.org/finding-your-way-with-map-aecb8ca038f6) -- [How to write your own map, filter and reduce functions in JavaScript — Hemand Nair](https://medium.freecodecamp.org/how-to-write-your-own-map-filter-and-reduce-functions-in-javascript-ab1e35679d26) -- [How to Manipulate Arrays in JavaScript — Bolaji Ayodeji](https://www.freecodecamp.org/news/manipulating-arrays-in-javascript/) -- [How to simplify your codebase with map(), reduce(), and filter() in JavaScript — Alex Permyakov](https://www.freecodecamp.org/news/15-useful-javascript-examples-of-map-reduce-and-filter-74cbbb5e0a1f) -- [.map(), .filter(), and .reduce() — Andy Pickle](https://dev.to/pickleat/map-filter-and-reduce-2efb) -- [Map/Filter/Reduce Crash Course — Chris Achard](https://dev.to/chrisachard/map-filter-reduce-crash-course-5gan) -- [Map, Filter and Reduce – Animated — JavaScript Teacher](https://medium.com/@js_tut/map-filter-and-reduce-animated-7fe391a35a47) -- [Map, Filter, Reduce and others Arrays Iterators You Must Know to Become an Algorithms Wizard — Mauro Bono](https://dev.to/uptheirons78/map-filter-reduce-and-others-arrays-iterators-you-must-know-to-become-an-algorithms-wizard-4209) -- [How to Use JavaScript's .map, .filter, and .reduce — Avery Duffin](https://betterprogramming.pub/how-to-javascripts-map-vs-filter-vs-reduce-80d87a5a0a24) -- [Using .map(), .filter() and .reduce() properly — Sasanka Kudagoda](https://medium.com/javascript-in-plain-english/using-map-filter-and-reduce-properly-50e07f80c8b2) -- [Mastering the JavaScript Reduce method ✂️ — sanderdebr](https://dev.to/sanderdebr/mastering-the-javascript-reduce-method-2foj) -- [JavaScript Map – How to Use the JS .map() Function (Array Method) — FreeCodeCamp](https://www.freecodecamp.org/news/javascript-map-how-to-use-the-js-map-function-array-method/) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Map, Filter and Reduce — Lydia Hallie](https://www.youtube.com/watch?v=UXiYii0Y7Nw) -- [Map, Filter and Reduce - Akshaay Saini](https://youtu.be/zdp0zrpKzIE?si=6QusFzD6tmwn-el4) -- [Functional JavaScript: Map, forEach, Reduce, Filter — Theodore Anderson](https://www.youtube.com/watch?v=vytzLlY_wmU) -- [JavaScript Array superpowers: Map, Filter, Reduce (part I) — Michael Rosata](https://www.youtube.com/watch?v=qTeeVd8hOFY) -- [JavaScript Array superpowers: Map, Filter, Reduce (part 2) — Michael Rosata](https://www.youtube.com/watch?v=gIm9xLYudL0) -- [JavaScript Higher Order Functions - Filter, Map, Sort & Reduce — Epicop](https://www.youtube.com/watch?v=zYBeEPxNSbw) -- [[Array Methods 2/3] .filter + .map + .reduce — CodeWithNick](https://www.youtube.com/watch?v=4qWlqD0yYTU) -- [Arrow functions in JavaScript - What, Why and How — Fun Fun Function](https://www.youtube.com/watch?v=6sQDTgOqh-I) -- [Learning Functional Programming with JavaScript — Anjana Vakil - JSUnconf](https://www.youtube.com/watch?v=e-5obm1G_FY&t=1521s) -- [Map - Parte 2 JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=bCqtb-Z5YGQ&t=17s) -- [Reduce basics - Part 3 of FP in JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=Wl98eZpkp-c) -- [Reduce Advanced - Part 4 of FP in JavaScript - Fun Fun Function](https://www.youtube.com/watch?v=1DMolJ2FrNY&t=621s) -- [reduce Array Method | JavaScript Tutorial - Florin Pop](https://www.youtube.com/watch?v=IXp06KekEjM) -- [map Array Method | JavaScript Tutorial - Florin Pop](https://www.youtube.com/watch?v=P4RAFdZDn3M) -- [Different array methods in 1 minute | Midudev (Spanish)](https://youtu.be/Ah7-PPjQ5Ls) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 20. Pure Functions, Side Effects, State Mutation and Event Propagation - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Javascript and Functional Programming — Pure Functions — Omer Goldberg](https://hackernoon.com/javascript-and-functional-programming-pt-3-pure-functions-d572bb52e21c) -- [Master the JavaScript Interview: What is a Pure Function? — Eric Elliott](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976) -- [JavaScript: What Are Pure Functions And Why Use Them? — James Jeffery](https://medium.com/@jamesjefferyuk/javascript-what-are-pure-functions-4d4d5392d49c) -- [Pure functions in JavaScript — @nicoespeon](http://www.nicoespeon.com/en/2015/01/pure-functions-javascript/) -- [Functional Programming: Pure Functions — Arne Brasseur](https://www.sitepoint.com/functional-programming-pure-functions/) -- [Making your JavaScript Pure — Jack Franklin](https://alistapart.com/article/making-your-javascript-pure) -- [Arrays, Objects and Mutations — Federico Knüssel](https://medium.com/@fknussel/arrays-objects-and-mutations-6b23348b54aa) -- [The State of Immutability — Maciej Sikora](https://medium.com/dailyjs/the-state-of-immutability-169d2cd11310) -- [Hablemos de Inmutabilidad — Kike Sanchez](https://medium.com/zurvin/hablemos-de-inmutabilidad-3dc65d290783) -- [How to deal with dirty side effects in your pure functional JavaScript — James Sinclair](https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/) -- [Preventing Side Effects in JavaScript — David Walsh](https://davidwalsh.name/preventing-sideeffects-javascript) -- [JavaScript: Pure Functions — William S. Vincent](https://wsvincent.com/javascript-pure-functions/) -- [Functional programming paradigms in modern JavaScript: Pure functions — Alexander Kondov](https://hackernoon.com/functional-programming-paradigms-in-modern-javascript-pure-functions-797d9abbee1) -- [Understanding Javascript Mutation and Pure Functions — Chidume Nnamdi](https://blog.bitsrc.io/understanding-javascript-mutation-and-pure-functions-7231cc2180d3) -- [Functional-ish JavaScript — Daniel Brain](https://medium.com/@bluepnume/functional-ish-javascript-205c05d0ed08) -- [Event Propagation — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) -- [Event Propagation — Bubbling and capturing](https://javascript.info/bubbling-and-capturing) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Pure Functions — Hexlet](https://www.youtube.com/watch?v=dZ41D6LDSBg) -- [Pure Functions - Functional Programming in JavaScript — Paul McBride](https://www.youtube.com/watch?v=Jh_Uzqzz_wM) -- [JavaScript Pure Functions — Seth Alexander](https://www.youtube.com/watch?v=frT3H-eBmPc) -- [JavaScript Pure vs Impure Functions Explained — Theodore Anderson](https://www.youtube.com/watch?v=AHbRVJzpB54) -- [Pure Functions - Programação Funcional: Parte 1 - Fun Fun Function](https://www.youtube.com/watch?v=BMUiFMZr7vk) -- [Event Propagation - JavaScript Event Bubbling and Propagation - Steve Griffith](https://www.youtube.com/watch?v=JYc7gr9Ehl0) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 21. Closures - -### Reference - -- [Closures — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) -- [Closure — JavaScript.Info](https://javascript.info/closure) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [I never understood JavaScript closures — Olivier De Meulder](https://medium.com/dailyjs/i-never-understood-javascript-closures-9663703368e8) -- [Understand JavaScript Closures With Ease — Richard Bovell](http://javascriptissexy.com/understand-javascript-closures-with-ease/) -- [Understanding JavaScript Closures — Codesmith](https://codeburst.io/understanding-javascript-closures-da6aab330302) -- [Understand Closures in JavaScript — Brandon Morelli](https://codeburst.io/understand-closures-in-javascript-d07852fa51e7) -- [A simple guide to help you understand closures in JavaScript — Prashant Ram](https://medium.freecodecamp.org/javascript-closures-simplified-d0d23fa06ba4) -- [Understanding JavaScript Closures: A Practical Approach — Paul Upendo](https://scotch.io/tutorials/understanding-javascript-closures-a-practical-approach) -- [Understanding JavaScript: Closures — Alexander Kondov](https://hackernoon.com/understanding-javascript-closures-4188edf5ea1b) -- [How to use JavaScript closures with confidence — Léna Faure](https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b) -- [JavaScript closures by example — tyler](https://howchoo.com/g/mge2mji2mtq/javascript-closures-by-example) -- [JavaScript — Closures and Scope — Alex Aitken](https://codeburst.io/javascript-closures-and-scope-3784c75b9290) -- [Discover the power of closures in JavaScript — Cristi Salcescu](https://medium.freecodecamp.org/discover-the-power-of-closures-in-javascript-5c472a7765d7) -- [Getting Closure — RealLifeJS](http://reallifejs.com/the-meat/getting-closure/) -- [Closure, Currying and IIFE in JavaScript — Ritik](https://dev.to/ritik_dev_js/what-the-hack-is-closure-currying-and-iife-in-javascript-32m9) -- [Understanding Closures in JavaScript — Sukhjinder Arora](https://blog.bitsrc.io/a-beginners-guide-to-closures-in-javascript-97d372284dda) -- [A basic guide to Closures in JavaScript — Parathan Thiyagalingam](https://medium.freecodecamp.org/a-basic-guide-to-closures-in-javascript-9fc8b7e3463e) -- [Closures: Using Memoization — Brian Barbour](https://dev.to/steelvoltage/closures-using-memoization-3597) -- [A Brief Introduction to Closures and Lexical Scoping in JavaScript — Ashutosh K Singh](https://betterprogramming.pub/a-brief-introduction-to-closures-and-lexical-scoping-in-javascript-8a5866496232) -- [Demystify Closures — stereobooster](https://dev.to/stereobooster/demystify-closures-5g42) -- [Scopes and Closures - JavaScript Concepts — Agney Menon](https://dev.to/boywithsilverwings/scopes-and-closures-javascript-concepts-4dfj) -- [Understanding Closures in JavaScript — Matt Popovich](https://dev.to/mattpopovich/understanding-closures-in-javascript-3k0d) -- [whatthefuck.is · A Closure - Dan Abramov](https://whatthefuck.is/closure) -- [Closures in JavaScript can... - Brandon LeBoeuf](https://dev.to/brandonleboeuf/closure-in-javascript-49n7) -- [Do you know Closures - Mohamed Khaled](https://dev.to/this_mkhy/do-you-know-es6-part-3-advanced-3fcl#Closures-2) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript The Hard Parts: Closure, Scope & Execution Context - Codesmith](https://www.youtube.com/watch?v=XTAzsODSCsM) -- [Namaste Javascript by Akshay Saini](https://youtu.be/qikxEIxsXco?si=fGFgUHuaOW49Wg9p) -- [Javascript Closure — techsith](https://www.youtube.com/watch?v=71AtaJpJHw0) -- [Closures — Fun Fun Function](https://www.youtube.com/watch?v=CQqwU2Ixu-U) -- [Closures in JavaScript — techsith](https://www.youtube.com/watch?v=-xqJo5VRP4A) -- [JavaScript Closures 101: What is a closure? — JavaScript Tutorials](https://www.youtube.com/watch?v=yiEeiMN2Khs) -- [Closures — freeCodeCamp](https://www.youtube.com/watch?v=1JsJx1x35c0) -- [JavaScript Closures — CodeWorkr](https://www.youtube.com/watch?v=-rLrGAXK8WE) -- [Closures in JS - Akshay Saini](https://www.youtube.com/watch?v=qikxEIxsXco) -- [CLOSURES en JavaScript: Qué son y cómo funcionan - Carlos Azaustre](https://youtu.be/xa8lhVwQBw4) -- [Learn Closures In 7 Minutes - Web Dev Simplified](https://www.youtube.com/watch?v=3a0I8ICR1Vg) - - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 22. High Order Functions - -### Books - -- [Eloquent JavaScript, 3rd Edition: Ch. 5 - Higher-order Functions](https://eloquentjavascript.net/05_higher_order.html) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Higher-Order Functions in JavaScript — M. David Green](https://www.sitepoint.com/higher-order-functions-javascript/) -- [Higher Order Functions: Using Filter, Map and Reduce for More Maintainable Code — Guido Schmitz](https://medium.freecodecamp.org/higher-order-functions-in-javascript-d9101f9cf528) -- [First-class and Higher Order Functions: Effective Functional JavaScript — Hugo Di Francesco](https://hackernoon.com/effective-functional-javascript-first-class-and-higher-order-functions-713fde8df50a) -- [Higher Order Functions in JavaScript — John Hannah](https://www.lullabot.com/articles/higher-order-functions-in-javascript) -- [Just a reminder on how to use high order functions — Pedro Filho](https://github.com/pedroapfilho/high-order-functions) -- [Understanding Higher-Order Functions in JavaScript — Sukhjinder Arora](https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad) -- [Higher Order Functions - A pragmatic approach — emmanuel ikwuoma](https://dev.to/nuel_ikwuoma/higher-order-functions-a-pragmatic-approach-51fb) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript Higher Order Functions & Arrays — Traversy Media](https://www.youtube.com/watch?v=rRgD1yVwIvE) -- [Higher Order Functions — Fun Fun Function](https://www.youtube.com/watch?v=BMUiFMZr7vk) -- [Higher Order Functions in Javascript — Raja Yogan](https://www.youtube.com/watch?v=dTlpYnmBW9I) -- [Higher Order Iterators in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=GYRMNp1SKXA) -- [Higher Order Functions in JavaScript — The Coding Train](https://www.youtube.com/watch?v=H4awPsyugS0) -- [Part 1: An Introduction to Callbacks and Higher Order Functions - Codesmith](https://www.youtube.com/watch?v=7E8ctomPQJw) -- [Part 2: Understanding Why We Need Higher Order Functions - Codesmith](https://www.youtube.com/watch?v=28MXziDZkE4) -- [Higher-Order Functions ft. Functional Programming - Akshay Saini](https://www.youtube.com/watch?v=HkWxvB1RJq0) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 23. Recursion - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Recursion in JavaScript — Kevin Ennis](https://medium.freecodecamp.org/recursion-in-javascript-1608032c7a1f) -- [Understanding Recursion in JavaScript — Zak Frisch](https://medium.com/@zfrisch/understanding-recursion-in-javascript-992e96449e03) -- [Learn and Understand Recursion in JavaScript — Brandon Morelli](https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea) -- [Recursion in Functional JavaScript — M. David Green](https://www.sitepoint.com/recursion-functional-javascript/) -- [Programming with JS: Recursion — Alexander Kondov](https://hackernoon.com/programming-with-js-recursion-31371e2bf808) -- [Anonymous Recursion in JavaScript — simo](https://dev.to/simov/anonymous-recursion-in-javascript) -- [Recursion, iteration and tail calls in JS — loverajoel](http://www.jstips.co/en/javascript/recursion-iteration-and-tail-calls-in-js/) -- [What is Recursion? A Recursive Function Explained with JavaScript Code Examples — Nathan Sebhastian](https://www.freecodecamp.org/news/what-is-recursion-in-javascript/) -- [Intro to Recursion — Brad Newman](https://medium.com/@newmanbradm/intro-to-recursion-984a8bd50f4b) -- [Accio Recursion!: Your New Favorite JavaScript Spell — Leanne Cabey](https://medium.datadriveninvestor.com/accio-recursion-your-new-favorite-javascript-spell-7e10d3125fb3) -- [Recursion Explained (with Examples) — Christina](https://dev.to/christinamcmahon/recursion-explained-with-examples-4k1m) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Recursion In JavaScript — techsith](https://www.youtube.com/watch?v=VtG0WAUvq2w) -- [Recursion — Fun Fun Function](https://www.youtube.com/watch?v=k7-N8R0-KY4) -- [Recursion and Recursive Functions — Hexlet](https://www.youtube.com/watch?v=vLhHyGTkjCs) -- [Recursion: Recursion() — JS Monthly — Lucas da Costa](https://www.youtube.com/watch?v=kGXVsd8pBLw) -- [Recursive Function in JavaScript — kudvenkat](https://www.youtube.com/watch?v=uyjsR9eNTIw) -- [What on Earth is Recursion? — Computerphile](https://www.youtube.com/watch?v=Mv9NEXX1VHc) -- [Javascript Tutorial 34: Introduction To Recursion — codedamn](https://www.youtube.com/watch?v=9NO5dXSlbv8) -- [Recursion, Iteration, and JavaScript: A Love Story | JSHeroes 2018 — Anjana Vakil](https://www.youtube.com/watch?v=FmiQr4nfoPQ) -- [Recursion crash course - Colt Steele](https://www.youtube.com/watch?v=lMBVwYrmFZQ&ab_channel=ColtSteele) -- [What Is Recursion - In Depth - Web Dev Simplified](https://www.youtube.com/watch?v=6oDQaB2one8) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 24. Collections and Generators - -### Reference - -- [Generator — MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [ES6 Collections: Using Map, Set, WeakMap, WeakSet — Kyle Pennell](https://www.sitepoint.com/es6-collections-map-set-weakmap-weakset/) -- [ES6 WeakMaps, Sets, and WeakSets in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-weakmaps-sets-and-weaksets-in-depth) -- [Map, Set, WeakMap and WeakSet — JavaScript.Info](https://javascript.info/map-set-weakmap-weakset) -- [Maps in ES6 - A Quick Guide — Ben Mildren](https://dev.to/mildrenben/maps-in-es6---a-quick-guide-35pk) -- [ES6 — Set vs Array — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-set-vs-array-what-and-when-efc055655e1a) -- [ES6 — Map vs Object — What and when? — Maya Shavin](https://medium.com/front-end-hacking/es6-map-vs-object-what-and-when-b80621932373) -- [Array vs Set vs Map vs Object — Real-time use cases in Javascript (ES6/ES7) — Rajesh Babu](https://codeburst.io/array-vs-set-vs-map-vs-object-real-time-use-cases-in-javascript-es6-47ee3295329b) -- [How to create an array of unique values in JavaScript using Sets — Claire Parker-Jones](https://dev.to/claireparker/how-to-create-an-array-of-unique-values-in-javascript-using-sets-5dg6) -- [What You Should Know About ES6 Maps — Just Chris](https://hackernoon.com/what-you-should-know-about-es6-maps-dc66af6b9a1e) -- [ES6 Maps in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-maps-in-depth) -- [What are JavaScript Generators and how to use them — Vladislav Stepanov](https://codeburst.io/what-are-javascript-generators-and-how-to-use-them-c6f2713fd12e) -- [Understanding JavaScript Generators With Examples — Arfat Salman](https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5) -- [The Basics of ES6 Generators — Kyle Simpson](https://davidwalsh.name/es6-generators) -- [An Introduction to JavaScript Generators — Alice Kallaugher](https://dev.to/kallaugher/an-introduction-to-javascript-generators-1224) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript ES6 / ES2015 Set, Map, WeakSet and WeakMap — Traversy Media](https://www.youtube.com/watch?v=ycohYSx5h9w) -- [JavaScript ES6 / ES2015 - \[11\] Generators - Traversy Media](https://www.youtube.com/watch?v=dcP039DYzmE) -- [The Differences between ES6 Maps and Sets — Steve Griffith](https://www.youtube.com/watch?v=m4abICrldQI) -- [Javascript Generators - THEY CHANGE EVERYTHING - ES6 Generators Harmony Generators — LearnCode.academy](https://www.youtube.com/watch?v=QO07THdLWQo) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 25. Promises - -### Reference - -- [Promise — MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [JavaScript Promises for Dummies ― Jecelyn Yeen](https://scotch.io/tutorials/javascript-promises-for-dummies) -- [Understanding promises in JavaScript — Gokul N K](https://hackernoon.com/understanding-promises-in-javascript-13d99df067c1) -- [Master the JavaScript Interview: What is a Promise? — Eric Elliott](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261) -- [An Overview of JavaScript Promises — Sandeep Panda](https://www.sitepoint.com/overview-javascript-promises/) -- [How to use Promises in JavaScript — Prashant Ram](https://medium.freecodecamp.org/promises-in-javascript-explained-277b98850de) -- [Implementing Promises In JavaScript — Maciej Cieslar](https://medium.freecodecamp.org/how-to-implement-promises-in-javascript-1ce2680a7f51) -- [JavaScript: Promises explained with simple real life analogies — Shruti Kapoor](https://codeburst.io/javascript-promises-explained-with-simple-real-life-analogies-dd6908092138) -- [Promises for Asynchronous Programming — Exploring JS](http://exploringjs.com/es6/ch_promises.html) -- [JavaScript Promises Explained By Gambling At A Casino — Kevin Kononenko](https://blog.codeanalogies.com/2018/08/26/javascript-promises-explained-by-gambling-at-a-casino/) -- [ES6 Promises: Patterns and Anti-Patterns — Bobby Brennan](https://medium.com/datafire-io/es6-promises-patterns-and-anti-patterns-bbb21a5d0918) -- [A Simple Guide to ES6 Promises — Brandon Morelli](https://codeburst.io/a-simple-guide-to-es6-promises-d71bacd2e13a) -- [The ES6 Promises — Manoj Singh Negi](https://codeburst.io/the-es6-promises-87a979ab27e4) -- [ES6 Promises in Depth — Nicolás Bevacqua](https://ponyfoo.com/articles/es6-promises-in-depth) -- [Playing with Javascript Promises: A Comprehensive Approach — Rajesh Babu](https://codeburst.io/playing-with-javascript-promises-a-comprehensive-approach-25ab752c78c3) -- [How to Write a JavaScript Promise — Brandon Wozniewicz](https://medium.freecodecamp.org/how-to-write-a-javascript-promise-4ed8d44292b8) -- [A Coding Writer's Guide: An Introduction To ES6 Promises — Andrew Ly](https://medium.com/@andrewly07/a-coding-writers-guide-an-introduction-to-es6-promises-9ff9f9e88f6c) -- [Understanding Promises in JavaScript — Chris Noring](https://dev.to/itnext/reverse-engineering-understand-promises-1jfc) -- [Converting callbacks to promises — Zell Liew](https://dev.to/zellwk/converting-callbacks-to-promises-nhn) -- [JavaScript Promises: Zero To Hero Plus Cheat Sheet — Joshua Saunders](https://medium.com/dailyjs/javascript-promises-zero-to-hero-plus-cheat-sheet-64d75051cffa) -- [Promises - JavaScript concepts — Agney Menon](https://dev.to/boywithsilverwings/promises-javascript-concepts-293c) -- [Javascript `Promise` 101 — Igor Irianto](https://dev.to/iggredible/javascript-promise-101-3idl) -- [Simplify JavaScript Promises — Sunny Singh](https://dev.to/sunnysingh/simplify-javascript-promises-4djb) -- [JavaScript Visualized: Promises & Async/Await — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke) -- [Promises in JavaScript — Peter Klingelhofer](https://dev.to/peterklingelhofer/promises-in-javascript-3h5k) -- [Best Practices for ES6 Promises — Basti Ortiz](https://dev.to/somedood/best-practices-for-es6-promises-36da) -- [Lo que debemos saber de EScript 2020 — Kike Sanchez](https://medium.com/zurvin/lo-que-debemos-saber-de-escript-2020-5fc61da5e4cd) -- [Promise Basics - javascript.info](https://javascript.info/promise-basics) -- [The Complete JavaScript Promise Guide](https://blog.webdevsimplified.com/2021-09/javascript-promises) -- [Promise Chaining - javascript.info](https://javascript.info/promise-chaining) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Let's Learn ES6 - Promises — Ryan Christiani](https://www.youtube.com/watch?v=vQ3MoXnKfuQ) -- [JavaScript ES6 / ES2015 Promises — Traversy Media](https://www.youtube.com/watch?v=XJEHuBZQ5dU) -- [Promises — Fun Fun Function](https://www.youtube.com/watch?v=2d7s3spWAzo) -- [Error Handling Promises in JavaScript — Fun Fun Function](https://www.youtube.com/watch?v=f8IgdnYIwOU) -- [Promises Part 1 - Topics of JavaScript/ES6 — The Coding Train](https://www.youtube.com/watch?v=QO4NXhWo_NM) -- [JavaScript Promise in 100 Seconds](https://www.youtube.com/watch?v=RvYYCGs45L4) -- [JavaScript Promise in 9 Minutes](https://youtu.be/3NjdOtHpcBM) -- [JavaScript Promises In 10 Minutes — Web Dev Simplified ](https://www.youtube.com/watch?v=DHvZLI7Db8E) -- [Promises | Ep 02 Season 02 - Namaste JavaScript - Akshay Saini ](https://youtu.be/ap-6PPAuK1Y?si=Ri1fopXeYjlrHzpf) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 26. async/await - -### Reference - -- [async/await — JavaScript.Info](https://javascript.info/async-await) - -### Books - -- [Eloquent JavaScript, 3rd Edition: Ch. 11 - Asynchronous Programming](https://eloquentjavascript.net/11_async.html) -- [Exploring JS: Asynchronous Programming](http://exploringjs.com/es6/ch_async.html) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Understanding async/await in Javascript — Gokul N K](https://hackernoon.com/understanding-async-await-in-javascript-1d81bb079b2c) -- [Asynchronous Javascript using async/await — Joy Warugu](https://scotch.io/tutorials/asynchronous-javascript-using-async-await) -- [Modern Asynchronous JavaScript with async/await — Flavio Copes](https://flaviocopes.com/javascript-async-await/) -- [Javascript — ES8 Introducing async/await Functions — Ben Garrison](https://medium.com/@_bengarrison/javascript-es8-introducing-async-await-functions-7a471ec7de8a) -- [How to escape async/await hell — Aditya Agarwal](https://medium.freecodecamp.org/avoiding-the-async-await-hell-c77a0fb71c4c) -- [Understanding JavaScript's async await — Nicolás Bevacqua](https://ponyfoo.com/articles/understanding-javascript-async-await) -- [JavaScript Async/Await: Serial, Parallel and Complex Flow — TechBrij](https://techbrij.com/javascript-async-await-parallel-sequence) -- [From JavaScript Promises to Async/Await: why bother? — Chris Nwamba](https://blog.pusher.com/promises-async-await/) -- [Flow Control in Modern JS: Callbacks to Promises to Async/Await — Craig Buckler](https://www.sitepoint.com/flow-control-callbacks-promises-async-await/) -- [How to improve your asynchronous Javascript code with async and await — Indrek Lasn](https://medium.freecodecamp.org/improve-your-asynchronous-javascript-code-with-async-and-await-c02fc3813eda) -- [Making Fetches Easy With Async Await — Mickey Sheridan](https://medium.com/@micksheridan.24/making-fetches-easy-with-async-await-8a1246efa1f6) -- [7 Reasons Why JavaScript Async/Await Is Better Than Plain Promises — Mostafa Gaafar](https://dev.to/gafi/7-reasons-to-always-use-async-await-over-plain-promises-tutorial-4ej9) -- [Asynchronous Operations in JavaScript — Jscrambler](https://dev.to/jscrambler/asynchronous-operations-in-javascript-2p6b) -- [JavaScript: Promises or async-await — Gokul N K](https://medium.com/better-programming/should-i-use-promises-or-async-await-126ab5c98789) -- [Async / Await: From Zero to Hero — Zhi Yuan](https://dev.to/zhiyuanamos/async-await-from-zero-to-hero-a22) -- [JavaScript Visualized: Promises & Async/Await — Lydia Hallie](https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke) -- [Making asynchronous programming easier with async and await — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) -- [JavaScript Async/Await Tutorial – Learn Callbacks, Promises, and Async/Await in JS by Making Ice Cream](https://www.freecodecamp.org/news/javascript-async-await-tutorial-learn-callbacks-promises-async-await-by-making-icecream/) -- [Better Than Promises - JavaScript Async/Await](https://blog.webdevsimplified.com/2021-11/async-await/) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Asynchronous JavaScript Crash Course](https://www.youtube.com/watch?v=exBgWAIeIeg) -- [Async + Await — Wes Bos](https://www.youtube.com/watch?v=9YkUCxvaLEk) -- [Asynchrony: Under the Hood — Shelley Vohr](https://www.youtube.com/watch?v=SrNQS8J67zc) -- [async/await in JavaScript - What, Why and How — Fun Fun Function](https://www.youtube.com/watch?v=568g8hxJJp4&index=3&list=PL0zVEGEvSaeHJppaRLrqjeTPnCH6) -- [async/await Part 1 - Topics of JavaScript/ES8 — The Coding Train](https://www.youtube.com/watch?v=XO77Fib9tSI&index=3&list=PLRqwX-V7Uu6bKLPQvPRNNE65kBL62mVfx) -- [async/await Part 2 - Topics of JavaScript/ES8 — The Coding Train](https://www.youtube.com/watch?v=chavThlNz3s&index=4&list=PLRqwX-V7Uu6bKLPQvPRNNE65kBL62mVfx) -- [Complete Guide to JS Async & Await ES2017/ES8 — Colt Steele](https://www.youtube.com/watch?v=krAYA4rvbdA) -- [Tips for using async/await in JavaScript — James Q Quick](https://www.youtube.com/watch?v=_9vgd9XKlDQ) -- [JavaScript Async Await — Web Dev Simplified](https://www.youtube.com/watch?v=V_Kr9OSfDeU) -- [Promise async and await in javascript — Hitesh Choudhary](https://youtu.be/Gjbr21JLfgg?si=SDCVKr9ONw2GsNdT) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 27. Data Structures - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Data Structures in JavaScript — Thon Ly](https://medium.com/siliconwat/data-structures-in-javascript-1b9aed0ea17c) -- [Algorithms and Data Structures in JavaScript — Oleksii Trekhleb](https://itnext.io/algorithms-and-data-structures-in-javascript-a71548f902cb) -- [Data Structures: Objects and Arrays ― Chris Nwamba](https://scotch.io/courses/10-need-to-know-javascript-concepts/data-structures-objects-and-arrays) -- [Data structures in JavaScript — Benoit Vallon](http://blog.benoitvallon.com/data-structures-in-javascript/data-structures-in-javascript/) -- [Playing with Data Structures in Javascript — Anish K.](https://blog.cloudboost.io/playing-with-data-structures-in-javascript-stack-a55ebe50f29d) -- [The Little Guide of Queue in JavaScript — Germán Cutraro](https://hackernoon.com/the-little-guide-of-queue-in-javascript-4f67e79260d9) -- [All algorithms writing with JavaScript in the book 'Algorithms Fourth Edition'](https://github.com/barretlee/algorithms) -- [Collection of classic computer science paradigms in JavaScript](https://github.com/nzakas/computer-science-in-javascript) -- [All the things you didn't know you wanted to know about data structures](https://github.com/jamiebuilds/itsy-bitsy-data-structures) -- [JavaScript Data Structures: 40 Part Series — miku86](https://dev.to/miku86/series/3259) -- [Data Structures: Understanding Graphs — Rachel Hawa](https://medium.com/javascript-in-plain-english/data-structures-understanding-graphs-82509d35e6b5) -- [Data Structures Two Ways: Linked List (Pt 1) — Freddie Duffield](https://dev.to/freddieduffield/data-structures-two-ways-linked-list-2n61) -- [Data Structures Two Ways: Linked List (Pt 2) — Freddie Duffield](https://dev.to/freddieduffield/data-structures-two-ways-linked-list-pt2-2i60) -- [Graph Data Structures Explained in JavaScript — Adrian Mejia](https://dev.to/amejiarosario/graph-data-structures-for-beginners-5edn) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Algorithms In Javascript | Ace Your Interview — Eduonix Learning Solutions](https://www.youtube.com/watch?v=H_EBPZgiAas&list=PLDmvslp_VR0zYUSth_8O69p4_cmvZEgLa) -- [Data Structures and Algorithms in JavaScript — freeCodeCamp](https://www.youtube.com/watch?v=Gj5qBheGOEo&list=PLWKjhJtqVAbkso-IbgiiP48n-O-JQA9PJ) -- [Learning JavaScript Data Structures and Algorithms: Sorting — Packt Video](https://www.youtube.com/watch?v=Ymh_AurrMbA) -- [JavaScript Data Structures: Getting Started — Academind](https://www.youtube.com/watch?v=41GSinwoMYA&ab_channel=Academind) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 28. Expensive Operation and Big O Notation - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Big O Notation in Javascript — César Antón Dorantes](https://medium.com/cesars-tech-insights/big-o-notation-javascript-25c79f50b19b) -- [Time Complexity/Big O Notation — Tim Roberts](https://medium.com/javascript-scene/time-complexity-big-o-notation-1a4310c3ee4b) -- [Big O in JavaScript — Gabriela Medina](https://medium.com/@gmedina229/big-o-in-javascript-36ff67766051) -- [Big O Search Algorithms in JavaScript — Bradley Braithwaite](https://www.bradoncode.com/blog/2012/04/big-o-algorithm-examples-in-javascript.html) -- [Algorithms in plain English: time complexity and Big-O Notation — Michael Olorunnisola](https://medium.freecodecamp.org/time-is-complex-but-priceless-f0abd015063c) -- [An Introduction to Big O Notation — Joseph Trettevik](https://dev.to/lofiandcode/an-introduction-to-big-o-notation-210o) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript: Intro to Big O Notation and Function Runtime — Eric Traub](https://www.youtube.com/watch?v=HgA5VOFan5E) -- [Essential Big O for JavaScript Developers — Dave Smith](https://www.youtube.com/watch?v=KatlvCFHPRo) -- [Big O Notation - Time Complexity Analysis — WebTunings](https://www.youtube.com/watch?v=ALl86xJiTD8) -- [Learn Big O Notation In 12 Minutes - Web Dev Simplified](https://www.youtube.com/watch?v=itn09C2ZB9Y) -- [JavaScript Algorithms: Big-O Notation - Codevolution](https://www.youtube.com/watch?v=3yUuo7TqMW8) -- [JavaScript Algorithms Crash Course: Learn Algorithms & "Big O" from the Ground Up! - Academind](https://www.youtube.com/watch?v=JgWm6sQwS_I) -- [Big O Notation - Data Structures and Algorithms in Javascript - RoadSideCoder](https://www.youtube.com/watch?v=LaexPVi1VRE) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 29. Algorithms - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Data Structures and Algorithms using ES6](https://github.com/Crizstian/data-structure-and-algorithms-with-ES6) -- [Algorithms and data structures implemented in JavaScript with explanations and links to further readings](https://github.com/trekhleb/javascript-algorithms) -- [JS: Interview Algorithm](http://www.thatjsdude.com/interview/js1.html) -- [Algorithms in JavaScript — Thon Ly](https://medium.com/siliconwat/algorithms-in-javascript-b0bed68f4038) -- [JavaScript Objects, Square Brackets and Algorithms — Dmitri Grabov](https://medium.freecodecamp.org/javascript-objects-square-brackets-and-algorithms-e9a2916dc158) -- [Atwood's Law applied to CS101 - Classic algorithms and data structures implemented in JavaScript](https://github.com/felipernb/algorithms.js) -- [Data Structures and Algorithms library in JavaScript](https://github.com/yangshun/lago) -- [Collection of computer science algorithms and data structures written in JavaScript](https://github.com/idosela/algorithms-in-javascript) -- [Algorithms and Data Structures in JavaScript — Oleksii Trekhleb](https://dev.to/trekhleb/algorithms-and-data-structures-in-javascript-49i3) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- 🎥 [JavaScript Algorithms - Codevolution](https://www.youtube.com/playlist?list=PLC3y8-rFHvwiRYB4-HHKHblh3_bQNJTMa) -- 🎥 [Dynamic Programming - Learn to Solve Algorithmic Problems & Coding Challenges - FreeCodeCamp](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=1021s) -- 🎥 [Data Structures and Algorithms in Javascript | DSA with JS - RoadsideCoder](https://www.youtube.com/playlist?list=PLKhlp2qtUcSZtJefDThsXcsAbRBCSTgW4) -- 🎥 [Javascript Algorithms + Data Structures - KodingKevin](https://www.youtube.com/playlist?list=PLn2ipk-jqgZiAHiA70hOxAj8RMUeqYNK3) -- 🎥 [JavaScript Data Structures: Getting Started - Academind](https://www.youtube.com/watch?v=41GSinwoMYA) -- 🎥 [Algorithms and Data Structures - The Coding Train (Daniel Shiffman)](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 30. Inheritance, Polymorphism and Code Reuse - -### Reference - -- [Inheritance in JavaScript — MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance) -- [Class inheritance, super — JavaScript.Info](https://javascript.info/class-inheritance) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Inheritance in JavaScript — Rupesh Mishra](https://hackernoon.com/inheritance-in-javascript-21d2b82ffa6f) -- [Simple Inheritance with JavaScript — David Catuhe](https://www.sitepoint.com/simple-inheritance-javascript/) -- [JavaScript — Inheritance, delegation patterns and Object linking — NC Patro](https://codeburst.io/javascript-inheritance-25fe61ab9f85) -- [Object Oriented JavaScript: Polymorphism with examples — Knoldus Blogs](https://blog.knoldus.com/object-oriented-javascript-polymorphism-with-examples/) -- [Program Like Proteus — A beginner's guide to polymorphism in Javascript — Sam Galson](https://medium.com/yld-blog/program-like-proteus-a-beginners-guide-to-polymorphism-in-javascript-867bea7c8be2) -- [Object-oriented JavaScript: A Deep Dive into ES6 Classes — Jeff Mott](https://www.sitepoint.com/object-oriented-javascript-deep-dive-es6-classes/) -- [Unlocking the Power of Polymorphism in JavaScript: A Deep Dive](https://prototypr.io/post/unlocking-the-power-of-polymorphism-in-javascript-a-deep-dive) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Inheritance in JavaScript — kudvenkat](https://www.youtube.com/watch?v=yXlFR81tDBM) -- [JavaScript ES6 Classes and Inheritance — Traversy Media](https://www.youtube.com/watch?v=RBLIm5LMrmc) -- [Polymorphism in JavaScript — kudvenkat](https://www.youtube.com/watch?v=zdovG9cuEBA) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 31. Design Patterns - -### Books - -- [Learning JavaScript Design Patterns — Addy Osmani](https://addyosmani.com/resources/essentialjsdesignpatterns/book/) -- [Pro JavaScript Design Patterns — Ross Harmes and Dustin Diaz](https://pepa.holla.cz/wp-content/uploads/2016/08/Pro-JavaScript-Design-Patterns.pdf) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [JavaScript Design Patterns – Explained with Examples — Germán Cocca](https://www.freecodecamp.org/news/javascript-design-patterns-explained/) -- [4 JavaScript Design Patterns You Should Know — Devan Patel](https://scotch.io/bar-talk/4-javascript-design-patterns-you-should-know) -- [JavaScript Design Patterns – Beginner's Guide to Mobile Web Development — Soumyajit Pathak](https://medium.com/beginners-guide-to-mobile-web-development/javascript-design-patterns-25f0faaaa15) -- [JavaScript Design Patterns — Akash Pal](https://medium.com/front-end-hacking/javascript-design-patterns-ed9d4c144c81) -- [JavaScript Design Patterns: Understanding Design Patterns in JavaScript - Sukhjinder Arora](https://blog.bitsrc.io/understanding-design-patterns-in-javascript-13345223f2dd) -- [All the 23 (GoF) design patterns implemented in Javascript — Felipe Beline](https://github.com/fbeline/Design-Patterns-JS) -- [The Power of the Module Pattern in JavaScript — jsmanifest](https://medium.com/better-programming/the-power-of-the-module-pattern-in-javascript-3c73f7cd10e8) -- [Design Patterns for Developers using JavaScript pt. I — Oliver Mensah](https://dev.to/omensah/design-patterns-for-developers-using-javascript----part-one--b3e) -- [Design Patterns for Developers using JavaScript pt. II — Oliver Mensah](https://dev.to/omensah/design-patterns-for-developers-using-javascript---part-two--3p39) -- [Design patterns in modern JavaScript development](https://levelup.gitconnected.com/design-patterns-in-modern-javascript-development-ec84d8be06ca) -- [Understanding Design Patterns: Iterator using Dev.to and Medium social networks! — Carlos Caballero](https://dev.to/carlillo/understanding-design-patterns-iterator-using-dev-to-and-medium-social-networks-3bdd) -- [JavaScript Design Patterns - Factory Pattern — KristijanFištrek](https://dev.to/kristijanfistrek/javascript-design-patterns-factory-pattern-562p) -- [JavaScript Design Pattern — Module Pattern - Factory Pattern — Moon](https://medium.com/javascript-in-plain-english/javascript-design-pattern-module-pattern-555737eccecd) -- [Design Patterns: Null Object - Carlos Caballero](https://medium.com/better-programming/design-patterns-null-object-5ee839e37892) -- [Strategy Pattern - Francesco Ciulla](https://dev.to/francescoxx/strategy-pattern-5oh) -- [Adapter Pattern - Francesco Ciulla](https://dev.to/francescoxx/adapter-pattern-5bjk) -- [The Power of Composite Pattern in JavaScript - jsmanifest](https://dev.to/jsmanifest/the-power-of-composite-pattern-in-javascript-2732) -- [In Defense of Defensive Programming - Adam Nathaniel Davis](https://dev.to/bytebodger/in-defense-of-defensive-programming-k45) -- [JavaScript Patterns Workshop — Lydia Hallie](https://javascriptpatterns.vercel.app/patterns) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [JavaScript Design Patterns — Udacity](https://www.udacity.com/course/javascript-design-patterns--ud989) -- [JavaScript Patterns for 2017 — Scott Allen](https://www.youtube.com/watch?v=hO7mzO83N1Q) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 32. Partial Applications, Currying, Compose and Pipe - -### Books - -- [Functional-Light JavaScript: Ch. 3 - Managing Function Inputs — Kyle Simpson](https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch3.md) - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Composition and Currying Elegance in JavaScript — Pragyan Das](https://medium.com/@pragyan88/writing-middleware-composition-and-currying-elegance-in-javascript-8b15c98a541b) -- [Functional JavaScript: Function Composition For Every Day Use — Joel Thoms](https://hackernoon.com/javascript-functional-composition-for-every-day-use-22421ef65a10) -- [Functional Composition: compose() and pipe() — Anton Paras](https://medium.com/@acparas/what-i-learned-today-july-2-2017-ab9a46dbf85f) -- [Why The Hipsters Compose Everything: Functional Composing In JavaScript — A. Sharif](http://busypeoples.github.io/post/functional-composing-javascript/) -- [A Gentle Introduction to Functional JavaScript pt III: Functions for making functions — James Sinclair](https://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-functions/) -- [Curry And Compose (why you should be using something like ramda in your code) — jsanchesleao](https://jsleao.wordpress.com/2015/02/22/curry-and-compose-why-you-should-be-using-something-like-ramda-in-your-code/) -- [Function Composition in JavaScript with Pipe — Andy Van Slaars](https://vanslaars.io/post/create-pipe-function/) -- [Practical Functional JavaScript with Ramda — Andrew D'Amelio, Yuri Takhteyev](https://developer.telerik.com/featured/practical-functional-javascript-ramda/) -- [The beauty in Partial Application, Currying, and Function Composition — Joel Thoms](https://hackernoon.com/the-beauty-in-partial-application-currying-and-function-composition-d885bdf0d574) -- [Curry or Partial Application? — Eric Elliott](https://medium.com/javascript-scene/curry-or-partial-application-8150044c78b8) -- [Partial Application in JavaScript — Ben Alman](http://benalman.com/news/2012/09/partial-application-in-javascript/) -- [Partial Application of Functions — Functional Reactive Ninja](https://hackernoon.com/partial-application-of-functions-dbe7d9b80760) -- [Partial Application in ECMAScript 2015 — Ragan Wald](http://raganwald.com/2015/04/01/partial-application.html) -- [So You Want to be a Functional Programmer pt. I — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-1-1f15e387e536) -- [So You Want to be a Functional Programmer pt. II — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-2-7005682cec4a) -- [So You Want to be a Functional Programmer pt. III — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-3-1b0fd14eb1a7) -- [So You Want to be a Functional Programmer pt. IV — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-4-18fbe3ea9e49) -- [So You Want to be a Functional Programmer pt. V — Charles Scalfani](https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a) -- [An introduction to the basic principles of Functional Programming — TK](https://medium.freecodecamp.org/an-introduction-to-the-basic-principles-of-functional-programming-a2c2a15c84) -- [Concepts of Functional Programming in javascript — TK](https://medium.com/the-renaissance-developer/concepts-of-functional-programming-in-javascript-6bc84220d2aa) -- [An Introduction to Functional Programming Style in JavaScript — JavaScript Teacher](https://medium.freecodecamp.org/an-introduction-to-functional-programming-style-in-javascript-71fcc050f064) -- [A practical guide to writing more functional JavaScript — Nadeesha Cabral](https://medium.freecodecamp.org/a-practical-guide-to-writing-more-functional-javascript-db49409f71) -- [A simple explanation of functional pipe in JavaScript — Ben Lesh](https://dev.to/benlesh/a-simple-explanation-of-functional-pipe-in-javascript-2hbj) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- [Compose vs Pipe: Functional Programming in JavaScript — Chyld Studios](https://www.youtube.com/watch?v=Wl2ejJOqHUU) -- [JavaScript Functional Programing: Compose — Theodore Anderson](https://www.youtube.com/watch?v=jigHxo9YR30) -- [Function Composition - Functional JavaScript — NWCalvank](https://www.youtube.com/watch?v=mth5WpEc4Qs) -- [JavaScript Function Composition Explained — Theodore Anderson](https://www.youtube.com/watch?v=Uam37AlzPYw) -- [Let's code with function composition — Fun Fun Function](https://www.youtube.com/watch?v=VGB9HbL1GHk) -- [Partial Application vs. Currying — NWCalvank](https://www.youtube.com/watch?v=DzLkRsUN2vE) -- [JavaScript Partial Application — Theodore Anderson](https://www.youtube.com/watch?v=jkebgHEcvac) -- [call, apply and bind method in JavaScript](https://www.youtube.com/watch?v=75W8UPQ5l7k&t=261s) - -**[⬆ Back to Top](#table-of-contents)** - ---- - -## 33. Clean Code - -### <img align= center width=40px height=40px src="https://cdn-icons-png.flaticon.com/512/1945/1945940.png"> Articles - -- [Clean Code Explained – A Practical Introduction to Clean Coding for Beginners — freeCodeCamp](https://www.freecodecamp.org/news/clean-coding-for-beginners/) -- [Clean Code concepts adapted for JavaScript — Ryan McDermott](https://github.com/ryanmcdermott/clean-code-javascript) -- [Function parameters in JavaScript Clean Code — Kevin Peters](https://medium.com/@kevin_peters/function-parameters-in-javascript-clean-code-4caac109159b) -- [Keeping your code clean — Samuel James](https://codeburst.io/keeping-your-code-clean-d30bcffd1a10) -- [Best Practices for Using Modern JavaScript Syntax — M. David Green](https://www.sitepoint.com/modern-javascript-best-practices/) -- [best practices for cross node/web development - Jimmy Wärting](https://github.com/cross-js/cross-js) -- [Writing Clean Code - Dylan Paulus](https://dev.to/ganderzz/on-writing-clean-code-57cm) -- [Writing Clean Code and The Practice of Programming - Nityesh Agarwal](https://dev.to/nityeshaga/writing-clean-code-and-the-practice-of-programming-actionable-advice-for-beginners-5f0k) -- [Clean code, dirty code, human code - Daniel Irvine](https://dev.to/d_ir/clean-code-dirty-code-human-code-6nm) -- [Practical Ways to Write Better JavaScript - Ryland G](https://dev.to/taillogs/practical-ways-to-write-better-javascript-26d4) -- [The Must-Know Clean Code Principles - Kesk on Medium](https://medium.com/swlh/the-must-know-clean-code-principles-1371a14a2e75) -- [The Clean Code Book - Robert C Martin](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/) -- [How to use destructuring in JavaScript to write cleaner, more powerful code - freecodecamp](https://www.freecodecamp.org/news/how-to-use-destructuring-in-javascript-to-write-cleaner-more-powerful-code-9d1b38794050/) -- [Write Clean Code Using JavaScript Object Destructuring - Asel Siriwardena](https://betterprogramming.pub/write-clean-code-using-javascript-object-destructuring-3551302130e7) - -### <img align=center width="40" height="40" src="https://img.icons8.com/dusk/64/video.png" alt="video"/> Videos - -- 🎥 [JavaScript Pro Tips - Code This, NOT That](https://www.youtube.com/watch?v=Mus_vwhTCq0) -- 🎥 [Clean Code playlist - Beau teaches](https://www.youtube.com/watch?v=b9c5GmmS7ks&list=PLWKjhJtqVAbkK24EaPurzMq0-kw5U9pJh&index=1) -- 🎥 [JavaScript Best Practices and Coding Conventions - Write Clean Code](https://youtu.be/RMN_bkZ1KM0?si=Ssg3cNZ_DB7CIwKQ) -- 🎥 [JavaScript Clean Code](https://youtu.be/vPXzVNmCPg4?si=QR1k4E6Zx5H4mfcs) -- 🎥 [Tips On Learning How To Code](https://www.youtube.com/watch?v=0wHyoBPc6zs) - -**[⬆ Back to Top](#table-of-contents)** - -## <img align="center" width="35" height="35" src="https://moein.video/wp-content/uploads/2022/05/license-GIF-Certificate-Royalty-Free-Animated-Icon-350px-after-effects-project.gif"> <a id="license">License</a> - -This software is licensed under MIT License. See [License](https://github.com/leonardomso/33-js-concepts/blob/master/LICENSE) for more information ©Leonardo Maldonado. - -**[⬆ Back to Top](#table-of-contents)** - -<hr> - <div align="center"> - <strong>Happy Learning! ⭐</strong> - <p>If you find this repository helpful, please consider giving it a star!</p> + <strong>If you find this helpful, please star the repo!</strong> </div> diff --git a/TRANSLATIONS.md b/TRANSLATIONS.md new file mode 100644 index 00000000..98374658 --- /dev/null +++ b/TRANSLATIONS.md @@ -0,0 +1,54 @@ +# Translations + +This project has been translated into 40+ languages thanks to our amazing community of contributors. + +## Available Translations + +- [اَلْعَرَبِيَّةُ‎ (Arabic)](https://github.com/amrsekilly/33-js-concepts) — Amr Elsekilly +- [Български (Bulgarian)](https://github.com/thewebmasterp/33-js-concepts) — thewebmasterp +- [汉语 (Chinese)](https://github.com/stephentian/33-js-concepts) — Re Tian +- [Português do Brasil (Brazilian Portuguese)](https://github.com/tiagoboeing/33-js-concepts) — Tiago Boeing +- [한국어 (Korean)](https://github.com/yjs03057/33-js-concepts.git) — Suin Lee +- [Español (Spanish)](https://github.com/adonismendozaperez/33-js-conceptos) — Adonis Mendoza +- [Türkçe (Turkish)](https://github.com/ilker0/33-js-concepts) — İlker Demir +- [русский язык (Russian)](https://github.com/gumennii/33-js-concepts) — Mihail Gumennii +- [Tiếng Việt (Vietnamese)](https://github.com/nguyentranchung/33-js-concepts) — Nguyễn Trần Chung +- [Polski (Polish)](https://github.com/lip3k/33-js-concepts) — Dawid Lipinski +- [فارسی (Persian)](https://github.com/majidalavizadeh/33-js-concepts) — Majid Alavizadeh +- [Bahasa Indonesia (Indonesian)](https://github.com/rijdz/33-js-concepts) — Rijdzuan Sampoerna +- [Français (French)](https://github.com/robinmetral/33-concepts-js) — Robin Métral +- [हिन्दी (Hindi)](https://github.com/vikaschauhan/33-js-concepts) — Vikas Chauhan +- [Ελληνικά (Greek)](https://github.com/DimitrisZx/33-js-concepts) — Dimitris Zarachanis +- [日本語 (Japanese)](https://github.com/oimo23/33-js-concepts) — oimo23 +- [Deutsch (German)](https://github.com/burhannn/33-js-concepts) — burhannn +- [украї́нська мо́ва (Ukrainian)](https://github.com/AndrewSavetchuk/33-js-concepts-ukrainian-translation) — Andrew Savetchuk +- [සිංහල (Sinhala)](https://github.com/ududsha/33-js-concepts) — Udaya Shamendra +- [Italiano (Italian)](https://github.com/Donearm/33-js-concepts) — Gianluca Fiore +- [Latviešu (Latvian)](https://github.com/ANormalStick/33-js-concepts) — Jānis Īvāns +- [Afaan Oromoo (Oromo)](https://github.com/Amandagne/33-js-concepts) — Amanuel Dagnachew +- [ภาษาไทย (Thai)](https://github.com/ninearif/33-js-concepts) — Arif Waram +- [Català (Catalan)](https://github.com/marioestradaf/33-js-concepts) — Mario Estrada +- [Svenska (Swedish)](https://github.com/FenixHongell/33-js-concepts/) — Fenix Hongell +- [ខ្មែរ (Khmer)](https://github.com/Chhunneng/33-js-concepts) — Chrea Chanchhunneng +- [አማርኛ (Ethiopian)](https://github.com/hmhard/33-js-concepts) — Miniyahil Kebede (ምንያህል ከበደ) +- [Беларуская мова (Belarussian)](https://github.com/Yafimau/33-js-concepts) — Dzianis Yafimau +- [O'zbekcha (Uzbek)](https://github.com/smnv-shokh/33-js-concepts) — Shokhrukh Usmonov +- [Urdu (اردو)](https://github.com/sudoyasir/33-js-concepts) — Yasir Nawaz +- [हिन्दी (Hindi)](https://github.com/milostivyy/33-js-concepts) — Mahima Chauhan +- [বাংলা (Bengali)](https://github.com/Jisan-mia/33-js-concepts) — Jisan Mia +- [ગુજરાતી (Gujarati)](https://github.com/VatsalBhuva11/33-js-concepts) — Vatsal Bhuva +- [سنڌي (Sindhi)](https://github.com/Sunny-unik/33-js-concepts) — Sunny Gandhwani +- [भोजपुरी (Bhojpuri)](https://github.com/debnath003/33-js-concepts) — Pronay Debnath +- [ਪੰਜਾਬੀ (Punjabi)](https://github.com/Harshdev098/33-js-concepts) — Harsh Dev Pathak +- [Latin (Latin)](https://github.com/Harshdev098/33-js-concepts) — Harsh Dev Pathak +- [മലയാളം (Malayalam)](https://github.com/Stark-Akshay/33-js-concepts) — Akshay Manoj +- [Yorùbá (Yoruba)](https://github.com/ayobaj/33-js-concepts) — Ayomide Bajulaye +- [עברית‎ (Hebrew)](https://github.com/rafyzg/33-js-concepts) — Refael Yzgea +- [Nederlands (Dutch)](https://github.com/dlvisser/33-js-concepts) — Dave Visser +- [தமிழ் (Tamil)](https://github.com/UdayaKrishnanM/33-js-concepts) — Udaya Krishnan M + +--- + +## Want to Translate? + +We'd love to have more translations! See our [Contributing Guidelines](CONTRIBUTING.md) for details on how to submit a translation. From e647452c91b2060ce8c870700b9604b7710ae84a Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 19:25:50 -0300 Subject: [PATCH 126/128] ci: add GitHub Actions workflow for running tests - Run tests on push and pull requests to master/main - Test against Node.js 18.x, 20.x, and 22.x - Generate coverage report on Node 20.x - Uses npm ci for faster, reliable installs --- .github/workflows/tests.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..3e3fad81 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,35 @@ +name: Tests + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Run tests with coverage + if: matrix.node-version == '20.x' + run: npm run test:coverage From 053a2e8ecb86e7eac9d4da47124e4092efe11a53 Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 19:29:44 -0300 Subject: [PATCH 127/128] fix(ci): remove Node 18.x from test matrix Vitest 4.x requires Node.js 20+. Node 18.x fails with ERR_REQUIRE_ESM due to Vite 6's pure ESM modules. Node 18.x reaches EOL April 2025. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3e3fad81..3a6b6dbc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [20.x, 22.x] steps: - name: Checkout repository From d3566f55034f028b59be3127c41e4630a2fd8b9c Mon Sep 17 00:00:00 2001 From: Leonardo Maldonado <leonardomso11@gmail.com> Date: Fri, 2 Jan 2026 19:39:52 -0300 Subject: [PATCH 128/128] ci: simplify to single workflow with latest LTS Node.js - Remove old markdown link checker workflow (action.yml) - Simplify tests.yml to use only latest LTS Node.js - Remove coverage step for simpler workflow --- .github/workflows/action.yml | 10 ---------- .github/workflows/tests.yml | 12 ++---------- 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 .github/workflows/action.yml diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml deleted file mode 100644 index 2c3a04d1..00000000 --- a/.github/workflows/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Check Markdown links - -on: push - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@v1 \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a6b6dbc..718f1abf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,18 +10,14 @@ jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.x, 22.x] - steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup Node.js ${{ matrix.node-version }} + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 'lts/*' cache: 'npm' - name: Install dependencies @@ -29,7 +25,3 @@ jobs: - name: Run tests run: npm test - - - name: Run tests with coverage - if: matrix.node-version == '20.x' - run: npm run test:coverage