In an AI-driven world, a strong foundation in core technologies is essential. To keep my skills sharp, I'm starting a regular blog series covering the tech stack I use. Welcome to the first post in my Next.js journey, where we'll start by mastering the JavaScript fundamentals.
JavaScript
Index
- Index
- Foundations
- Operators and Logic
- Strings
- Control Flow
- Arrays
- Functions and Scope
- Modern ES6+ Features
- Objects
- Classes and OOP
- Built-in Objects and Web APIs
- Document Object Model (DOM)
- Asynchronous JavaScript
- Modules (Import and Export)
Foundations
How to Install JavaScript
- We don't install javascript. If we have any internet browser, there is support for JavaScript there.
- Chrome/Safari/Firefox all have javascript support.
- Along with browsers we can also use platforms like codepen.io or platforms like scrimba.com
Putting Comments in the Code
- To put comment in the code we use
//
let val = "apple" // It is a comment
- For multiline comment we can use
/* */
let fruit = "apple"
/* This is a
Multiline
Comment
*/
let number = 9
The Variables
- JavaScript has three types of variables var, let and const.
- var can be used to define a variable and the values can be redefined. It was used mainly in the past as it was the only option but now a days it is not intended to be used normally. It is because it has issues like ignoring block scope, and getting attached to the window scope in global scope. var has function scope meaning if defined inside a function it will be remembered only inside that function. var variables can also be redeclared which can cause silent bugs.
var fruitName = "Apple";
var fruitName = "Mango"; // Redeclaration is allowed (can cause bugs)
fruitName = "Banana"; // Reassignment is allowed
console.log(fruitName); // "Banana"
if (true) {
var blockVar = "I ignore block scope!";
}
console.log(blockVar); // "I ignore block scope!" (Accessible outside the block)
-
Implicit global variable creation - Recipe for bugs: If you assign a value to a variable without declaring it with
var,let, orconst, JavaScript automatically makes it a global variable (attached to thewindowobject). This can lead to unexpected overwrites and hard-to-trace bugs.
function createBug() {
// Implicit global variable creation
rogueFruit = "Mango";
}
createBug();
console.log(rogueFruit); // Outputs "Mango" even outside the function!
- let is similar as var but with block scoping. Also in case of hoisting var is set as undefined while let and const can go to TDZ (Temporal Dead Zone) meaning they will have no value and get Reference Error if accessed. let does not allow redeclaration of the same variable and so does const.
let myName = "Preyum Kumar";
myName = "Preyum"; // Allowed: Reassignment
// let myName = "Kumar"; // Error: Cannot redeclare block-scoped variable
- const is used when we want to define a variable only once and freeze its reference. Strings and numbers defined as const are locked in while arrays can be inserted with more values as only their reference is constant. const needs to be defined at the time of declaration unlike the other two variables.
const favoriteFruit = "Apple";
// favoriteFruit = "Banana"; // TypeError: Assignment to constant variable.
const fruitBasket = ["Apple"];
fruitBasket.push("Orange"); // Allowed: We are modifying the array, not the reference.
console.log(fruitBasket); // ["Apple", "Orange"]
The DataTypes
- undefined: Represents a variable that has been declared but not yet assigned a value.
- number: Represents both integer and floating-point numbers.
-
boolean: Represents a logical entity and can have two values:
trueorfalse. - string: Represents a sequence of characters used to represent text.
- null: Represents the intentional absence of any object value.
- object: Represents a collection of properties (key-value pairs) or complex data structures (like arrays, functions).
- symbol: Represents a unique and immutable primitive value, often used as object property keys.
-
bigint: Represents whole numbers larger than the maximum safe integer (
Number.MAX_SAFE_INTEGER, which is9007199254740991or2^53 - 1).
let notAssigned;
console.log(typeof notAssigned); // "undefined"
let age = 25;
console.log(typeof age); // "number"
let isComputerScienceExpert = true;
console.log(typeof isComputerScienceExpert); // "boolean"
let name = "Preyum Kumar";
console.log(typeof name); // "string"
let emptyValue = null;
console.log(typeof emptyValue); // "object" (This is a known quirk in JavaScript!)
let person = { name: "Preyum", domain: "Computer Science" };
console.log(typeof person); // "object"
let uniqueId = Symbol("id");
console.log(typeof uniqueId); // "symbol"
// The 'n' at the end tells JavaScript to treat this as a BigInt
let largeNumber = 9007199254740992n; // 1 larger than MAX_SAFE_INTEGER
console.log(typeof largeNumber); // "bigint"
Auto-Boxing
-
Auto-boxing is a process where JavaScript automatically wraps primitive data types (like strings, numbers, and booleans) in their corresponding object wrappers (
String,Number,Boolean) behind the scenes when you try to access properties or methods on them. - Primitives themselves don't have properties or methods. However, thanks to auto-boxing, you can use methods as if they were objects. JavaScript creates a temporary object, calls the method, and then immediately discards the temporary object.
let myString = "hello world";
// "myString" is a primitive string, not an object.
// But when we access .length, JavaScript temporarily wraps it in a String object.
console.log(myString.length); // 11
// Same happens when we call methods like .toUpperCase()
console.log(myString.toUpperCase()); // "HELLO WORLD"
let myNum = 123.456;
// Number primitive temporarily boxed to use .toFixed()
console.log(myNum.toFixed(1)); // "123.5"
Operators and Logic
Operators, Shorthand and Semicolon
- The lines are recognizable with or without semicolon. Semicolons are optional but recommended for clarity.
let fruit1 = "Apple"
let fruit2 = "Orange"; // Both lines are valid
- Operators
+,-,/,%,<,>,<=,>=,*,&&,||etc and their shorthand use:
let total = 10 + 5; // 15
let diff = 10 - 5; // 5
let mult = 10 * 5; // 50
let div = 10 / 5; // 2
let remainder = 10 % 3; // 1
// Comparison
console.log(10 > 5); // true
console.log(10 <= 10); // true
// Logical
console.log(true && false); // false
console.log(true || false); // true
// Shorthand assignments
let count = 5;
count += 5; // Equivalent to: count = count + 5 (Result: 10)
count *= 2; // Equivalent to: count = count * 2 (Result: 20)
- Operators like
==and===also!=and!==:-
==checks for value equality (with type coercion). -
===checks for strict equality (both value and type must match).
-
console.log(5 == "5"); // true (Type coercion happens)
console.log(5 === "5"); // false (Different types: Number vs String)
console.log(5 != "5"); // false
console.log(5 !== "5"); // true
-
Object.is(): A static method that determines whether two values are the same value. It is similar to
===but has two important differences: it considersNaNto be equal toNaN, and it considers+0and-0to be different.
console.log(Object.is(5, 5)); // true
console.log(Object.is("5", 5)); // false
// Differences from ===
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
- Also incrementing and decrementing shorthand like
++,--:
let step = 1;
step++; // Increments step by 1 (Result: 2)
step--; // Decrements step by 1 (Result: 1)
Operator Precedence
- Operator precedence determines the order in which operators are evaluated. Operators with higher precedence are evaluated first.
- Multiplication (
*) and division (/) have higher precedence than addition (+) and subtraction (-). - You can use parentheses
()to override the default precedence and force a specific order of evaluation.
let result1 = 10 + 5 * 2; // 20 (Multiplication first)
let result2 = (10 + 5) * 2; // 30 (Parentheses evaluated first)
console.log(result1);
console.log(result2);
Type Coercion and Conversion
- Type Coercion is the implicit (automatic) conversion of values from one data type to another by JavaScript. Because JavaScript is a weakly-typed language, it will often try to "help" you by converting types on the fly to complete an operation. This can lead to unexpected bugs.
// --- Implicit Type Coercion ---
// 1. String Coercion: Happens when using the + operator with a string
let strCoercion = "5" + 2; // "52" (The number 2 is coerced into a string)
let boolCoercion = "Hello " + true; // "Hello true"
// 2. Number Coercion: Happens with math operators like -, *, / (but NOT +)
let numCoercion1 = "5" - 2; // 3 (The string "5" is coerced into a number)
let numCoercion2 = "10" * "2"; // 20
// 3. Boolean Coercion: Happens in if-statements or logical operators
if ("hello") {
console.log("This runs because 'hello' is truthy!");
}
Type Conversion (Explicit Casting)
- To avoid bugs caused by implicit coercion, you should use Type Conversion (explicitly forcing a value to be a specific type).
- You can do this using the built-in functions:
Number(),String(), andBoolean().
// --- Explicit Type Conversion ---
// 1. Convert to Number (Prevents the "52" concatenation bug)
let userInput = "5";
let total = Number(userInput) + 2;
console.log(total); // 7 (Correct math, not "52")
// 2. Convert to String
let myNum = 100;
let explicitString = String(myNum); // "100"
// Alternatively, using the auto-boxed .toString() method:
// let explicitString = myNum.toString();
// 3. Convert to Boolean
// Often used to quickly force a truthy/falsy value into an actual true/false boolean
let hasData = Boolean("Some User Data"); // true
let hasEmptyData = Boolean(""); // false
// A common shorthand for converting to boolean is using double negation (!!)
let quickBool = !!"Some Data"; // true
Truthy and Falsy Values
- In JavaScript, a truthy value is a value that is considered
truewhen encountered in a Boolean context (like anifstatement). A falsy value is a value that is consideredfalse. - Everything in JavaScript is truthy except for a specific list of falsy values.
- The Falsy values are exactly these:
false-
0(zero) -
-0(minus zero) -
0n(BigInt zero) -
"",'',(empty strings) *null*undefined*NaN(Not-a-Number) * Truthy values are everything else, including empty arrays ([]), empty objects ({}), and strings with just a space (" ").
if ("") {
console.log("This won't run because an empty string is falsy.");
}
if (" ") {
console.log("This WILL run because a string with a space is truthy.");
}
if ([]) {
console.log("This WILL run because an empty array is truthy.");
}
Ternary Operator and Short-Circuit Evaluation
-
Ternary Operator: A concise way to write an
if-elsestatement. It takes three operands: a condition followed by a question mark (?), an expression to execute if truthy, and a colon (:), followed by an expression to execute if falsy. Heavily used in React for conditional rendering.
let age = 20;
// Condition ? exprIfTrue : exprIfFalse
let canVote = age >= 18 ? "Yes" : "No";
console.log(canVote); // "Yes"
-
Short-Circuit Evaluation (
&&and||):-
&&(Logical AND): Returns the first falsy value, or the last truthy value if all are truthy. Very commonly used in React to render components conditionally (e.g.,isLoggedIn && <Dashboard />). -
||(Logical OR): Returns the first truthy value, or the last falsy value if all are falsy. Useful for assigning default values.
-
let isLoggedIn = true;
// The second part only executes if the first part is true
isLoggedIn && console.log("User dashboard rendered");
let userTheme = null;
let defaultTheme = "dark";
// If userTheme is falsy (like null), it falls back to "dark"
let activeTheme = userTheme || defaultTheme;
console.log(activeTheme); // "dark"
Optional Chaining and Nullish Coalescing
-
Optional Chaining (
?.): Enables reading the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid. It returnsundefinedif the reference is nullish (nullorundefined) instead of throwing an error. Very helpful when dealing with API data in Next.js.
let userProfile = {
name: "Preyum Kumar",
// address is missing here
};
// Trying to access userProfile.address.city directly would throw an error.
// Using ?. safely handles it:
let city = userProfile?.address?.city;
console.log(city); // undefined
-
Nullish Coalescing Operator (
??): A logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.-
Why use
??instead of||? The Logical OR (||) operator falls back to the right side if the left side is any falsy value (like0,"",false,null,undefined). This causes bugs when0or""are actually valid, intended values (e.g., a video with 0 views). The??operator fixes this by only falling back if the value is explicitly missing (nullorundefined).
-
Why use
// Scenario: A video has exactly 0 views.
let videoViews = 0;
let defaultViews = 10;
// Bug with OR operator (||):
// JavaScript sees 0 as "falsy" and thinks the value is missing, so it applies the default.
console.log(videoViews || defaultViews); // Output: 10 (Incorrect: it overwrote our valid 0 views!)
// Fix with Nullish Coalescing (??):
// JavaScript checks if videoViews is exactly `null` or `undefined`. Since it's a valid 0, it keeps it.
console.log(videoViews ?? defaultViews); // Output: 0 (Correct!)
// If the value was actually missing:
let missingViews = null;
console.log(missingViews ?? defaultViews); // Output: 10 (Correctly applies default)
Strings
The Escape Character and Strings
- Example of string with
""and inside also double quotes:
let quote = "Preyum Kumar said, \"Computer Vision is fascinating!\"";
- Other ways like using single quote to cover double quotes:
let singleQuoteStr = 'Preyum Kumar said, "NLP is amazing!"';
- Other way to use back ticks
``to put both single and double quotes inside:
let backtickStr = `Preyum's favorite subject is "Computer Science".`;
- To put backslash itself we use two backslashes:
let path = "C:\\Users\\PreyumKumar\\Documents";
Backtick for Strings with values
- Backticks (
) are used to create Template Literals, allowing for string interpolation (embedding variables/expressions inside strings). - Example with backtick strings with values:
let author = "Preyum Kumar";
let topic = "NLP";
let message = `${author} is an expert in ${topic}.`;
console.log(message);
- Also if we want $ sign also to be printed use escape character for the same:
let price = 500;
let costMessage = `The total cost is \$${price}.`;
console.log(costMessage); // "The total cost is $500."
Control Flow
For, While, Do While and Switch
- For Loop: Repeats a block of code a specific number of times.
let fruits = ["Apple", "Mango", "Banana"];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
- While Loop: Executes a block of code as long as a specified condition is true.
let count = 0;
while (count < 3) {
console.log("Count is: " + count);
count++;
}
- Do...While Loop: Similar to a while loop, but it executes the block of code at least once before checking the condition.
let runs = 0;
do {
console.log("This runs at least once!");
runs++;
} while (runs < 0);
- Switch Statement: Evaluates an expression, matching the expression's value to a case clause, and executes statements associated with that case.
let chosenFruit = "Apple";
switch (chosenFruit) {
case "Banana":
console.log("You chose Banana.");
break;
case "Apple":
console.log("You chose Apple.");
break;
default:
console.log("Unknown fruit.");
}
Break and Continue
- Control flow statements like loops can be altered using
breakandcontinue. -
break: Immediately exits the entire loop (or switch statement). Execution continues with the code that follows the loop. -
continue: Skips the current iteration of the loop and immediately jumps to the next iteration.
// Using 'break' to stop the loop early
for (let i = 1; i <= 5; i++) {
if (i === 3) {
console.log("Found 3! Breaking out of the loop.");
break; // Loop stops completely here
}
console.log("Iteration: " + i);
}
/* Output:
Iteration: 1
Iteration: 2
Found 3! Breaking out of the loop.
*/
// Using 'continue' to skip a specific iteration
for (let j = 1; j <= 5; j++) {
if (j === 3) {
console.log("Skipping 3!");
continue; // Skips to j = 4
}
console.log("Iteration: " + j);
}
/* Output:
Iteration: 1
Iteration: 2
Skipping 3!
Iteration: 4
Iteration: 5
*/
Error Handling (try...catch and throw)
-
try...catch: Allows you to test a block of code for errors (try) and handle the error if one occurs (catch). This prevents the entire script from crashing. -
throw: Used to create custom errors. You can "throw" an exception (an object, string, or number). -
finally: A block that runs regardless of whether an error was thrown or caught.
function checkAge(age) {
if (age < 18) {
// Throwing a custom error
throw new Error("You must be at least 18 years old.");
}
return "Access granted.";
}
try {
console.log(checkAge(15));
} catch (error) {
// Handling the error
console.error("Caught an error:", error.message);
} finally {
console.log("Age check process completed.");
}
-
Synchronous Limitation of
throw: A standardthrowstatement is synchronous. It only works within the current execution stack. If you try to usethrowinside an asynchronous callback (likesetTimeout), it cannot be caught by atry...catchblock that surrounds the async call. This is whyrejectis used for future (asynchronous) errors.
// --- 1. The PROBLEM: throw fails in Async ---
try {
setTimeout(() => {
// throw new Error("Async Error!");
// ^ This would crash the program because the 'try...catch'
// block below has already finished executing!
}, 1000);
} catch (e) {
console.log("This will NEVER run:", e);
}
// --- 2. The SOLUTION: reject() in a Promise ---
const asyncTask = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Async Error caught via Promise!");
}, 1000);
});
};
asyncTask()
.then(msg => console.log(msg))
.catch(err => console.log("SUCCESSFULLY caught:", err));
Arrays
Array Methods
-
push()andpop():push()adds one or more elements to the end of an array and returns the new length.pop()removes the last element from an array and returns that element.
let fruits = ["Apple", "Mango"];
fruits.push("Banana"); // ["Apple", "Mango", "Banana"]
let last = fruits.pop(); // Removes "Banana". Array is back to ["Apple", "Mango"]
-
splice(): Changes the contents of an array by removing or replacing existing elements and/or adding new elements in place. It mutates the original array.- Syntax:
array.splice(startIndex, deleteCount, item1, item2, ...)
- Syntax:
let colors = ["Red", "Green", "Blue", "Yellow"];
// Start at index 1, delete 2 elements ("Green", "Blue"), and insert "Purple"
colors.splice(1, 2, "Purple");
console.log(colors); // ["Red", "Purple", "Yellow"]
-
forEach(): Executes a provided function once for each array element. It is used purely for executing side-effects (like logging or modifying external variables) and always returnsundefined, unlikemap()which returns a new array.
let techStack = ["JavaScript", "React", "Next.js"];
techStack.forEach((tech, index) => {
console.log(index + ": " + tech);
});
// Output:
// 0: JavaScript
// 1: React
// 2: Next.js
-
map(): Creates a new array populated with the results of calling a provided function on every element in the calling array. This is the primary way to render lists of elements in React.
let numbers = [1, 2, 3];
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
-
filter(): Creates a shallow copy of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function. Constantly used for manipulating state arrays, like deleting an item from a list.
let ages = [15, 21, 18, 12, 30];
let adults = ages.filter(age => age >= 18);
console.log(adults); // [21, 18, 30]
-
reduce(): Executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
let prices = [10, 20, 30];
let totalCost = prices.reduce((acc, current) => acc + current, 0);
console.log(totalCost); // 60
Typed Arrays
- Typed Arrays are array-like objects that provide a mechanism for reading and writing raw binary data in memory buffers. While standard arrays can grow dynamically and hold any data type, Typed Arrays have a fixed length and only store a specific numeric type (like integers or floats).
- They are heavily used in performance-critical applications, WebGL, and when handling binary data from APIs or files.
- Common types:
Int8Array,Uint8Array,Int32Array,Float64Array, etc.
// Create a buffer of 16 bytes
let buffer = new ArrayBuffer(16);
// Create a view that treats the buffer as an array of 32-bit signed integers
let int32View = new Int32Array(buffer);
int32View[0] = 42;
int32View[1] = 100;
console.log(int32View.length); // 4 (Since 16 bytes / 4 bytes per Int32 = 4)
console.log(int32View[0]); // 42
// You can also initialize directly with an array
let uint8 = new Uint8Array([10, 20, 30]);
console.log(uint8[1]); // 20
Functions and Scope
Regular, Anonymous and Arrow Functions
-
Regular Functions: Declared using the
functionkeyword. They have their ownthiscontext.
function greet(name) {
return "Hello, " + name;
}
console.log(greet("Preyum Kumar"));
- Anonymous Functions: Functions without a name, often assigned to variables or passed as callbacks (discussed in detail later).
let sayGoodbye = function(name) {
return "Goodbye, " + name;
};
-
Arrow Functions: A concise syntax for writing functions. They do not have their own
thisbinding (they inherit it from the parent scope).-
Explicit Return: When using curly braces
{}, you must explicitly use thereturnkeyword to return a value. -
Implicit Return: If you omit the curly braces, the arrow function implicitly returns the single evaluated expression. You can wrap the expression in parentheses
()for multi-line implicit returns, which is very common in React JSX.
-
Explicit Return: When using curly braces
// Explicit Return (uses {})
const greetArrow = (name) => {
return "Hello, " + name;
};
// Implicit Return (no {}, evaluates the expression on the same line)
const greetShort = name => "Hello, " + name;
// Implicit Return with Parentheses (useful for returning objects)
const getUser = (name) => ({
name: name,
role: "Admin"
});
Closures
- A Closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function's variablesโa scope chain. Closures are a fundamental concept that explains how React Hooks (like
useStateanduseEffect) "remember" values between renders.
function makeCounter() {
let count = 0; // count is a local variable created by makeCounter
return function() { // The inner function is a closure
count++; // It has access to count from the outer function
return count;
};
}
let myCounter = makeCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3
The this Keyword
- The
thiskeyword refers to the context in which a function is executed. Its value depends on how the function is called, not where it's defined (except for arrow functions).
1. Global Context
- In the global scope (outside any function),
thisrefers to the global object (windowin browsers).
console.log(this === window); // true
2. Inside a Regular Function
-
Non-strict mode:
thisdefaults to the global object (window). -
Strict mode (
'use strict'):thisisundefined. This is a safety feature to prevent accidental modification of the global object.
function showThis() {
"use strict";
console.log(this);
}
showThis(); // undefined
3. Inside a Method (Object Context)
- When a function is called as a method of an object,
thisrefers to the object itself.
const user = {
name: "Preyum",
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
user.greet(); // "Hello, my name is Preyum"
4. Arrow Functions (Lexical this)
- Arrow functions do not have their own
this. Instead, they inheritthisfrom the surrounding (lexical) scope at the time they are created. This makes them ideal for callbacks (like insetTimeoutor array methods) where you want to keep the original context.
const group = {
title: "Our Team",
members: ["Preyum", "John"],
showList() {
// Regular function inside forEach would have 'this' as undefined (in strict)
this.members.forEach((member) => {
// Arrow function inherits 'this' from showList()
console.log(`${this.title}: ${member}`);
});
}
};
group.showList();
// "Our Team: Preyum"
// "Our Team: John"
5. Changing this Context (call, apply, bind)
JavaScript provides three methods to explicitly set the value of
this..call(thisArg, arg1, arg2, ...): Invokes the function immediately with the specifiedthisand arguments passed individually..apply(thisArg, [argsArray]): Invokes the function immediately with the specifiedthisand arguments passed as an array..bind(thisArg, arg1, ...): Returns a new function withthispermanently bound to the specified object. It does not invoke the function immediately.
const person1 = { name: "Preyum" };
const person2 = { name: "Alice" };
function introduce(skill, hobby) {
console.log(`I'm ${this.name}. I'm good at ${skill} and I like ${hobby}.`);
}
// .call() - Arguments passed individually
introduce.call(person1, "NLP", "Chess");
// .apply() - Arguments passed as an array
introduce.apply(person2, ["Computer Vision", "Gaming"]);
// .bind() - Creates a new function
const boundIntro = introduce.bind(person1, "Next.js", "Blogging");
boundIntro(); // Can be called later
Function Arguments
- Default Arguments use case: We can assign default values to parameters to ensure the function works even if some arguments are missing.
function makeJuice(fruit1, fruit2 = "Water") {
return `Making juice with ${fruit1} and ${fruit2}`;
}
console.log(makeJuice("Apple")); // "Making juice with Apple and Water"
- Order in which default arguments must be: Default arguments must be placed at the end of the parameter list. If they are placed first, JavaScript won't know which arguments to skip when fewer arguments are passed.
// WRONG: function wrongOrder(fruit1 = "Apple", fruit2) { ... }
// CORRECT:
function correctOrder(fruit1, fruit2 = "Banana") {
console.log(fruit1, fruit2);
}
Modern ES6+ Features
Spread Operator
- The spread operator (
...) allows an iterable (like an array or string) to be expanded in places where zero or more arguments or elements are expected, or an object expression to be expanded. - How spread operator is used to actually copy array:
let originalFruits = ["Apple", "Mango"];
// let badCopy = originalFruits; // This just copies the reference!
let goodCopy = [...originalFruits]; // Creates a true, independent copy
goodCopy.push("Banana");
console.log(originalFruits); // ["Apple", "Mango"]
console.log(goodCopy); // ["Apple", "Mango", "Banana"]
Rest Parameters
- The Rest Parameter syntax (
...args) allows a function to accept an indefinite number of arguments as an array. While the spread operator expands iterables into individual elements, the rest parameter collects multiple elements and condenses them into a single array.
function calculateSum(...numbers) {
// numbers is an array containing all passed arguments
return numbers.reduce((total, num) => total + num, 0);
}
console.log(calculateSum(1, 2, 3)); // 6
console.log(calculateSum(10, 20, 30, 40, 50)); // 150
Destructuring object values for assignment
- Destructuring makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
- Destructing single layer example:
let user = {
name: "Preyum Kumar",
domain: "Computer Science"
};
let { name, domain } = user;
console.log(name); // "Preyum Kumar"
- Destructuring nested object for assignment:
let expertUser = {
profile: {
firstName: "Preyum",
expertise: "Computer Vision"
}
};
let { profile: { firstName, expertise } } = expertUser;
console.log(expertise); // "Computer Vision"
Objects
Objects: Dot and Bracket Notation
- Objects store data in key-value pairs. You can access these values using either dot notation or bracket notation.
-
Dot Notation (
object.property): The most common and readable way to access properties. The property name must be a valid JavaScript identifier (no spaces, doesn't start with a number). -
Bracket Notation (
object["property"]): Required when the property name contains spaces, special characters, or when you are accessing a property dynamically using a variable.
let car = {
make: "Toyota",
"model year": 2022,
color: "blue"
};
// Dot notation
console.log(car.make); // "Toyota"
// Bracket notation required for strings with spaces
console.log(car["model year"]); // 2022
// Bracket notation for dynamic property access
let propName = "color";
console.log(car[propName]); // "blue"
Object Shorthand Syntax
- ES6 introduced concise ways to define properties and methods inside objects, reducing boilerplate code.
-
Shorthand Property Names: If the key and the variable name you want to assign to it are the exact same, you can just write the variable name once instead of
key: value. -
Shorthand Method Names: You can omit the
functionkeyword and the colon:when defining methods inside an object.
let name = "Preyum";
let role = "Admin";
// Traditional ES5 way
let oldUser = {
name: name,
role: role,
greet: function() {
return "Hello " + this.name;
}
};
// ES6 Shorthand Syntax (Cleaner and more common in React/Next.js)
let modernUser = {
name, // Shorthand property
role, // Shorthand property
greet() { // Shorthand method
return "Hello " + this.name;
}
};
console.log(modernUser.greet()); // "Hello Preyum"
Object Methods
- JavaScript provides several built-in methods to work with objects, returning arrays of their keys, values, or entries (key-value pairs).
-
Object.keys(): Returns an array of a given object's own enumerable string-keyed property names. -
Object.values(): Returns an array of a given object's own enumerable string-keyed property values. -
Object.entries(): Returns an array of a given object's own enumerable string-keyed property[key, value]pairs.
let user = {
name: "Preyum Kumar",
domain: "Computer Science",
expertise: "Computer Vision"
};
console.log(Object.keys(user));
// ["name", "domain", "expertise"]
console.log(Object.values(user));
// ["Preyum Kumar", "Computer Science", "Computer Vision"]
console.log(Object.entries(user));
// [["name", "Preyum Kumar"], ["domain", "Computer Science"], ["expertise", "Computer Vision"]]
Shallow and Deep Copy
- In JavaScript, primitive types (like numbers and strings) are passed by value, but objects and arrays are passed by reference. This means if you assign an object to a new variable and modify it, you are modifying the original object. To prevent this, we need to create copies.
Shallow Copy
- A shallow copy creates a new object and copies over the top-level properties. However, if the object contains nested objects or arrays, the nested structures are still passed by reference.
-
Ways to create a shallow copy:
- Spread Operator (
...) for both objects and arrays. -
Object.assign()for objects. -
.slice()specifically for arrays. It returns a shallow copy of a portion of an array into a new array object. If used without arguments, it copies the entire array.
- Spread Operator (
const originalArray = ["Apple", "Mango", "Banana"];
// Direct assignment just copies the reference (BAD!)
// const badCopy = originalArray;
// Using .slice() to create a true shallow copy (GOOD!)
const copiedArray = originalArray.slice();
copiedArray.push("Orange"); // Modifies only the copy
console.log(originalArray); // ["Apple", "Mango", "Banana"]
console.log(copiedArray); // ["Apple", "Mango", "Banana", "Orange"]
let originalObj = {
name: "Preyum",
details: { age: 25 }
};
// Shallow copy using Spread Operator
let shallowCopy = { ...originalObj };
shallowCopy.name = "Preyum Kumar"; // Only changes the copy (top-level)
shallowCopy.details.age = 30; // Changes BOTH original and copy! (nested)
console.log(originalObj.name); // "Preyum"
console.log(originalObj.details.age); // 30 (Oops, original was mutated)
Deep Copy
- A deep copy creates a completely independent clone of the original object, including all nested objects and arrays. Modifying the deep copy will never affect the original.
-
Ways to create a deep copy:
-
JSON.parse(JSON.stringify(object)): The traditional, hacky way. It works for most simple objects but fails if the object contains functions,undefined,Dateobjects, or circular references. -
structuredClone(object): The modern, native JavaScript way (introduced recently in browsers/Node.js). It perfectly clones complex objects, including Dates, Sets, Maps, and more.
-
let user = {
name: "Preyum",
details: { age: 25 },
joined: new Date()
};
// Deep copy using structuredClone (Modern native method)
let deepCopy = structuredClone(user);
deepCopy.details.age = 30; // Modifies ONLY the copy
console.log(user.details.age); // 25 (Original remains completely untouched)
Getters and Setters
-
Getters (
get) bind an object property to a function that will be called when that property is looked up. -
Setters (
set) bind an object property to a function to be called when there is an attempt to set that property.
let person = {
firstName: "Preyum",
lastName: "Kumar",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set updateFirstName(newName) {
if(newName.length > 0) {
this.firstName = newName;
} else {
console.log("Name cannot be empty");
}
}
};
console.log(person.fullName); // "Preyum Kumar"
person.updateFirstName = "PreyumK";
console.log(person.fullName); // "PreyumK Kumar"
Classes and OOP
Classes and Private Fields (#)
- Classes are a blueprint for creating objects. They encapsulate data with code that works on that data.
-
Private Fields (
#): By default, class properties and methods are public. To make them private, prefix their name with a hash#. Private fields cannot be accessed or called from outside the class or from its subclasses. - Benefits of Private Fields: Encapsulation, preventing unintended access to internal state, and reducing potential for bugs.
class User {
// 1. Private fields declaration (must be declared at the top)
#id;
#password;
constructor(name, id, password) {
this.name = name; // Public property
this.#id = id; // Private property
this.#password = password;
}
// 2. Public method
displayInfo() {
console.log(`User: ${this.name}, ID: ${this.#getId()}`);
}
// 3. Private method
#getId() {
return this.#id;
}
// 4. Getter for private field (can add validation)
get userId() {
return this.#id;
}
}
const myUser = new User("Preyum", 12345, "securePass123");
console.log(myUser.name); // "Preyum"
// console.log(myUser.#id); // Syntax Error: Private field '#id' must be declared in an enclosing class
myUser.displayInfo(); // "User: Preyum, ID: 12345"
// Using getter to access private field
console.log(myUser.userId); // 12345
Inheritance (extends and super())
- Inheritance allows a class to inherit properties and methods from another class. This promotes code reuse and hierarchical organization.
-
extends: Keyword used to create a child class from a parent class. -
super(): This keyword is used to call the constructor of the parent class. It must be called before usingthisin the child class constructor. -
super.methodName(): It can also be used to call methods from the parent class, which is useful when overriding a method but still wanting to include the parent's logic.
// Parent Class
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
// Child Class using extends
class Dog extends Animal {
constructor(name, breed) {
// Call the parent constructor using super()
super(name);
this.breed = breed;
}
// Overriding a parent method and calling it using super.methodName()
eat() {
super.eat(); // Call the parent's eat method
console.log(`${this.name} the ${this.breed} is still hungry!`);
}
bark() {
console.log("Woof! Woof!");
}
displayInfo() {
console.log(`I am a ${this.breed} named ${this.name}.`);
}
}
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.eat();
/* Output:
Buddy is eating.
Buddy the Golden Retriever is still hungry!
*/
myDog.bark(); // "Woof! Woof!"
myDog.displayInfo(); // "I am a Golden Retriever named Buddy."
Built-in Objects and Web APIs
- JavaScript provides several globally available built-in objects and functions to perform common tasks without needing to write custom logic.
-
Global Functions: Functions like
parseInt(),parseFloat(), andisNaN()can be called directly from anywhere. -
Static Objects: Objects like
Math,JSON, andArray(when used for its static methods) are static. This means you do not use thenewkeyword with them; you call their properties and methods directly (e.g.,Math.max(),JSON.stringify()).
Array.isArray()
- In JavaScript, arrays are technically objects. If you use the
typeofoperator on an array, it returns"object", which isn't very helpful when you need to know if a variable is specifically a list. - To solve this, the
Arraybuilt-in object provides a static method calledArray.isArray()which reliably returnstrueif a value is an array, andfalseotherwise.
let myData = [1, 2, 3];
let myObj = { name: "Preyum" };
console.log(typeof myData); // "object" (Not very helpful!)
console.log(Array.isArray(myData)); // true (Reliable check)
console.log(Array.isArray(myObj)); // false
-
Instantiable Objects: Objects like
Dateare constructors. You must use thenewkeyword to create an instance before you can use most of their methods.
// Global Functions: Parsing strings and checking values
console.log(parseInt("10.5")); // 10
console.log(parseFloat("10.5")); // 10.5
console.log(isNaN("Hello")); // true
// Static Object: Math (No 'new' keyword)
console.log(Math.round(4.7)); // 5
console.log(Math.floor(4.9)); // 4
console.log(Math.max(10, 20, 5)); // 20
// Instantiable Object: Date (Requires 'new' keyword)
let now = new Date();
console.log(now.getFullYear()); // Gets current year
// Instantiable Object: Error
// Used to create custom error objects, usually to be 'thrown'
let myError = new Error("Something went wrong!");
console.log(myError.message); // "Something went wrong!"
// Instantiable Object: Function
// Every JavaScript function is actually a Function object.
// While you can create them dynamically like this, standard declaration is preferred.
let sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6)); // 8
// Primitive Wrapper Objects: String, Number, Boolean
// These are the objects that JavaScript uses behind the scenes for Auto-Boxing.
// WARNING: Do NOT use the 'new' keyword with them in modern code, as it creates an object instead of a primitive, leading to strict equality (===) bugs.
// They should only be used without 'new' for Type Conversion (e.g., Number("5")).
let badString = new String("Hello");
console.log(typeof badString); // "object" (This is bad!)
let goodString = String("Hello");
console.log(typeof goodString); // "string" (This is good!)
-
JSON Object (Static): The
JSONobject contains methods for parsing JavaScript Object Notation (JSON) and converting values to JSON. This is crucial when sending or receiving data from APIs.-
JSON.stringify(): Converts a JavaScript object or value to a JSON string. -
JSON.parse(): Parses a JSON string, constructing the JavaScript value or object described by the string.
-
let user = { name: "Preyum", domain: "CS" };
// Converting an object to a string (e.g., to send to an API or save in local storage)
let jsonString = JSON.stringify(user);
console.log(jsonString); // '{"name":"Preyum","domain":"CS"}'
console.log(typeof jsonString); // "string"
// Parsing a string back into a JavaScript object
let parsedUser = JSON.parse(jsonString);
console.log(parsedUser.name); // "Preyum"
console.log(typeof parsedUser); // "object"
-
Web Storage API (localStorage): While not technically part of the core JavaScript language (it's part of the browser's Web API),
localStorageis an incredibly common built-in object used to store key-value pairs in the web browser. The data survives even after the browser window is closed.-
Note:
localStorageonly stores strings. If you want to store an object, you MUST useJSON.stringify()before saving, andJSON.parse()when retrieving.
-
Note:
// Saving data (key, value)
localStorage.setItem("theme", "dark");
// Retrieving data
let currentTheme = localStorage.getItem("theme");
console.log(currentTheme); // "dark"
// Saving an object requires JSON.stringify
let sessionUser = { name: "Preyum", isLoggedIn: true };
localStorage.setItem("userSession", JSON.stringify(sessionUser));
// Retrieving an object requires JSON.parse
let retrievedSession = JSON.parse(localStorage.getItem("userSession"));
console.log(retrievedSession.isLoggedIn); // true
-
Console Object: The
consoleobject provides access to the browser's debugging console (or the terminal in Node.js). It is incredibly useful for logging information, warnings, and errors during development.-
console.log(): Prints general informational messages. -
console.error(): Prints an error message (usually styled in red with a stack trace in browsers). -
console.warn(): Prints a warning message (usually styled in yellow). -
console.table(): Displays tabular data (like an array of objects) as a neat table. -
console.time()andconsole.timeEnd(): Starts and stops a timer, useful for measuring how long an operation takes.
-
console.log("This is a standard log message.");
console.error("This is an error message!");
console.warn("This is a warning.");
// Displaying data nicely
let users = [
{ name: "Preyum", role: "Admin" },
{ name: "John", role: "User" }
];
console.table(users);
// Measuring performance
console.time("Loop Time");
for(let i = 0; i < 1000000; i++) {} // Some heavy operation
console.timeEnd("Loop Time"); // e.g., "Loop Time: 2.5ms"
Document Object Model (DOM)
- The Document Object Model (DOM) is a programming interface for web documents. It represents the page so that programs can change the document structure, style, and content.
- When a web page is loaded, the browser creates a Document Object Model of the page, structured as a tree of objects.
- The
documentitself is a built-in object provided by the browser that models the entire webpage. Because it represents the HTML document, it acts as the entry point to the DOM tree. - JavaScript can manipulate the DOM to create dynamic and interactive web experiences.
- Note: In modern frameworks like React and Next.js, you rarely manipulate the DOM directly. Instead, you manipulate the "Virtual DOM" through state, and React handles the actual DOM updates. However, understanding the real DOM is essential.
The window Object (The Global Object)
- While the DOM represents the document (the webpage content), the BOM (Browser Object Model) represents the browser window itself.
- The core of the BOM is the
windowobject. It is the global object in a browser environment. - Because it is the global object, every globally scoped variable, function, and built-in object is implicitly attached to it.
- This means that objects like
document,console, and functions likealert(),setTimeout(), orsetInterval()are actually properties and methods of thewindowobject. Because it is the global scope, you can usually omit typingwindow..
// The following two lines do the exact same thing:
window.console.log("Hello from the window!");
console.log("Hello from the window!");
// The 'document' is actually a property of the window:
// window.document.getElementById(...)
// Alert dialogs are window methods:
// window.alert("Warning!");
// alert("Warning!");
// Getting the inner width and height of the browser window itself
console.log("Window width:", window.innerWidth);
console.log("Window height:", window.innerHeight);
The document Object Properties
- You can directly access high-level parts of the webpage using built-in properties on the
documentobject:-
document.title: Gets or sets the title of the document (the text shown in the browser tab). -
document.body: Returns the<body>element of the document, allowing you to manipulate the main content area directly.
-
// Reading the current page title
console.log(document.title);
// Changing the page title dynamically
document.title = "New Page Title!";
// Changing the background color of the entire webpage body
document.body.style.backgroundColor = "lightblue";
Common DOM Methods
-
Selecting Elements:
-
document.getElementById(id): Selects a single element by its ID. -
document.querySelector(selector): Returns the first element that matches a CSS selector. -
document.querySelectorAll(selector): Returns a NodeList of all elements that match a CSS selector.
-
-
Modifying Elements:
-
element.textContent: Gets or sets the text content of a node and its descendants. -
element.innerHTML: Gets or sets the HTML markup contained within the element. -
element.style.property: Modifies the inline CSS style of an element. -
element.classList.add(className) / .remove() / .toggle(): Modifies the classes on an element.
-
-
Event Listeners and Handlers:
-
Event Handlers (e.g.,
onclick,onmouseover): Properties on DOM elements that allow you to assign a single function to an event. It's an older method but still widely used for simple interactions. -
addEventListener(event, function): The modern, preferred way to attach events. It allows attaching multiple functions to the same event on the same element without overwriting them.
-
Event Handlers (e.g.,
// --- Selecting an element ---
// Let's assume there is a <button id="myBtn" class="btn primary">Click Me!</button> in the HTML
let btn = document.getElementById("myBtn");
let sameBtn = document.querySelector(".btn.primary");
// --- Modifying the element ---
// Change the text inside the button
btn.textContent = "Processing...";
// Add a new CSS class
btn.classList.add("loading");
// Change a specific style directly
btn.style.backgroundColor = "blue";
// --- Adding interactivity (Events) ---
// Method 1: Using the event handler property (onclick)
// Note: If you assign another function to btn.onclick later, it will overwrite this one.
btn.onclick = function() {
console.log("Button clicked using onclick!");
};
// Method 2: Using addEventListener (Preferred)
btn.addEventListener("click", function(event) {
console.log("Button clicked using addEventListener!");
// 'event' object contains details about the action (e.g., mouse coordinates)
console.log("Mouse X:", event.clientX, "Mouse Y:", event.clientY);
});
Asynchronous JavaScript
- JavaScript is single-threaded and synchronous by default. Asynchronous JavaScript allows the code to continue running other tasks while waiting for long-running operations (like fetching data, reading files) to complete, preventing the browser or server from freezing.
console.log("1. Start");
// This mimics a long-running task
setTimeout(() => {
console.log("2. Async Operation Finished");
}, 1000);
console.log("3. End");
// Output order will be: 1, 3, 2
setTimeout() and setInterval()
- Both are built-in methods of the
windowobject used to schedule the execution of functions.
setTimeout()
-
setTimeout()executes a block of code once after a specified delay (in milliseconds).
console.log("Waiting for my Apple...");
let timeoutId = setTimeout(() => {
console.log("Apple is served!");
}, 2000); // Delays execution by 2000 milliseconds (2 seconds)
// You can cancel the timeout before it executes using clearTimeout()
// clearTimeout(timeoutId);
setInterval()
-
setInterval()executes a block of code repeatedly, with a fixed time delay between each call. - It will continue running indefinitely until you explicitly stop it using
clearInterval().
let count = 1;
console.log("Starting timer...");
// Runs every 1 second (1000 ms)
let intervalId = setInterval(() => {
console.log("Tick: " + count);
// Stop the interval after 3 ticks
if (count === 3) {
clearInterval(intervalId);
console.log("Timer stopped.");
}
count++;
}, 1000);
Callbacks
- A callback is a function passed as an argument to another function, which is then invoked inside the outer function to complete some kind of routine or action.
- Example of order and production (without order, production cannot start):
let order = (call_production) => {
console.log("Order placed, please call production.");
// We execute the callback function here
call_production();
};
let production = () => {
console.log("Order received, starting production of Apple Juice.");
};
order(production);
- Issues with callbacks, the callback hell: Callback hell (or Pyramid of Doom) happens when multiple asynchronous operations are chained together using nested callbacks, making the code hard to read and maintain. Promises (discussed next) solve this.
// Callback Hell Example
step1(function() {
step2(function() {
step3(function() {
step4(function() {
console.log("All steps finished!");
});
});
});
});
Promises
- A Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It solves the callback hell problem by allowing chaining.
-
reject()vsthrow:-
throwis for immediate, synchronous errors. It pauses execution and looks for the nearestcatchblock in the current stack. -
reject()is for future, asynchronous errors. It signals that an async operation failed. Since the originaltry...catchmight have already finished executing while waiting for the async task,reject()allows the error to be handled later via.catch()or anasync/awaittry...catch.
-
function makeOrder() {
return new Promise((resolve, reject) => {
let isStockAvailable = false;
setTimeout(() => {
if (isStockAvailable) {
resolve("Order placed successfully");
} else {
// Signals failure for an ASYNC operation
reject("Error: Out of stock");
}
}, 1000);
});
}
makeOrder()
.then(msg => console.log(msg))
.catch(err => console.error("Caught async error:", err));
- Change example to use promises:
function makeOrder() {
return new Promise((resolve, reject) => {
let isStockAvailable = true;
if (isStockAvailable) {
resolve("Order placed successfully"); // This goes to the first .then()
} else {
reject("Error: Out of stock"); // This skips directly to .catch()
}
});
}
makeOrder()
// No semicolons between then, catch, finally so the code can cascade
.then((message) => {
// Step 1: Receives the resolved value from makeOrder
console.log(message); // Output: "Order placed successfully"
return "Starting production"; // This returned value is passed to the NEXT .then()
})
.then((nextStep) => {
// Step 2: Receives the returned value from the previous .then()
console.log(nextStep); // Output: "Starting production"
})
.catch((error) => {
// Step 3: Only runs if reject() was called, or if an error was thrown above
console.log(error);
})
.finally(() => {
// Step 4: Always runs at the very end, whether it was resolved or rejected
console.log("Operation closed (runs regardless of success or failure).");
});
Async and Await
-
asyncandawaitare syntactic sugar on top of Promises, making asynchronous code look and behave more like synchronous code, which is much easier to read. - An
asyncfunction always returns a Promise. Theawaitkeyword pauses the execution of the async function until the Promise settles (resolves or rejects).
function fetchFruit() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Mango");
}, 1500);
});
}
async function prepareSmoothie() {
console.log("2. Starting prep inside async function...");
try {
// Execution inside this function pauses here until fetchFruit is done.
// However, the rest of the synchronous code outside this function keeps running!
let fruit = await fetchFruit();
console.log(`4. Smoothie made with ${fruit}!`);
} catch (error) {
console.log("Failed to get fruit.");
}
}
console.log("1. Ordering Smoothie...");
// Calling the async function. It starts running synchronously until it hits 'await'.
prepareSmoothie();
console.log("3. Doing other tasks while waiting for the smoothie...");
/*
Output Order:
1. Ordering Smoothie...
2. Starting prep inside async function...
3. Doing other tasks while waiting for the smoothie...
(After 1.5 seconds delay)
4. Smoothie made with Mango!
*/
Modules (Import and Export)
- Modules allow you to break your code into separate files. This makes it easier to maintain the code base. React and Next.js are entirely built around importing and exporting components and functions.
-
Important Note on Usage: To actually use ES6 modules natively in a browser, the HTML script tag importing the main JavaScript file MUST include the
type="module"\attribute (e.g.,<script type="module" src="app.js"></script>\). In modern frameworks like React/Next.js, the build tools handle this for you automatically.
Named Exports
- You can create named exports two ways: individually or all at once at the bottom. You can have multiple named exports per file.
- When importing, you must use the exact same name inside curly braces
{}\. You can also rename (alias) imports using theas\keyword to avoid naming conflicts.
// mathUtils.js (Exporting)
// 1. Exporting individually on the same line
export const add = (a, b) => a + b;
// 2. Declaring first, exporting at the bottom
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;
export { subtract, multiply };
// app.js (Importing)
// Using 'as' to alias 'add' to 'addNumbers'
import { add as addNumbers, subtract, multiply } from './mathUtils.js';
console.log(addNumbers(5, 3)); // 8
console.log(multiply(2, 4)); // 8
Default Exports
- You can only have one default export per file. It is often used for exporting the main component in a file.
- When importing, you can name it anything you want and you do not use curly braces.
// Greeting.js (Exporting)
const Greeting = () => "Hello, Preyum!";
export default Greeting;
// app.js (Importing)
// We can name it whatever we want, let's call it GreetComp
import GreetComp from './Greeting.js';
console.log(GreetComp()); // "Hello, Preyum!"
Stay tuned for my next post, where we'll be exploring React! While this covers the essentials, If thereโs anything you feel I should have included, let me know in the comments below and I'll gladly add it!
Top comments (0)