Skip to content

Latest commit

 

History

History
1260 lines (995 loc) · 38.9 KB

File metadata and controls

1260 lines (995 loc) · 38.9 KB

🚀 Comprehensive JavaScript Developer Reference Manual

A definitive, in-depth guide to modern JavaScript covering fundamentals, advanced patterns, and production-ready techniques.


Table of Contents

  1. Introduction to JavaScript
  2. Basics
  3. Control Flow
  4. Functions
  5. Scope and Closures
  6. Objects and Arrays
  7. DOM Manipulation
  8. ES6+ Features
  9. Asynchronous JavaScript
  10. Error Handling
  11. Browser APIs
  12. Debugging
  13. Best Practices
  14. Common Mistakes
  15. Real-world Examples

1. Introduction to JavaScript

What is JavaScript?

JavaScript (JS) is a high-level, lightweight, interpreted (and Just-In-Time compiled) programming language. It is best known as the scripting language for the Web, allowing developers to implement complex, interactive features on web pages.

JavaScript is characterized by being:

  • High-level: Abstracts away machine-level details like memory management (handled via Garbage Collection).
  • Multi-paradigm: Supports object-oriented (prototype-based), imperative, and declarative (functional) programming styles.
  • Single-threaded & Non-blocking: Executes one operation at a time on a single main thread, but delegates heavy tasks (like I/O or timers) to web APIs to prevent blocking the thread.
  • Dynamic: Variable types are evaluated at runtime, and objects can have their structure altered dynamically.

History and Evolution

  • 1995: Created by Brendan Eich at Netscape in just 10 days. Originally called Mocha, then LiveScript, and finally JavaScript (for marketing purposes to ride the Java hype, despite having little to do with Java).
  • 1997: Standardized by ECMA International, resulting in ECMAScript (ES).
  • 2009: ES5 released (introduced Strict Mode, JSON support, array methods like forEach, map).
  • 2015: ES6 (ECMAScript 2015) released. A massive update that introduced let/const, arrow functions, classes, modules, promises, and more.
  • Present: Yearly release cycles (ES2016, ES2017, etc.) ensure steady, manageable updates.

Role in Web Development

The traditional Web Development trifecta consists of:

  1. HTML: The structural layer (nouns/bones). Defines the content.
  2. CSS: The presentation layer (adjectives/skin). Defines the look and feel.
  3. JavaScript: The behavioral layer (verbs/muscle). Defines interactivity, logic, and data handling.

Beyond the browser, JavaScript runs on servers (Node.js, Deno, Bun), mobile devices (React Native, Ionic), desktop apps (Electron), and even IoT devices.


2. Basics

Variables (var, let, const)

Variables are named containers used to store data in memory. Prior to ES6, var was the only way to declare variables. ES6 introduced let and const for better scoping rules and predictability.

var

  • Scope: Function-scoped. If declared outside a function, it's global.
  • Hoisting: Declarations are moved to the top of their scope. The variable can be accessed before initialization (results in undefined).
  • Redeclaration: Can be redeclared within the same scope.
console.log(name); // Output: undefined (hoisted but not initialized)
var name = "Alice";
var name = "Bob"; // Perfectly fine, overwrites "Alice"

let

  • Scope: Block-scoped (confined to { ... } blocks).
  • Hoisting: Hoisted, but not initialized. Accessing before declaration results in a ReferenceError (Temporal Dead Zone - TDZ).
  • Redeclaration: Cannot be redeclared in the same block scope.
let age = 25;
age = 26; // Reassignment is allowed

if (true) {
  let localAge = 30; // Block scoped
}
// console.log(localAge); // ReferenceError: localAge is not defined

const

  • Scope: Block-scoped.
  • Reassignment: Cannot be reassigned. Must be initialized at declaration.
  • Mutability: The binding is constant, but if the value is an object or array, its properties/elements can be mutated.
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable.

const user = { name: "John" };
user.name = "Jane"; // Allowed! The object itself mutated, the reference didn't change.

Best Practice: Default to const. Use let only when you know the variable's value needs to change (e.g., in loops or accumulators). Never use var in modern JavaScript.

Data Types

JavaScript has two categories of data types: Primitives and Objects (Reference types).

Primitive Types (Stored by Value)

  1. String: Text data.
    const greeting = "Hello, World!";
  2. Number: Integers and floating-point numbers. Also includes Infinity, -Infinity, and NaN (Not a Number).
    const count = 42;
    const price = 99.99;
  3. BigInt: For numbers larger than the maximum safe integer (2^53 - 1).
    const hugeNumber = 9007199254740991n;
  4. Boolean: Logical entity (true or false).
    const isActive = true;
  5. Undefined: A variable declared but not assigned a value.
    let futureValue;
    console.log(futureValue); // undefined
  6. Null: Intentional absence of any object value.
    const emptyBox = null;
  7. Symbol: Unique, immutable identifiers, primarily used for object properties.
    const uniqueId = Symbol('id');

Reference Types (Stored by Reference)

  • Objects: Collections of key-value pairs.
  • Arrays: Ordered list of values (technically a type of object).
  • Functions: Executable blocks of code (also a type of object).

Operators

Arithmetic Operators

let a = 10;
let b = 3;

console.log(a + b); // 13 (Addition)
console.log(a - b); // 7 (Subtraction)
console.log(a * b); // 30 (Multiplication)
console.log(a / b); // 3.333... (Division)
console.log(a % b); // 1 (Modulo/Remainder)
console.log(a ** b); // 1000 (Exponentiation)

a++; // Increment (a becomes 11)
b--; // Decrement (b becomes 2)

Assignment Operators

let x = 10;
x += 5; // Equivalent to x = x + 5 (x is now 15)
x *= 2; // Equivalent to x = x * 2 (x is now 30)

Comparison Operators

console.log(5 == "5");  // true (Loose equality: performs type coercion)
console.log(5 === "5"); // false (Strict equality: checks value AND type)
console.log(10 !== 5);  // true (Strict inequality)
console.log(10 > 5);    // true

Rule of Thumb: Always use === and !== to prevent unpredictable type coercion bugs.

Logical Operators

  • && (AND): Returns true if both operands are truthy.
  • || (OR): Returns true if at least one operand is truthy.
  • ! (NOT): Inverts truthiness.
const isAdult = true;
const hasLicense = false;

console.log(isAdult && hasLicense); // false
console.log(isAdult || hasLicense); // true
console.log(!isAdult); // false

Truthy and Falsy Values

In JavaScript, a value is falsy if it converts to false when evaluated in a boolean context. The only 6 falsy values are: false, 0, "" (empty string), null, undefined, and NaN. Everything else is truthy (including empty arrays [] and empty objects {}).


3. Control Flow

Control flow determines the order in which statements are executed.

if-else Statements

Used for executing code blocks based on conditional logic.

const temperature = 25;

if (temperature > 30) {
  console.log("It's hot outside.");
} else if (temperature > 20) {
  console.log("The weather is nice.");
} else {
  console.log("It's cold.");
}

switch Statement

An alternative to chaining multiple else if conditions when testing a single expression against multiple potential values. It uses strict equality (===).

const role = "admin";

switch (role) {
  case "admin":
    console.log("Full Access Granted");
    break; // Crucial to prevent fall-through
  case "editor":
    console.log("Edit Access Granted");
    break;
  case "viewer":
    console.log("Read-only Access");
    break;
  default:
    console.log("Role Unknown");
}

Loops

The for Loop

Best when the number of iterations is known.

for (let i = 0; i < 5; i++) {
  console.log(`Iteration number: ${i}`);
}

The while Loop

Best when the loop needs to run until a specific condition becomes false.

let count = 0;
while (count < 3) {
  console.log(`Count is ${count}`);
  count++;
}

The do-while Loop

Guarantees that the code block will execute at least once before evaluating the condition.

let diceRoll = 0;
do {
  console.log(`You rolled a ${diceRoll}`);
  diceRoll++;
} while (diceRoll < 0); // Loop terminates immediately, but block ran once.

Loop Control (break and continue)

  • break: Completely exits the loop.
  • continue: Skips the rest of the current iteration and jumps to the next evaluation.
for (let i = 0; i < 10; i++) {
  if (i === 3) continue; // Skips printing 3
  if (i === 7) break;    // Stops the loop completely when i reaches 7
  console.log(i);
}

4. Functions

Functions are the fundamental building blocks in JavaScript—reusable blocks of code designed to perform a specific task. They are "First-Class Citizens", meaning they can be assigned to variables, passed as arguments, and returned from other functions.

Function Declarations vs Expressions

Function Declaration

Function declarations are hoisted to the top of their enclosing scope, meaning you can call them before they are defined in the code.

// Valid because of hoisting
greetUser("Alice"); 

function greetUser(name) {
  console.log(`Hello, ${name}!`);
}

Function Expression

A function assigned to a variable. They are not hoisted.

// calculateArea(5, 5); // ReferenceError: Cannot access before initialization

const calculateArea = function(width, height) {
  return width * height;
};

console.log(calculateArea(5, 5)); // 25

Arrow Functions

Introduced in ES6, arrow functions provide a shorter syntax and inherently bind this lexically (they do not have their own this context).

// Standard syntax
const add = (a, b) => {
  return a + b;
};

// Implicit return syntax (when body is a single expression)
const multiply = (a, b) => a * b;

// Single parameter syntax (parentheses are optional)
const square = x => x * x;

// Returning an object literal requires wrapping in parentheses
const createUser = name => ({ id: Date.now(), name: name });

Parameters and Arguments

  • Arguments: The actual values passed to the function when invoked.
  • Parameters: The variable names in the function definition.

Default Parameters

function greet(name = "Guest") {
  console.log(`Welcome, ${name}`);
}

greet(); // "Welcome, Guest"
greet("Sarah"); // "Welcome, Sarah"

Rest Parameters (...)

Allows a function to accept an indefinite number of arguments as an array.

function sumAll(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sumAll(1, 2, 3, 4, 5)); // 15

5. Scope and Closures

Understanding scope and closures is vital for mastering JavaScript state management and debugging.

Scope Hierarchy

Scope determines the accessibility/visibility of variables.

  1. Global Scope: Variables declared outside any function or block. Accessible from everywhere.
    const globalVar = "I am global";
  2. Function/Local Scope: Variables declared inside a function. Accessible only within that function.
    function scopeTest() {
      const functionVar = "I am local";
    }
    // console.log(functionVar); // ReferenceError
  3. Block Scope: Variables declared inside {} with let or const.
    if (true) {
      let blockVar = "I am block scoped";
    }
    // console.log(blockVar); // ReferenceError

Lexical Scope

Functions in JavaScript are lexically scoped. This means an inner function has access to the variables defined in its outer (parent) function's scope, based on where the function was authored physically in the code.

function outer() {
  const message = "Hello from outer!";
  
  function inner() {
    // inner() can access variables from outer()
    console.log(message);
  }
  
  inner();
}
outer(); // Output: "Hello from outer!"

Closures

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In simpler terms, a closure gives a function access to its outer scope even after the outer function has finished executing.

Practical Use Case: Data Privacy / Encapsulation

function createCounter() {
  let count = 0; // 'count' is private to the outer function

  // The returned object and its methods form closures
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

const myCounter = createCounter();
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2
console.log(myCounter.getCount());  // 2
// console.log(myCounter.count);    // undefined (count is inaccessible directly)

Closures are everywhere in JavaScript: event listeners, asynchronous callbacks (e.g., setTimeout), and partial application (currying).


6. Objects and Arrays

Objects

Objects are collections of keyed properties. Keys are typically strings (or Symbols), and values can be any data type, including other objects or functions.

Creation and Property Access

const developer = {
  name: "Jane Doe",
  language: "JavaScript",
  experience: 5,
  greet() {
    // Methods inside objects have a 'this' context referring to the object itself
    console.log(`Hi, I am ${this.name}.`);
  }
};

// Dot notation (preferred)
console.log(developer.name); 

// Bracket notation (required when keys have spaces, special chars, or are dynamic)
const prop = "language";
console.log(developer[prop]); // "JavaScript"

// Adding/Modifying
developer.role = "Senior Frontend";
developer.experience = 6;

// Deleting
delete developer.role;

Arrays

Arrays are specialized objects designed to hold ordered lists of values.

const colors = ["red", "green", "blue"];
console.log(colors[0]); // "red"
console.log(colors.length); // 3

Essential Array Methods

1. Mutating Methods (Change the original array)

  • push(item): Adds to the end. Returns new length.
  • pop(): Removes from the end. Returns removed item.
  • unshift(item): Adds to the beginning. Returns new length.
  • shift(): Removes from the beginning. Returns removed item.
  • splice(start, deleteCount, item1, ...): Adds/removes items anywhere.
  • sort((a, b) => a - b): Sorts the array.

2. Non-Mutating Methods (Return a new array or value)

  • slice(start, end): Extracts a section of the array.
  • concat(arr2): Merges arrays.
  • includes(item): Returns boolean if item exists.
  • indexOf(item): Returns the first index of the item, or -1.

3. Higher-Order Iteration Methods These are the core of modern functional JavaScript programming.

  • forEach(): Executes a callback for each element. Returns undefined.
    const users = ["Alice", "Bob", "Charlie"];
    users.forEach((user, index) => console.log(`${index}: ${user}`));
  • map(): Creates a new array populated with the results of the callback.
    const nums = [1, 2, 3];
    const doubled = nums.map(num => num * 2); // [2, 4, 6]
  • filter(): Creates a new array with all elements that pass the test implemented by the callback.
    const ages = [12, 18, 25, 8];
    const adults = ages.filter(age => age >= 18); // [18, 25]
  • reduce(): Executes a reducer function on each element, resulting in a single output value.
    const cartValues = [10, 20, 30];
    const total = cartValues.reduce((accumulator, currentValue) => {
      return accumulator + currentValue;
    }, 0); // 0 is the initial accumulator value. Total is 60.
  • find(): Returns the first element that satisfies the testing function.
    const inventory = [{name: 'apple', qty: 2}, {name: 'banana', qty: 0}];
    const found = inventory.find(fruit => fruit.name === 'banana'); // {name: 'banana', qty: 0}

Destructuring

Destructuring assignment allows you to unpack values from arrays, or properties from objects, into distinct variables.

Object Destructuring

const userProfile = {
  id: 101,
  username: "j_doe",
  contact: {
    email: "j@example.com",
    phone: "555-1234"
  }
};

// Extracting properties
const { id, username } = userProfile;

// Extracting with aliasing (renaming)
const { username: accountName } = userProfile; // Variable is accountName

// Nested destructuring and default values
const { contact: { email }, bio = "No bio provided" } = userProfile;

Array Destructuring

const rgb = [255, 128, 0];
const [red, green, blue] = rgb;

// Skipping items
const [,, justBlue] = rgb; // justBlue is 0

// Rest parameter
const scores = [98, 85, 70, 65];
const [topScore, ...otherScores] = scores; // topScore = 98, otherScores = [85, 70, 65]

7. DOM Manipulation

The Document Object Model (DOM) is a programming interface for HTML. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects (a tree structure).

Selecting Elements

// Selects by ID (returns single Element)
const header = document.getElementById("main-header");

// Selects by Class (returns an HTMLCollection)
const buttons = document.getElementsByClassName("btn");

// Modern approach: querySelector (returns the first matching element)
const firstBtn = document.querySelector(".btn");
const submitForm = document.querySelector("form#checkout");

// Modern approach: querySelectorAll (returns a NodeList of all matches)
const allLinks = document.querySelectorAll("a.nav-link");

Modifying Content and Styles

Content

const title = document.querySelector("h1");

// innerText / textContent: updates text, preventing HTML injection
title.textContent = "Welcome to the Dashboard";

// innerHTML: parses string as HTML (Warning: XSS vulnerability if data is user-generated)
document.querySelector(".container").innerHTML = "<p><strong>Bold Text</strong></p>";

Styles and Classes

const banner = document.querySelector(".banner");

// Modifying inline styles
banner.style.backgroundColor = "blue"; // Note camelCase for CSS properties
banner.style.marginTop = "20px";

// Modifying classes (Preferred way to handle styling)
banner.classList.add("active", "visible");
banner.classList.remove("hidden");
banner.classList.toggle("dark-mode"); // Adds if missing, removes if present
const hasClass = banner.classList.contains("active"); // returns true/false

Event Handling

Events are actions that happen in the system you are programming, which the system tells you about so your code can react.

addEventListener

The standard way to handle events. It allows multiple listeners on a single element and gives fine control.

const submitBtn = document.querySelector("#submit");

submitBtn.addEventListener("click", function(event) {
  // 'event' (or 'e') contains data about the event that occurred
  console.log("Button clicked!");
  console.log("Coordinates:", event.clientX, event.clientY);
});

Event Object and Default Behavior

Forms submit and refresh the page by default; links navigate to a URL. We often need to prevent this.

const form = document.querySelector("form");

form.addEventListener("submit", function(e) {
  e.preventDefault(); // STOPS the default form submission/page reload
  
  const inputValue = document.querySelector("#username").value;
  console.log("Form data:", inputValue);
  
  // Custom API submission logic goes here...
});

Event Delegation

Instead of attaching listeners to multiple individual child elements, attach one listener to their parent element. It utilizes Event Bubbling (events bubble up from the target to the root).

// HTML structure: <ul id="list"> <li>Item 1</li> <li>Item 2</li> </ul>

const list = document.getElementById("list");

list.addEventListener("click", function(e) {
  // e.target is the actual element that was clicked
  if (e.target.tagName === "LI") {
    console.log(`You clicked on: ${e.target.textContent}`);
    e.target.classList.toggle("completed");
  }
});
// This works even if new <li> elements are added dynamically later!

8. ES6+ Features

Modern JavaScript (ES6 and beyond) introduced syntax that makes code cleaner and more expressive.

Template Literals

Enclosed by backticks (`), allowing embedded expressions and multi-line strings.

const product = "Laptop";
const price = 1200;

// Old way (String concatenation)
const oldStr = "The " + product + " costs $" + price + ".";

// New way (Template literal interpolation)
const newStr = `The ${product} costs $${price}.`;

// Multi-line support
const markup = `
  <div class="card">
    <h2>${product}</h2>
    <p>$${price}</p>
  </div>
`;

Spread (...) and Rest (...) Operators

Though they use the same syntax (...), they do opposite things.

Spread: Expands iterables (arrays, strings, objects) into individual elements.

// Copying arrays (Shallow copy)
const originalArr = [1, 2, 3];
const copyArr = [...originalArr, 4, 5]; // [1, 2, 3, 4, 5]

// Merging objects
const userBase = { name: "John", age: 30 };
const userAuth = { role: "admin", token: "12345" };
const completeUser = { ...userBase, ...userAuth, active: true };

Rest: Collects multiple elements and condenses them into a single array (used in function params or destructuring).

const [first, second, ...remaining] = [10, 20, 30, 40, 50];
// remaining is [30, 40, 50]

Modules (import/export)

Modules allow splitting code into multiple files. Each file acts as a module with its own scope. Variables and functions must be explicitly exported to be used elsewhere.

mathUtils.js

// Named exports
export const PI = 3.14159;
export function square(x) { return x * x; }

// Default export (Only one per file)
export default function add(a, b) { return a + b; }

main.js

// Importing default and named exports
import add, { PI, square } from './mathUtils.js';

console.log(PI); // 3.14159
console.log(square(4)); // 16
console.log(add(5, 5)); // 10

(Note: To use modules in browsers, the script tag needs type="module")

Optional Chaining (?.) and Nullish Coalescing (??)

Optional Chaining: Safely access deeply nested object properties without getting a TypeError if a reference is undefined/null.

const user = { details: { address: { city: "New York" } } };

// Without optional chaining: 
// const zip = user.details && user.details.address && user.details.address.zipcode;

// With optional chaining:
const zip = user?.details?.address?.zipcode; // Evaluates to undefined safely

Nullish Coalescing: Returns the right-hand side operand when its left-hand side is null or undefined (but NOT other falsy values like 0 or "").

const score = 0;
// Using || would incorrectly default 0 to 50
const finalScore = score ?? 50; 
console.log(finalScore); // 0

9. Asynchronous JavaScript

JavaScript is single-threaded. To prevent UI blocking during heavy tasks (like fetching data over the network), it relies on asynchronous programming utilizing the Event Loop.

The Event Loop (High-Level)

  1. Call Stack: Where synchronous code executes.
  2. Web APIs: Browser features (setTimeout, DOM, fetch). Asynchronous tasks are handed off here.
  3. Callback Queue (Task Queue): Where callbacks wait to be executed after Web API tasks finish.
  4. Event Loop: Constantly monitors the Call Stack. If the Stack is empty, it pushes the first task from the Callback Queue onto the Stack.

Callbacks

A function passed into another function as an argument, to be executed later. Problem: Nested callbacks lead to "Callback Hell" (Pyramid of Doom), making code unreadable and hard to handle errors.

setTimeout(() => {
  console.log("Step 1 done");
  setTimeout(() => {
    console.log("Step 2 done");
    // ... continues nesting
  }, 1000);
}, 1000);

Promises

An object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. A Promise is in one of three states:

  • Pending: Initial state, neither fulfilled nor rejected.
  • Fulfilled: Operation completed successfully.
  • Rejected: Operation failed.
const fetchUserData = new Promise((resolve, reject) => {
  // Simulate network request
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve({ id: 1, username: "devGuru" }); // Fulfills the promise
    } else {
      reject("Failed to fetch data."); // Rejects the promise
    }
  }, 2000);
});

// Consuming the promise
fetchUserData
  .then((data) => {
    console.log("Data received:", data);
    return data.id; // Passing data to the next .then block
  })
  .then((id) => {
    console.log("User ID is:", id);
  })
  .catch((error) => {
    console.error("Error occurred:", error); // Handles any rejection in the chain
  })
  .finally(() => {
    console.log("Operation complete, cleanup can go here."); // Runs regardless of success/fail
  });

Async / Await (ES2017)

Syntactic sugar on top of Promises. It makes asynchronous code look and behave a bit more like synchronous code, improving readability drastically.

  • async before a function means it always returns a promise.
  • await pauses the execution of the async function until the Promise resolves or rejects.
async function getUserProfile() {
  try {
    console.log("Fetching profile...");
    // Execution pauses here until fetchUserData promise resolves
    const user = await fetchUserData; 
    console.log("Profile fetched:", user);
    
    // You can await multiple things sequentially
    // const posts = await fetchUserPosts(user.id);
  } catch (error) {
    // Rejected promises are caught in the catch block
    console.error("Error fetching profile:", error);
  }
}

getUserProfile();

10. Error Handling

Uncaught errors will halt script execution. Proper error handling ensures the application can recover gracefully or fail informatively.

try...catch...finally

function parseJSON(jsonString) {
  try {
    // Code that might throw an error
    const data = JSON.parse(jsonString);
    console.log("Parsed successfully:", data);
    return data;
  } catch (error) {
    // Executes if an error is thrown in the try block
    console.error("Parsing failed!");
    console.error("Error Name:", error.name);       // e.g., "SyntaxError"
    console.error("Error Message:", error.message); // Details about the failure
    return null; // Graceful fallback
  } finally {
    // Executes unconditionally, useful for closing resources/loaders
    console.log("Parse attempt finished.");
  }
}

parseJSON('{"valid": true}'); // Works
parseJSON('{invalid_json}');  // Caught by catch block

Throwing Custom Errors

You can generate your own errors to enforce logic constraints.

function withdrawMoney(amount, balance) {
  if (typeof amount !== 'number') {
    throw new TypeError("Amount must be a number");
  }
  if (amount > balance) {
    throw new Error("Insufficient funds");
  }
  return balance - amount;
}

try {
  withdrawMoney(500, 100);
} catch (e) {
  console.log("Transaction blocked:", e.message); // Transaction blocked: Insufficient funds
}

11. Browser APIs

Browsers provide APIs that allow JavaScript to interact with the environment.

The Fetch API

The modern standard for making HTTP requests (AJAX). It returns a Promise.

GET Request

fetch('https://jsonplaceholder.typicode.com/users/1')
  .then(response => {
    if (!response.ok) { // Check if HTTP status is 200-299
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json(); // Parses the response stream as JSON (returns a Promise)
  })
  .then(data => console.log("User data:", data))
  .catch(error => console.error("Fetch failed:", error));

POST Request with Async/Await

async function createPost(postData) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(postData) // Convert object to JSON string
    });
    
    if (!response.ok) throw new Error('Failed to create post');
    
    const data = await response.json();
    console.log("Post created:", data);
  } catch (error) {
    console.error(error);
  }
}

createPost({ title: 'New Post', body: 'Content here', userId: 1 });

Local Storage and Session Storage

Web Storage APIs used to store data locally in the user's browser across sessions. Data is stored as key-value pairs (strings only).

  • localStorage: Data persists even after the browser is closed.
  • sessionStorage: Data is cleared when the page session ends (tab closed).
// Saving data (Requires stringification for objects/arrays)
const userPrefs = { theme: 'dark', fontSize: 16 };
localStorage.setItem('preferences', JSON.stringify(userPrefs));

// Retrieving data
const storedPrefsString = localStorage.getItem('preferences');
if (storedPrefsString) {
  const prefs = JSON.parse(storedPrefsString);
  console.log("Theme is:", prefs.theme);
}

// Removing items
localStorage.removeItem('preferences');

// Clearing everything
localStorage.clear();

Timers

Used to delay execution or execute repeatedly.

// setTimeout: Execute ONCE after a delay
const timeoutId = setTimeout(() => {
  console.log("Executed after 2 seconds");
}, 2000);

// clearTimeout(timeoutId); // Cancels the timeout before it fires

// setInterval: Execute REPEATEDLY every X milliseconds
let count = 0;
const intervalId = setInterval(() => {
  count++;
  console.log(`Tick ${count}`);
  if (count === 5) {
    clearInterval(intervalId); // Stops the interval after 5 ticks
    console.log("Timer stopped");
  }
}, 1000);

12. Debugging

Effective debugging is a critical skill for any developer.

The Console API

Beyond standard logging, the console offers powerful tools:

// Basic logging
console.log("General info");
console.info("Informational message");
console.warn("Warning: API deprecated");
console.error("Critical failure!");

// Formatting Objects / Arrays
const people = [{name: 'John', age: 30}, {name: 'Jane', age: 25}];
console.table(people); // Renders a beautiful table in the console

// Grouping
console.group("User Details");
console.log("Name: John");
console.log("Status: Active");
console.groupEnd();

// Performance timing
console.time("LoopTime");
for(let i=0; i<1000000; i++) {} // Some heavy operation
console.timeEnd("LoopTime"); // Prints the time taken e.g. "LoopTime: 2.5ms"

The debugger Statement

Inserting debugger; in your code acts like a programmatic breakpoint. If DevTools is open, execution will pause on that exact line, allowing you to inspect variables and step through code.

function complexCalculation(data) {
  let result = data * 2;
  debugger; // Browser pauses here!
  result += 100;
  return result;
}

Browser DevTools (F12 or Ctrl+Shift+I)

  • Elements Panel: Inspect and edit HTML/CSS in real-time. Good for layout issues.
  • Console Panel: Execute ad-hoc JS, view errors and logs.
  • Sources Panel: The main debugger. View source files, set visual breakpoints, watch variables, and utilize Call Stack tracing.
  • Network Panel: Monitor all incoming and outgoing network requests (XHR/Fetch). View request headers, payloads, and response times.

13. Best Practices

Writing code that works is only the first step; writing code that is maintainable, readable, and performant is the mark of a professional.

  1. Use Meaningful Naming:

    • Variables/Functions: camelCase (e.g., userList, calculateTotal()).
    • Classes/Constructors: PascalCase (e.g., UserProfile).
    • Constants: UPPER_SNAKE_CASE (e.g., MAX_RETRY_COUNT).
    • Booleans should sound like questions (e.g., isValid, hasPermission).
  2. Keep Functions Small (Single Responsibility Principle): A function should do one thing and do it well. If a function is over 30-50 lines, consider breaking it down.

  3. Avoid Magic Numbers/Strings: Assign arbitrary values to explicitly named constants.

    // Bad
    if (status === 2) { ... }
    
    // Good
    const STATUS_COMPLETED = 2;
    if (status === STATUS_COMPLETED) { ... }
  4. Prefer Early Returns (Guard Clauses): Avoid deep nesting of if statements. Return early if conditions fail.

    // Bad
    function processUser(user) {
      if (user) {
        if (user.isActive) {
          // Do stuff
        }
      }
    }
    
    // Good
    function processUser(user) {
      if (!user || !user.isActive) return;
      // Do stuff
    }
  5. Use Strict Mode: Add "use strict"; at the top of scripts to opt into a restricted variant of JavaScript that catches common coding bloopers (modules use strict mode by default).

  6. Immutability when possible: Instead of modifying existing objects/arrays, return new ones (using spread operators or map/filter). This prevents unexpected side effects, especially in frameworks like React.


14. Common Mistakes

  1. Confusing == and ===: Using == triggers type coercion ("0" == 0 is true). Always use === which checks type and value ("0" === 0 is false).

  2. Losing this Context: Passing an object method as a callback detaches it from its object, making this evaluate to undefined (or window). Fix: Use arrow functions, or .bind(this).

  3. Memory Leaks via Event Listeners: Continuously adding event listeners to DOM elements (especially in single-page apps) without calling removeEventListener when elements are destroyed will consume memory.

  4. Mutating State Directly:

    const myArr = [1, 2, 3];
    const newArr = myArr.reverse(); // WAIT! .reverse() mutates the original array!
    // myArr is now [3, 2, 1]
    
    // Fix: Copy first
    const correctArr = [...myArr].reverse();
  5. Not Handling Promise Rejections: Always append a .catch() to promises or wrap await calls in try...catch blocks. Unhandled rejections can crash Node.js applications.

  6. The typeof null Bug:

    console.log(typeof null); // "object" -- This is a historic JS bug!
    // To correctly check for null:
    if (myVar === null) { ... }

15. Real-world Examples

Example 1: Robust Form Validation

// HTML: <form id="register-form"> <input id="email" required> <button type="submit">Submit</button> </form>
// <div id="error-msg"></div>

const form = document.getElementById('register-form');
const emailInput = document.getElementById('email');
const errorMsg = document.getElementById('error-msg');

form.addEventListener('submit', function(e) {
  // 1. Prevent default submission
  e.preventDefault();
  
  // 2. Clear previous errors
  errorMsg.textContent = "";
  
  const emailVal = emailInput.value.trim();
  
  // 3. Basic Validation Logic
  if (!emailVal) {
    showError("Email is required.");
    return;
  }
  
  // Simple regex for email validation
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(emailVal)) {
    showError("Please enter a valid email format.");
    return;
  }
  
  // 4. Success State
  console.log("Form is valid. Submitting data:", { email: emailVal });
  // Make API call here...
  form.reset(); // Clear form
});

function showError(message) {
  errorMsg.textContent = message;
  errorMsg.style.color = "red";
}

Example 2: API Call and DOM Rendering

// HTML: <button id="load-btn">Load Users</button>
// <ul id="user-list"></ul>

const loadBtn = document.getElementById('load-btn');
const userList = document.getElementById('user-list');

// Using async/await for cleaner syntax
loadBtn.addEventListener('click', async () => {
  // Show loading state
  loadBtn.disabled = true;
  loadBtn.textContent = "Loading...";
  userList.innerHTML = ""; // Clear existing list
  
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    if (!response.ok) throw new Error("Network response was not ok");
    
    const users = await response.json();
    
    // Render data using map and join
    const htmlFragments = users.map(user => `
      <li class="user-card">
        <h3>${user.name}</h3>
        <p>Email: ${user.email}</p>
        <p>Company: ${user.company.name}</p>
      </li>
    `);
    
    userList.innerHTML = htmlFragments.join('');
    
  } catch (error) {
    userList.innerHTML = `<li style="color:red;">Failed to load users: ${error.message}</li>`;
  } finally {
    // Reset button state regardless of success or failure
    loadBtn.disabled = false;
    loadBtn.textContent = "Load Users";
  }
});

Example 3: Interactive Tab Component

// HTML structure assumption:
// <div class="tab-container">
//   <div class="tab-headers">
//     <button class="tab-btn active" data-target="#tab1">Tab 1</button>
//     <button class="tab-btn" data-target="#tab2">Tab 2</button>
//   </div>
//   <div class="tab-content">
//     <div id="tab1" class="tab-pane active">Content 1</div>
//     <div id="tab2" class="tab-pane hidden">Content 2</div>
//   </div>
// </div>

// Implementing Event Delegation
document.querySelector('.tab-headers').addEventListener('click', function(e) {
  // Ensure we clicked a button
  if (!e.target.matches('.tab-btn')) return;
  
  const clickedBtn = e.target;
  const targetSelector = clickedBtn.getAttribute('data-target');
  
  // 1. Remove active state from all buttons
  document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
  
  // 2. Hide all tab panes
  document.querySelectorAll('.tab-pane').forEach(pane => {
    pane.classList.remove('active');
    pane.classList.add('hidden');
  });
  
  // 3. Add active state to clicked button
  clickedBtn.classList.add('active');
  
  // 4. Show the corresponding target pane
  const targetPane = document.querySelector(targetSelector);
  targetPane.classList.remove('hidden');
  targetPane.classList.add('active');
});

This comprehensive guide serves as a foundational and advanced reference for JavaScript development. Keep experimenting, writing code, and consulting official documentation (like MDN) to deepen your understanding.