The language of the web from basics to deep.
3 topics
JavaScript has three ways to declare variables: var (function-scoped, hoisted — avoid in modern code), let (block-scoped, reassignable), and const (block-scoped, not reassignable — use by default). JavaScript is dynamically typed with seven primitive types: string, number, bigint, boolean, undefined, null, and symbol. Objects are collections of key-value pairs and include arrays, functions, dates, and regular expressions. Understanding type coercion (how JavaScript converts between types automatically) prevents subtle bugs — learn the difference between == (loose equality with coercion) and === (strict equality without coercion). typeof checks types at runtime, and the nullish coalescing operator (??) handles null/undefined defaults.
2 resources
Functions are first-class citizens in JavaScript — they can be assigned to variables, passed as arguments, and returned from other functions. Understand function declarations (hoisted), function expressions (not hoisted), arrow functions (lexical this, concise syntax), default parameters, rest parameters (...args), and the spread operator. Scope determines variable visibility: global scope (accessible everywhere), function scope (var declarations), and block scope (let/const within {}). Closures occur when a function remembers its surrounding scope even after the outer function has returned — they power callbacks, event handlers, and data privacy patterns. The this keyword behaves differently in regular functions (dynamic, depends on how called) vs arrow functions (lexical, inherited from surrounding scope). Understanding hoisting, the temporal dead zone, and the scope chain is essential for debugging.
Arrays and objects are the workhorses of JavaScript. Master array methods: map (transform each element), filter (select elements), reduce (accumulate a result), find/findIndex (locate elements), some/every (boolean checks), flat/flatMap (flatten nested arrays), sort (with custom comparators), and forEach (side effects). Destructuring extracts values from arrays and objects: const { name, age } = user and const [first, ...rest] = items. The spread operator (...) copies and merges arrays and objects. Object methods include Object.keys(), Object.values(), Object.entries(), Object.assign(), and Object.freeze(). Optional chaining (?.) safely accesses nested properties. Understand how objects are passed by reference (not by value), shallow vs deep copying, and JSON serialization (JSON.stringify/JSON.parse). These patterns are used constantly in React and Node.js development.
ECMAScript 2015 (ES6) and later versions introduced features that transformed how JavaScript is written. Essential ES6+ features include: template literals (backtick strings with ${expression} interpolation), classes (syntactic sugar over prototypal inheritance, with constructors, methods, getters/setters, static methods, and inheritance via extends), modules (import/export for code organization), symbols (unique identifiers), Map and Set data structures (more capable than plain objects/arrays for certain use cases), WeakMap and WeakSet (for memory-efficient references), for...of loops, computed property names, and shorthand property/method definitions. Later additions include optional chaining (?.), nullish coalescing (??), logical assignment (&&=, ||=, ??=), Array.at(), Object.hasOwn(), structuredClone() for deep copying, and top-level await. Stay current with new features as they ship annually.
Asynchronous programming is fundamental to JavaScript. The event loop processes a single call stack but uses callback queues and microtask queues to handle concurrent operations. Promises represent the eventual result of an async operation — they can be pending, fulfilled (resolved), or rejected. Chain promises with .then() and .catch(), or use async/await for cleaner syntax. Key patterns: Promise.all() runs multiple promises in parallel and waits for all (rejects if any reject), Promise.allSettled() waits for all regardless of outcome, Promise.race() resolves/rejects with the first to complete, and Promise.any() resolves with the first to fulfill. The Fetch API returns promises for HTTP requests. Always handle errors with try/catch in async functions. Understand microtask vs macrotask queue priority (promises before setTimeout). Avoid common pitfalls like unhandled promise rejections and accidental sequential execution when parallel is possible.
The iteration protocol defines how objects can be iterated over with for...of loops, spread syntax, and destructuring. Any object with a Symbol.iterator method that returns an iterator (an object with a next() method returning {value, done}) is iterable. Arrays, strings, Maps, and Sets are built-in iterables. Generators are special functions (declared with function*) that can pause and resume execution using the yield keyword. They return iterators and are perfect for lazy evaluation (processing large datasets one item at a time), infinite sequences, and async iteration. Async generators (async function*) combined with for await...of enable streaming data processing. While generators are not used daily, understanding the iteration protocol helps you work with custom data sources, implement pagination, and understand how libraries like Redux-Saga work under the hood.
The Document Object Model (DOM) is the browser's in-memory representation of your HTML as a tree of nodes. JavaScript can read and modify this tree dynamically. Query elements with document.querySelector (CSS selector, returns first match) and querySelectorAll (returns all matches as a NodeList). Modify content with textContent (safe, text only) or innerHTML (parses HTML — beware of XSS). Manage CSS classes with element.classList (add, remove, toggle, contains). Create elements with document.createElement, append with appendChild or append, and remove with element.remove(). Access and modify attributes with getAttribute/setAttribute. Traverse the tree with parentElement, children, nextElementSibling, and previousElementSibling. For performance-sensitive updates, use DocumentFragment to batch DOM changes and requestAnimationFrame for visual updates. Modern frameworks like React abstract DOM manipulation, but understanding the underlying API is essential for debugging, library development, and performance optimization.
The DOM event system is how your page responds to user interactions and browser lifecycle changes. Events follow three phases: capturing (from window down to the target), target (the element that triggered the event), and bubbling (from target back up to window). Use addEventListener to attach handlers, with the third argument controlling capture vs bubble phase. Event delegation is a powerful pattern — instead of adding listeners to many child elements, add one listener to a parent and check event.target. This is more performant and handles dynamically added elements. Key methods: event.preventDefault() stops default behavior (form submission, link navigation), event.stopPropagation() stops the event from propagating further. Common events include click, input, change, submit, keydown/keyup, focus/blur, scroll, resize, DOMContentLoaded, and load. Custom events can be created with new CustomEvent() for component communication.
The Fetch API is the modern way to make HTTP requests from JavaScript, replacing the older XMLHttpRequest. fetch() returns a Promise that resolves with a Response object. Basic usage: const response = await fetch(url), then await response.json() to parse the body. For POST requests, pass an options object with method, headers (Content-Type), and body (JSON.stringify for JSON data). Handle errors properly — fetch only rejects on network failure, not HTTP errors; check response.ok or response.status to detect 4xx/5xx errors. Advanced features include: AbortController for canceling requests, streaming responses with response.body (ReadableStream), FormData for file uploads, and request/response headers manipulation. Understand CORS (Cross-Origin Resource Sharing) — the browser blocks requests to different origins unless the server allows it with appropriate headers. Libraries like Axios, ky, and ofetch provide nicer APIs on top of fetch.
npm (Node Package Manager) is the default package manager for JavaScript, with over 2 million packages in its registry. package.json is the manifest file for your project — it defines: name and version, scripts (shortcuts for common commands like dev, build, test, lint), dependencies (packages needed at runtime), devDependencies (packages needed only for development/testing), and engines (Node.js version requirements). Understand semantic versioning (^1.2.3 means >=1.2.3 <2.0.0), lock files (package-lock.json ensures reproducible installs), npx (run packages without installing), and workspaces for monorepo setups. Alternative package managers include pnpm (faster, disk-efficient with content-addressable storage) and yarn (parallel installs, plug'n'play). Keep dependencies updated with npm audit for security vulnerabilities and tools like Renovate or Dependabot for automated update PRs.
Bundlers transform your source code (modules, TypeScript, JSX, CSS) into optimized bundles that browsers can execute. Vite is the modern standard — it uses native ES modules during development for near-instant server start and hot module replacement (HMR), and Rollup under the hood for optimized production builds. Vite supports TypeScript, JSX, CSS modules, PostCSS, and static assets out of the box. Webpack is the older, highly configurable bundler still used in many enterprise projects — it uses loaders for file transformation and plugins for build customization. esbuild is an extremely fast bundler written in Go, used by Vite and other tools for rapid transformation. Turbopack is the Rust-based bundler powering Next.js development. Key concepts across all bundlers: entry points, code splitting, tree shaking (removing unused exports), source maps (mapping bundled code to original source for debugging), and asset handling (images, fonts, CSS).
Testing JavaScript applications ensures correctness and prevents regressions. Vitest is the modern testing framework designed for Vite — it has a Jest-compatible API, native TypeScript support, in-source testing, and parallel execution for speed. Jest is the established alternative, widely used with React. Write unit tests for pure functions, utility modules, and business logic. Use expect() with matchers (toBe, toEqual, toContain, toThrow) for assertions. Mock dependencies with vi.mock() or jest.mock() to isolate the unit under test. Code coverage (vitest --coverage) shows which lines are tested. For React components, use Testing Library to render components and query them by accessible roles, labels, and text — testing behavior rather than implementation. Beyond unit tests, integration tests verify multiple modules working together, and tools like Playwright or Cypress handle end-to-end browser testing.