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
- How to Install JavaScript
- Putting Comments in the Code
- The Variables
- The DataTypes
- Operators, Shorthand and Semicolon
- Ternary Operator and Short-Circuit Evaluation
- The Escape Character and Strings
- For, While, Do While and Switch
- Array Methods (map, filter, reduce)
- Regular, Anonymous and Arrow Functions
- Closures
- Function Arguments
- Spread Operator
- Rest Parameters
- Destructuring object values for assignment
- Object Methods
- Optional Chaining and Nullish Coalescing
- Backtick for Strings with values
- Getters and Setters
- Asynchronous JavaScript
- The setTimeout() Function
- Callbacks
- Promises
- Async and Await
- Modules (Import and Export)
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"
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
- Also incrementing and decrementing shorthand like
++,--:
let step = 1;
step++; // Increments step by 1 (Result: 2)
step--; // Decrements step by 1 (Result: 1)
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"
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";
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.");
}
Array Methods (map, filter, reduce)
-
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
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
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);
}
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"
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"]]
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)
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."
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"
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
The setTimeout() Function
-
setTimeout()is a method that calls a function or evaluates an expression after a specified number of milliseconds.
console.log("Waiting for my Apple...");
setTimeout(() => {
console.log("Apple is served!");
}, 2000); // Delays execution by 2000 milliseconds (2 seconds)
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.
- 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.
-
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 theaskeyword 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 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 blog, where I'll dive into React! In the meantime, if you feel I missed anything here, drop a comment below and I'll make sure to add it.
Top comments (0)