0% found this document useful (0 votes)
1 views44 pages

Complete JavaScript Tutorial (Beginner to Advanced)

This comprehensive JavaScript tutorial covers topics from basic concepts to advanced techniques, including variables, data types, DOM manipulation, asynchronous programming, and ES6 features. It provides practical examples and best practices for both front-end and back-end development. The tutorial is structured to allow learners to jump to relevant sections based on their skill level.

Uploaded by

Vishal Rokkam
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views44 pages

Complete JavaScript Tutorial (Beginner to Advanced)

This comprehensive JavaScript tutorial covers topics from basic concepts to advanced techniques, including variables, data types, DOM manipulation, asynchronous programming, and ES6 features. It provides practical examples and best practices for both front-end and back-end development. The tutorial is structured to allow learners to jump to relevant sections based on their skill level.

Uploaded by

Vishal Rokkam
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 44

JavaScript Tutorial: From Beginner to Advanced

This tutorial covers JavaScript from the very basics to advanced concepts. It's
designed to be comprehensive, but you can jump to sections that are most relevant
to you.

Table of Contents
1. Introduction to JavaScript
2. Basic Concepts
○ Variables
○ Data Types
○ Operators
○ Control Flow
○ Functions
3. Objects and Arrays
○ Objects
○ Arrays
○ Common Array Methods
4. DOM Manipulation
○ Selecting Elements
○ Modifying Elements
○ Event Handling
5. Asynchronous JavaScript
○ Callbacks
○ Promises
○ Async/Await
○ Fetch API
6. Advanced Concepts
○ Closures
○ Prototype and Inheritance
○ Classes
○ Error Handling
○ Modules
○ Iterators and Generators
○ Metaprogramming (Proxy and Reflect)
7. ES6 and Beyond
○ Let and Const
○ Arrow Functions
○ Destructuring
○ Spread and Rest Operators
○ Template Literals
○ Enhanced Object Literals
○ Symbol
○ Set and Map
8. JavaScript in the Browser
○ The Window Object
○ The Document Object
○ Browser Storage (Cookies, Local Storage, Session Storage)
○ Web Workers
9. Best Practices and Further Learning

1. Introduction to JavaScript
JavaScript is a versatile, high-level programming language. It's one of the core
technologies of the web, along with HTML and CSS. Here's what you need to know:
● What is JavaScript? It's a scripting language, meaning it's interpreted (not
compiled). It was initially designed to make web pages interactive.
● What can you do with JavaScript?
○ Front-end web development: Create interactive web pages, handle user
input, and dynamically modify content.
○ Back-end web development: (with Node.js) Build server-side applications,
handle databases, and create APIs.
○ Mobile app development: (with frameworks like React Native) Build apps for
iOS and Android.
○ Desktop app development: (with frameworks like Electron) Build cross-
platform desktop applications.
○ Game development: Create browser-based games.
○ And much more: The possibilities are vast, including machine learning, data
visualization, and more.
● How to run JavaScript:
○ In a browser: Use the browser's developer console (right-click on a page,
select "Inspect" or "Inspect Element", and go to the "Console" tab). You can
also embed JavaScript in an HTML file using the <script> tag.
○ With Node.js: Install Node.js, and you can run JavaScript files from your
command line using node your-file.js.
Here's a very basic HTML page with JavaScript:
<!DOCTYPE html>
<html>
<head>
<title>My First Webpage</title>
</head>
<body>
<h1>Hello, World!</h1>
<button id="myButton">Click Me</button>

<script>
// This is JavaScript code
document.getElementById("myButton").addEventListener("click", function() {
alert("Hello!");
});
</script>
</body>
</html>

Explanation:
● <!DOCTYPE html>: Declares the document type as HTML5.
● <html>, <head>, and <body>: Basic HTML structure.
● <script>: This tag contains the JavaScript code.
● document.getElementById("myButton"): Selects the HTML element with the ID
"myButton".
● .addEventListener("click", function() { ... }): Adds a listener to the button. When
the button is clicked, the function inside {} will run.
● alert("Hello!"): Displays a pop-up box with the message "Hello!".
2. Basic Concepts
Let's dive into the fundamental building blocks of JavaScript.

Variables
Variables are used to store data. You can think of them as containers that hold values.
In modern JavaScript, we declare variables using let or const. var is an older way, but
it's best to avoid it in new code.
● let: Declares a variable that can be reassigned.
● const: Declares a variable that cannot be reassigned (it's constant). However, if a
const variable holds an object or array, you can modify the properties or
elements, but you can't assign a completely new object or array to it.
● var: (Don't use this in modern code) Declares a variable. It has some quirks in
how it handles scope, which can lead to unexpected behavior.
let message = "Hello!"; // Declare a variable named 'message' and assign the string
"Hello!"
console.log(message); // Output: Hello!

message = "Goodbye!"; // Reassign a new value to 'message'


console.log(message); // Output: Goodbye!

const pi = 3.14159; // Declare a constant variable 'pi'


console.log(pi); // Output: 3.14159

// pi = 3.14; // This would cause an error: Assignment to constant variable.

const myObject = { name: "Alice", age: 30 };


myObject.age = 31; // You can modify properties of an object declared with const
console.log(myObject); // Output: { name: "Alice", age: 31 }

// myObject = { name: "Bob", age: 25 }; // Error: Can't reassign the entire object

Key Points:
● Declaration vs. Assignment: let x; is a declaration (you're creating the variable).
x = 10; is an assignment (you're giving the variable a value). You can combine
them: let x = 10;
● Naming: Variable names should be descriptive and follow these rules:
○ Start with a letter, underscore (_), or dollar sign ($).
○ Cannot start with a number.
○ Can contain letters, numbers, underscores, and dollar signs.
○ Are case-sensitive (myVar is different from myvar).
○ Cannot be reserved keywords (like if, for, function).
● camelCase: It's common practice to use camelCase for variable names (e.g.,
myVariableName, numberOfStudents).
Data Types
JavaScript has several built-in data types:
● Primitive Data Types: These are the basic, immutable (cannot be changed
directly) data types.
○ String: Represents text. Enclosed in single quotes ('...'), double quotes ("..."),
or backticks (`...`).
○ Number: Represents numeric values, including integers and floating-point
numbers.
○ BigInt: Represents integers of arbitrary length.
○ Boolean: Represents a logical value: true or false.
○ Undefined: Represents a variable that has been declared but has not yet
been assigned a value.
○ Null: Represents the intentional absence of a value.
○ Symbol: Represents a unique identifier (ES6 feature, we'll cover this later).
● Object (Reference Data Type): A collection of key-value pairs. Objects are
mutable (their properties can be changed).
○ Object: A general container for data.
○ Array: A special type of object for storing ordered lists of values.
○ Function: Technically, functions are also objects in JavaScript.

// String
let name = "John Doe";
let greeting = `Hello, ${name}!`; // Template literal (ES6)

// Number
let age = 30;
let price = 19.99;

// BigInt
const largeNumber = 12345678901234567890123456789012345n;

// Boolean
let isAdult = true;
let isRaining = false;

// Undefined
let city; // city is declared, but not assigned a value
console.log(city); // Output: undefined
// Null
let emptyValue = null; // Represents the intentional absence of a value

// Symbol (Introduced in ES6, more later)


const uniqueKey = Symbol("description");

// Object
let person = {
name: "Alice",
age: 25,
city: "New York"
};

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

// Function (we'll cover these in detail later)


function greet(name) {
return "Hello, " + name + "!";
}

Key Points:
● Template Literals: Backticks (`...`) allow you to embed variables directly within
strings using ${variableName}.
● Typeof Operator: You can use the typeof operator to determine the data type of
a value or variable:
console.log(typeof name); // Output: "string"
console.log(typeof age); // Output: "number"
console.log(typeof isAdult); // Output: "boolean"
console.log(typeof city); // Output: "undefined"
console.log(typeof emptyValue); // Output: "object" (Note: This is a quirk of
JavaScript)
console.log(typeof person); // Output: "object"
console.log(typeof numbers); // Output: "object"
console.log(typeof greet); // Output: "function"
console.log(typeof largeNumber); //Output: "bigint"
Operators
Operators are symbols that perform operations on values (called operands). Here's a
breakdown:
● Arithmetic Operators: Perform mathematical calculations.
○ +: Addition
○ -: Subtraction
○ *: Multiplication
○ /: Division
○ %: Modulo (remainder after division)
○ **: Exponentiation (ES7)
○ ++: Increment (add 1)
○ --: Decrement (subtract 1)
● Assignment Operators: Assign values to variables.
○ =: Simple assignment
○ +=: Add and assign (e.g., x += 5 is the same as x = x + 5)
○ -=: Subtract and assign
○ *=: Multiply and assign
○ /=: Divide and assign
○ %=: Modulo and assign
○ **=: Exponentiation and assign
● Comparison Operators: Compare values and return a Boolean result (true or
false).
○ ==: Equal to (values are compared after type coercion)
○ ===: Strict equal to (values and types must be the same)
○ !=: Not equal to (values are compared after type coercion)
○ !==: Strict not equal to (values and types must be different)
○ >: Greater than
○ <: Less than
○ >=: Greater than or equal to
○ <=: Less than or equal to
● Logical Operators: Combine or modify Boolean values.
○ &&: Logical AND (returns true if both operands are true)
○ ||: Logical OR (returns true if at least one operand is true)
○ !: Logical NOT (reverses the Boolean value)
● String Operators:
○ +: Concatenation (joins strings together)
● Other Operators:
○ ?:: Conditional (ternary) operator
○ ,: Comma operator
○ typeof: Returns the data type of a value
○ instanceof: Checks if an object is an instance of a constructor
○ in: Checks if a property exists in an object
// Arithmetic Operators
let x = 10;
let y = 5;

console.log(x + y); // Output: 15


console.log(x - y); // Output: 5
console.log(x * y); // Output: 50
console.log(x / y); // Output: 2
console.log(x % y); // Output: 0
console.log(x ** y); // Output: 100000 (10 to the power of 5)
console.log(x++); // Output: 10 (x is incremented *after* the value is used)
console.log(x); // Output: 11
console.log(++y); // Output: 6 (y is incremented *before* the value is used)
console.log(y); // Output: 6

// Assignment Operators
let a = 20;
a += 5; // a = a + 5
console.log(a); // Output: 25

// Comparison Operators
console.log(x == "11"); // Output: true (type coercion: "11" is converted to 11)
console.log(x === "11"); // Output: false (strict comparison: types are different)
console.log(x != y); // Output: true
console.log(x !== y); // Output: true
console.log(x > y); // Output: true
console.log(x < y); // Output: false
console.log(x >= y); // Output: true
console.log(x <= y); // Output: false

// Logical Operators
let p = true;
let q = false;

console.log(p && q); // Output: false


console.log(p || q); // Output: true
console.log(!p); // Output: false

// String Operator
let firstName = "John";
let lastName = "Doe";
let fullName = firstName + " " + lastName;
console.log(fullName); // Output: John Doe

// Conditional (Ternary) Operator


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

// typeof Operator
console.log(typeof x); //Output: "number"

// in operator
const myCar = {make: 'Honda', model: 'Accord', year: 1998};
console.log('make' in myCar); // Output: true
console.log('color' in myCar); // Output: false

Key Points:
● Loose vs. Strict Equality: == compares values after converting them to a
common type (type coercion). === compares values without type coercion. It's
almost always better to use === to avoid unexpected behavior. The same applies
to != and !==.
● Operator Precedence: Operators have a specific order of precedence (like in
math). For example, multiplication and division are performed before addition and
subtraction. Use parentheses () to control the order of operations if needed.
Control Flow
Control flow statements determine the order in which code is executed. They allow
you to make decisions and repeat actions.
● Conditional Statements: Execute different blocks of code based on conditions.
○ if statement: Executes a block of code if a condition is true.
○ if...else statement: Executes one block of code if a condition is true, and
another block if it's false.
○ if...else if...else statement: Chains multiple conditions together.
○ switch statement: Selects one of several code blocks to execute based on
the value of an expression.
● Loop Statements: Repeat a block of code multiple times.
○ for loop: Executes a block of code a specific number of times.
○ while loop: Executes a block of code as long as a condition is true.
○ do...while loop: Similar to while, but the code block is executed at least once.
○ for...in loop: Iterates over the properties of an object.
○ for...of loop: Iterates over iterable objects (like arrays, strings, maps, sets)
(ES6).
● Jump Statements:
○ break: Terminates a loop or switch statement.
○ continue: Skips the rest of the current iteration of a loop and continues with
the next iteration.
// Conditional Statements
let age = 20;

if (age >= 18) {


console.log("You are an adult.");
} else {
console.log("You are not an adult.");
}

let grade = "B";


if (grade === "A") {
console.log("Excellent!");
} else if (grade === "B") {
console.log("Good job!");
} else if (grade === "C") {
console.log("Keep practicing.");
} else {
console.log("Needs improvement.");
}
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");
}

// Loop Statements
for (let i = 0; i < 5; i++) {
console.log("Iteration:", i);
}

let count = 0;
while (count < 3) {
console.log("Count:", count);
count++;
}

let j = 0;
do {
console.log("Do While:", j);
j++;
} while (j < 3);

let person = { name: "Alice", age: 30 };


for (let key in person) {
console.log(key + ": " + person[key]);
}
let numbers = [10, 20, 30, 40, 50];
for (let number of numbers) {
console.log("Number:", number);
}

// Jump Statements
for (let i = 0; i < 10; i++) {
if (i === 3) {
break; // Exit the loop when i is 3
}
console.log("Break:", i);
}

for (let i = 0; i < 5; i++) {


if (i === 2) {
continue; // Skip iteration when i is 2
}
console.log("Continue:", i);
}

Key Points:
● Truthy and Falsy: In JavaScript, values are not strictly true or false. Some values
are considered "truthy" (evaluate to true in a Boolean context), and others are
"falsy" (evaluate to false).
○ Falsy values: false, 0, "" (empty string), null, undefined, NaN (Not a Number).
○ Truthy values: All values that are not falsy (e.g., true, 1, "hello", [ ] (empty
array), { } (empty object)).
● Short-circuiting: Logical operators && and || can exhibit short-circuiting
behavior.
○ &&: If the first operand is false, the second operand is not evaluated, and the
result is false.
○ ||: If the first operand is true, the second operand is not evaluated, and the
result is true.
Functions
Functions are reusable blocks of code that perform a specific task. They are essential
for organizing your code and making it more modular.
● Function Declaration: Defines a function with a name.
● Function Expression: Defines a function as part of an expression (often
assigned to a variable).
● Parameters and Arguments: Functions can accept input values called
parameters. When you call a function, you provide the actual values, which are
called arguments.
● Return Value: Functions can optionally return a value using the return statement.
If a function doesn't have a return statement, or if the return statement doesn't
specify a value, the function returns undefined.
● Scope: The scope of a variable determines where it can be accessed. Variables
declared with let and const have block scope (they are only accessible within the
code block where they're defined). Variables declared with var have function
scope (they are accessible within the entire function).
// Function Declaration
function greet(name) {
return "Hello, " + name + "!";
}

let message = greet("Alice");


console.log(message); // Output: Hello, Alice!

// Function Expression
const add = function(a, b) {
return a + b;
};

let sum = add(5, 3);


console.log(sum); // Output: 8

// Parameters and Arguments


function multiply(x, y) { // x and y are parameters
return x * y;
}

let product = multiply(4, 6); // 4 and 6 are arguments


console.log(product); // Output: 24
// Return Value
function getArea(length, width) {
let area = length * width;
return area;
}

let rectangleArea = getArea(10, 5);


console.log(rectangleArea); // Output: 50

function sayHello(name) {
console.log("Hello, " + name);
// No return statement, so the function implicitly returns undefined
}

let result = sayHello("Bob"); // Prints "Hello, Bob" to the console


console.log(result); // Output: undefined

// Scope
function myFunction() {
let localVar = "This is local"; // local variable
var varVar = "var variable";
const constVar = "const variable";
console.log(localVar);
console.log(varVar);
console.log(constVar);
}

myFunction();
// console.log(localVar); // Error: localVar is not defined outside myFunction
console.log(varVar); // Accessible, because var has function scope.
// console.log(constVar); // Error: constVar is not defined outside myFunction.

if (true) {
let blockVar = "Block variable";
var varVar2 = "var variable 2";
console.log(blockVar);
}
// console.log(blockVar); // Error: blockVar is not defined outside the if block
console.log(varVar2); // Accessible, because var has function scope

Key Points:
● Hoisting: Function declarations are hoisted, which means you can call the
function before it's actually defined in the code. Function expressions are not
hoisted (you'll get an error if you try to call them before they're defined). var
declarations are hoisted, but their assignments are not. let and const
declarations are also hoisted, but you cannot access them before their
declaration in the code, which will lead to a ReferenceError.
● First-Class Functions: In JavaScript, functions are first-class citizens. This
means you can:
○ Assign functions to variables.
○ Pass functions as arguments to other functions.
○ Return functions from other functions.
○ Store functions in data structures (like objects and arrays).
● Anonymous Functions: Functions without a name are called anonymous
functions. Function expressions often use anonymous functions.
● Immediately Invoked Function Expressions (IIFEs): A function expression that
is executed immediately after it's defined. Used to create a new scope and avoid
polluting the global scope.
(function() {
let message = "Hello from IIFE!";
console.log(message);
})(); // The () at the end immediately invokes the function

3. Objects and Arrays


Objects and arrays are fundamental data structures in JavaScript, used to store and
organize collections of data.#### Objects

An object is a collection of key-value pairs. The keys are typically strings (but can also
be symbols), and the values can be any data type (including other objects, arrays,
and functions). Objects are used to represent entities with properties and behaviors.
● Creating Objects:
○ Object literal: Using curly braces {}.
○ Constructor function: Using the new keyword with a constructor function
(older way, but still used).
○ Object.create(): Creates a new object with the specified prototype object
and properties.
○ Class (ES6): A more modern way to create objects (syntactic sugar over
constructor functions and prototypes).
● Accessing Properties:
○ Dot notation: objectName.propertyName
○ Bracket notation: objectName["propertyName"] (useful when property
names have spaces or special characters, or when the property name is a
variable).
● Modifying Properties:
○ Assign a new value to an existing property: objectName.propertyName =
newValue;
○ Add a new property: objectName.newPropertyName = value;
○ Delete a property: delete objectName.propertyName;
● Methods: Functions that are associated with an object are called methods. They
are defined as properties whose values are functions. Inside a method, you can
use the this keyword to refer to the object itself.
// Object literal
const person = {
name: "Alice",
age: 30,
city: "New York",
greet: function() {
console.log("Hello, my name is " + this.name);
}
};

console.log(person.name); // Output: Alice (dot notation)


console.log(person["age"]); // Output: 30 (bracket notation)
person.greet(); // Output: Hello, my name is Alice

person.age = 31; // Modify property


person.job = "Engineer"; // Add property
delete person.city; // Delete property
console.log(person);

// Constructor Function (older way)


function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.getDetails = function() {
return `${this.make} ${this.model} (${this.year})`;
}
}

const myCar = new Car("Honda", "Civic", 2020);


console.log(myCar.getDetails()); // Output: Honda Civic (2020)

// Object.create()
const animal = {
type: 'mammal',
describe() {
console.log(`This is a ${this.type}`);
}
};

const dog = Object.create(animal);


dog.type = 'dog';
dog.describe(); // Output: This is a dog

// Class (ES6) - Syntactic sugar over constructor functions and prototypes


class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person2 = new Person("Bob", 25);
person2.greet(); // Output: Hello, my name is Bob

// Accessing properties with variables


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

const keyName = "name";


console.log(person[keyName]); // Output: "Alice"

Key Points:
● this Keyword: Inside a method, this refers to the object that the method is called
on. The value of this depends on how the function is called.
● Object.keys(), Object.values(), Object.entries(): These methods provide ways
to get arrays of the keys, values, or key-value pairs of an object, respectively.
const myObject = { a: 1, b: 2, c: 3 };
console.log(Object.keys(myObject)); // Output: ["a", "b", "c"]
console.log(Object.values(myObject)); // Output: [1, 2, 3]
console.log(Object.entries(myObject)); // Output: [["a", 1], ["b", 2], ["c", 3]]

● Computed Property Names (ES6): You can use expressions within square
brackets when defining object properties.
const prefix = "user";
const user = {
[`${prefix}Name`]: "John", // Computed property name
[`${prefix}Age`]: 30
};
console.log(user); // Output: { userName: "John", userAge: 30 }

● Optional Chaining (ES2020): The optional chaining operator ?. allows you to


access nested object properties without having to check if each level of the
object exists. If a property in the chain is null or undefined, it returns undefined
instead of throwing an error.
const myObject = {
a: {
b: {
c: "Hello"
}
}
};

console.log(myObject.a?.b?.c); // Output: "Hello"


console.log(myObject.a?.b?.d); // Output: undefined (no error)
console.log(myObject.x?.y?.z); // Output: undefined

● Nullish Coalescing Operator (ES2020): The nullish coalescing operator ??


provides a way to provide a default value when a variable is null or undefined.
const value1 = null ?? 'default value'; // value1 is 'default value'
const value2 = undefined ?? 'another value'; // value2 is 'another value'
const value3 = 0 ?? 'zero value'; // value3 is 0
const value4 = '' ?? 'empty string'; // value4 is ''

Arrays
An array is an ordered list of values. The values in an array can be of any data type,
and you can even have arrays of arrays (multidimensional arrays). Arrays are a special
type of object in JavaScript, optimized for storing sequences of data.
● Creating Arrays:
○ Array literal: Using square brackets [].
○ new Array() constructor: (Less common, generally avoid)
● Accessing Elements:
○ Array elements are accessed using their index, which starts at 0. So, the first
element is at index 0, the second element is at index 1, and so on.
○ Use square brackets to access elements: arrayName[index]
● Modifying Elements:
○ Assign a new value to an existing element: arrayName[index] = newValue;
○ Add elements:
■ push(): Adds one or more elements to the end of the array.
■ unshift(): Adds one or more elements to the beginning of the array.
■ splice(): Adds or removes elements from any position in the array.
○ Remove elements:
■ pop(): Removes the last element from the array.
■ shift(): Removes the first element from the array.
■ splice(): Removes elements from any position in the array.
○ length property: The length property of an array returns the number of
elements in the array.
// Array literal
const fruits = ["apple", "banana", "orange"];

console.log(fruits[0]); // Output: apple


console.log(fruits[1]); // Output: banana

fruits[1] = "pear"; // Modify element


console.log(fruits); // Output: ["apple", "pear", "orange"]

fruits.push("grape"); // Add to end


console.log(fruits); // Output: ["apple", "pear", "orange", "grape"]

fruits.unshift("kiwi"); // Add to beginning


console.log(fruits); // Output: ["kiwi", "apple", "pear", "orange", "grape"]

fruits.splice(2, 0, "mango"); // Insert "mango" at index 2 (no elements removed)


console.log(fruits); // Output: ["kiwi", "apple", "mango", "pear", "orange", "grape"]

fruits.splice(3, 1); // Remove 1 element at index 3


console.log(fruits); // Output: ["kiwi", "apple", "mango", "orange", "grape"]

fruits.pop(); // Remove last element


console.log(fruits); // Output: ["kiwi", "apple", "mango", "orange"]

fruits.shift(); // Remove first element


console.log(fruits); // Output: ["apple", "mango", "orange"]

console.log(fruits.length); // Output: 3

// Using the constructor (less common)


const numbers = new Array(1, 2, 3, 4, 5);
console.log(numbers); // Output: [1, 2, 3, 4, 5]

const emptyArray = new Array(10); // Creates an array of length 10 with undefined


values
console.log(emptyArray); // Output: [undefined, undefined, undefined, undefined,
undefined, undefined, undefined, undefined, undefined, undefined]

Key Points:
● Arrays are zero-indexed.
● Arrays can hold values of different data types (e.g., [1, "hello", true, { name:
"John" }]).
● The length property is dynamic; it's automatically updated when you add or
remove elements.
● You can create multidimensional arrays (arrays of arrays):
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];

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

Common Array Methods


Arrays in JavaScript come with a rich set of built-in methods that make it easy to
manipulate and work with data. Here are some of the most commonly used ones:
● forEach(): Executes a provided function once for each array element.
● map(): Creates a new array with the results of calling a provided function on
every element in the calling array.
● filter(): Creates a new array with all elements that pass the test implemented by
the provided function.
● reduce(): Applies a function against an accumulator and each element in the
array (from left to right) to reduce it to a single value.
● find(): Returns the value of the first element in the array that satisfies the
provided testing function. Otherwise undefined is returned.
● findIndex(): Returns the index of the first element in the array that satisfies the
provided testing function. Otherwise, it returns -1.
● sort(): Sorts the elements of an array in place (modifies the original array) and
returns the sorted array.
● concat(): Returns a new array that is the concatenation of two or more arrays.
● slice(): Returns a new array containing a portion of the original array.
● splice(): Changes the contents of an array by removing or replacing existing
elements and/or adding new elements in place (modifies the original array). (We
saw this earlier for adding/removing elements).
● join(): Creates and returns a new string by concatenating all of the elements in
an array, separated by a specified separator string.
● reverse(): Reverses the order of the elements in an array in place (modifies the
original array).
● includes(): Determines whether an array includes a certain value among its
entries, returning true or false as appropriate.
● indexOf(): Returns the first index at which a given element can be found in the
array, or -1 if it is not present.
● lastIndexOf(): Returns the last index at which a given element can be found in
the array, or -1 if it is not present.
● isArray(): Determines whether the passed value is an Array.
const numbers = [1, 2, 3, 4, 5];

// forEach()
numbers.forEach(function(number) {
console.log(number * 2);
}); // Output: 2, 4, 6, 8, 10

// map()
const doubledNumbers = numbers.map(function(number) {
return number * 2;
});
console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
console.log(numbers); // Original array is unchanged

// filter()
const evenNumbers = numbers.filter(function(number) {
return number % 2 === 0;
});
console.log(evenNumbers); // Output: [2, 4]
console.log(numbers); // Original array is unchanged

// reduce()
const sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0); // 0 is the initial value of the accumulator
console.log(sum); // Output: 15
console.log(numbers); // Original array is unchanged

// find()
const firstEven = numbers.find(number => number % 2 === 0);
console.log(firstEven); // Output: 2

// findIndex()
const firstEvenIndex = numbers.findIndex(number => number % 2 === 0);
console.log(firstEvenIndex); // Output: 1

// sort()
const unsorted = [3, 1, 4, 1, 5, 9, 2, 6];
const sorted = unsorted.sort(); // Sorts alphabetically, which is not correct for
numbers
console.log(sorted); // Output: [1, 1, 2, 3, 4, 5, 6, 9]

const sortedNumbers = unsorted.sort(function(a, b) {


return a - b; // Sort in ascending order
});
console.log(sortedNumbers); // Output: [1, 1, 2, 3, 4, 5, 6, 9]

const sortedDescending = unsorted.sort((a, b) => b - a); // Sort descending


console.log(sortedDescending);

// concat()
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const combinedArray = array1.concat(array2);
console.log(combinedArray); // Output: [1, 2, 3, 4, 5, 6]

// slice()
const slicedArray = numbers.slice(1, 4); // Returns elements from index 1 up to (but
not including) index 4
console.log(slicedArray); // Output: [2, 3, 4]
console.log(numbers); // Original array is unchanged

// splice() - We saw this earlier for adding/removing. It modifies the original array.
const months = ["Jan", "Feb", "Mar", "Apr", "May"];
months.splice(2, 1, "March"); // Replace 1 element at index 2 with "March"
console.log(months); // Output: ["Jan", "Feb", "March", "Apr", "May"]

// join()
const joinedString = numbers.join(", ");
console.log(joinedString); // Output: "1, 2, 3, 4, 5"

// reverse()
const reversedNumbers = numbers.reverse(); // Modifies the original array
console.log(reversedNumbers); // Output: [5, 4, 3, 2, 1]
console.log(numbers); // Original array is now reversed

// includes()
console.log(numbers.includes(3)); // Output: true
console.log(numbers.includes(10)); // Output: false

// indexOf()
console.log(numbers.indexOf(3)); // Output: 2
console.log(numbers.indexOf(10)); // Output: -1

// lastIndexOf()
const arr = [2, 5, 9, 5];
console.log(arr.lastIndexOf(5)); // Output: 3
console.log(arr.lastIndexOf(7)); // Output: -1

// isArray()
console.log(Array.isArray(numbers)); // Output: true
console.log(Array.isArray({})); // Output: false

Key Points:
● Immutable vs. Mutable Methods: It's important to distinguish between array
methods that mutate (modify) the original array and those that return a new array
without changing the original. For example:
○ Mutating: push(), pop(), shift(), unshift(), splice(), sort(), reverse()
○ Non-mutating: map(), filter(), reduce(), concat(), slice(), join()
● Chaining: Many array methods return a new array, which allows you to chain
them together to perform multiple operations in a concise way.
const result = numbers
.filter(num => num % 2 !== 0) // Filter out even numbers: [1, 3, 5]
.map(num => num * 2) // Double each odd number: [2, 6, 10]
.reduce((acc, curr) => acc + curr, 0); // Sum the doubled odd numbers: 18

console.log(result); // Output: 18

● Iterating over arrays: Besides forEach, for...of loop is the modern way to iterate
over arrays:
for (const number of numbers) {
console.log(number);
}

4. DOM Manipulation
The Document Object Model (DOM) is a programming interface for HTML documents.
It represents the structure of the document as a tree of objects, where each object
represents a part of the HTML document (e.g., an element, an attribute, or a text
node). JavaScript can use the DOM to access and manipulate HTML elements,
allowing you to dynamically change the content, structure, and style of a web page.
● The DOM Tree: The DOM represents the HTML document as a tree-like
structure.
○ The document object is the root of the DOM tree.
○ HTML elements are represented as objects called nodes.
○ Nodes have relationships with each other (parent, child, sibling).
● Selecting Elements: To manipulate an HTML element, you first need to select it.
The document object provides several methods for selecting elements:
○ document.getElementById(id): Selects the element with the specified ID. IDs
should be unique within a document.
○ document.getElementsByClassName(className): Selects all elements with
the specified class name. Returns an HTMLCollection (an array-like object).
○ document.getElementsByTagName(tagName): Selects all elements with the
specified tag name (e.g., "p", "div", "a"). Returns an HTMLCollection.
○ document.querySelector(selector): Selects the first element that matches the
specified CSS selector (e.g., "#myId", ".myClass", "p", "div > span").
○ document.querySelectorAll(selector): Selects all elements that match the
specified CSS selector. Returns a NodeList (an array-like object).
● Modifying Elements: Once you've selected an element, you can modify its
properties and content:
○ innerHTML: Gets or sets the HTML content of an element (can include HTML
tags).
○ textContent: Gets or sets the text content of an element (ignores HTML tags).
Use this when you are setting plain text to avoid security vulnerabilities.
○ style: Gets or sets the inline CSS styles of an element.
○ setAttribute(name, value): Sets the value of an attribute.
○ getAttribute(name): Gets the value of an attribute.
○ classList: Provides methods for working with the CSS classes of an element:
■ classList.add(className): Adds one or more classes.
■ classList.remove(className): Removes one or more classes.
■ classList.toggle(className): Toggles a class (adds it if it's not present,
removes it if it is).
■ classList.contains(className): Checks if the element has a specific class.
○ Creating new elements:
■ document.createElement(tagName): Creates a new HTML element.
■ document.createTextNode(text): Creates a new text node.
○ Adding and removing elements:
■ parentElement.appendChild(newElement): Appends a new element as the
last child of a parent element.
■ parentElement.insertBefore(newElement, referenceElement): Inserts a
new element before a reference element.
■ element.remove(): Removes the element from the DOM.
■ parentElement.removeChild(childElement): Removes a child element from
a parent element.
● Event Handling: Events are actions or occurrences that happen in the browser
(e.g., a user clicks a button, a page finishes loading, a form is submitted). Event
handling is the process of responding to these events.
○ addEventListener(eventType, callbackFunction): Attaches an event listener to
an element. When the specified event occurs on that element, the callback
function is executed.
○ Common event types:
■ click: A mouse click.
■ mouseover: The mouse pointer is moved onto an element.
■ mouseout: The mouse pointer is moved away from an element.
■ keydown: A key is pressed down.
■ keyup: A key is released.
■ submit: A form is submitted.
■ load: A page or an element (like an image) has finished loading.
■ change: The value of an input element has changed.
○ Event object: When an event occurs, an event object is created. This object
contains information about the event (e.g., the target element, the
coordinates of a mouse click, the key that was pressed). The event object is
passed as an argument to the callback function.
○ removeEventListener(eventType, callbackFunction): Detaches an event
listener from an element. It's important to remove listeners when you no
longer need them to prevent memory leaks.
○ preventDefault(): Prevents the default behavior of an event (e.g., preventing a
form from submitting and reloading the page, preventing a link from
navigating to a new page).
○ stopPropagation(): Stops the event from propagating (bubbling up) to parent
elements.
○ Event bubbling: Events propagate up the DOM tree from the target element
to its parent elements. This is called event bubbling.
○ Event capturing: Events propagate down the DOM tree. This is less common
than bubbling, and you need to specify it when adding the event listener:
element.addEventListener(eventType, callbackFunction, { capture: true })
<!DOCTYPE html>
<html>
<head>
<title>DOM Manipulation Example</title>
<style>
.highlight {
background-color: yellow;
font-weight: bold;
}
#myDiv {
width: 200px;
height: 100px;
background-color: lightblue;
border: 1px solid black;
padding: 10px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<h1 id="mainHeading">Welcome!</h1>
<p class="myParagraph">This is a paragraph.</p>
<p class="myParagraph">Another paragraph.</p>
<div id="myDiv">This is a div.</div>
<button id="myButton">Click Me</button>
<a href="https://www.example.com" id="myLink">Visit Example.com</a>

<script>
// Selecting Elements
const heading = document.getElementById("mainHeading");
console.log(heading);

const paragraphs = document.getElementsByClassName("myParagraph");


console.log(paragraphs); // Returns an HTMLCollection (array-like)

const firstParagraph = document.querySelector(".myParagraph");


console.log(firstParagraph);

const allParagraphs = document.querySelectorAll(".myParagraph");


console.log(allParagraphs); // Returns a NodeList (array-like)

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


const myButton = document.getElementById("myButton");
const myLink = document.getElementById("myLink");

// Modifying Elements
heading.innerHTML = "Welcome to My Page!"; // Change the heading text
heading.style.color = "blue"; // Change the heading color

paragraphs[0].textContent = "This is the first paragraph."; // Change textContent


paragraphs[1].classList.add("highlight"); // Add a CSS class

myDiv.style.backgroundColor = "lightgreen"; // Change background color


myDiv.style.width = "300px"; // Change width

myLink.setAttribute("href", "https://www.mozilla.org"); // Change link


myLink.setAttribute("target", "_blank"); // Open in new tab
const linkHref = myLink.getAttribute("href");
console.log(linkHref);

// Creating and Adding Elements


const newParagraph = document.createElement("p");
newParagraph.textContent = "This is a new paragraph added by JavaScript.";
document.body.appendChild(newParagraph); // Add to the end of the body

const newHeading = document.createElement("h2");


newHeading.textContent = "New Heading";
document.body.insertBefore(newHeading, heading); // Insert before heading

const anotherParagraph = document.createElement("p");


const paragraphText = document.createTextNode("This is another new
paragraph.");
anotherParagraph.appendChild(paragraphText);
document.body.appendChild(anotherParagraph);

// Removing Elements
// document.body.removeChild(newParagraph); // Remove the new paragraph
// newParagraph.remove();

// Event Handling
myButton.addEventListener("click", function(event) {
alert("Button clicked!");
console.log(event); // Log the event object
console.log(event.target); // Log the target element (the button)
heading.classList.toggle("highlight"); // Toggle class on heading
});
myLink.addEventListener("click", function(event) {
event.preventDefault(); // Prevent the link from navigating
console.log("Link clicked, but navigation prevented!");
});

paragraphs[0].addEventListener("mouseover", function() {
this.style.backgroundColor = "yellow"; // this refers to the paragraph
});

paragraphs[0].addEventListener("mouseout", function() {
this.style.backgroundColor = "";
});

document.addEventListener('keydown', (event) => {


console.log(`Key pressed: ${event.key}`);
});

// Event Bubbling Example


document.getElementById('myDiv').addEventListener('click', () => {
console.log('Div clicked');
});

document.body.addEventListener('click', () => {
console.log('Body clicked');
});

// Event capturing example:


window.addEventListener('click', () => {
console.log('Window clicked - capturing');
}, { capture: true });
document.body.addEventListener('click', () => {
console.log('Body clicked - capturing');
}, { capture: true });
document.getElementById('myDiv').addEventListener('click', () => {
console.log('Div clicked - capturing');
}, { capture: true });
</script>
</body>
</html>

Key Points:
● NodeList vs. HTMLCollection: Both are array-like objects, but they have some
differences. NodeList can contain any type of node (elements, text nodes, etc.),
while HTMLCollection only contains element nodes. Also, NodeList returned by
querySelectorAll is static (it doesn't update automatically if the DOM changes),
while HTMLCollection is live (it automatically updates when the DOM changes).
It's generally better to use querySelectorAll because it's more flexible and
consistent.
● Event Delegation: Instead of attaching event listeners to many individual
elements, you can attach a single listener to a parent element and use the
event.target property to determine which element triggered the event. This can
improve performance, especially when dealing with a large number of elements.
● DocumentFragment: If you need to make multiple changes to the DOM, it's
more efficient to use a DocumentFragment. A DocumentFragment is a
lightweight, virtual DOM node. You can add elements to the DocumentFragment,
and then append the entire DocumentFragment to the DOM. This reduces the
number of reflows and repaints, which can improve performance.
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const p = document.createElement("p");
p.textContent = "Paragraph " + (i + 1);
fragment.appendChild(p);
}
document.body.appendChild(fragment); // Only one reflow and repaint

5. Asynchronous JavaScript
JavaScript is single-threaded, which means it can only execute one operation at a
time. This can lead to problems when performing long-running operations, such as
fetching data from a server or reading a large file. If you perform these operations
synchronously (in the main thread), the browser will become unresponsive until the
operation is complete. To avoid this, JavaScript provides mechanisms for performing
asynchronous operations.
● Synchronous vs. Asynchronous:
○ Synchronous: Operations are executed one after another, in a blocking
manner. Each operation must complete before the next one can start.
○ Asynchronous: Operations can start and run independently of the main
thread. The program can continue to execute other code while the
asynchronous operation is in progress. When the asynchronous operation is
complete, a callback function is typically used to handle the result.
● The Event Loop: JavaScript uses an event loop to manage asynchronous
operations. The event loop continuously checks the message queue for tasks to
execute.
○ Call Stack: The call stack is where synchronous function calls are managed.
When a function is called, it's pushed onto the stack. When the function
returns, it's popped off the stack.
○ Message Queue (Task Queue): Asynchronous operations (like setTimeout,
fetch, and event handlers) don't go directly onto the call stack. Instead, they
are placed in the message queue. When the call stack is empty, the event
loop takes the first task from the message queue and pushes it onto the call
stack to be executed.
○ Web APIs: The browser provides Web APIs (like setTimeout, fetch, and DOM
event listeners) that handle the actual asynchronous operations. These APIs
run in the background, outside of the main JavaScript thread.
● Callbacks: A callback function is a function that is passed as an argument to
another function and is executed after the asynchronous operation is complete.
function doSomething(callback) {
setTimeout(function() {
console.log("Async operation complete");
callback("Result"); // Call the callback function with the result
}, 1000); // Simulate a 1-second delay
}

function handleResult(result) {
console.log("Result:", result);
}

doSomething(handleResult); // Pass handleResult as the callback function


console.log("Continuing..."); // This line executes *before* "Async operation
complete"

● Promises: Promises are a more modern way to handle asynchronous operations.


A Promise represents the eventual completion (or failure) of an asynchronous
operation and its resulting value. Promises provide a cleaner syntax and better
error handling than callbacks.
○ A Promise can be in one of three states:
■ Pending: The initial state; the operation has not yet completed.
■ Fulfilled (Resolved): The operation completed successfully, and the
Promise has a value.
■ Rejected: The operation failed, and the Promise has a reason (an error).
○ Creating a Promise: Use the new Promise() constructor, which takes a
function called the executor function. The executor function takes two
arguments: resolve and reject. You call resolve(value) to fulfill the Promise
with a value, or reject(reason) to reject the Promise with a reason.
○ Chaining Promises: Promises have a then() method that you use to specify
what to do when the Promise is fulfilled or rejected. then() returns a new
Promise, which allows you to chain multiple asynchronous operations
together in a sequence.
○ Error Handling: Use the catch() method to handle errors that occur during the
Promise chain. You can also use a second argument to then() to handle
rejections, but catch() is generally preferred.
○ Promise.all(): Takes an array of Promises and returns a new Promise that is
fulfilled when all of the Promises in the array are fulfilled, or rejected if any of
the Promises are rejected.
○ Promise.race(): Takes an array of Promises and returns a new Promise that is
fulfilled or rejected as soon as one of the Promises in the array is fulfilled or
rejected.
○ Promise.resolve(value): Returns a Promise object that is resolved with the
given value.
○ Promise.reject(reason): Returns a Promise object that is rejected with the
given reason.
function fetchData() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
const data = { name: "John", age: 30 };
// resolve(data); // Simulate success
reject("Failed to fetch data"); // Simulate an error
}, 1500);
});
}
fetchData()
.then(function(result) {
console.log("Data:", result);
return "Processed " + result.name; // You can return a value to the
next .then()
})
.then(function(processedResult) {
console.log("Processed Result:", processedResult);
})
.catch(function(error) {
console.error("Error:", error);
});

console.log("Fetching data..."); // This line executes before the Promise resolves


or rejects

// Promise.all()
const promise1 = Promise.resolve(1);
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 500));

Promise.all([promise1, promise2, promise3])


.then(results => console.log(results)) // Output: [1, 2, 3] after 500ms
.catch(error => console.error(error));

// Promise.race()
const racePromise1 = new Promise(resolve => setTimeout(() => resolve('one'),
500));
const racePromise2 = new Promise(resolve => setTimeout(() => resolve('two'),
100));

Promise.race([racePromise1, racePromise2])
.then(value => console.log(value)); // Output: "two"

● Async/Await: async and await are syntactic sugar over Promises, introduced in
ES2017. They provide a more concise and readable way to work with
asynchronous code, making it look more like synchronous code.
○ async function: A function declared with the async keyword. Async functions
always return a Promise. If you return a value from an async function, it will be
wrapped in a resolved Promise. If you throw an error, it will be wrapped in a
rejected Promise.
○ await operator: The await operator can only be used inside an async function.
It pauses the execution of the function until the Promise that is being awaited
is settled (either fulfilled or rejected). If the Promise is fulfilled, await returns
the fulfilled value. If the Promise is rejected, await throws an error (which you
can catch using a try...catch block).
async function fetchDataAsync() {
try {
console.log("Fetching data...");
const result = await new Promise(resolve => setTimeout(() => resolve({ name:
"Jane", age: 25 }), 1000));
console.log("Data:", result);
const processedResult = "Processed " + result.name;
console.log("Processed result:", processedResult);
return processedResult;
} catch (error) {
console.error("Error:", error);
throw error; // Re-throw the error to be caught by the caller, if needed
}
}

fetchDataAsync()
.then(finalResult => console.log("Final Result", finalResult))
.catch(error => console.log("Caught outside", error));

console.log("Continuing..."); // This line executes before the Promise resolves or


rejects

● Fetch API: The Fetch API provides a modern, Promise-based interface for
making HTTP requests (e.g., getting data from a server). It's a replacement for
the older XMLHttpRequest object.
○ fetch(url, options): The fetch() function takes a URL as its first argument and
an optional options object as its second argument. It returns a Promise that
resolves to the Response object representing the server's response.
○ Response object: The Response object provides methods for accessing the
response headers, status code, and body. To get the actual data from the
response body, you need to use one of the following methods:
■ response.text(): Returns a Promise that resolves to the response body as
a string.
■ response.json(): Returns a Promise that resolves to the response body
parsed as JSON.
■ response.blob(): Returns a Promise that resolves to the response body as
a Blob (a raw binary data object).
■ response.arrayBuffer(): Returns a Promise that resolves to the response
body as an ArrayBuffer.
○ Request headers: You can set request headers using the headers option in
the fetch() function.
○ HTTP methods: The fetch() function defaults to the GET method. To use other
HTTP methods (e.g., POST, PUT, DELETE), you need to specify the method
option.
○ Request body: To send data in the request body (e.g., with POST or PUT
requests), you need to use the body option. The data should be a string. If
you're sending JSON data, you need to stringify it using JSON.stringify().
// GET request
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // Parse the response body as JSON
})
.then(data => console.log(data))
.catch(error => console.error("Error:", error));

// POST request
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-type": "application/json; charset=UTF-8"
},
body: JSON.stringify({
title: "foo",
body: "bar",
userId: 1
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));

// Async/await with fetch


async function getPosts() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error", error);
}
}

getPosts();

Key Points:
● Error Handling: It's crucial to handle errors in asynchronous operations. With
callbacks, you typically use an error-first callback pattern (the first argument to
the callback is an error object). With Promises, you use .catch(). With
async/await, you use try...catch. The Fetch API's fetch() function only rejects for
network errors, not for HTTP errors (like 404 or 500). You need to check the
response.ok property in the then() block to handle HTTP errors.
● Chaining Asynchronous Operations: Promises and async/await make it much
easier to chain asynchronous operations together in a sequence, compared to
nested callbacks (which can lead to "callback hell").
● Non-Blocking: Asynchronous operations allow your program to remain
responsive while waiting for long-running tasks to complete.
● Microtask Queue: Promises use a microtask queue, which has higher priority
than the message queue. This means that Promises are resolved before other
asynchronous tasks (like setTimeout callbacks).
● setImmediate() and process.nextTick() (Node.js): In Node.js, there are
additional ways to schedule asynchronous tasks:
○ setImmediate(callback): Schedules a callback function to be executed in the
next iteration of the event loop.
○ process.nextTick(callback): Schedules a callback function to be executed at
the end of the current event loop iteration, before any tasks in the message
queue. process.nextTick() callbacks are executed before setTimeout or
setImmediate callbacks.
6. Advanced Concepts
Once you have a solid understanding of the basics, you can delve into more advanced
JavaScript concepts. These concepts can help you write more efficient, maintainable,
and powerful code.

Closures
A closure is a function that "remembers" its lexical scope even when the function is
executed outside that scope. In other words, a closure allows a function to access
variables from its outer (enclosing) function's scope, even after the outer function has
finished executing.
● Lexical Scope: Lexical scope means that the scope of a variable is determined
by its position in the source code. Inner functions have access to variables
declared in their outer functions.
● How Closures Work: When a function is declared, it creates a closure over the
variables in its surrounding scope. These variables are stored in the function's
[[Environment]] record. When the function is executed, it can access these
variables.
● Uses of Closures:
○ Data hiding and encapsulation: Closures can be used to create private
variables and methods, preventing them from being accessed from outside
the function.
○ Creating function factories: Functions that return other functions, where
the returned functions "remember" the arguments passed to the factory
function.
○ Maintaining state in asynchronous operations: Closures can be used to
preserve variables across asynchronous callbacks.
function outerFunction(outerVar) {
const innerVar = "Inner variable";

function innerFunction() {
console.log("Outer variable:", outerVar);
console.log("Inner variable:", innerVar);
}

return innerFunction;
}
const myClosure = outerFunction("Hello from outer"); // myClosure now holds a
reference to innerFunction, and the closure allows it to access outerVar
myClosure(); // Output:
// Outer variable: Hello from outer
// Inner variable: Inner variable

function createCounter() {
let count = 0; // Private variable

return {
increment: function() {
count++;
console.log("Count:", count);
},
decrement: function() {
count--;
console.log("Count:", count);
},
getCount: function() {
return count;
}
};
}

const counter = createCounter();


counter.increment(); // Output: Count: 1
counter.increment(); // Output: Count: 2
counter.decrement(); // Output: Count: 1
console.log(counter.getCount()); // Output: 1
// console.log(counter.count); // Error: count is not accessible from outside

function sayHelloDelayed(name, delay) {


setTimeout(function() {
console.log("Hello, " + name + " after " + delay + "ms");
}, delay);
}
sayHelloDelayed("Alice", 1000); // Closure over "Alice" and 1000
sayHelloDelayed("Bob", 500); // Closure over "Bob" and 500

Key Points:
● Closures are created when a function is declared, not when it's called.
● Closures can lead to memory leaks if you're not careful. If a closure references a
large object, that object will not be garbage collected until the closure itself is no
longer reachable.
● Closures are a powerful feature of JavaScript, but they can be tricky to
understand at first. It's important to visualize how the scope chain works and how
functions retain access to variables from their surrounding scopes.
Prototype and Inheritance
JavaScript is a prototype-based language, which means that objects inherit
properties and methods from other objects via a mechanism called the prototype
chain. This is different from classical inheritance, which is used in languages like Java
and C++.
● Prototypes: Every function in JavaScript (except for arrow functions, which don't
have their own this) has a prototype property. The prototype property is an
object. When you create an object using the new keyword with a constructor
function, the new object's internal [[Prototype]] property (accessible via
Object.getPrototypeOf(obj) or __proto__ - but the latter is deprecated) is set to
the constructor function's prototype object.
● Prototype Chain: When you try to access a property on an object, JavaScript
first looks for the property on the object itself. If it doesn't find the property, it
then looks for the property on the object's prototype. If it still doesn't find the
property, it continues to search up the prototype chain (the chain of prototypes)
until it reaches the end of the chain (which is null). This is how inheritance works
in JavaScript.
● Object.create(): The Object.create() method creates a new object with the
specified prototype object and properties. This provides a more flexible way to
create objects with specific prototypes.
● Constructor Functions: Constructor functions are functions that are designed
to be used with the new keyword to create objects. The this keyword inside a
constructor function refers to the newly created object. Constructor functions
are used to initialize the properties of the new object.
// Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}

// Add a method to the Person prototype


Person.prototype.greet = function() {
console.log("Hello, my name is " + this.name);
};

const person1 = new Person("Alice", 30);


const person2 = new Person("Bob", 25);

person1.greet(); // Output: Hello, my name is Alice


person2.greet(); // Output: Hello, my name is Bob

console.log(person1.name); // Accessing a property on the object itself


console.log(person1.age);
console.log(person1.__proto__ === Person.prototype); // true (deprecated way)
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true

// Using Object.create()
const animal = {
type: "animal",
eat: function() {
console.log("Eating...");
}
};

const dog = Object.create(animal);


dog.name = "Fido";
dog.bark = function() {
console.log("Woof!");
};
dog.eat(); // Inherited from animal
dog.bark();
console.log(dog.type); // Inherited from animal

const cat = Object.create(animal, {


name: {
value: "Whiskers",
writable: true,
enumerable: true,
configurable: true
},
breed: {
value: "Siamese"
}
});

console.log(cat.name); // "Whiskers"
console.log(cat.breed); // "Siamese"
cat.eat();

Key Points:
● Every object in JavaScript inherits from Object.prototype (except for objects
created with Object.create(null), which have no prototype).
● When you define a method on a constructor function's prototype, all objects
created with that constructor function share the same method. This is more
efficient than defining the method directly on each object, as it saves memory.
● You can add properties and methods to built-in object prototypes (like
Array.prototype or String.prototype), but it's generally not recommended, as it
can lead to conflicts if other libraries or code also modify the same prototypes. If
you do, use caution and check if the property or method already exists before
adding it.
if (!Array.prototype.myMap) { // Check if myMap already exists
Array.prototype.myMap = function(callback) {
const newArray = [];
for (let i = 0; i < this.length; i++) {
newArray.push(callback(this[i], i, this));
}
return newArray;
};
}

const numbers = [1, 2, 3];


const doubled = numbers.myMap(num => num * 2);
console.log(doubled); // [2, 4, 6]

Classes
Classes, introduced in ES6, provide a more structured and familiar way to create
objects and deal with inheritance in JavaScript. However, it's important to understand
that classes in JavaScript are syntactic sugar over the existing prototype-based
inheritance. They don't introduce a new inheritance model; they just provide a more
convenient syntax for working with prototypes.
● Class Declaration: Use the class keyword to declare a class.
● Constructor: The constructor() method is a special method within a class that is
called when you create a new object using the new keyword. It's used to initialize
the properties of the object.
● Methods: You define methods inside the class body. Methods are added to the
prototype of the class.
● Inheritance: Use the extends keyword to create a subclass (derived class) that
inherits from a superclass (base class).
● super(): Inside the constructor of a subclass, you must call super() before
accessing this. super() calls the constructor of the superclass and initializes the
this context. You can also use super to call methods on the superclass.
● static Methods: Static methods are defined on the class itself, not on the
prototype. You call them directly on the class (e.g., ClassName.staticMethod()),
not on instances of the class.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}

greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy