Master JavaScript: A Complete Guide

Table of contents

Great! Let's start with Chapter 1: Introduction to JavaScript.


Chapter 1: Introduction to JavaScript

What is JavaScript?

  • JavaScript is a high-level, dynamic programming language that is primarily used for creating interactive features on websites. It enables developers to manipulate web content, respond to user actions, and create dynamic elements on webpages.

  • JavaScript runs on the client-side (in the browser) and is known as the language of the web.

Role of JavaScript in Web Development

  • JavaScript is essential for adding interactivity to a website. It works alongside HTML (which structures the content) and CSS (which styles the content).

  • With JavaScript, you can:

    • Create dynamic content (e.g., showing/hiding elements, real-time updates)

    • Handle user inputs and events (e.g., form submissions, button clicks)

    • Make HTTP requests to servers (using AJAX, Fetch API)

    • Modify the Document Object Model (DOM), which represents the structure of a webpage.

How JavaScript Works in Browsers

  • Browsers have a built-in JavaScript engine (e.g., Chrome uses V8, Firefox uses SpiderMonkey) that interprets and executes JavaScript code.

  • JavaScript code can run directly in the browser without the need for any compilation (like other languages such as Java or C++).


Adding JavaScript to HTML

There are several ways to include JavaScript in an HTML document:

  1. Inline JavaScript
    This places JavaScript directly in the HTML tags.

     <button onclick="alert('Button Clicked!')">Click Me</button>
    
    • The onclick event calls the JavaScript function alert(), which displays a message when the button is clicked.
  2. Internal JavaScript You can place JavaScript within the <script> tag inside the HTML file, typically in the <head> or at the end of the <body> tag.

     <!DOCTYPE html>
     <html>
     <head>
       <script>
         function showMessage() {
           alert("Hello, World!");
         }
       </script>
     </head>
     <body>
       <button onclick="showMessage()">Show Message</button>
     </body>
     </html>
    
    • The JavaScript is written inside the <script> tag and can be referenced by the HTML elements.
  3. External JavaScript For better separation of concerns, JavaScript can be placed in an external file (with a .js extension) and linked to the HTML document using the <script> tag.

     <!-- HTML file (index.html) -->
     <html>
     <head>
       <script src="script.js"></script>  <!-- External JS file -->
     </head>
     <body>
       <button onclick="showMessage()">Show Message</button>
     </body>
     </html>
    
     // External JS file (script.js)
     function showMessage() {
       alert("Hello from External JavaScript!");
     }
    
    • This keeps your HTML and JavaScript code separate, which is cleaner and easier to maintain.

Basic Syntax in JavaScript

  • Case Sensitivity: JavaScript is case-sensitive, meaning variable and Variable are different.

  • Comments: You can add comments to explain the code:

    • Single-line comment: // This is a comment

    • Multi-line comment: /* This is a multi-line comment */

  • Statements: JavaScript code is written in statements, usually ending with a semicolon ;.

      var name = "John";
      console.log(name);
    
  • Whitespace: JavaScript ignores extra spaces and line breaks, so you can structure your code in a readable way without affecting its behavior.


That's the introduction to JavaScript! In the next chapter, we'll dive into the basics of JavaScript, such as variables, data types, operators, and functions.

Chapter 2: JavaScript Basics


1. Variables

Variables are used to store data that can be used and manipulated throughout your JavaScript code.

Variable Declaration

In modern JavaScript (ES6 and beyond), there are three ways to declare variables:

  1. var (older way, now rarely used):

    • var has function scope or global scope, and it is subject to hoisting.

    • It can be re-declared and updated.

    var name = "John";
    var age = 25;
  1. let (modern and recommended):

    • let has block scope (limited to the nearest {} block).

    • It can be updated but cannot be re-declared within the same scope.

    let city = "New York";
    city = "Los Angeles";  // Valid
  1. const (for constants):

    • const also has block scope.

    • It cannot be updated or re-declared once assigned.

    const pi = 3.14159;
    // pi = 3.14;  // Error: Assignment to constant variable

2. Data Types

JavaScript has different data types for storing various types of data. The two main categories are primitive data types and reference data types.

Primitive Data Types
  • Number: Represents numeric values (both integers and floats).

      let age = 30;      // Integer
      let salary = 5000.5; // Float
    
  • String: Represents text enclosed in quotes ("", '', or backticks `` ).

      let name = "Alice";
      let greeting = 'Hello';
      let message = `Hi, ${name}!`; // Template literals
    
  • Boolean: Represents true or false.

      let isOnline = true;
      let hasDiscount = false;
    
  • Undefined: A variable that has been declared but not assigned a value.

      let score;  // Undefined by default
    
  • Null: Represents an intentional absence of any value.

      let data = null;  // Explicitly set to null
    
  • Symbol: A unique and immutable value (often used for object property keys).

      let uniqueID = Symbol('id');
    
  • BigInt: Used for integers larger than Number.MAX_SAFE_INTEGER.

      let largeNumber = BigInt(9007199254740991);
    
Reference Data Types
  • Object: Represents a collection of key-value pairs (used for more complex data structures).

      let user = {
        name: "John",
        age: 30,
        isAdmin: false
      };
    
  • Array: Represents a list of values.

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

3. Operators

Arithmetic Operators

Used for performing arithmetic calculations:

  • +: Addition

  • -: Subtraction

  • *: Multiplication

  • /: Division

  • %: Modulus (remainder)

  • **: Exponentiation

let x = 10;
let y = 5;

console.log(x + y); // 15
console.log(x - y); // 5
console.log(x * y); // 50
console.log(x / y); // 2
console.log(x % y); // 0
console.log(x ** y); // 100000
Comparison Operators

Used to compare values:

  • ==: Equal to (checks for value equality, loose comparison)

  • ===: Strict equal to (checks for value and type equality)

  • !=: Not equal to

  • !==: Strict not equal to

  • >: Greater than

  • <: Less than

  • >=: Greater than or equal to

  • <=: Less than or equal to

console.log(5 == '5');  // true (loose comparison)
console.log(5 === '5'); // false (strict comparison)
console.log(10 > 5);    // true
Logical Operators

Used to combine or manipulate boolean values:

  • &&: Logical AND (both conditions must be true)

  • ||: Logical OR (one or both conditions must be true)

  • !: Logical NOT (negates a condition)

let a = true;
let b = false;

console.log(a && b); // false
console.log(a || b); // true
console.log(!a);     // false
Assignment Operators

Used to assign values to variables:

  • =: Assign

  • +=: Add and assign

  • -=: Subtract and assign

  • *=: Multiply and assign

  • /=: Divide and assign

let num = 10;
num += 5;  // num = num + 5; => 15

4. Conditionals

if-else Statements

Used to execute code based on conditions:

let age = 20;

if (age >= 18) {
  console.log("You are an adult.");
} else {
  console.log("You are a minor.");
}
else if Statements

For multiple conditions:

let score = 75;

if (score >= 90) {
  console.log("A grade");
} else if (score >= 75) {
  console.log("B grade");
} else {
  console.log("C grade");
}
switch Statements

For checking multiple values:

let day = 3;

switch(day) {
  case 1:
    console.log("Monday");
    break;
  case 2:
    console.log("Tuesday");
    break;
  case 3:
    console.log("Wednesday");
    break;
  default:
    console.log("Invalid day");
}

5. Loops

for Loop

Executes code a specified number of times:

for (let i = 0; i < 5; i++) {
  console.log(i);  // 0 1 2 3 4
}
while Loop

Executes code while a condition is true:

let i = 0;
while (i < 5) {
  console.log(i);  // 0 1 2 3 4
  i++;
}
do-while Loop

Executes the code block once before checking the condition:

let i = 0;
do {
  console.log(i);  // 0 1 2 3 4
  i++;
} while (i < 5);

6. Functions

Functions are reusable blocks of code that perform a specific task.

Function Declaration
function greet() {
  console.log("Hello!");
}
greet();  // Outputs: Hello!
Function with Parameters
function greet(name) {
  console.log(`Hello, ${name}!`);
}
greet("Alice");  // Outputs: Hello, Alice!
Returning Values
function add(a, b) {
  return a + b;
}
let sum = add(5, 10);  // sum = 15
Anonymous Functions

Functions can also be defined without names and assigned to variables:

let greet = function(name) {
  console.log(`Hello, ${name}!`);
};
greet("John");
Arrow Functions (ES6)

A concise way to write functions:

const multiply = (a, b) => a * b;
console.log(multiply(5, 3));  // Outputs: 15

That concludes Chapter 2: JavaScript Basics. We covered variables, data types, operators, conditionals, loops, and functions.

Chapter 3: Control Structures in JavaScript


In this chapter, we will explore advanced control structures that allow us to control the flow of code execution beyond basic if-else statements and loops.


1. Advanced Conditionals

Ternary Operator

The ternary operator is a shorthand for if-else statements. It allows you to write conditionals in a single line.

let age = 18;
let canVote = (age >= 18) ? "Yes" : "No";
console.log(canVote);  // Outputs: Yes

In the example above, the condition age >= 18 is checked. If true, "Yes" is returned; otherwise, "No" is returned.

Nullish Coalescing Operator (??)

This operator returns the right-hand side value when the left-hand side is null or undefined. It's useful for providing default values.

let username = null;
let defaultName = "Guest";

let displayName = username ?? defaultName;
console.log(displayName);  // Outputs: Guest

If username is null, displayName will be "Guest".

Optional Chaining (?.)

This operator allows safe access to deeply nested properties without explicitly checking for null or undefined.

let user = {
  name: "Alice",
  address: { city: "New York" }
};

// Safe access to deeply nested properties
let city = user?.address?.city;
console.log(city);  // Outputs: New York

let country = user?.address?.country;
console.log(country);  // Outputs: undefined (safe access, no error)

2. Switch Statement

The switch statement evaluates an expression and matches it against multiple cases. It can be useful for handling multiple conditions based on a single variable or expression.

let day = 3;

switch (day) {
  case 1:
    console.log("Monday");
    break;
  case 2:
    console.log("Tuesday");
    break;
  case 3:
    console.log("Wednesday");
    break;
  default:
    console.log("Invalid day");
}

3. Loops with Control

break Statement

The break statement is used to exit a loop early when a certain condition is met.

for (let i = 0; i < 5; i++) {
  if (i === 3) {
    break;  // Exit the loop when i is 3
  }
  console.log(i);  // Outputs: 0 1 2
}
continue Statement

The continue statement skips the current iteration and moves to the next iteration of the loop.

for (let i = 0; i < 5; i++) {
  if (i === 2) {
    continue;  // Skip the iteration when i is 2
  }
  console.log(i);  // Outputs: 0 1 3 4
}
for...of Loop

This loop is used to iterate over iterable objects like arrays, strings, etc.

let fruits = ["Apple", "Banana", "Mango"];

for (let fruit of fruits) {
  console.log(fruit);
}
// Outputs:
// Apple
// Banana
// Mango
for...in Loop

This loop is used to iterate over the keys (or properties) of an object.

let person = { name: "John", age: 30, city: "New York" };

for (let key in person) {
  console.log(key + ": " + person[key]);
}
// Outputs:
// name: John
// age: 30
// city: New York

4. Error Handling (try-catch)

Error handling in JavaScript can be managed using the try-catch block, which allows you to handle exceptions and errors without stopping the program's execution.

try-catch Block
try {
  let result = 10 / 0;  // Division by zero (though no error in JS)
  console.log(result);

  // Intentionally cause an error
  let data = JSON.parse("invalid JSON");
} catch (error) {
  console.log("An error occurred:", error.message);
}
  • The try block contains code that might throw an error.

  • If an error occurs, control passes to the catch block, which handles the error.

finally Block

The finally block is always executed after try-catch, regardless of whether an error occurred or not.

try {
  let data = JSON.parse("invalid JSON");
} catch (error) {
  console.log("An error occurred:", error.message);
} finally {
  console.log("This will always run.");
}

5. Throwing Custom Errors

You can throw custom errors using the throw statement. This can be useful when you want to generate your own errors based on certain conditions.

function checkAge(age) {
  if (age < 18) {
    throw new Error("You must be at least 18 years old.");
  }
  console.log("Age is valid.");
}

try {
  checkAge(16);  // Throws an error
} catch (error) {
  console.log(error.message);  // Outputs: You must be at least 18 years old.
}

Summary of Control Structures in JavaScript:

  • Ternary Operator: Shorthand for if-else.

  • Nullish Coalescing (??): Provides default values for null or undefined.

  • Optional Chaining (?.): Safely access nested object properties.

  • Switch Statement: Used for checking multiple conditions.

  • Break & Continue: Control loop execution flow.

  • for...of: Iterate over iterable objects (arrays, strings).

  • for...in: Iterate over object keys.

  • try-catch-finally: Error handling with custom errors using throw.


That concludes Chapter 3.

Chapter 4: Functions in JavaScript


Functions are one of the most fundamental building blocks in JavaScript. In this chapter, we will explore how to define and use functions, as well as advanced topics like arrow functions, higher-order functions, and closures.


1. Defining Functions

A function is a block of reusable code that can be executed whenever needed.

Function Declaration

This is the most common way to define a function.

function greet() {
  console.log("Hello, World!");
}

greet();  // Outputs: Hello, World!
Function with Parameters

You can pass values (arguments) to functions to perform operations on them.

function greet(name) {
  console.log("Hello, " + name + "!");
}

greet("Alice");  // Outputs: Hello, Alice!
Function with Return Value

Functions can return values using the return statement.

function add(a, b) {
  return a + b;
}

let result = add(5, 3);
console.log(result);  // Outputs: 8

2. Function Expressions

In JavaScript, functions can also be defined as expressions and assigned to variables.

const multiply = function (x, y) {
  return x * y;
};

console.log(multiply(4, 5));  // Outputs: 20

Unlike function declarations, function expressions are not hoisted, meaning they must be defined before they are used.


3. Arrow Functions

Arrow functions provide a more concise syntax for writing functions. They are often used in modern JavaScript development.

const subtract = (a, b) => a - b;

console.log(subtract(10, 3));  // Outputs: 7
  • If there is only one parameter, you can omit the parentheses: x => x + 1.

  • If the function body consists of only a single expression, you can omit the curly braces and return keyword.

Arrow Functions with Multiple Statements
const divide = (a, b) => {
  if (b === 0) {
    return "Cannot divide by zero";
  }
  return a / b;
};

console.log(divide(10, 2));  // Outputs: 5

4. Default Parameters

You can define default values for parameters in functions. If a value is not passed for a parameter, the default value will be used.

function greet(name = "Guest") {
  console.log("Hello, " + name + "!");
}

greet();         // Outputs: Hello, Guest!
greet("Alice");  // Outputs: Hello, Alice!

5. Rest Parameters (...)

Rest parameters allow you to pass an indefinite number of arguments as an array.

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

console.log(sum(1, 2, 3, 4));  // Outputs: 10

The ...numbers collects all arguments into an array.


6. Higher-Order Functions

Higher-order functions are functions that can take other functions as arguments or return functions as values. They are a key feature of functional programming in JavaScript.

Function as Argument
function greetPerson(person, callback) {
  const message = `Hello, ${person}!`;
  callback(message);
}

function printMessage(message) {
  console.log(message);
}

greetPerson("Alice", printMessage);  // Outputs: Hello, Alice!
Function Returning a Function
function createMultiplier(factor) {
  return function (number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
console.log(double(5));  // Outputs: 10

7. Closures

A closure is a function that remembers the variables from its outer scope even after the outer function has finished executing.

function outer() {
  let count = 0;
  return function inner() {
    count++;
    return count;
  };
}

const increment = outer();

console.log(increment());  // Outputs: 1
console.log(increment());  // Outputs: 2

In this example, the inner function retains access to the count variable even after outer has executed.


8. Immediately Invoked Function Expressions (IIFE)

An IIFE is a function that is executed as soon as it is defined. This is useful when you want to create a separate scope and avoid polluting the global namespace.

(function () {
  console.log("This function runs immediately!");
})();

9. Function Scope

JavaScript functions have their own scope. Variables defined inside a function are not accessible from outside the function.

function sayHello() {
  let message = "Hello, World!";
  console.log(message);  // Outputs: Hello, World!
}

sayHello();
console.log(message);  // Error: message is not defined

Variables declared inside a function can only be accessed within that function.


Summary of Functions in JavaScript:

  • Function Declaration: A basic way to define reusable code blocks.

  • Function Expression: Functions as expressions, can be assigned to variables.

  • Arrow Functions: Concise syntax, especially for one-liners.

  • Default Parameters: Assign default values for function parameters.

  • Rest Parameters: Handle a variable number of arguments.

  • Higher-Order Functions: Functions that take or return other functions.

  • Closures: Functions that "remember" variables from their outer scope.

  • IIFE: Functions that are immediately executed upon definition.

  • Function Scope: Variables within functions are locally scoped.


Chapter 5: Objects in JavaScript


Objects are a fundamental concept in JavaScript, used to group related data and functionalities. They are collections of key-value pairs, where the key is a string (or symbol) and the value can be any type, including other objects or functions.


1. Creating Objects

There are multiple ways to create objects in JavaScript.

Object Literal Notation

This is the most common way to create an object.

const person = {
  name: "John",
  age: 30,
  greet: function () {
    console.log("Hello, " + this.name + "!");
  },
};

console.log(person.name);  // Outputs: John
person.greet();            // Outputs: Hello, John!

In this example, person is an object with properties name, age, and a method greet.

Using the new Object() Syntax

You can also create objects using the Object() constructor.

const car = new Object();
car.brand = "Toyota";
car.model = "Camry";
car.year = 2021;

console.log(car.brand);  // Outputs: Toyota

This method is less common than object literal notation.


2. Accessing Object Properties

There are two ways to access object properties:

Dot Notation
console.log(person.name);  // Outputs: John
Bracket Notation

Bracket notation allows you to use variable keys or keys with spaces.

console.log(person["name"]);  // Outputs: John

const key = "age";
console.log(person[key]);     // Outputs: 30

3. Adding and Modifying Properties

You can add or modify properties of an object after it's created.

person.job = "Engineer";       // Adding a new property
person.age = 31;               // Modifying an existing property
console.log(person.job);       // Outputs: Engineer
console.log(person.age);       // Outputs: 31

4. Removing Properties

To delete a property from an object, use the delete operator.

delete person.age;
console.log(person.age);  // Outputs: undefined

5. Object Methods

Objects can have methods, which are functions defined as properties. Methods often use the this keyword to refer to the object itself.

const dog = {
  name: "Buddy",
  bark: function () {
    console.log(this.name + " says woof!");
  },
};

dog.bark();  // Outputs: Buddy says woof!

In this case, this.name refers to the name property of the dog object.


6. this Keyword

The this keyword refers to the object in which the function is called. It allows you to access the object's properties within its methods.

const user = {
  name: "Alice",
  greet: function () {
    console.log("Hi, " + this.name + "!");
  },
};

user.greet();  // Outputs: Hi, Alice!

If a function is called in a different context (e.g., detached from the object), the value of this can change.


7. Nested Objects

Objects can contain other objects as properties, leading to a nested structure.

const company = {
  name: "Tech Corp",
  address: {
    street: "123 Main St",
    city: "New York",
  },
};

console.log(company.address.city);  // Outputs: New York

You can access properties of nested objects using dot notation.


8. Object Destructuring

Destructuring allows you to extract properties from an object into variables.

const { name, age } = person;
console.log(name);  // Outputs: John
console.log(age);   // Outputs: 31

Destructuring makes it easy to extract multiple properties from an object.


9. Checking for Properties

You can check whether an object has a certain property using the in operator or the hasOwnProperty() method.

console.log("name" in person);           // Outputs: true
console.log(person.hasOwnProperty("age"));  // Outputs: true

10. Iterating Over Object Properties

You can iterate over the properties of an object using for...in.

for (let key in person) {
  console.log(key + ": " + person[key]);
}

This will iterate over all enumerable properties of the object.


11. Object.assign()

Object.assign() is used to copy the properties of one or more objects into a target object.

const target = { a: 1 };
const source = { b: 2, c: 3 };
const newObject = Object.assign(target, source);

console.log(newObject);  // Outputs: { a: 1, b: 2, c: 3 }

12. Object.keys(), Object.values(), and Object.entries()

These methods allow you to get the keys, values, and key-value pairs from an object.

  • Object.keys() returns an array of the object's keys.

      console.log(Object.keys(person));  // Outputs: ["name", "job", "greet"]
    
  • Object.values() returns an array of the object's values.

      console.log(Object.values(person));  // Outputs: ["John", "Engineer", function]
    
  • Object.entries() returns an array of key-value pairs.

      console.log(Object.entries(person));
      // Outputs: [["name", "John"], ["job", "Engineer"], ["greet", function]]
    

Summary of Objects in JavaScript:

  • Objects are collections of key-value pairs used to store and manage data.

  • You can create objects using object literals or the Object() constructor.

  • Object properties can be accessed, modified, and deleted using dot or bracket notation.

  • Objects can have methods, and the this keyword inside methods refers to the object itself.

  • JavaScript supports nested objects, destructuring, and various built-in methods like Object.keys() and Object.assign() to manipulate objects.


Chapter 6: Arrays in JavaScript


Arrays in JavaScript are used to store multiple values in a single variable. They are list-like objects that come with many built-in methods for working with ordered collections of data.


1. Creating Arrays

You can create arrays using square brackets [] or the Array constructor.

// Using square brackets
const fruits = ["apple", "banana", "cherry"];

// Using the Array constructor
const numbers = new Array(1, 2, 3, 4);

2. Accessing Array Elements

Array elements are accessed using their index, starting from 0.

console.log(fruits[0]);  // Outputs: apple
console.log(numbers[2]);  // Outputs: 3

3. Modifying Arrays

You can modify arrays by assigning new values to specific indices or using array methods.

fruits[1] = "orange";
console.log(fruits);  // Outputs: ["apple", "orange", "cherry"]

4. Array Properties and Methods

  • Length Property: The length property gives the number of elements in the array.
console.log(fruits.length);  // Outputs: 3
  • Push and Pop: push adds an element to the end of the array, and pop removes the last element.
fruits.push("grape");
console.log(fruits);  // Outputs: ["apple", "orange", "cherry", "grape"]

fruits.pop();
console.log(fruits);  // Outputs: ["apple", "orange", "cherry"]
  • Shift and Unshift: shift removes the first element, and unshift adds an element to the beginning of the array.
fruits.shift();
console.log(fruits);  // Outputs: ["orange", "cherry"]

fruits.unshift("strawberry");
console.log(fruits);  // Outputs: ["strawberry", "orange", "cherry"]

5. Looping Through Arrays

You can loop through arrays using for, for...of, or forEach loops.

for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

for (let fruit of fruits) {
  console.log(fruit);
}

fruits.forEach((fruit) => {
  console.log(fruit);
});

6. Array Methods

JavaScript provides several methods for working with arrays:

  • map(): Creates a new array by applying a function to each element.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled);  // Outputs: [2, 4, 6, 8]
  • filter(): Creates a new array with elements that pass a condition.
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers);  // Outputs: [2, 4]
  • reduce(): Reduces the array to a single value.
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum);  // Outputs: 10
  • find(): Returns the first element that satisfies a condition.
const found = numbers.find(num => num > 2);
console.log(found);  // Outputs: 3
  • some() and every(): Checks if some or every element passes a condition.
console.log(numbers.some(num => num > 3));  // Outputs: true
console.log(numbers.every(num => num > 0));  // Outputs: true
  • sort(): Sorts the elements of an array.
const fruits = ["banana", "apple", "cherry"];
fruits.sort();
console.log(fruits);  // Outputs: ["apple", "banana", "cherry"]
  • concat(): Combines two or more arrays.
const moreFruits = ["pear", "mango"];
const combined = fruits.concat(moreFruits);
console.log(combined);  // Outputs: ["apple", "banana", "cherry", "pear", "mango"]
  • slice(): Returns a portion of the array without modifying the original array.
const sliced = fruits.slice(1, 3);
console.log(sliced);  // Outputs: ["banana", "cherry"]
  • splice(): Adds or removes elements from an array.
fruits.splice(1, 1, "grape");  // Removes "banana" and adds "grape"
console.log(fruits);  // Outputs: ["apple", "grape", "cherry"]

7. Multidimensional Arrays

Arrays can also contain other arrays (multidimensional arrays).

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

console.log(matrix[1][2]);  // Outputs: 6

8. Array Destructuring

You can use destructuring to extract values from arrays.

const [first, second, third] = fruits;
console.log(first, second, third);  // Outputs: apple grape cherry

Most Used Array Methods:

JavaScript Array Methods with Examples


Arrays in JavaScript come with many built-in methods that allow us to perform operations such as adding, removing, and manipulating elements. Let’s go through each array method along with examples and detailed explanations.


1. push()

Adds one or more elements to the end of an array and returns the new length of the array.

Example:

let fruits = ['apple', 'banana'];
let newLength = fruits.push('orange', 'mango');
console.log(fruits); // Output: ['apple', 'banana', 'orange', 'mango']
console.log(newLength); // Output: 4

Explanation: We add 'orange' and 'mango' to the end of the fruits array. The method returns the new length of the array (4).


2. pop()

Removes the last element from an array and returns that element.

Example:

let fruits = ['apple', 'banana', 'orange'];
let lastFruit = fruits.pop();
console.log(fruits); // Output: ['apple', 'banana']
console.log(lastFruit); // Output: 'orange'

Explanation: The pop() method removes the last element ('orange') from the fruits array and returns it.


3. shift()

Removes the first element from an array and returns that element. This method changes the length of the array.

Example:

let fruits = ['apple', 'banana', 'orange'];
let firstFruit = fruits.shift();
console.log(fruits); // Output: ['banana', 'orange']
console.log(firstFruit); // Output: 'apple'

Explanation: The first element ('apple') is removed from the array, and the array is updated.


4. unshift()

Adds one or more elements to the beginning of an array and returns the new length of the array.

Example:

let fruits = ['banana', 'orange'];
let newLength = fruits.unshift('apple', 'mango');
console.log(fruits); // Output: ['apple', 'mango', 'banana', 'orange']
console.log(newLength); // Output: 4

Explanation: We add 'apple' and 'mango' to the beginning of the array, and the new length of the array is returned.


5. concat()

Merges two or more arrays and returns a new array.

Example:

let arr1 = [1, 2];
let arr2 = [3, 4];
let mergedArray = arr1.concat(arr2);
console.log(mergedArray); // Output: [1, 2, 3, 4]

Explanation: The arrays arr1 and arr2 are merged into a new array [1, 2, 3, 4].


6. slice()

Returns a shallow copy of a portion of an array into a new array object. The slice begins at the specified start index and ends before the specified end index (non-inclusive).

Example:

let fruits = ['apple', 'banana', 'orange', 'mango'];
let slicedFruits = fruits.slice(1, 3);
console.log(slicedFruits); // Output: ['banana', 'orange']

Explanation: The slice() method extracts elements from index 1 to index 3 (excluding index 3).


7. splice()

Changes the contents of an array by removing, replacing, or adding elements.

Example (Removing elements):

let fruits = ['apple', 'banana', 'orange', 'mango'];
let removedFruits = fruits.splice(1, 2);
console.log(fruits); // Output: ['apple', 'mango']
console.log(removedFruits); // Output: ['banana', 'orange']

Explanation: We remove 2 elements starting from index 1 ('banana', 'orange'). The original array is modified.

Example (Adding elements):

let fruits = ['apple', 'mango'];
fruits.splice(1, 0, 'banana', 'orange');
console.log(fruits); // Output: ['apple', 'banana', 'orange', 'mango']

Explanation: At index 1, we insert 'banana' and 'orange' without removing any elements.


8. forEach()

Executes a provided function once for each array element.

Example:

let numbers = [1, 2, 3];
numbers.forEach(function(num) {
  console.log(num * 2);
});
// Output: 2, 4, 6

Explanation: The forEach() method iterates over each element in the numbers array, doubling it and logging the result.


9. map()

Creates a new array populated with the results of calling a provided function on every element in the calling array.

Example:

let numbers = [1, 2, 3];
let doubled = numbers.map(num => num * 2);
console.log(doubled); // Output: [2, 4, 6]

Explanation: The map() method returns a new array where each element is the result of multiplying the original element by 2.


10. filter()

Creates a new array with all elements that pass the test implemented by the provided function.

Example:

let numbers = [1, 2, 3, 4, 5];
let evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]

Explanation: The filter() method returns a new array containing only the even numbers.


11. reduce()

Executes a reducer function on each element of the array, resulting in a single output value.

Example:

let numbers = [1, 2, 3, 4];
let sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // Output: 10

Explanation: The reduce() method adds all elements of the numbers array, starting with an initial value of 0.


12. find()

Returns the value of the first element in the array that satisfies the provided testing function.

Example:

let numbers = [5, 12, 8, 130, 44];
let found = numbers.find(num => num > 10);
console.log(found); // Output: 12

Explanation: The find() method returns the first number in the array that is greater than 10.


13. findIndex()

Returns the index of the first element that satisfies the provided testing function. Otherwise, it returns -1.

Example:

let numbers = [5, 12, 8, 130, 44];
let index = numbers.findIndex(num => num > 10);
console.log(index); // Output: 1

Explanation: The findIndex() method returns the index of the first element greater than 10, which is at index 1.


14. every()

Tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.

Example:

let numbers = [2, 4, 6];
let allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // Output: true

Explanation: The every() method checks if all elements in the array are even. Since all numbers are even, it returns true.


15. some()

Tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.

Example:

let numbers = [1, 2, 3, 4];
let hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // Output: true

Explanation: The some() method checks if at least one element is even. Since 2 and 4 are even, it returns true.


16. includes()

Determines whether an array includes a certain value among its entries, returning true or false.

Example:

let fruits = ['apple', 'banana', 'mango'];
console.log(fruits.includes('banana')); // Output: true
console.log(fruits.includes('grape')); // Output: false

Explanation: The includes() method checks whether 'banana' exists in the array and returns true.


17. sort()

Sorts the elements of an array in place and returns the sorted array.

Example:

let numbers = [3, 1, 4, 2];
numbers.sort();
console.log(numbers); // Output: [1, 2, 3, 4]

Explanation: The sort() method sorts the array in ascending order.

Example with custom comparator:

let numbers = [10, 5, 20, 1];

numbers.sort((a, b) => a - b); console.log(numbers); // Output: [1, 5, 10, 20]

**Explanation**: Using a custom comparator function `(a, b) => a - b`, the array is sorted in numerical order.

---

### **18. `reverse()`**
Reverses the order of the elements in an array.

#### **Example:**
```js
let numbers = [1, 2, 3];
numbers.reverse();
console.log(numbers); // Output: [3, 2, 1]

Explanation: The reverse() method reverses the order of the array elements.


19. join()

Joins all elements of an array into a string, separated by a specified separator.

Example:

let fruits = ['apple', 'banana', 'mango'];
let fruitString = fruits.join(', ');
console.log(fruitString); // Output: 'apple, banana, mango'

Explanation: The join() method creates a string with the array elements separated by , .


20. flat()

Creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.

Example:

let nestedArray = [1, [2, 3], [4, [5]]];
let flatArray = nestedArray.flat(2);
console.log(flatArray); // Output: [1, 2, 3, 4, 5]

Explanation: The flat() method flattens the nested arrays up to a depth of 2.


These are some of the most commonly used array methods in JavaScript, each with its specific purpose and behavior!


Summary of Arrays in JavaScript:

  • Arrays are used to store multiple values in a single variable.

  • They come with many built-in methods for performing operations like adding, removing, and manipulating data.

  • Arrays can be looped through and destructured for efficient handling of data.

  • Advanced methods like map, reduce, filter, and sort allow you to work with arrays in a functional way.


Chapter 7: Strings in JavaScript


Strings in JavaScript are used to represent textual data. They are a sequence of characters enclosed in single quotes ('), double quotes ("), or backticks (` ). JavaScript provides a wide range of methods and properties for working with strings.


1. Creating Strings

You can create strings using different types of quotes:

const singleQuoteStr = 'Hello, world!';
const doubleQuoteStr = "Hello, world!";
const templateLiteralStr = `Hello, world!`;
  • Single and Double Quotes: Strings created with single (') or double (") quotes behave similarly.

  • Template Literals: Strings created with backticks (` ) allow for embedding expressions and multi-line strings.


2. String Properties

  • length: Returns the length (number of characters) in a string.
const str = "JavaScript";
console.log(str.length);  // Outputs: 10

3. Accessing Characters in a String

You can access individual characters in a string using bracket notation or the charAt() method.

const str = "JavaScript";
console.log(str[0]);  // Outputs: "J"
console.log(str.charAt(1));  // Outputs: "a"

4. String Methods

JavaScript provides many built-in methods for string manipulation:

  • toUpperCase() and toLowerCase(): Converts the string to upper or lower case.
console.log(str.toUpperCase());  // Outputs: "JAVASCRIPT"
console.log(str.toLowerCase());  // Outputs: "javascript"
  • indexOf(): Returns the index of the first occurrence of a substring. If not found, returns -1.
console.log(str.indexOf("Script"));  // Outputs: 4
console.log(str.indexOf("script"));  // Outputs: -1
  • includes(): Checks if a substring is present in the string, returning true or false.
console.log(str.includes("Java"));  // Outputs: true
  • slice(): Extracts a section of the string and returns it as a new string.
console.log(str.slice(0, 4));  // Outputs: "Java"
  • substring(): Similar to slice(), but does not accept negative indices.
console.log(str.substring(4, 10));  // Outputs: "Script"
  • replace(): Replaces the first occurrence of a substring with another.
const newStr = str.replace("Java", "Type");
console.log(newStr);  // Outputs: "TypeScript"
  • split(): Splits a string into an array of substrings based on a specified delimiter.
const sentence = "This is JavaScript";
const words = sentence.split(" ");
console.log(words);  // Outputs: ["This", "is", "JavaScript"]

5. String Concatenation

You can concatenate (combine) strings using the + operator or template literals.

const greeting = "Hello, " + "world!";
console.log(greeting);  // Outputs: "Hello, world!"

// Using template literals
const name = "John";
const message = `Hello, ${name}! Welcome to JavaScript.`;
console.log(message);  // Outputs: "Hello, John! Welcome to JavaScript."

6. Template Literals

Template literals allow for embedding expressions inside strings using ${}. They also support multi-line strings without the need for special characters.

const age = 25;
const introduction = `I am ${age} years old.`;
console.log(introduction);  // Outputs: "I am 25 years old."

// Multi-line string
const multiLine = `This is
a multi-line
string.`;
console.log(multiLine);

7. Escape Characters

You can include special characters in a string using escape sequences. Some common escape characters:

  • \n: Newline

  • \t: Tab

  • \': Single quote

  • \": Double quote

  • \\: Backslash

const text = 'He said, "It\'s a beautiful day."';
console.log(text);  // Outputs: He said, "It's a beautiful day."

8. String Immutability

Strings in JavaScript are immutable, meaning they cannot be changed after creation. Any operation on a string, such as concatenation or replacement, creates a new string rather than modifying the original.

let str = "Hello";
str[0] = "h";  // This will not change the string
console.log(str);  // Outputs: "Hello"

9. Comparing Strings

You can compare strings using relational operators like ==, ===, >, <, etc. String comparison is case-sensitive.

console.log("apple" > "banana");  // Outputs: false (alphabetical comparison)
console.log("Apple" === "apple");  // Outputs: false (case-sensitive)

10. Trim Method

  • trim(): Removes whitespace from both ends of a string.
const str = "   Hello, world!   ";
console.log(str.trim());  // Outputs: "Hello, world!"

Most used String Methods:

Here’s a comprehensive list of commonly used JavaScript string methods with explanations and examples:


1. length

Returns the length of a string.

Example:

let str = "Hello";
console.log(str.length); // Output: 5

Explanation: length returns the number of characters in the string.


2. charAt()

Returns the character at a specified index.

Example:

let str = "Hello";
console.log(str.charAt(1)); // Output: 'e'

Explanation: charAt(1) returns the character at index 1, which is 'e'.


3. charCodeAt()

Returns the Unicode value of the character at a specified index.

Example:

let str = "A";
console.log(str.charCodeAt(0)); // Output: 65

Explanation: charCodeAt(0) returns the Unicode value of the character 'A', which is 65.


4. concat()

Concatenates (joins) two or more strings.

Example:

let str1 = "Hello";
let str2 = "World";
console.log(str1.concat(" ", str2)); // Output: 'Hello World'

Explanation: concat() joins two strings together.


5. includes()

Checks if a string contains a specified substring, returning true or false.

Example:

let str = "Hello World";
console.log(str.includes("World")); // Output: true

Explanation: includes() checks if 'World' exists in the string.


6. endsWith()

Checks if a string ends with a specified substring.

Example:

let str = "Hello World";
console.log(str.endsWith("World")); // Output: true

Explanation: endsWith() checks if the string ends with 'World'.


7. startsWith()

Checks if a string starts with a specified substring.

Example:

let str = "Hello World";
console.log(str.startsWith("Hello")); // Output: true

Explanation: startsWith() checks if the string starts with 'Hello'.


8. indexOf()

Returns the index of the first occurrence of a specified substring.

Example:

let str = "Hello World";
console.log(str.indexOf("o")); // Output: 4

Explanation: indexOf() returns the index of the first occurrence of 'o', which is 4.


9. lastIndexOf()

Returns the index of the last occurrence of a specified substring.

Example:

let str = "Hello World";
console.log(str.lastIndexOf("o")); // Output: 7

Explanation: lastIndexOf() returns the last occurrence of 'o', which is at index 7.


10. slice()

Extracts a part of a string and returns it as a new string.

Example:

let str = "Hello World";
console.log(str.slice(0, 5)); // Output: 'Hello'

Explanation: slice(0, 5) extracts the substring from index 0 to index 5 (excluding 5).


11. substring()

Extracts characters between two specified indices.

Example:

let str = "Hello World";
console.log(str.substring(0, 5)); // Output: 'Hello'

Explanation: Similar to slice(), substring(0, 5) extracts characters from index 0 to 5.


12. substr()

Extracts a substring from a specified start index and length (deprecated).

Example:

let str = "Hello World";
console.log(str.substr(0, 5)); // Output: 'Hello'

Explanation: substr(0, 5) extracts 5 characters starting from index 0.


13. toLowerCase()

Converts a string to lowercase.

Example:

let str = "Hello World";
console.log(str.toLowerCase()); // Output: 'hello world'

Explanation: toLowerCase() converts all characters to lowercase.


14. toUpperCase()

Converts a string to uppercase.

Example:

let str = "Hello World";
console.log(str.toUpperCase()); // Output: 'HELLO WORLD'

Explanation: toUpperCase() converts all characters to uppercase.


15. trim()

Removes whitespace from both sides of a string.

Example:

let str = "   Hello World   ";
console.log(str.trim()); // Output: 'Hello World'

Explanation: trim() removes leading and trailing whitespace.


16. trimStart() / trimLeft()

Removes whitespace from the beginning of a string.

Example:

let str = "   Hello World";
console.log(str.trimStart()); // Output: 'Hello World'

Explanation: trimStart() removes whitespace from the beginning.


17. trimEnd() / trimRight()

Removes whitespace from the end of a string.

Example:

let str = "Hello World   ";
console.log(str.trimEnd()); // Output: 'Hello World'

Explanation: trimEnd() removes whitespace from the end.


18. replace()

Replaces a substring with a new value.

Example:

let str = "Hello World";
console.log(str.replace("World", "JavaScript")); // Output: 'Hello JavaScript'

Explanation: replace() replaces the first occurrence of 'World' with 'JavaScript'.


19. replaceAll()

Replaces all occurrences of a substring with a new value.

Example:

let str = "apple apple";
console.log(str.replaceAll("apple", "banana")); // Output: 'banana banana'

Explanation: replaceAll() replaces all instances of 'apple' with 'banana'.


20. split()

Splits a string into an array of substrings, based on a specified separator.

Example:

let str = "apple, banana, mango";
let fruits = str.split(", ");
console.log(fruits); // Output: ['apple', 'banana', 'mango']

Explanation: split(", ") splits the string into an array of fruits.


21. padStart()

Pads the beginning of a string with another string until a given length is reached.

Example:

let str = "5";
console.log(str.padStart(3, "0")); // Output: '005'

Explanation: padStart(3, "0") adds zeros to the start until the length is 3.


22. padEnd()

Pads the end of a string with another string until a given length is reached.

Example:

let str = "5";
console.log(str.padEnd(3, "0")); // Output: '500'

Explanation: padEnd(3, "0") adds zeros to the end until the length is 3.


23. repeat()

Repeats a string a specified number of times.

Example:

let str = "Hello ";
console.log(str.repeat(3)); // Output: 'Hello Hello Hello '

Explanation: repeat(3) repeats the string 'Hello ' three times.


24. match()

Matches a string against a regular expression and returns an array of results.

Example:

let str = "Hello 123";
let result = str.match(/\d+/);
console.log(result); // Output: ['123']

Explanation: match() returns an array containing the digits in the string.


25. matchAll()

Returns an iterator for all matches against a regular expression.

Example:

let str = "apple banana apple";
let result = [...str.matchAll(/apple/g)];
console.log(result); // Output: [['apple'], ['apple']]

Explanation: matchAll() finds all occurrences of 'apple'.


Searches for a match between a regular expression and the string and returns the index of the match.

Example:

let str = "Hello 123";
console.log(str.search(/\d+/)); // Output: 6

Explanation: search() returns the index of the first number in the string.


27. toString()

Returns the string representation of an object (usually called on non-string objects).

Example:

let num = 123;
console.log(num.toString()); // Output: '123'

Explanation: toString() converts the number 123 into a string '123'.


28. valueOf()

Returns the primitive value of

a string object.

Example:

let str = new String("Hello");
console.log(str.valueOf()); // Output: 'Hello'

Explanation: valueOf() returns the primitive string value.


These are the key methods for manipulating strings in JavaScript! Let me know if you'd like more details on any of them.


Summary of Strings in JavaScript:

  • Strings are used to represent and manipulate text.

  • JavaScript provides many built-in methods for string manipulation, such as slice(), replace(), and split().

  • Strings can be concatenated using the + operator or template literals.

  • Template literals allow for embedding expressions and creating multi-line strings.

  • Strings are immutable, meaning that they cannot be changed after creation.


Chapter 8: ES6 Features (Modern JavaScript)


ECMAScript 6 (ES6), also known as ECMAScript 2015, introduced several new features and enhancements to JavaScript, making it more powerful, concise, and easier to work with. These features have since become standard in modern JavaScript development.

In this chapter, we will explore the most important ES6 features:


1. let and const

Before ES6, variables in JavaScript were declared using var. ES6 introduced two new keywords for declaring variables: let and const.

  • let: Declares a block-scoped variable, which can be reassigned.
let age = 25;
age = 30;  // Reassignment is allowed
  • const: Declares a block-scoped constant that cannot be reassigned. Once assigned, its value cannot change.
const name = "John";
name = "Jane";  // Error: Assignment to constant variable
  • Block Scope: Both let and const are block-scoped, meaning they only exist within the block {} where they are declared.
if (true) {
  let x = 10;
  console.log(x);  // Outputs: 10
}
console.log(x);  // Error: x is not defined

2. Arrow Functions

Arrow functions provide a more concise syntax for writing functions and automatically bind this to the surrounding context.

  • Traditional Function:
const add = function (a, b) {
  return a + b;
};
console.log(add(5, 10));  // Outputs: 15
  • Arrow Function:
const add = (a, b) => a + b;
console.log(add(5, 10));  // Outputs: 15

Arrow functions can omit parentheses if they take a single parameter:

const greet = name => `Hello, ${name}!`;
console.log(greet("John"));  // Outputs: "Hello, John!"

3. Template Literals

Template literals (introduced in the previous chapter) allow embedding expressions and multi-line strings.

const name = "John";
const greeting = `Hello, ${name}! Welcome to ES6.`;
console.log(greeting);  // Outputs: "Hello, John! Welcome to ES6."

4. Destructuring

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

  • Array Destructuring:
const arr = [1, 2, 3];
const [first, second, third] = arr;
console.log(first, second, third);  // Outputs: 1 2 3
  • Object Destructuring:
const person = { name: "John", age: 25 };
const { name, age } = person;
console.log(name, age);  // Outputs: John 25

5. Default Parameters

Default parameters allow you to set default values for function parameters if no argument is passed.

const greet = (name = "Guest") => `Hello, ${name}!`;
console.log(greet());  // Outputs: "Hello, Guest!"
console.log(greet("John"));  // Outputs: "Hello, John!"

6. Rest and Spread Operators

  • Rest Operator (...): Collects all remaining elements into an array.
const sum = (a, b, ...rest) => {
  console.log(rest);  // Outputs: [3, 4, 5]
  return a + b;
};
sum(1, 2, 3, 4, 5);  // Outputs: 3
  • Spread Operator (...): Spreads elements of an array or object into individual elements.
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2);  // Outputs: [1, 2, 3, 4, 5]

const obj1 = { name: "John" };
const obj2 = { ...obj1, age: 25 };
console.log(obj2);  // Outputs: { name: "John", age: 25 }

7. Enhanced Object Literals

ES6 introduced shorthand notation for object properties and methods.

const name = "John";
const age = 25;

// Property shorthand
const person = { name, age };
console.log(person);  // Outputs: { name: "John", age: 25 }

// Method shorthand
const user = {
  sayHi() {
    console.log("Hi!");
  }
};
user.sayHi();  // Outputs: "Hi!"

8. Classes

ES6 introduced classes, which are syntactical sugar over JavaScript's existing prototype-based inheritance.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

const john = new Person("John", 25);
john.greet();  // Outputs: "Hello, my name is John."

9. Modules

ES6 introduced native support for modules, allowing you to export and import code between files.

  • Exporting:
// module.js
export const greet = name => `Hello, ${name}!`;
  • Importing:
// main.js
import { greet } from './module.js';
console.log(greet("John"));  // Outputs: "Hello, John!"

10. Promises

Promises provide a cleaner way to handle asynchronous operations in JavaScript, avoiding "callback hell."

const myPromise = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("Operation succeeded!");
  } else {
    reject("Operation failed.");
  }
});

myPromise
  .then(result => console.log(result))  // Outputs: "Operation succeeded!"
  .catch(error => console.error(error));  // Handles rejection

11. Symbol

Symbol is a primitive data type introduced in ES6. Symbols are unique and immutable, making them useful for creating unique object properties.

const id = Symbol("id");
const user = {
  [id]: 123,
  name: "John"
};

console.log(user[id]);  // Outputs: 123

12. Iterators and Generators

  • Iterators: Objects that provide a sequence of values, typically used with loops like for...of.
const iterable = [1, 2, 3];
const iterator = iterable[Symbol.iterator]();

console.log(iterator.next());  // Outputs: { value: 1, done: false }
console.log(iterator.next());  // Outputs: { value: 2, done: false }
console.log(iterator.next());  // Outputs: { value: 3, done: false }
  • Generators: Functions that return iterators and can pause their execution with yield.
function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();
console.log(gen.next().value);  // Outputs: 1
console.log(gen.next().value);  // Outputs: 2
console.log(gen.next().value);  // Outputs: 3

Summary of ES6 Features

  • let and const provide block-scoped variable declarations.

  • Arrow functions offer a concise syntax and automatic binding of this.

  • Template literals allow embedding expressions and creating multi-line strings.

  • Destructuring simplifies extracting values from arrays and objects.

  • Default parameters, rest, and spread operators make function and array/object manipulation more powerful.

  • Classes provide a new syntax for working with object-oriented code.

  • Modules enable exporting and importing code between files.

  • Promises improve handling of asynchronous operations.

  • Symbols create unique object keys.

  • Iterators and generators provide powerful tools for working with sequences.


The this Keyword in JavaScript


The this keyword is one of the most important and sometimes confusing concepts in JavaScript. It refers to the context in which a function is invoked. The value of this depends on where and how the function is called, not where it is defined.

Let’s explore the various contexts in which this behaves differently.


1. Global Context (Outside of any Function)

In the global execution context, this refers to the global object:

  • In browsers, the global object is window.

  • In Node.js, it's the global object.

console.log(this);  // In browsers, this will output the window object

2. Inside a Regular Function

In non-strict mode, when this is used inside a function, it refers to the global object (window in the browser).

function show() {
  console.log(this);  // Outputs: window (global object in browser)
}
show();

In strict mode, this in a regular function is undefined.

"use strict";
function show() {
  console.log(this);  // Outputs: undefined
}
show();

3. Inside an Object Method

When a function is invoked as a method of an object, this refers to the object to which the method belongs.

const person = {
  name: "John",
  greet: function () {
    console.log(this.name);  // 'this' refers to the 'person' object
  }
};

person.greet();  // Outputs: "John"

4. Constructor Functions and Classes

When using constructor functions or classes, this refers to the newly created instance of the object.

  • Constructor Function:
function Person(name) {
  this.name = name;
}

const john = new Person("John");
console.log(john.name);  // Outputs: "John"
  • Class Syntax:
class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const john = new Person("John");
john.greet();  // Outputs: "Hello, my name is John"

5. this in Arrow Functions

Arrow functions behave differently with this. Unlike regular functions, arrow functions do not have their own this context. Instead, they inherit this from the surrounding lexical scope (i.e., where the arrow function is defined).

const person = {
  name: "John",
  greet: function () {
    const inner = () => {
      console.log(this.name);  // Arrow function inherits 'this' from the outer method
    };
    inner();
  }
};

person.greet();  // Outputs: "John"

6. this in Event Handlers

In event handlers, this refers to the element that fired the event.

const button = document.querySelector("button");
button.addEventListener("click", function () {
  console.log(this);  // 'this' refers to the button element
});

If you use an arrow function in an event handler, this will not refer to the element, as arrow functions inherit this from the enclosing context.

button.addEventListener("click", () => {
  console.log(this);  // 'this' refers to the global object or undefined in strict mode
});

7. Explicitly Setting this with call(), apply(), and bind()

JavaScript provides methods to explicitly set the value of this in function calls:

  • call(): Immediately invokes the function with a specified this value.

  • apply(): Similar to call(), but arguments are passed as an array.

  • bind(): Returns a new function with the specified this value, which can be called later.

function showName() {
  console.log(this.name);
}

const person1 = { name: "John" };
const person2 = { name: "Jane" };

showName.call(person1);  // Outputs: "John"
showName.apply(person2);  // Outputs: "Jane"

const boundShowName = showName.bind(person1);
boundShowName();  // Outputs: "John"

8. this in DOM Manipulation

When interacting with the DOM, this refers to the HTML element in context:

<button onclick="sayHello()">Click me</button>
<script>
  function sayHello() {
    console.log(this);  // 'this' refers to the button element
  }
</script>

However, using arrow functions in DOM events leads to different behavior, as this refers to the surrounding lexical scope:

<button id="btn">Click me</button>
<script>
  document.getElementById("btn").addEventListener("click", () => {
    console.log(this);  // 'this' refers to the global object
  });
</script>

Summary of this Keyword Behavior

  1. Global Scope: this refers to the global object (window/global).

  2. Regular Functions: In non-strict mode, this refers to the global object; in strict mode, it's undefined.

  3. Methods: Inside object methods, this refers to the object itself.

  4. Constructors/Classes: this refers to the new instance of the object created.

  5. Arrow Functions: this is lexically bound, meaning it inherits from the enclosing context.

  6. Event Handlers: this refers to the element that triggered the event.

  7. Explicit this Binding: Methods like call(), apply(), and bind() can explicitly set the value of this.

Understanding how this behaves in different contexts is crucial for mastering JavaScript, especially in larger codebases and frameworks like React.


Chapter8: Asynchronous JavaScript


Asynchronous JavaScript is crucial for managing tasks that take time to complete without blocking the main thread. This allows the browser or application to remain responsive while waiting for tasks like API calls, file reading, or animations to finish.

Asynchronous behavior in JavaScript can be handled using several mechanisms, including callbacks, promises, and async/await. Let’s explore them.


1. Synchronous vs Asynchronous Execution

In JavaScript, by default, code is executed line by line, i.e., synchronously. However, certain tasks like fetching data from a server or reading a file can take some time to complete, and during that period, we don't want the rest of the program to wait. Asynchronous JavaScript allows other tasks to be executed while waiting for long-running tasks to complete.

Synchronous Example:

console.log('Start');
console.log('Middle');
console.log('End');
// Output:
// Start
// Middle
// End

Asynchronous Example:

console.log('Start');

setTimeout(() => {
  console.log('This is async');
}, 2000);

console.log('End');
// Output:
// Start
// End
// This is async (after 2 seconds)

2. Callbacks

A callback is a function passed into another function as an argument. The callback is invoked after a task is completed, enabling asynchronous behavior.

function fetchData(callback) {
  setTimeout(() => {
    console.log('Data fetched');
    callback();
  }, 2000);
}

function processData() {
  console.log('Processing data...');
}

fetchData(processData);  // Outputs: "Data fetched" then "Processing data..."

While callbacks work for asynchronous tasks, they can lead to callback hell if there are multiple nested callbacks:

function first(callback) {
  setTimeout(() => {
    console.log('First');
    callback();
  }, 1000);
}

function second(callback) {
  setTimeout(() => {
    console.log('Second');
    callback();
  }, 1000);
}

function third(callback) {
  setTimeout(() => {
    console.log('Third');
    callback();
  }, 1000);
}

first(() => {
  second(() => {
    third(() => {
      console.log('All done');
    });
  });
});

3. Promises

Promises provide a cleaner way to handle asynchronous operations and avoid callback hell. A Promise represents a value that may be available now, in the future, or never. It can be in one of three states:

  • Pending: The initial state, neither fulfilled nor rejected.

  • Fulfilled: The operation was successful, and the promise has a result.

  • Rejected: The operation failed, and the promise has a reason for the failure.

Basic Promise Syntax:

const myPromise = new Promise((resolve, reject) => {
  // Do something asynchronous
  setTimeout(() => {
    let success = true;
    if (success) {
      resolve('Operation successful');
    } else {
      reject('Operation failed');
    }
  }, 2000);
});

myPromise
  .then((message) => {
    console.log(message);  // Outputs: "Operation successful"
  })
  .catch((error) => {
    console.error(error);  // Handles the error if rejected
  });
  • resolve(value): Marks the promise as fulfilled and passes value to the next .then() block.

  • reject(reason): Marks the promise as rejected and passes reason to the .catch() block.


4. Chaining Promises

Promises can be chained to handle multiple asynchronous operations in sequence.

const promise1 = new Promise((resolve) => {
  setTimeout(() => resolve('First step'), 1000);
});

promise1
  .then((result) => {
    console.log(result);  // "First step"
    return 'Second step';
  })
  .then((result) => {
    console.log(result);  // "Second step"
    return 'Third step';
  })
  .then((result) => {
    console.log(result);  // "Third step"
  })
  .catch((error) => {
    console.error('Error:', error);
  });

5. Promise.all() and Promise.race()

Promise.all():

Runs multiple promises in parallel and waits for all of them to complete. If any promise fails, Promise.all() fails.

const promiseA = Promise.resolve('A');
const promiseB = new Promise((resolve) => setTimeout(() => resolve('B'), 1000));
const promiseC = Promise.resolve('C');

Promise.all([promiseA, promiseB, promiseC])
  .then((results) => {
    console.log(results);  // ["A", "B", "C"]
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Promise.race():

Returns the result of the first promise to complete, whether fulfilled or rejected.

const promiseX = new Promise((resolve) => setTimeout(() => resolve('X'), 500));
const promiseY = new Promise((resolve) => setTimeout(() => resolve('Y'), 1000));

Promise.race([promiseX, promiseY])
  .then((result) => {
    console.log(result);  // "X" (as it resolves faster)
  });

6. async and await

The async and await keywords were introduced to simplify working with promises and asynchronous code. An async function returns a promise, and await pauses the function execution until the promise is resolved or rejected.

Basic Syntax:

async function fetchData() {
  const response = await new Promise((resolve) => {
    setTimeout(() => resolve('Data fetched'), 2000);
  });

  console.log(response);  // Outputs: "Data fetched"
}

fetchData();

You can use try...catch for error handling:

async function fetchDataWithErrorHandling() {
  try {
    const response = await new Promise((_, reject) => {
      setTimeout(() => reject('Error fetching data'), 2000);
    });

    console.log(response);
  } catch (error) {
    console.error(error);  // Outputs: "Error fetching data"
  }
}

fetchDataWithErrorHandling();

7. Example: Fetch API with Async/Await

A common use of asynchronous JavaScript is to make HTTP requests using the Fetch API. Here’s how to use async/await with fetch():

async function getUserData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const users = await response.json();

    console.log(users);  // Outputs fetched user data
  } catch (error) {
    console.error('Error fetching users:', error);
  }
}

getUserData();

Summary

  • Asynchronous JavaScript is essential for tasks like HTTP requests, timers, and file handling.

  • Callbacks: Simple but can lead to callback hell.

  • Promises: Provide a cleaner way to manage asynchronous code and allow for chaining.

  • async/await: A more readable and modern way to work with promises.

  • Methods like Promise.all() and Promise.race() help manage multiple promises efficiently.


Chapter 9: JavaScript and the Browser


JavaScript's interaction with the browser is what makes it such a powerful language for building web applications. When a JavaScript file runs in a browser, it gains access to various browser-specific APIs that enable it to interact with web pages, handle user events, manipulate the DOM (Document Object Model), and more.

In this chapter, we’ll cover how JavaScript interacts with the browser, including the DOM, events, storage mechanisms, and other browser-specific features.


1. The DOM (Document Object Model)

The Document Object Model (DOM) represents the structure of an HTML document as a tree of nodes (elements). JavaScript can manipulate the DOM to dynamically change the content, structure, and style of a web page.

Basic DOM Example:

HTML structure:

<!DOCTYPE html>
<html>
  <head>
    <title>DOM Example</title>
  </head>
  <body>
    <h1 id="main-heading">Hello, World!</h1>
    <p class="message">Welcome to the DOM world.</p>
  </body>
</html>

You can manipulate the DOM using JavaScript:

// Select the element with id="main-heading" and change its text content
document.getElementById('main-heading').textContent = 'Welcome to JavaScript!';

// Select all elements with the class "message" and change their style
document.querySelector('.message').style.color = 'blue';

Common DOM Methods:

  • document.getElementById(id): Selects an element by its ID.

  • document.querySelector(selector): Selects the first element that matches a CSS selector.

  • document.querySelectorAll(selector): Selects all elements that match a CSS selector.

  • element.textContent: Sets or gets the text content of an element.

  • element.innerHTML: Sets or gets the HTML content of an element.

  • element.style: Accesses or modifies inline CSS styles of an element.


2. DOM Manipulation

You can dynamically create, modify, or remove elements from the DOM using JavaScript.

Creating and Appending Elements:

// Create a new element
const newParagraph = document.createElement('p');

// Set its text content
newParagraph.textContent = 'This is a new paragraph created by JavaScript.';

// Append it to the body of the document
document.body.appendChild(newParagraph);

Removing Elements:

const elementToRemove = document.getElementById('main-heading');
elementToRemove.remove();  // Removes the element from the DOM

Modifying Attributes:

You can get or set attributes like src, href, class, id, etc., of DOM elements.

const link = document.querySelector('a');
link.setAttribute('href', 'https://www.example.com');  // Set href attribute
link.getAttribute('href');  // Get href attribute
link.removeAttribute('href');  // Remove href attribute

3. Events in JavaScript

Events in JavaScript represent user interactions or other actions that occur on a web page. Examples include clicks, key presses, and form submissions.

Event Listeners:

You can add event listeners to DOM elements to execute code when a specific event occurs.

const button = document.querySelector('button');

// Add a click event listener
button.addEventListener('click', function () {
  alert('Button was clicked!');
});

Common Event Types:

  • click: Fired when an element is clicked.

  • mouseover: Fired when the mouse is over an element.

  • keydown: Fired when a key is pressed on the keyboard.

  • submit: Fired when a form is submitted.

  • load: Fired when a page or image finishes loading.


4. Event Delegation

Instead of attaching event listeners to multiple child elements, you can use event delegation. This involves attaching a single event listener to a parent element and using event bubbling to handle events on child elements.

const list = document.querySelector('ul');

// Use event delegation by attaching the event listener to the parent element (ul)
list.addEventListener('click', function (event) {
  if (event.target.tagName === 'LI') {
    console.log('List item clicked:', event.target.textContent);
  }
});

5. Browser Storage

JavaScript provides mechanisms for storing data in the browser, allowing web applications to save user preferences or cache data.

Local Storage:

Local storage allows you to store key-value pairs in the browser, and this data persists even after the browser is closed.

// Store data
localStorage.setItem('name', 'Purna');

// Retrieve data
const name = localStorage.getItem('name');
console.log(name);  // Output: Purna

// Remove data
localStorage.removeItem('name');

// Clear all data
localStorage.clear();

Session Storage:

Similar to local storage, but the data is only available for the duration of the page session (until the browser or tab is closed).

sessionStorage.setItem('sessionName', 'Temporary Session');
console.log(sessionStorage.getItem('sessionName'));  // Output: Temporary Session

6. Cookies

Cookies allow storing small amounts of data in the browser, often used for tracking sessions and user authentication.

// Set a cookie
document.cookie = 'username=Purna; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/';

// Get cookies
console.log(document.cookie);  // Outputs all cookies in the document

7. The Window Object

The window object represents the browser's window and is the global object for all JavaScript running in the browser. It includes properties and methods for controlling the browser window and interacting with the user.

Common Window Methods:

  • window.alert(message): Displays an alert box with a message.

  • window.prompt(message): Displays a dialog box asking for user input.

  • window.confirm(message): Displays a confirmation dialog with OK and Cancel buttons.

  • window.location: Represents the current URL and can be used to redirect or reload the page.

window.alert('Hello, world!');
window.location.href = 'https://www.example.com';  // Redirects the browser to a new URL

8. The document Object

The document object represents the web page loaded in the browser. It provides methods to access and manipulate the content and structure of the page.

Common Document Methods:

  • document.title: Sets or gets the title of the document.

  • document.body: Refers to the <body> element of the document.

  • document.createElement(tagName): Creates a new HTML element.

  • document.write(content): Writes directly to the document (rarely used).

document.title = 'New Title';
console.log(document.body);  // Outputs the body element

9. Timers

JavaScript provides functions to execute code after a delay or repeatedly at a given interval.

setTimeout():

Executes a function once after a specified delay.

setTimeout(() => {
  console.log('This runs after 2 seconds');
}, 2000);  // 2 seconds = 2000 milliseconds

setInterval():

Executes a function repeatedly at a given interval.

const intervalId = setInterval(() => {
  console.log('This runs every 2 seconds');
}, 2000);

// To stop the interval, use clearInterval
clearInterval(intervalId);

10. AJAX and Fetch API

AJAX (Asynchronous JavaScript and XML) allows you to send HTTP requests and receive responses without refreshing the page. The Fetch API is a modern, promise-based way to make HTTP requests.

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Summary

In this chapter, we explored how JavaScript interacts with the browser:

  • The DOM allows you to manipulate the structure and content of the web page.

  • Events help handle user interactions like clicks and key presses.

  • Browser storage mechanisms like local storage and session storage allow you to save data.

  • Cookies are useful for tracking sessions and storing small pieces of data.

  • The window and document objects give access to various browser-specific properties and methods.

Chapter 10: JavaScript Engines and Runtime


JavaScript engines and runtimes are fundamental to how JavaScript code is executed. JavaScript is not a compiled language; instead, it is interpreted or just-in-time compiled by JavaScript engines. Understanding how JavaScript engines and runtime environments work is essential for grasping how JavaScript interacts with different platforms, such as browsers or Node.js.

In this chapter, we’ll explore what a JavaScript engine is, how the runtime environment works, and what happens behind the scenes when you run JavaScript code.


1. What is a JavaScript Engine?

A JavaScript engine is a program or an interpreter that reads and executes JavaScript code. Every modern browser has its own JavaScript engine to run JavaScript code.

  • V8: Developed by Google, V8 is used in Google Chrome and Node.js.

  • SpiderMonkey: Developed by Mozilla, SpiderMonkey is used in Firefox.

  • JavaScriptCore (Nitro): Developed by Apple, used in Safari.

  • Chakra: Developed by Microsoft, used in the old versions of Edge (before it switched to Chromium).

The primary job of a JavaScript engine is to:

  • Parse JavaScript code.

  • Compile it to machine code (using techniques like Just-in-Time compilation).

  • Execute the machine code.

How the V8 Engine Works:

The V8 engine compiles JavaScript to native machine code using a Just-in-Time (JIT) compiler. Here’s a simplified view of how the V8 engine works:

  1. Parsing: The JavaScript source code is parsed into an Abstract Syntax Tree (AST).

  2. Interpreter: V8 uses an interpreter called "Ignition" to execute the AST.

  3. Compilation: Frequently executed code is compiled to highly optimized machine code using a JIT compiler called "TurboFan."

  4. Garbage Collection: V8 uses a garbage collector to reclaim memory no longer in use by objects that are no longer needed.


2. JavaScript Runtime Environment

The JavaScript runtime environment provides everything necessary for a JavaScript program to execute. It includes not only the JavaScript engine but also APIs, libraries, and runtime-specific features that allow JavaScript to interact with the surrounding environment (e.g., browser, server).

Key Components of the Runtime Environment:

  1. JavaScript Engine: Executes JavaScript code (e.g., V8 engine).

  2. Call Stack: Manages the function execution order.

  3. Heap: A memory storage space for dynamically allocated memory (e.g., objects, arrays).

  4. Web APIs (in browsers): Provide APIs for interacting with browser-specific features like DOM, Fetch, and timers.

  5. Event Loop: Manages asynchronous callbacks and ensures that functions are executed in order.

  6. Callback Queue: A queue where asynchronous callbacks (like setTimeout) are stored before being executed.


3. The Call Stack

The call stack is a mechanism that tracks the function calls in a JavaScript program. When a function is called, it is added to the stack, and when it finishes, it is removed from the stack.

Example:

function first() {
  console.log('First function');
}

function second() {
  first();
  console.log('Second function');
}

second();

Call Stack Process:

  1. second() is added to the call stack.

  2. Inside second(), first() is called, so first() is added to the stack.

  3. first() finishes, so it is removed from the stack.

  4. second() finishes and is removed from the stack.


4. The Event Loop and Asynchronous Execution

JavaScript is single-threaded, meaning it can only execute one piece of code at a time. However, it handles asynchronous tasks using the event loop, allowing the execution of callbacks after the synchronous code is complete.

Event Loop:

  • The event loop continuously checks if the call stack is empty. If the stack is empty, it processes tasks from the callback queue (e.g., setTimeout callbacks, promises).

  • Web APIs (in browsers) or Node.js APIs handle asynchronous tasks like HTTP requests, timers, or I/O operations. Once these tasks are completed, the results (callbacks) are added to the callback queue, and the event loop pushes them to the call stack when it's free.

Example:

console.log('Start');

setTimeout(() => {
  console.log('This runs later');
}, 2000);

console.log('End');

Output:

Start
End
This runs later

Explanation: The synchronous code (console.log('Start') and console.log('End')) is executed first. The setTimeout callback is added to the callback queue and executed later by the event loop.


5. JavaScript in the Browser

In a browser environment, JavaScript has access to browser-specific features through the Web APIs. These APIs are not part of the JavaScript language itself, but they are provided by the browser runtime to allow JavaScript to interact with the web page, make network requests, or handle user input.

Key Web APIs:

  • DOM (Document Object Model): Manipulate HTML content and structure.

  • Fetch API: Make network requests (e.g., fetch data from an API).

  • Timers: setTimeout and setInterval allow you to schedule code execution.

  • LocalStorage/SessionStorage: Store data in the browser.

Example of Fetch API in the browser:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

6. JavaScript in Node.js

Node.js is a server-side JavaScript runtime built on the V8 engine. It provides access to server-side features like the file system, networking, and child processes.

Key Node.js Features:

  • Modules: Organize code into reusable files (require and import/export).

  • File System: Read/write files on the server.

  • HTTP: Handle incoming HTTP requests and send responses.

Example in Node.js:

const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err);
  } else {
    console.log('File content:', data);
  }
});

7. Just-in-Time Compilation (JIT)

JavaScript engines like V8 use Just-in-Time (JIT) compilation to optimize performance. Instead of interpreting JavaScript code line by line, the engine compiles frequently executed code into machine code, which runs faster.

JIT Compilation Process:

  1. Parsing: JavaScript code is parsed into an Abstract Syntax Tree (AST).

  2. Interpreter: The interpreter runs the code in its parsed form.

  3. Profiling: The engine monitors code execution and identifies "hot" (frequently run) functions.

  4. Optimization: "Hot" code is optimized and compiled into machine code.

  5. Deoptimization: If assumptions about the code (e.g., types) are incorrect, the engine can revert back to the interpreted code.


8. Garbage Collection

JavaScript engines use garbage collection to automatically reclaim memory that is no longer in use by the program. This helps prevent memory leaks, where memory is consumed by unused objects.

How Garbage Collection Works:

  • Most JavaScript engines use a form of reference counting or mark-and-sweep algorithm to track objects in memory.

  • If an object is no longer referenced, it is considered unreachable and can be cleaned up by the garbage collector.


Summary

In this chapter, we covered the internal workings of JavaScript engines and runtime environments:

  • JavaScript engines (like V8) execute JavaScript code by parsing, interpreting, and compiling it.

  • The JavaScript runtime environment includes the engine, call stack, heap, event loop, and Web APIs (in the browser).

  • The call stack manages function calls, while the event loop ensures asynchronous code is executed after synchronous tasks complete.

  • Just-in-Time (JIT) compilation optimizes frequently executed code.

  • Garbage collection automatically manages memory, cleaning up unused objects.

Understanding these concepts helps you write more efficient JavaScript and gives you insight into how the language interacts with different environments (e.g., browsers or Node.js).