Skip to content

A structured and evolving collection of my JavaScript learning journey — from fundamentals to intermediate and project-ready concepts. These notes are aimed at simplifying complex ideas with clear examples and practical tips, helping both beginners and revisers to grasp JavaScript effectively.

Notifications You must be signed in to change notification settings

aj05hacker/learn_JavaScript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 

Repository files navigation

JavaScript Notes


📝 Variable Declarations: var, let, const

  • var

    • Function-scoped, can be re-declared and updated. Hoisted to the top of its scope.
    • Example:
      var x = 5;
      x = 10;
      var x = 20; // Allowed
  • let

    • Block-scoped, can be updated but not re-declared in the same scope. Not hoisted in the same way as var.
    • Example:
      let y = 5;
      y = 10;
      // let y = 20; // Error: y has already been declared in this scope
  • const

    • Block-scoped, cannot be updated or re-declared. Must be initialized at declaration.
    • Example:
      const z = 5;
      // z = 10; // Error: Assignment to constant variable
      // const z = 20; // Error: z has already been declared

📦 Understanding Scope and Block

  • Scope

    • The area in code where a variable is accessible. In JavaScript, the main types are:
      • Global Scope: Variables declared outside any function/block are accessible everywhere.
      • Function Scope: Variables declared inside a function are only accessible within that function.
      • Block Scope: Variables declared inside a block (like inside { ... }) are only accessible within that block.
  • Block

    • A block is any code wrapped in curly braces { ... }. Common blocks are in if statements, loops, and functions.
    • Example:
      if (true) {
        let message = "Hello"; // message is only accessible inside this block
      }
      // console.log(message); // Error: message is not defined

🔀 Conditional Statements

  • if statement

    • Executes a block of code if a specified condition is true.
    • Example:
      let age = 18;
      if (age >= 18) {
        console.log("You are an adult");
      }
  • if...else statement

    • Executes one block if the condition is true, another if it's false.
    • Example:
      let age = 16;
      if (age >= 18) {
        console.log("You are an adult");
      } else {
        console.log("You are a minor");
      }
  • if...else if...else statement

    • Tests multiple conditions in sequence.
    • Example:
      let score = 85;
      if (score >= 90) {
        console.log("Grade: A");
      } else if (score >= 80) {
        console.log("Grade: B");
      } else if (score >= 70) {
        console.log("Grade: C");
      } else {
        console.log("Grade: F");
      }
  • Nested if statements

    • You can put if statements inside other if statements.
    • Example:
      let age = 20;
      let hasLicense = true;
      
      if (age >= 18) {
        if (hasLicense) {
          console.log("You can drive");
        } else {
          console.log("You need a license");
        }
      } else {
        console.log("You are too young to drive");
      }
  • Comparison Operators

    • Common operators used in conditions:
    • Example:
      let x = 5;
      let y = 10;
      
      if (x == y) console.log("Equal");           // == (loose equality)
      if (x === y) console.log("Strict equal");   // === (strict equality)
      if (x != y) console.log("Not equal");       // != (loose inequality)
      if (x !== y) console.log("Strict not equal"); // !== (strict inequality)
      if (x < y) console.log("Less than");        // <
      if (x > y) console.log("Greater than");     // >
      if (x <= y) console.log("Less or equal");   // <=
      if (x >= y) console.log("Greater or equal"); // >=
  • switch statement

    • The switch statement is used to perform different actions based on different conditions. It is often used as a cleaner alternative to many else if statements.
    • Example:
      let day = 3;
      let dayName;
      
      switch (day) {
        case 1:
          dayName = "Monday";
          break;
        case 2:
          dayName = "Tuesday";
          break;
        case 3:
          dayName = "Wednesday";
          break;
        case 4:
          dayName = "Thursday";
          break;
        case 5:
          dayName = "Friday";
          break;
        case 6:
          dayName = "Saturday";
          break;
        case 7:
          dayName = "Sunday";
          break;
        default:
          dayName = "Invalid day";
      }
      
      console.log(dayName); // 'Wednesday'
    • The break statement stops the execution inside the switch. The default case runs if no case matches.
  • Ternary Operator (?:)

    • The ternary operator is a shorthand for if...else. It evaluates a condition and returns one value if true, and another if false.
    • Syntax:
      condition ? valueIfTrue : valueIfFalse
    • Example:
      let age = 20;
      let message = (age >= 18) ? "Adult" : "Minor";
      console.log(message); // 'Adult'
    • You can also nest ternary operators, but for readability, avoid making them too complex.
      let score = 85;
      let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";
      console.log(grade); // 'B'

🔁 Loops

  • for loop

    • Repeats a block of code a specific number of times.
    • Example:
      for (let i = 0; i < 5; i++) {
        console.log(i);
      }
      // Output: 0 1 2 3 4
  • while loop

    • Repeats a block of code as long as a condition is true.
    • Example:
      let i = 0;
      while (i < 5) {
        console.log(i);
        i++;
      }
      // Output: 0 1 2 3 4
  • do...while loop

    • Like a while loop, but always runs the block at least once.
    • Example:
      let i = 0;
      do {
        console.log(i);
        i++;
      } while (i < 5);
      // Output: 0 1 2 3 4
  • for...of loop

    • Loops over iterable objects (like arrays, strings).
    • Example:
      let arr = ["a", "b", "c"];
      for (let value of arr) {
        console.log(value);
      }
      // Output: a b c
  • for...in loop

    • Loops over the enumerable properties of an object.
    • Example:
      let obj = {a: 1, b: 2, c: 3};
      for (let key in obj) {
        console.log(key, obj[key]);
      }
      // Output: a 1, b 2, c 3
  • for...in loop with Objects

    • Loops over the enumerable properties of an object. Useful for iterating through object keys.
    • Example:
      let person = {
        name: "John",
        age: 30,
        city: "New York"
      };
      
      for (let key in person) {
        console.log(key + ": " + person[key]);
      }
      // Output:
      // name: John
      // age: 30
      // city: New York
  • hasOwnProperty() with for...in

    • Use hasOwnProperty() to avoid inherited properties from the prototype chain.
    • Example:
      let obj = {
        ownProp: "I'm own property"
      };
      
      // Add a property to Object.prototype (not recommended in practice)
      Object.prototype.inheritedProp = "I'm inherited";
      
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          console.log(key + ": " + obj[key]);
        }
      }
      // Output: ownProp: I'm own property
  • break statement

    • Exits the loop immediately, even if the condition is still true.
    • Example:
      for (let i = 0; i < 10; i++) {
        if (i === 5) break;
        console.log(i);
      }
      // Output: 0 1 2 3 4
  • continue statement

    • Skips the current iteration and continues with the next one.
    • Example:
      for (let i = 0; i < 5; i++) {
        if (i === 2) continue;
        console.log(i);
      }
      // Output: 0 1 3 4

String Properties & Methods

  • .length

    • Returns the length of the string.
    • Example:
      let name = "JavaScript";
      console.log(name.length); // 10
  • .charAt(index)

    • Returns the character at the specified index.
    • Example:
      let str = "Hello";
      console.log(str.charAt(1)); // 'e'
  • .indexOf(substring)

    • Returns the index of the first occurrence of the specified substring, or -1 if not found.
    • Example:
      let str = "banana";
      console.log(str.indexOf("a")); // 1
  • .lastIndexOf(substring)

    • Returns the index of the last occurrence of the specified substring, or -1 if not found.
    • Example:
      let str = "banana";
      console.log(str.lastIndexOf("a")); // 5
  • .slice(start, end)

    • Returns a section of the string from the start index up to, but not including, the end index. If end is omitted, extracts to the end of the string.
    • Example:
      let str = "JavaScript";
      console.log(str.slice(0, 4)); // 'Java'
  • .toUpperCase()

    • Converts the string to uppercase letters.
    • Example:
      let str = "hello";
      console.log(str.toUpperCase()); // 'HELLO'
  • .toLowerCase()

    • Converts the string to lowercase letters.
    • Example:
      let str = "HELLO";
      console.log(str.toLowerCase()); // 'hello'
  • .includes(substring)

    • Checks if the string contains the specified substring. Returns true or false.
    • Example:
      let str = "JavaScript";
      console.log(str.includes("Script")); // true
  • .split(separator)

    • Splits the string into an array of substrings using the specified separator.
    • Example:
      let str = "a,b,c";
      console.log(str.split(",")); // ['a', 'b', 'c']
  • .trim()

    • Removes whitespace from both ends of a string (spaces, tabs, newlines).
    • Example:
      let str = "   Hello World!   ";
      let trimmed = str.trim();
      console.log(trimmed); // 'Hello World!'

🔢 Number Function

  • Number(value)
    • Converts a value (string, boolean, etc.) to a number. If the value cannot be converted, it returns NaN (Not a Number).
    • Examples:
      Number("123");      // 123
      Number("12.34");    // 12.34
      Number("abc");      // NaN
      Number(true);        // 1
      Number(false);       // 0
      Number(null);        // 0
      Number(undefined);   // NaN

🔢 Number Methods & Usage

  • Number.isInteger(value)

    • Checks if the value is an integer.
    • Example:
      Number.isInteger(10);    // true
      Number.isInteger(10.5);  // false
  • parseFloat(string)

    • Parses a string and returns a floating point number.
    • Example:
      parseFloat("3.14");     // 3.14
      parseFloat("10");       // 10
  • parseInt(string, radix)

    • Parses a string and returns an integer. The radix is the base (e.g., 10 for decimal).
    • Example:
      parseInt("42");         // 42
      parseInt("101", 2);     // 5 (binary to decimal)
  • number.toFixed(digits)

    • Formats a number using fixed-point notation (keeps specified decimal places, returns a string).
    • Example:
      let n = 3.14159;
      n.toFixed(2);            // '3.14'
  • number.toString([radix])

    • Converts a number to a string. Optionally, you can specify a radix (base).
    • Example:
      let n = 255;
      n.toString();            // '255'
      n.toString(16);          // 'ff' (hexadecimal)
  • Chaining Methods

    • You can chain methods to perform multiple operations in one line.
    • Example:
      let n = 3.14159;
      let result = n.toFixed(2).toString(); // '3.14'
  • Number.isNaN(value)

    • Checks if the value is NaN (and of type number).
    • Example:
      Number.isNaN(NaN);       // true
      Number.isNaN("abc");    // false
      Number.isNaN(parseInt("abc")); // true
  • isNaN(value)

    • Checks if the value is NaN (tries to convert the value to a number first).
    • Example:
      isNaN(NaN);              // true
      isNaN("abc");           // true
      isNaN("123");           // false

📐 Math Object & Methods

  • What is Math?

    • Math is a built-in JavaScript object that provides mathematical constants and functions. It's not a constructor, so you don't use 'new' with it.
    • Example:
      // Math is an object with properties and methods
      console.log(Math.PI);        // 3.141592653589793
      console.log(Math.E);         // 2.718281828459045
      console.log(Math.random());  // Random number between 0 and 1
  • Math.trunc(number)

    • Removes the decimal part of a number, returning the integer part.
    • Example:
      Math.trunc(3.7);   // 3
      Math.trunc(-3.7);  // -3
      Math.trunc(3);     // 3
  • Math.round(number)

    • Rounds a number to the nearest integer.
    • Example:
      Math.round(3.4);   // 3
      Math.round(3.5);   // 4
      Math.round(3.6);   // 4
      Math.round(-3.5);  // -3
  • Math.ceil(number)

    • Rounds a number up to the nearest integer (always rounds up).
    • Example:
      Math.ceil(3.1);    // 4
      Math.ceil(3.9);    // 4
      Math.ceil(3);      // 3
      Math.ceil(-3.1);   // -3
  • Math.floor(number)

    • Rounds a number down to the nearest integer (always rounds down).
    • Example:
      Math.floor(3.1);   // 3
      Math.floor(3.9);   // 3
      Math.floor(3);     // 3
      Math.floor(-3.1);  // -4
  • Math.pow(base, exponent)

    • Returns the value of a base raised to the power of an exponent.
    • Example:
      Math.pow(2, 3);    // 8 (2^3)
      Math.pow(5, 2);    // 25 (5^2)
      Math.pow(10, 0);   // 1
  • Math.min(...numbers)

    • Returns the smallest number from a list of numbers.
    • Example:
      Math.min(1, 2, 3);     // 1
      Math.min(-1, -2, -3);  // -3
      Math.min(5, 5, 5);     // 5
  • Math.max(...numbers)

    • Returns the largest number from a list of numbers.
    • Example:
      Math.max(1, 2, 3);     // 3
      Math.max(-1, -2, -3);  // -1
      Math.max(5, 5, 5);     // 5
  • Math.random()

    • Returns a random number between 0 (inclusive) and 1 (exclusive).
    • Example:
      Math.random();          // Random number between 0 and 1
      Math.random() * 10;     // Random number between 0 and 10
      Math.floor(Math.random() * 10); // Random integer between 0 and 9
  • Chaining Math Methods

    • You can chain Math methods to perform complex calculations.
    • Example:
      // Generate random integer between 1 and 100
      let randomNum = Math.floor(Math.random() * 100) + 1;
      
      // Round the result of a power calculation
      let result = Math.round(Math.pow(2.5, 3)); // 16
      
      // Find the maximum of rounded numbers
      let maxRounded = Math.max(
        Math.round(3.2), 
        Math.round(4.7), 
        Math.round(2.9)
      ); // 5

📚 More Math Methods

For a complete list of Math methods and properties, visit the MDN Math Reference


🧩 Functions

  • Function Declaration

    • Defines a named function that can be called anywhere in the scope.
    • Example:
      function greet(name) {
        return "Hello, " + name + "!";
      }
      console.log(greet("Alice")); // 'Hello, Alice!'
  • Function Expression

    • Defines a function as part of an expression, often assigned to a variable.
    • Example:
      const add = function(a, b) {
        return a + b;
      };
      console.log(add(2, 3)); // 5
  • Arrow Function

    • A shorter syntax for writing function expressions. Does not have its own 'this'.
    • Example:
      const multiply = (a, b) => a * b;
      console.log(multiply(3, 4)); // 12
  • Parameters and Arguments

    • Parameters are variables listed in the function definition. Arguments are values passed to the function when called.
    • Example:
      function sayHello(name) { // 'name' is a parameter
        console.log("Hello, " + name);
      }
      sayHello("Bob"); // 'Bob' is an argument
  • Return Statement

    • The return statement ends function execution and specifies the value to be returned.
    • Example:
      function square(x) {
        return x * x;
      }
      let result = square(5); // 25

📚 Arrays & Array Methods

  • Array Creation

    • Arrays are used to store multiple values in a single variable.
    • Example:
      let fruits = ["apple", "banana", "cherry"];
  • .push(item)

    • Adds an item to the end of the array.
    • Example:
      fruits.push("orange");
      // ['apple', 'banana', 'cherry', 'orange']
  • .pop()

    • Removes and returns the last item from the array.
    • Example:
      let last = fruits.pop();
      // last: 'orange', fruits: ['apple', 'banana', 'cherry']
  • .shift()

    • Removes and returns the first item from the array.
    • Example:
      let first = fruits.shift();
      // first: 'apple', fruits: ['banana', 'cherry']
  • .unshift(item)

    • Adds an item to the beginning of the array.
    • Example:
      fruits.unshift("mango");
      // ['mango', 'banana', 'cherry']
  • .splice(start, deleteCount, ...items)

    • Adds/removes items at a specific index.
    • Example:
      fruits.splice(1, 1, "kiwi");
      // Removes 1 item at index 1 and adds 'kiwi': ['mango', 'kiwi', 'cherry']
  • .slice(start, end)

    • Returns a shallow copy of a portion of the array (does not modify original).
    • Example:
      let someFruits = fruits.slice(0, 2);
      // ['mango', 'kiwi']
  • .concat(array2)

    • Combines two or more arrays and returns a new array.
    • Example:
      let moreFruits = ["pear", "grape"];
      let allFruits = fruits.concat(moreFruits);
      // ['mango', 'kiwi', 'cherry', 'pear', 'grape']
  • .join(separator)

    • Joins all elements into a string, separated by the specified separator.
    • Example:
      let str = fruits.join(", ");
      // 'mango, kiwi, cherry'
  • .indexOf(item)

    • Returns the first index of the item, or -1 if not found.
    • Example:
      let idx = fruits.indexOf("kiwi");
      // 1
  • .includes(item)

    • Checks if the array contains the item. Returns true or false.
    • Example:
      fruits.includes("cherry");
      // true
  • .forEach(callback)

    • Executes a function for each array element.
    • Example:
      fruits.forEach(function(fruit) {
        console.log(fruit);
      });
      // Output: mango kiwi cherry

🏗️ Objects

  • Object Creation

    • Objects are used to store key-value pairs. They can contain various data types.
    • Example:
      let person = {
        name: "John",
        age: 30,
        isStudent: false,
        hobbies: ["reading", "gaming"],
        address: {
          street: "123 Main St",
          city: "New York"
        }
      };
  • Accessing Properties

    • You can access object properties using dot notation or bracket notation.
    • Example:
      console.log(person.name);        // 'John'
      console.log(person["age"]);      // 30
      console.log(person.address.city); // 'New York'
  • Adding/Modifying Properties

    • You can add new properties or modify existing ones.
    • Example:
      person.email = "john@example.com";
      person.age = 31;
      person["phone"] = "555-1234";
  • Object Methods

    • Objects can contain functions as properties (methods).
    • Example:
      let calculator = {
        add: function(a, b) {
          return a + b;
        },
        subtract(a, b) {
          return a - b;
        }
      };
      
      console.log(calculator.add(5, 3));      // 8
      console.log(calculator.subtract(10, 4)); // 6
  • Nested Objects

    • Objects can contain other objects as properties.
    • Example:
      let company = {
        name: "Tech Corp",
        employees: {
          manager: {
            name: "Alice",
            salary: 75000
          },
          developer: {
            name: "Bob",
            salary: 65000
          }
        }
      };
      
      console.log(company.employees.manager.name); // 'Alice'
  • Object Destructuring

    • Extract values from objects into variables.
    • Example:
      let { name, age } = person;
      console.log(name); // 'John'
      console.log(age);  // 31
      
      // With default values
      let { name: fullName, country = "USA" } = person;
      console.log(fullName); // 'John'
      console.log(country);  // 'USA'
  • Object Methods (Built-in)

    • Common methods for working with objects.
    • Example:
      let keys = Object.keys(person);
      console.log(keys); // ['name', 'age', 'isStudent', 'hobbies', 'address']
      
      let values = Object.values(person);
      console.log(values); // ['John', 31, false, ['reading', 'gaming'], {...}]
      
      let entries = Object.entries(person);
      console.log(entries); // [['name', 'John'], ['age', 31], ...]
  • Spread Operator with Objects

    • Create new objects by spreading existing ones.
    • Example:
      let personCopy = { ...person };
      let personWithJob = { ...person, job: "Developer" };
      let updatedPerson = { ...person, age: 32 };

🧩 Object Inheritance

  • Object Inheritance

    • JavaScript uses prototype-based inheritance. Objects can inherit properties and methods from other objects.
    • Example:
      // Parent object
      let animal = {
        name: "Animal",
        speak() {
          return `${this.name} makes a sound`;
        }
      };
      
      // Child object inheriting from animal
      let dog = Object.create(animal);
      dog.name = "Dog";
      dog.bark() {
        return "Woof!";
      }
      
      console.log(dog.speak()); // 'Dog makes a sound'
      console.log(dog.bark());  // 'Woof!'
  • Constructor Functions

    • Functions used to create objects with shared properties and methods.
    • Example:
      function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      
      Person.prototype.greet = function() {
        return `Hello, I'm ${this.name}`;
      };
      
      let person1 = new Person("Alice", 25);
      let person2 = new Person("Bob", 30);
      
      console.log(person1.greet()); // 'Hello, I'm Alice'
      console.log(person2.greet()); // 'Hello, I'm Bob'
  • Class Inheritance

    • ES6 classes provide a cleaner syntax for inheritance.
    • Example:
      class Vehicle {
        constructor(brand, model) {
          this.brand = brand;
          this.model = model;
        }
        
        getInfo() {
          return `${this.brand} ${this.model}`;
        }
      }
      
      class Car extends Vehicle {
        constructor(brand, model, year) {
          super(brand, model); // Call parent constructor
          this.year = year;
        }
        
        getFullInfo() {
          return `${this.getInfo()} (${this.year})`;
        }
      }
      
      let myCar = new Car("Toyota", "Camry", 2020);
      console.log(myCar.getFullInfo()); // 'Toyota Camry (2020)'
  • Method Overriding

    • Child classes can override parent methods.
    • Example:
      class Animal {
        speak() {
          return "Some sound";
        }
      }
      
      class Dog extends Animal {
        speak() {
          return "Woof!";
        }
      }
      
      class Cat extends Animal {
        speak() {
          return "Meow!";
        }
      }
      
      let dog = new Dog();
      let cat = new Cat();
      
      console.log(dog.speak()); // 'Woof!'
      console.log(cat.speak()); // 'Meow!'
  • Prototype Chain

    • JavaScript looks up properties through the prototype chain.
    • Example:
      let parent = {
        value: 42,
        getValue() {
          return this.value;
        }
      };
      
      let child = Object.create(parent);
      child.value = 100;
      
      let grandchild = Object.create(child);
      
      console.log(grandchild.getValue()); // 100 (from child)
      console.log(grandchild.hasOwnProperty('value')); // false
      console.log(child.hasOwnProperty('value')); // true
  • Multiple Inheritance (Mixins)

    • Combine multiple objects to create complex inheritance.
    • Example:
      let canFly = {
        fly() {
          return "Flying high!";
        }
      };
      
      let canSwim = {
        swim() {
          return "Swimming deep!";
        }
      };
      
      let duck = Object.assign({}, canFly, canSwim);
      duck.name = "Donald";
      
      console.log(duck.fly());  // 'Flying high!'
      console.log(duck.swim()); // 'Swimming deep!'

👨‍👩‍👧‍👦 Parent-Child Class Relationships

  • Basic Inheritance

    • Child classes inherit properties and methods from parent classes. The 'extends' keyword creates this relationship.
    • Example:
      class Animal {
        constructor(name, species) {
          this.name = name;
          this.species = species;
          this.isAlive = true;
        }
        
        eat() {
          return `${this.name} is eating`;
        }
        
        sleep() {
          return `${this.name} is sleeping`;
        }
        
        getInfo() {
          return `Name: ${this.name}, Species: ${this.species}`;
        }
      }
      
      class Dog extends Animal {
        constructor(name, breed) {
          super(name, "Canis"); // Call parent constructor
          this.breed = breed;
          this.legs = 4;
        }
        
        bark() {
          return `${this.name} says: Woof!`;
        }
        
        fetch() {
          return `${this.name} fetches the ball`;
        }
      }
      
      let myDog = new Dog("Buddy", "Golden Retriever");
      console.log(myDog.getInfo()); // 'Name: Buddy, Species: Canis'
      console.log(myDog.eat());     // 'Buddy is eating'
      console.log(myDog.bark());    // 'Buddy says: Woof!'
  • Method Overriding

    • Child classes can override parent methods to provide different behavior while keeping the same method name.
    • Example:
      class Vehicle {
        constructor(brand, model) {
          this.brand = brand;
          this.model = model;
          this.isRunning = false;
        }
        
        start() {
          this.isRunning = true;
          return `${this.brand} ${this.model} is starting`;
        }
        
        stop() {
          this.isRunning = false;
          return `${this.brand} ${this.model} is stopping`;
        }
        
        getInfo() {
          return `${this.brand} ${this.model} (Running: ${this.isRunning})`;
        }
      }
      
      class Car extends Vehicle {
        constructor(brand, model, year) {
          super(brand, model);
          this.year = year;
          this.doors = 4;
        }
        
        // Override parent method
        start() {
          super.start(); // Call parent method first
          return `${this.brand} ${this.model} engine is running smoothly`;
        }
        
        // Add new method specific to Car
        honk() {
          return `${this.brand} ${this.model} honks: Beep! Beep!`;
        }
        
        // Override getInfo with additional car-specific info
        getInfo() {
          return `${super.getInfo()}, Year: ${this.year}, Doors: ${this.doors}`;
        }
      }
      
      class Motorcycle extends Vehicle {
        constructor(brand, model, engineSize) {
          super(brand, model);
          this.engineSize = engineSize;
          this.wheels = 2;
        }
        
        // Override with motorcycle-specific behavior
        start() {
          super.start();
          return `${this.brand} ${this.model} engine is revving loudly`;
        }
        
        wheelie() {
          return `${this.brand} ${this.model} does a wheelie!`;
        }
      }
      
      let car = new Car("Toyota", "Camry", 2020);
      let bike = new Motorcycle("Honda", "CBR", "600cc");
      
      console.log(car.start());   // 'Toyota Camry engine is running smoothly'
      console.log(bike.start());  // 'Honda CBR engine is revving loudly'
      console.log(car.honk());    // 'Toyota Camry honks: Beep! Beep!'
      console.log(bike.wheelie()); // 'Honda CBR does a wheelie!'
  • Constructor Chaining

    • Child constructors must call parent constructors using super() before accessing 'this'.
    • Example:
      class Employee {
        constructor(name, id, salary) {
          this.name = name;
          this.id = id;
          this.salary = salary;
          this.department = "General";
        }
        
        work() {
          return `${this.name} is working`;
        }
        
        getSalary() {
          return `$${this.salary}`;
        }
      }
      
      class Manager extends Employee {
        constructor(name, id, salary, teamSize) {
          super(name, id, salary); // Must call super() first
          this.teamSize = teamSize;
          this.department = "Management";
        }
        
        manage() {
          return `${this.name} is managing ${this.teamSize} employees`;
        }
        
        // Override with bonus calculation
        getSalary() {
          const bonus = this.salary * 0.2;
          return `$${this.salary + bonus} (including 20% bonus)`;
        }
      }
      
      class Developer extends Employee {
        constructor(name, id, salary, programmingLanguage) {
          super(name, id, salary);
          this.programmingLanguage = programmingLanguage;
          this.department = "Engineering";
        }
        
        code() {
          return `${this.name} is coding in ${this.programmingLanguage}`;
        }
      }
      
      let manager = new Manager("Alice", "M001", 80000, 5);
      let developer = new Developer("Bob", "D001", 70000, "JavaScript");
      
      console.log(manager.manage());     // 'Alice is managing 5 employees'
      console.log(developer.code());     // 'Bob is coding in JavaScript'
      console.log(manager.getSalary());  // '$96000 (including 20% bonus)'
      console.log(developer.getSalary()); // '$70000'
  • Multi-level Inheritance

    • Classes can inherit from classes that inherit from other classes, creating an inheritance chain.
    • Example:
      class LivingThing {
        constructor(name) {
          this.name = name;
          this.isAlive = true;
        }
        
        breathe() {
          return `${this.name} is breathing`;
        }
      }
      
      class Animal extends LivingThing {
        constructor(name, species) {
          super(name);
          this.species = species;
          this.legs = 4;
        }
        
        move() {
          return `${this.name} is moving`;
        }
      }
      
      class Dog extends Animal {
        constructor(name, breed) {
          super(name, "Canis");
          this.breed = breed;
        }
        
        bark() {
          return `${this.name} barks: Woof!`;
        }
        
        // Override move with dog-specific behavior
        move() {
          return `${this.name} runs on ${this.legs} legs`;
        }
      }
      
      class Puppy extends Dog {
        constructor(name, breed, age) {
          super(name, breed);
          this.age = age;
          this.isTrained = false;
        }
        
        play() {
          return `${this.name} is playing with toys`;
        }
        
        // Override bark with puppy-specific behavior
        bark() {
          return `${this.name} barks softly: Yip! Yip!`;
        }
      }
      
      let puppy = new Puppy("Max", "Golden Retriever", 6);
      console.log(puppy.breathe()); // 'Max is breathing' (from LivingThing)
      console.log(puppy.move());    // 'Max runs on 4 legs' (from Animal, overridden by Dog)
      console.log(puppy.bark());    // 'Max barks softly: Yip! Yip!' (overridden by Puppy)
      console.log(puppy.play());    // 'Max is playing with toys' (Puppy-specific)
  • The 'super' Keyword

    • 'super' is used to call parent class methods and constructor. It's essential for proper inheritance.
    • Example:
      class BankAccount {
        constructor(owner, balance = 0) {
          this.owner = owner;
          this.balance = balance;
          this.accountNumber = this.generateAccountNumber();
        }
        
        generateAccountNumber() {
          return Math.floor(Math.random() * 1000000);
        }
        
        deposit(amount) {
          this.balance += amount;
          return `Deposited $${amount}. Balance: $${this.balance}`;
        }
        
        withdraw(amount) {
          if (amount <= this.balance) {
            this.balance -= amount;
            return `Withdrew $${amount}. Balance: $${this.balance}`;
          }
          return "Insufficient funds";
        }
        
        getInfo() {
          return `Account: ${this.accountNumber}, Owner: ${this.owner}, Balance: $${this.balance}`;
        }
      }
      
      class SavingsAccount extends BankAccount {
        constructor(owner, balance, interestRate = 0.02) {
          super(owner, balance); // Call parent constructor
          this.interestRate = interestRate;
          this.accountType = "Savings";
        }
        
        addInterest() {
          const interest = this.balance * this.interestRate;
          this.balance += interest;
          return `Added $${interest.toFixed(2)} interest. New balance: $${this.balance}`;
        }
        
        // Override withdraw with minimum balance requirement
        withdraw(amount) {
          const minBalance = 100;
          if (this.balance - amount >= minBalance) {
            return super.withdraw(amount); // Call parent method
          }
          return `Cannot withdraw. Minimum balance of $${minBalance} required`;
        }
        
        // Override getInfo with additional savings info
        getInfo() {
          return `${super.getInfo()}, Type: ${this.accountType}, Interest Rate: ${this.interestRate * 100}%`;
        }
      }
      
      let savings = new SavingsAccount("John", 1000, 0.03);
      console.log(savings.getInfo());     // Account info with savings details
      console.log(savings.addInterest()); // 'Added $30.00 interest. New balance: $1030'
      console.log(savings.withdraw(50));  // 'Withdrew $50. Balance: $980'
      console.log(savings.withdraw(900)); // 'Cannot withdraw. Minimum balance of $100 required'

🏛️ Classes

  • Class Declaration

    • Classes are templates for creating objects. They provide a cleaner syntax for constructor functions.
    • Example:
      class Person {
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
        
        greet() {
          return `Hello, I'm ${this.name}`;
        }
      }
      
      let person = new Person("Alice", 25);
      console.log(person.greet()); // 'Hello, I'm Alice'
  • Constructor Method

    • The constructor method is called when a new instance is created. It initializes object properties.
    • Example:
      class Car {
        constructor(brand, model, year) {
          this.brand = brand;
          this.model = model;
          this.year = year;
          this.isRunning = false;
        }
      }
      
      let myCar = new Car("Toyota", "Camry", 2020);
      console.log(myCar.brand); // 'Toyota'
  • Instance Methods

    • Methods that belong to instances of the class.
    • Example:
      class BankAccount {
        constructor(owner, balance = 0) {
          this.owner = owner;
          this.balance = balance;
        }
        
        deposit(amount) {
          this.balance += amount;
          return `Deposited $${amount}. New balance: $${this.balance}`;
        }
        
        withdraw(amount) {
          if (amount <= this.balance) {
            this.balance -= amount;
            return `Withdrew $${amount}. New balance: $${this.balance}`;
          } else {
            return "Insufficient funds";
          }
        }
      }
      
      let account = new BankAccount("John", 1000);
      console.log(account.deposit(500)); // 'Deposited $500. New balance: $1500'
      console.log(account.withdraw(200)); // 'Withdrew $200. New balance: $1300'
  • Static Methods

    • Methods that belong to the class itself, not instances. Called on the class, not objects.
    • Example:
      class MathUtils {
        static add(a, b) {
          return a + b;
        }
        
        static multiply(a, b) {
          return a * b;
        }
        
        static isEven(num) {
          return num % 2 === 0;
        }
      }
      
      console.log(MathUtils.add(5, 3));      // 8
      console.log(MathUtils.multiply(4, 6)); // 24
      console.log(MathUtils.isEven(10));     // true
  • Getters and Setters

    • Special methods that allow you to get and set property values with additional logic.
    • Example:
      class Circle {
        constructor(radius) {
          this._radius = radius;
        }
        
        get radius() {
          return this._radius;
        }
        
        set radius(value) {
          if (value > 0) {
            this._radius = value;
          } else {
            throw new Error("Radius must be positive");
          }
        }
        
        get area() {
          return Math.PI * this._radius ** 2;
        }
      }
      
      let circle = new Circle(5);
      console.log(circle.radius); // 5
      console.log(circle.area);   // 78.54...
      circle.radius = 10;
      console.log(circle.area);   // 314.16...
  • Class Inheritance

    • Classes can inherit from other classes using the extends keyword.
    • Example:
      class Animal {
        constructor(name) {
          this.name = name;
        }
        
        speak() {
          return `${this.name} makes a sound`;
        }
      }
      
      class Dog extends Animal {
        constructor(name, breed) {
          super(name); // Call parent constructor
          this.breed = breed;
        }
        
        speak() {
          return `${this.name} barks: Woof!`;
        }
        
        fetch() {
          return `${this.name} fetches the ball`;
        }
      }
      
      let dog = new Dog("Buddy", "Golden Retriever");
      console.log(dog.speak()); // 'Buddy barks: Woof!'
      console.log(dog.fetch()); // 'Buddy fetches the ball'
  • Private Fields (ES2022)

    • Private fields are only accessible within the class.
    • Example:
      class BankAccount {
        #balance = 0; // Private field
        
        constructor(owner) {
          this.owner = owner;
        }
        
        deposit(amount) {
          this.#balance += amount;
          return `Balance: $${this.#balance}`;
        }
        
        getBalance() {
          return this.#balance;
        }
      }
      
      let account = new BankAccount("John");
      console.log(account.deposit(100)); // 'Balance: $100'
      console.log(account.getBalance());  // 100
      // console.log(account.#balance); // Error: Private field

📄 JSON (JavaScript Object Notation)

  • What is JSON?

    • JSON is a lightweight data format used for storing and transporting data. It's based on JavaScript object syntax but is language-independent.
    • Example:
      // JSON string
      let jsonString = '{"name": "John", "age": 30, "city": "New York"}';
      
      // JavaScript object
      let jsObject = {
        name: "John",
        age: 30,
        city: "New York"
      };
  • JSON.stringify()

    • Converts a JavaScript object or value to a JSON string.
    • Example:
      let person = {
        name: "Alice",
        age: 25,
        hobbies: ["reading", "gaming"],
        address: {
          street: "123 Main St",
          city: "Boston"
        },
        isStudent: true
      };
      
      let jsonString = JSON.stringify(person);
      console.log(jsonString);
      // Output: {"name":"Alice","age":25,"hobbies":["reading","gaming"],"address":{"street":"123 Main St","city":"Boston"},"isStudent":true}
      
      // With formatting (pretty print)
      let prettyJson = JSON.stringify(person, null, 2);
      console.log(prettyJson);
      // Output:
      // {
      //   "name": "Alice",
      //   "age": 25,
      //   "hobbies": ["reading", "gaming"],
      //   "address": {
      //     "street": "123 Main St",
      //     "city": "Boston"
      //   },
      //   "isStudent": true
      // }
  • JSON.parse()

    • Converts a JSON string back to a JavaScript object.
    • Example:
      let jsonString = '{"name": "Bob", "age": 30, "skills": ["JavaScript", "Python"]}';
      let person = JSON.parse(jsonString);
      
      console.log(person.name);    // 'Bob'
      console.log(person.age);     // 30
      console.log(person.skills);  // ['JavaScript', 'Python']
      console.log(person.skills[0]); // 'JavaScript'
  • JSON.stringify() with Replacer Function

    • You can customize how values are converted using a replacer function.
    • Example:
      let data = {
        name: "John",
        password: "secret123",
        email: "john@example.com",
        age: 25
      };
      
      // Remove sensitive data
      let safeJson = JSON.stringify(data, (key, value) => {
        if (key === 'password') {
          return undefined; // This property will be omitted
        }
        return value;
      });
      
      console.log(safeJson);
      // Output: {"name":"John","email":"john@example.com","age":25}
  • JSON.stringify() with Array Replacer

    • You can specify which properties to include using an array.
    • Example:
      let user = {
        id: 1,
        name: "Alice",
        email: "alice@example.com",
        password: "secret",
        role: "admin",
        lastLogin: "2023-01-15"
      };
      
      // Only include specific properties
      let publicInfo = JSON.stringify(user, ['id', 'name', 'email', 'role']);
      console.log(publicInfo);
      // Output: {"id":1,"name":"Alice","email":"alice@example.com","role":"admin"}
  • Handling Special Values

    • JSON.stringify() handles special JavaScript values.
    • Example:
      let data = {
        string: "Hello",
        number: 42,
        boolean: true,
        nullValue: null,
        undefinedValue: undefined,
        function: function() { return "test"; },
        date: new Date(),
        array: [1, 2, 3],
        object: { key: "value" }
      };
      
      let json = JSON.stringify(data);
      console.log(json);
      // Output: {"string":"Hello","number":42,"boolean":true,"nullValue":null,"date":"2023-01-15T10:30:00.000Z","array":[1,2,3],"object":{"key":"value"}}
      // Note: undefined and functions are omitted
  • Error Handling with JSON.parse()

    • Always wrap JSON.parse() in try-catch to handle invalid JSON.
    • Example:
      function safeParse(jsonString) {
        try {
          return JSON.parse(jsonString);
        } catch (error) {
          console.error("Invalid JSON:", error.message);
          return null;
        }
      }
      
      // Valid JSON
      let valid = safeParse('{"name": "John", "age": 30}');
      console.log(valid); // {name: "John", age: 30}
      
      // Invalid JSON
      let invalid = safeParse('{"name": "John", "age": 30,}'); // Extra comma
      console.log(invalid); // null
  • Working with Arrays and Objects

    • JSON can represent complex nested structures.
    • Example:
      let company = {
        name: "Tech Corp",
        employees: [
          {
            id: 1,
            name: "Alice",
            position: "Developer",
            skills: ["JavaScript", "React"]
          },
          {
            id: 2,
            name: "Bob",
            position: "Designer",
            skills: ["Photoshop", "Figma"]
          }
        ],
        departments: {
          engineering: {
            head: "Alice",
            members: 5
          },
          design: {
            head: "Bob",
            members: 3
          }
        }
      };
      
      let jsonString = JSON.stringify(company, null, 2);
      console.log(jsonString);
      // Output: Formatted JSON with all nested structures
      
      let parsedCompany = JSON.parse(jsonString);
      console.log(parsedCompany.employees[0].name); // 'Alice'
      console.log(parsedCompany.departments.engineering.head); // 'Alice'

⚠️ Error Handling

  • Try-Catch Blocks

    • Used to handle errors gracefully without crashing the program.
    • Example:
      try {
        let result = 10 / 0;
        console.log(result);
      } catch (error) {
        console.log("An error occurred:", error.message);
      }
      // Output: An error occurred: Infinity
  • Try-Catch-Finally

    • Finally block always executes, regardless of whether an error occurred.
    • Example:
      function divideNumbers(a, b) {
        try {
          if (b === 0) {
            throw new Error("Division by zero is not allowed");
          }
          return a / b;
        } catch (error) {
          console.log("Error:", error.message);
          return null;
        } finally {
          console.log("Division operation completed");
        }
      }
      
      console.log(divideNumbers(10, 2));  // 5, "Division operation completed"
      console.log(divideNumbers(10, 0));  // null, "Division operation completed"
  • Built-in Error Types

    • JavaScript has several built-in error types for different scenarios.
    • Example:
      // ReferenceError - accessing undefined variable
      try {
        console.log(undefinedVariable);
      } catch (error) {
        console.log("ReferenceError:", error.message);
      }
      
      // TypeError - wrong data type
      try {
        let str = "Hello";
        str.toUpperCase = null;
        str.toUpperCase();
      } catch (error) {
        console.log("TypeError:", error.message);
      }
      
      // SyntaxError - invalid syntax
      try {
        eval("let x = ;"); // Invalid syntax
      } catch (error) {
        console.log("SyntaxError:", error.message);
      }
      
      // RangeError - invalid array length
      try {
        let arr = new Array(-1);
      } catch (error) {
        console.log("RangeError:", error.message);
      }
  • Custom Error Classes

    • You can create custom error classes by extending the Error class.
    • Example:
      class ValidationError extends Error {
        constructor(message, field) {
          super(message);
          this.name = "ValidationError";
          this.field = field;
        }
      }
      
      class NetworkError extends Error {
        constructor(message, statusCode) {
          super(message);
          this.name = "NetworkError";
          this.statusCode = statusCode;
        }
      }
      
      function validateUser(user) {
        if (!user.name) {
          throw new ValidationError("Name is required", "name");
        }
        if (!user.email) {
          throw new ValidationError("Email is required", "email");
        }
        if (user.age < 18) {
          throw new ValidationError("User must be 18 or older", "age");
        }
      }
      
      try {
        validateUser({ name: "John", age: 16 });
      } catch (error) {
        if (error instanceof ValidationError) {
          console.log(`Validation failed for ${error.field}: ${error.message}`);
        }
      }
  • Error Handling with Async Functions

    • Error handling in asynchronous operations using try-catch with async/await.
    • Example:
      async function fetchUserData(userId) {
        try {
          const response = await fetch(`https://api.example.com/users/${userId}`);
          
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          
          const userData = await response.json();
          return userData;
        } catch (error) {
          console.log("Failed to fetch user data:", error.message);
          return null;
        }
      }
      
      // Usage
      fetchUserData(123).then(user => {
        if (user) {
          console.log("User data:", user);
        }
      });
  • Error Handling with Promises

    • Using .catch() to handle errors in Promise chains.
    • Example:
      function processData(data) {
        return new Promise((resolve, reject) => {
          if (!data) {
            reject(new Error("No data provided"));
            return;
          }
          
          if (typeof data !== 'string') {
            reject(new Error("Data must be a string"));
            return;
          }
          
          // Simulate processing
          setTimeout(() => {
            resolve(data.toUpperCase());
          }, 1000);
        });
      }
      
      processData("hello world")
        .then(result => {
          console.log("Processed:", result);
        })
        .catch(error => {
          console.log("Error:", error.message);
        });
      
      processData(null)
        .then(result => {
          console.log("Processed:", result);
        })
        .catch(error => {
          console.log("Error:", error.message);
        });
  • Error Handling Best Practices

    • Guidelines for effective error handling.
    • Example:
      class DatabaseError extends Error {
        constructor(message, code) {
          super(message);
          this.name = "DatabaseError";
          this.code = code;
        }
      }
      
      function connectToDatabase() {
        return new Promise((resolve, reject) => {
          // Simulate database connection
          const random = Math.random();
          
          if (random < 0.3) {
            reject(new DatabaseError("Connection timeout", "TIMEOUT"));
          } else if (random < 0.6) {
            reject(new DatabaseError("Authentication failed", "AUTH_FAILED"));
          } else {
            resolve({ status: "connected", id: "db_123" });
          }
        });
      }
      
      async function initializeApp() {
        try {
          console.log("Connecting to database...");
          const db = await connectToDatabase();
          console.log("Database connected:", db);
          
          // Continue with app initialization
          console.log("App initialized successfully");
        } catch (error) {
          if (error instanceof DatabaseError) {
            switch (error.code) {
              case "TIMEOUT":
                console.log("Database connection timed out. Retrying...");
                break;
              case "AUTH_FAILED":
                console.log("Database authentication failed. Check credentials.");
                break;
              default:
                console.log("Database error:", error.message);
            }
          } else {
            console.log("Unexpected error:", error.message);
          }
        }
      }
      
      initializeApp();

🌐 The DOM (Document Object Model) in Web Development

  • What is the DOM?
    • The DOM is a programming interface for web documents. It represents the structure of an HTML or XML document as a tree of objects (nodes). Each element, attribute, and piece of text becomes a node in this tree.
    • JavaScript can interact with the DOM to dynamically change the content, structure, and style of a web page.
    • Example:
      <body>
        <h1 id="main-title">Hello World</h1>
        <p class="intro">Welcome to my website.</p>
        <button>Click Me</button>
      </body>

🏷️ Accessing Elements in the DOM

  • By Tag Name

    • Use getElementsByTagName() to get all elements with a specific tag (returns an HTMLCollection).
    • Example:
      let paragraphs = document.getElementsByTagName('p');
      console.log(paragraphs[0].textContent); // 'Welcome to my website.'
  • By ID

    • Use getElementById() to get a single element with a specific ID (IDs should be unique).
    • Example:
      let title = document.getElementById('main-title');
      title.textContent = 'Welcome!'; // Changes the heading text
  • By Class Name

    • Use getElementsByClassName() to get all elements with a specific class (returns an HTMLCollection).
    • Example:
      let intros = document.getElementsByClassName('intro');
      intros[0].style.color = 'blue'; // Changes text color to blue
  • By CSS Selector

    • Use querySelector() to get the first element matching a CSS selector, or querySelectorAll() for all matches (returns a NodeList).
    • Example:
      let firstButton = document.querySelector('button');
      firstButton.style.backgroundColor = 'yellow';
      
      let allParagraphs = document.querySelectorAll('p.intro');
      allParagraphs.forEach(p => p.style.fontWeight = 'bold');

🛠️ Modifying HTML and CSS with JavaScript

  • Changing Content

    • Use textContent or innerHTML to change the text or HTML inside an element.
    • Example:
      let title = document.getElementById('main-title');
      title.textContent = 'New Title';
      // or
      title.innerHTML = '<span style="color:red">New Title</span>';
  • Changing Styles

    • Access the style property to change CSS directly from JavaScript.
    • Example:
      let button = document.querySelector('button');
      button.style.backgroundColor = 'green';
      button.style.fontSize = '20px';
  • Adding/Removing Classes

    • Use classList to add, remove, or toggle CSS classes.
    • Example:
      let para = document.querySelector('p');
      para.classList.add('highlight');
      para.classList.remove('intro');
      para.classList.toggle('active');

🖱️ Handling Events

  • Adding Event Listeners
    • Use addEventListener() to run code when users interact with elements (click, mouseover, etc.).
    • Example:
      let button = document.querySelector('button');
      button.addEventListener('click', function() {
        alert('Button was clicked!');
      });

🧑‍💻 Example: Putting It All Together

<!DOCTYPE html>
<html>
<head>
  <style>
    .highlight { color: orange; font-weight: bold; }
  </style>
</head>
<body>
  <h1 id="main-title">Hello World</h1>
  <p class="intro">Welcome to my website.</p>
  <button>Click Me</button>

  <script>
    // Access by ID
    let title = document.getElementById('main-title');
    title.textContent = 'Welcome to the DOM!';

    // Access by class
    let intro = document.querySelector('.intro');
    intro.classList.add('highlight');

    // Access by tag
    let button = document.querySelector('button');
    button.addEventListener('click', function() {
      alert('Button clicked!');
      button.style.backgroundColor = 'red';
    });
  </script>
</body>
</html>
  • This example shows how to access and modify elements by ID, class, and tag, and how to handle events and change styles dynamically.

📚 More Resources


🖱️ Event Listeners with Functions

  • What is an Event Listener?
    • An event listener is a way to run JavaScript code in response to user actions (like clicks, mouse movements, key presses, etc.) on elements in the DOM.
    • You attach an event listener to an element using addEventListener(). The listener takes two main arguments: the event type (like 'click') and a function to run when the event happens.

📌 Syntax

element.addEventListener(eventType, handlerFunction);
  • Where eventType is a string (e.g., 'click', 'mouseover', 'keydown'), and handlerFunction is the function to execute.

🟢 Example 1: Using a Named Function

<button id="greetBtn">Greet</button>
<p id="message"></p>

<script>
  function showGreeting() {
    document.getElementById('message').textContent = 'Hello, user!';
  }

  let button = document.getElementById('greetBtn');
  button.addEventListener('click', showGreeting);
</script>
  • Here, the showGreeting function is defined separately and passed as the event handler. When the button is clicked, the function runs and updates the paragraph text.

🟢 Example 2: Using an Anonymous Function

<button id="colorBtn">Change Color</button>
<div id="box" style="width:100px; height:100px; background:lightgray;"></div>

<script>
  document.getElementById('colorBtn').addEventListener('click', function() {
    document.getElementById('box').style.backgroundColor = 'skyblue';
  });
</script>
  • Here, an anonymous function is used directly in addEventListener. When the button is clicked, the box's background color changes.

🟢 Example 3: Using an Arrow Function

<input id="nameInput" placeholder="Type your name...">
<button id="sayHiBtn">Say Hi</button>
<p id="output"></p>

<script>
  document.getElementById('sayHiBtn').addEventListener('click', () => {
    const name = document.getElementById('nameInput').value;
    document.getElementById('output').textContent = `Hi, ${name || 'stranger'}!`;
  });
</script>
  • This example uses an arrow function as the event handler. When the button is clicked, it reads the input value and displays a greeting.

🟢 Example 4: Passing the Event Object

<button id="logBtn">Log Event</button>

<script>
  document.getElementById('logBtn').addEventListener('click', function(event) {
    console.log('Event type:', event.type);
    console.log('Button text:', event.target.textContent);
  });
</script>
  • The handler function receives an event object with information about the event (type, target, etc.). This is useful for advanced event handling.

🟢 Example 5: Removing an Event Listener

<button id="onceBtn">Click Me Once</button>
<p id="onceMsg"></p>

<script>
  function handleOnce() {
    document.getElementById('onceMsg').textContent = 'Button clicked! Listener removed.';
    document.getElementById('onceBtn').removeEventListener('click', handleOnce);
  }

  document.getElementById('onceBtn').addEventListener('click', handleOnce);
</script>
  • You can remove an event listener using removeEventListener. In this example, the button can only be clicked once before the handler is removed.

📚 More on Events

  • Common event types: click, mouseover, mouseout, keydown, keyup, submit, change, etc.

  • You can attach multiple listeners to the same element and event type.

  • Event listeners are the foundation of interactive web pages.

  • MDN: addEventListener()

  • MDN: Event reference


🌐 APIs and Web Storage API

🤖 What is an API?

  • API (Application Programming Interface)
    • An API is a set of rules and tools that allows different software applications to communicate with each other. In web development, APIs are often used to interact with web services, browsers, or the underlying system.
    • Examples: Fetching data from a server (Web APIs), accessing browser features (like storage, geolocation), or using third-party services (like Google Maps API).

💾 Web Storage API

  • What is the Web Storage API?
    • The Web Storage API provides mechanisms by which browsers can store key-value pairs locally within the user's browser. It is more secure and faster than using cookies.
    • There are two main types:
      • localStorage: Stores data with no expiration date. Data persists even after the browser is closed and reopened.
      • sessionStorage: Stores data for one session. Data is cleared when the page session ends (tab or window is closed).

📦 localStorage

  • Use localStorage to store data that should persist across browser sessions.
  • Data is stored as strings. You can store objects/arrays by converting them to JSON.

Basic Usage:

// Set an item
localStorage.setItem('username', 'Alice');

// Get an item
let user = localStorage.getItem('username'); // 'Alice'

// Remove an item
localStorage.removeItem('username');

// Clear all items
localStorage.clear();

Storing Objects:

let user = { name: 'Bob', age: 30 };
localStorage.setItem('user', JSON.stringify(user));

let storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 'Bob'

📦 sessionStorage

  • Use sessionStorage to store data for the duration of a page session (as long as the tab/window is open).
  • API is identical to localStorage, but data is cleared when the session ends.

Basic Usage:

// Set an item
sessionStorage.setItem('theme', 'dark');

// Get an item
let theme = sessionStorage.getItem('theme'); // 'dark'

// Remove an item
sessionStorage.removeItem('theme');

// Clear all items
sessionStorage.clear();

Storing Arrays:

let colors = ['red', 'green', 'blue'];
sessionStorage.setItem('colors', JSON.stringify(colors));

let storedColors = JSON.parse(sessionStorage.getItem('colors'));
console.log(storedColors[1]); // 'green'

🧑‍💻 Example: Using localStorage and sessionStorage in a Web Page

<!DOCTYPE html>
<html>
<body>
  <input id="nameInput" placeholder="Enter your name">
  <button id="saveBtn">Save to localStorage</button>
  <button id="loadBtn">Load from localStorage</button>
  <p id="output"></p>

  <script>
    document.getElementById('saveBtn').addEventListener('click', function() {
      const name = document.getElementById('nameInput').value;
      localStorage.setItem('savedName', name);
      alert('Name saved!');
    });

    document.getElementById('loadBtn').addEventListener('click', function() {
      const name = localStorage.getItem('savedName') || 'No name found';
      document.getElementById('output').textContent = name;
    });
  </script>
</body>
</html>
  • This example lets users save and load their name using localStorage. You can adapt it for sessionStorage by replacing localStorage with sessionStorage.

⚠️ Notes and Best Practices

  • Storage is limited (usually 5-10MB per origin).
  • Only strings can be stored directly. Use JSON.stringify and JSON.parse for objects/arrays.
  • Data is accessible to any script on the same origin (domain + protocol + port).
  • Do not store sensitive information (like passwords or tokens) in localStorage/sessionStorage.

📚 More Resources


📦 JavaScript Modules: import & export

📚 What are Modules?

  • Modules allow you to split your code into separate files. Each file is a module that can export variables, functions, or classes, and import them from other modules. This helps organize code, avoid naming conflicts, and enable code reuse.
  • Modules are supported natively in modern browsers and Node.js (with some differences).

🚚 Exporting from a Module

  • Named Exports

    • You can export multiple values from a module by name.
    • Example:
      // mathUtils.js
      export const PI = 3.14159;
      export function add(a, b) {
        return a + b;
      }
      export class Calculator {
        multiply(a, b) { return a * b; }
      }
  • Default Export

    • A module can have one default export. Useful for exporting a single main value (function, class, object, etc.).
    • Example:
      // greet.js
      export default function greet(name) {
        return `Hello, ${name}!`;
      }
  • Exporting at the End

    • You can export after declaration, or all at once at the end.
    • Example:
      // colors.js
      const red = '#ff0000';
      const green = '#00ff00';
      const blue = '#0000ff';
      export { red, green, blue };

📥 Importing from a Module

  • Importing Named Exports

    • Use curly braces to import specific exports by name.
    • Example:
      import { PI, add } from './mathUtils.js';
      console.log(add(2, 3)); // 5
  • Importing Default Export

    • No curly braces. You can name the import whatever you like.
    • Example:
      import greet from './greet.js';
      console.log(greet('Alice')); // 'Hello, Alice!'
  • Importing Everything as an Object

    • Use * as to import all named exports as properties of an object.
    • Example:
      import * as Colors from './colors.js';
      console.log(Colors.red); // '#ff0000'
  • Renaming Imports/Exports

    • You can rename exports or imports using as.
    • Example:
      // In module
      export { add as sum };
      // In another file
      import { sum } from './mathUtils.js';

📝 Example: Using Modules in Practice

mathUtils.js

export const PI = 3.14;
export function square(x) { return x * x; }
export default function cube(x) { return x * x * x; }

main.js

import cube, { PI, square } from './mathUtils.js';
console.log(PI);         // 3.14
console.log(square(4));  // 16
console.log(cube(3));    // 27

⚠️ Notes and Best Practices

  • Module files must use type="module" in the browser:
    <script type="module" src="main.js"></script>
  • Module imports are static (must be at the top level, not inside functions or blocks).
  • File paths must be correct and usually include the file extension (e.g., ./utils.js).
  • Modules are loaded once and cached. Imports are read-only views of the exported values.
  • Use modules to organize code, avoid polluting the global scope, and enable code reuse.

📚 More Resources


🔼 Higher Order Functions

🧠 What is a Higher Order Function?

  • Higher order functions are functions that do at least one of the following:
    • Take one or more functions as arguments (parameters)
    • Return a function as a result
  • They are a key feature of JavaScript and enable powerful patterns like callbacks, function composition, and functional programming.

📥 Passing Functions as Arguments

  • You can pass a function to another function to customize its behavior.
  • Example: Custom forEach
    function forEach(array, callback) {
      for (let i = 0; i < array.length; i++) {
        callback(array[i], i);
      }
    }
    
    forEach([1, 2, 3], function(num, idx) {
      console.log(`Index ${idx}: ${num}`);
    });
    // Output: Index 0: 1, Index 1: 2, Index 2: 3

📤 Returning Functions from Functions

  • A function can return another function, creating a closure.
  • Example: Multiplier Factory
    function makeMultiplier(factor) {
      return function(x) {
        return x * factor;
      };
    }
    
    const double = makeMultiplier(2);
    const triple = makeMultiplier(3);
    
    console.log(double(5)); // 10
    console.log(triple(5)); // 15

🔄 Common Higher Order Array Methods

  • JavaScript arrays have several built-in higher order functions:

.map()

  • Creates a new array by applying a function to every element.
  • Example:
    let nums = [1, 2, 3];
    let squares = nums.map(x => x * x);
    console.log(squares); // [1, 4, 9]

.filter()

  • Creates a new array with only elements that pass a test function.
  • Example:
    let nums = [1, 2, 3, 4, 5];
    let evens = nums.filter(x => x % 2 === 0);
    console.log(evens); // [2, 4]

.reduce()

  • Reduces an array to a single value by applying a function cumulatively.
  • Example:
    let nums = [1, 2, 3, 4];
    let sum = nums.reduce((acc, curr) => acc + curr, 0);
    console.log(sum); // 10

.forEach()

  • Executes a function for each array element (does not return a new array).
  • Example:
    let fruits = ['apple', 'banana', 'cherry'];
    fruits.forEach(fruit => console.log(fruit.toUpperCase()));
    // Output: APPLE BANANA CHERRY

.find()

  • Returns the first element that satisfies the provided testing function.
  • Example:
    let users = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ];
    let user = users.find(u => u.name === 'Bob');
    console.log(user); // { id: 2, name: 'Bob' }

🧩 Example: Combining Higher Order Functions

let numbers = [1, 2, 3, 4, 5, 6];

// Double the even numbers and sum them
let result = numbers
  .filter(n => n % 2 === 0)   // [2, 4, 6]
  .map(n => n * 2)            // [4, 8, 12]
  .reduce((sum, n) => sum + n, 0); // 24

console.log(result); // 24

⚠️ Notes and Best Practices

  • Higher order functions make code more concise, expressive, and reusable.
  • They are the foundation of functional programming in JavaScript.
  • You can create your own higher order functions for custom logic.

📚 More Resources

About

A structured and evolving collection of my JavaScript learning journey — from fundamentals to intermediate and project-ready concepts. These notes are aimed at simplifying complex ideas with clear examples and practical tips, helping both beginners and revisers to grasp JavaScript effectively.

Topics

Resources

Stars

Watchers

Forks