Skip to main content

Command Palette

Search for a command to run...

javascript basics

Updated
β€’44 min read

what is javascript?

  • a programming language commonly used in web development

  • creates interactive webpages [when used on client side]

  • it can manipulate DOM of a webpage and also the styles

difference b/w java and js

js:

  • Loosely typed (variables do not need explicit type declarations), dynamic language.

java:

  • Strongly typed (variables need explicit type declarations), statically typed language.

js engine

These engines are embedded in web browsers and other environments to run JavaScript scripts.

A JavaScript engine is responsible for:

  • Parsing JavaScript (turning code into tokens/AST)

  • Compiling it to machine code/executable code

  • Executing it efficiently

popular js engines:

  • v8 [chrome]

  • spidermonkey [firefox]

  • nodejs is an runtime environment which uses v8 engine

    • Node.js extends JavaScript's reach to server-side development, making it a powerful tool for building a variety of applications
  •      node -v  // cmd to check if node is installed or not
    
         /*so with nodejs installed in our system we dont need to attach 
         our script.js to html file and run code in browser's js engine,
         we can directly type 'node script.js' in terminal, we can see 
         consolelog outputs in terminal itself*/
    

    ECMAscript is a set of guidelines that javascript follows

How does the V8 engine optimize code execution over time? Explain JIT [just-in-time] compilation phases?

Step-by-step:

  1. Parser β†’ Converts JS into Abstract Syntax Tree (AST).

  2. Ignition Interpreter:

    • First executes the bytecode. [fastly generated, slower than machine code , intermediate format]

    • byte code help in fast start of code execution

  3. Profiler: Monitors code while it runs.

    • Identifies hot functions (frequently used).
  4. Turbofan Compiler:

    • Compiles hot functions into optimized machine code.
  5. Deoptimization:

    • If assumptions break (e.g., dynamic type changes), V8 falls back to unoptimized code.
function add(a, b) {
  return a + b;
}

If add() is always used with numbers, Turbofan optimizes for that. If suddenly passed a string, deoptimization is triggered.

Describe the difference between interpreter, compiler, and JIT in context of JavaScript engines.

Engine TypeDescriptionExample
InterpreterExecutes code line-by-line.V8's Ignition
CompilerConverts code to machine code ahead of time (AOT).C/C++ compilers
JIT CompilerMixes both: interprets then compiles during execution.V8's Turbofan

In V8:

  • JS is first interpreted (fast start)

  • Profiler finds hot code

  • That code is then compiled to optimized machine code

So you get the best of both worlds: fast startup and fast execution.

ES6:

ECMAScript 6 (ES6), also known as ECMAScript 2015, is a major update to the JavaScript language. It introduced many new features and improvements that make JavaScript more powerful and easier to work with

key features:

  • let,const

  • arrow fns

  • template literals

  • rest,spread operators

  • maps,sets etc....

  • array destructure

use camel case to name your js variables [eg: fullName]

Datatypes supported by js

primitive:

  • number

  • string

  • boolean

  • undefined : A variable that has been declared but not assigned a value

  • null: Represents the intentional absence of any value.

    • let y = null
  • symbol

  • bigInt

non-primitive:

  • object

  • array

  • function

template literals

template literals makes it easier to construct strings with variables and expressions. [uses back ticks ``]

/*Template literals can span multiple lines without
 needing special characters like \n for new lines*/
const multilineString = `This is a string
that spans multiple
lines.`;

/*You can embed expressions,variables inside a template literal
using string interpolation*/
const name = 'Alice';
const age = 25;
const greeting = `Hello, my name is ${name} and I am ${age} years old.`;

/*You can include any valid JavaScript expression inside the ${}. 
This includes function calls, mathematical operations, and more.*/
const a = 10;
const b = 20;
const result = `The sum of ${a} and ${b} is ${a + b}.`;

operators in js

//addition
let sum = 5 + 3;  // 8
let concatenated = 'Hello' + ' World';  // 'Hello World'
//subtraction
let difference = 10 - 2;  // 8
//multiplication
let product = 4 * 3;  // 12
//division
let quotient = 12 / 4;  // 3
//modulus
let remainder = 10 % 3;  // 1
//Exponentiation 
let power = 2 ** 3;  // 8

//Increment (++): 
let a = 5;
++a;//Increases a number by one in current step.
a++;  // Increases a number by one in next step.

let x = 5;
console.log(x++ + ++x); // 5+7 = 12

//Decrement (--): 
let b = 5;
b--;  // Decreases a number by one in next step.
//assignment operators
//Assignment (=): Assigns a value to a variable.
let x = 10;
x += 5;  // x is now 15
x -= 3;  // x is now 12
x *= 2;  // x is now 24
x /= 4;  // x is now 6
x %= 5;  // x is now 1
x **= 3;  // x is now 1
//comparision operators
/*Equal (==): Checks if two values are equal 
(type conversion is performed). loose check*/
5 == '5';  // true
/*Strict Equal (===): Checks if two values are equal and 
of the same type.*/
5 === '5';  // false
5 != '5';  // false
5 !== '5';  // true
10 > 5;  // true
10 >= 10;  // true
10 < 5;  // false
10 <= 10;  // true

console.log(5 == "5"); // true (type coercion happens)
console.log(5 === "5"); // false (types don't match coz no type coercion)

console.log(null == undefined); // true (special coercion rule)
console.log(null === undefined); // false
null + 1 // 1 
undefined + 1 // NaN
//logical operators
true && false;  // false
true || false;  // true
!true;  // false

//bitwise operators
5 & 1;  // 1 (0101 & 0001)
5 | 1;  // 5 (0101 | 0001)
5 ^ 1;  // 4 (0101 ^ 0001) -- bitwise XOR
5 << 1;  // 10 (0101 << 1 is 1010)
5 >> 1;  // 2 (0101 >> 1 is 0010)


console.log(0 || 1 && 2 || 3); // 2
/*AND (&&) has higher precedence than OR (||)
If operators have the same precedence, evaluation proceeds from left to right.*/

//step 1: 1 && 2 ---- Returns 2 coz -- && returns the last truthy value if all operands are truthy
//step 2: 0 || 2 --- Returns 2 coz || returns first truthy
//step 3: 2 || 3 ---- Returns 2 coz || returns first truthy

//ternary operator
let age = 18;
let canVote = (age >= 18) ? 'Yes' : 'No';  // 'Yes'
//type operators
//typeof: Returns the type of a variable.
typeof 42;  // 'number'

typeof Symbol("id") // "symbol"
typeof Math; // "object" --- Math is a built-in object that provides mathematical operations
typeof null // "object"  (2)
typeof alert // "function"  (3)

/*there’s no special β€œfunction” type in JavaScript. Functions belong to the object type.
 But typeof treats them differently, returning "function". That also comes from the early days 
of JavaScript. Technically, such behavior isn’t correct, but can be convenient in practice.*/

/*instanceof: Checks if an object is an instance of a 
particular class or constructor.*/
let date = new Date();
date instanceof Date;  // true

to know operator precedence use BODMAS Rule

shortcircuiting in js

  • || (OR) returns the first truthy value if all are truthys or the last value if all are falsy.

  • && (AND) returns the first falsy value if something false or the last value if all are truthy.

  •       console.log(null || "default");   // "default" (null is falsy)
          console.log("value" && "next");   // "next" (both truthy, returns last value)
          console.log(false || 0 || "JS");  // "JS" (first truthy value)
          console.log(10 && 0 && "Hello");  // 0 (first falsy value)
    

constructor fns in js[imp]

Constructor functions are often used when you want to create multiple objects with the same properties and methods

Defining a Constructor Function:

  • A constructor function is defined like a regular function but is typically named with a capital letter to distinguish it from regular functions.

  • Inside the constructor function, the this keyword is used to assign properties/methods to the objects that will be created.

function Person(name, age) {
    this.name = name;
    this.age = age;

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

let person1 = new Person("Alice", 30);
let person2 = new Person("Bob", 25);

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

In this case, person1 and person2 each have their own copy of the greet method. This can waste memory if many instances are created because each instance has its own copy of the method.

To avoid this duplication, JavaScript provides prototypes. Prototypes allow methods to be shared across all instances of an object type. This means there's only one copy of the method, and all instances refer to it.

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

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

let person1 = new Person("Alice", 30);
let person2 = new Person("Bob", 25);

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

/*In this approach, the greet method is not duplicated for each instance.
 Instead, both person1 and person2 refer to the single greet method defined 
on the Person.prototype.*/

When you call a method on an object, JavaScript first looks for that method on the object itself. If it doesn't find it, it looks up the prototype chain to find it:

  • person1.greet() looks for greet on person1.

  • If not found, it looks up to Person.prototype.greet.

This way, methods defined on the prototype are shared across all instances, saving memory and making the code more efficient.

to check if an object created by constructor fn has a particular property or not use hasOwnProperty() method

let person1 = new Person("Alice", 30);
person1.hasOwnProperty('firstName'); //gives true or false

Object.assign

it is used to copy the entries from one or more source objects to a target object. It returns the target object.

it is also used to merge 2 different objects into a target object

const target = { a: 1, b: 4 };
const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);

console.log(target); // Output: { a: 1, b: 2, c: 3 }
  • here target object starts with property 'a' and 'b' .

  • The source objects has properties b and c.

  • Object.assign copies properties from sources to target. If a property already exists in target, it is overwritten by the property from sources

shallow copy/clone v/s deep copy

A shallow copy copies only the first-level properties of an object. If the object contains nested objects or arrays, the references to those inner objects remain the same.

A deep copy creates a new object with completely independent copies of all nested objects and arrays.

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
obj2.b.c = 42;
obj2.a = 39;
console.log(obj1.b.c, obj1.a); // 42 (not a deep copy!) 1(deep copy)
/* Limitation: Only performs a shallow copy, so nested objects remain referenced,where as
first lvl objects are mantained as different copies*/.

const obj3 = { ...obj1 };
/*here the spread operator also does shallow copy only, so object.assign is same as spread 
operator no difference*/
//How to create a deep copy of an object instead of using Object.assign()?
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1)); // Deep copy
obj2.b.c = 42;
console.log(obj1.b.c); // 2 (original remains unchanged)

/* Since Object.assign() is shallow, use JSON.parse(JSON.stringify(obj)) for deep copies
 (but loses functions & undefined). */

/*So when to use it JSON.parse(JSON.stringify(obj)):
--------- When you need a quick deep copy of plain objects/arrays
---------But not when your object contains:
-----------------------functions
-----------------------undefined
-----------------------special types (e.g., Date, Map, Set, RegExp)
*/

//Use structuredClone(obj) in modern browsers

// Using structuredClone() βœ… (Modern & Preferred)

const deepClone = structuredClone(obj);
//βœ… Handles most types: nested objects, arrays, Dates, etc.
//❌ Not supported in very old browsers.

Object.create

Object.create is used to create a new object with the specified prototype object and properties. It returns the new object.

const personPrototype = {
    greet: function() {
        console.log("Hello, my name is " + this.name);
    }
};

const person = Object.create(personPrototype);
const person = Object.create(personPrototype, {
    name: { value: "Alice", 
            writable: true, configurable: true, enumerable: true }
});

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

/*
The writable attribute determines whether the value of a property can be 
changed. if false, such property's value is read-only

The configurable attribute determines whether the property descriptor 
can be changed and whether the property can be deleted from the object.
*/

In this example:

  • personPrototype is an object with a greet method.

  • Object.create(personPrototype) creates a new object with personPrototype as its prototype.

  • The new object person can use the greet method defined in personPrototype.

  • Now:

    • person.greet() works βœ…

    • But person.hasOwnProperty('greet') is ❌ false

Because greet is not on the person object itself, but on its prototype.

//enumerable example
/*The enumerable attribute determines whether the property will show up
 in enumeration of the object's properties. Enumeration is done, 
for example, with a for...in loop or Object.keys.
*/

const obj = {};

Object.defineProperty(obj, 'name', {
    value: 'Alice',
    enumerable: true
});

Object.defineProperty(obj, 'age', {
    value: 30,
    enumerable: false
});

for (let key in obj) {
    console.log(key); // Output: name
}

console.log(Object.keys(obj)); // Output: ['name']
console.log(obj.propertyIsEnumerable('name')); // Output: true
console.log(obj.propertyIsEnumerable('age')); // Output: false


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

console.log(Object.values(person));
// Output: ["John", 30, "New York"]

console.log(Object.entries(person));
/* Output:
[
  ["name", "John"],
  ["age", 30],
  ["city", "New York"]
]
*/

const arr = [["a", 1], ["b", 2]];
const obj = Object.fromEntries(arr);
console.log(obj); // { a: 1, b: 2 }
/*creating object back from entries --- Object.fromEntries*/
function Test1() {
  this.name = "Test";
  return 42; // Ignored
}
console.log(new Test1().name); // Test
/*If a primitive value (e.g., string, number, boolean) is returned from constructor fns
, it is ignored, and this (the newly created object) is returned instead. */


function Test2() {
  this.name = "Test";
  return { greeting: "Hello" }; // Replaces `this`
}
console.log(new Test2().greeting); // Hello
console.log(new Test2().name); // undefined
/*If an object is returned, that object replaces this.*/

How do you create private properties in a constructor function?

//Concepts tested: Encapsulation, closures
/*To create private properties, use a closure by defining variables inside the function scope instead
 of attaching them to this. */
// Private variables prevent direct modification and provide better encapsulation.

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

  this.increment = function () {
    count++;
    console.log(count);
  };

  this.getCount = function () {
    return count;
  };
}

const counter = new Counter();
counter.increment(); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined (private)

What is the difference between ES5 constructor functions and ES6 classes?

FeatureConstructor FunctionES6 Class

Syntax

Function-based

class keyword

Prototype Methods

Defined manually

Automatically added to prototype

Hoisting

Hoisted like regular functions

Not hoisted

super Keyword

Not available

Used for inheritance

new Requirement

Can be called without new (unsafe)

Must be called with new

ES6 classes are syntactic sugar over constructor functions but enforce better structure and safety.

// Constructor Function
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log(`${this.name} makes a sound`);
};

// ES6 Class
class AnimalClass {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

conditional stmts

//if
let x = 10;

if (x > 5) {
  console.log("x is greater than 5");
}
//if else
let x = 10;

if (x > 5) {
  console.log("x is greater than 5");
} else {
  console.log("x is not greater than 5");
}
// else if
let x = 10;

if (x > 15) {
  console.log("x is greater than 15");
} else if (x > 5) {
  console.log("x is greater than 5 but less than or equal to 15");
} else {
  console.log("x is 5 or less");
}
//switch
let day = 3;
let dayName;

switch (day) {
  case 1:
    dayName = "Monday";
    break;
  case 2:
    dayName = "Tuesday";
    break;
  default:
    dayName = "Invalid day";
}

console.log(dayName);

/*If you do not put a break statement in a switch case, the program will not
 stop after executing the matched case but will continue to execute the 
subsequent cases until a break is encountered or the switch statement ends. 
This behavior is known as "fall-through."*/
let day = 2;

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

output:
Tuesday
Wednesday
Thursday
Friday
Invalid day

/*if we return anything from switch case then break is not necessary for
such case, coz once we return anything we will come out of that entire 
switch block*/

loops

//for
for (let i = 0; i < 5; i++) {
  console.log(i);
}

//while -- Loops through a block of code as long as a specified condition is true.
let i = 0;

while (i < 5) {
  console.log(i);
  i++;
}

/*do while --- loops through a block of code as long as a specified 
condition is true, but it will always execute the block at least once. */
let j = 0;

do {
  console.log(j);
  j++;
} while (j < 5);

//for in loop -- Loops through the properties of an object.
let person = {fname: "John", lname: "Doe", age: 25};

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

// for of loop --- Loops through the values of an iterable object like an array.
let numbers = [10, 20, 30];

for (let num of numbers) {
  console.log(num);
}

break,continue, return differences

break:

When break is encountered, the loop or switch terminates immediately, and the program continues with the next statement after the loop or switch.

continue:

continue statement is used to skip the current iteration of a loop and continue with the next iteration. When continue is encountered, the rest of the code inside the loop for the current iteration is skipped.

for (let i = 0; i < 10; i++) {
  if (i === 5) {
    continue; // Skip the iteration when i is 5
  }
  console.log(i); // Output: 0 1 2 3 4 6 7 8 9
}

return

The return statement is used to exit a function and return a value from that function. When return is encountered, the function stops executing and returns the specified value to the caller.

function checkNumber(num) {
  if (num < 0) {
    return "Negative number"; // Exit function if num is negative
  }
  return "Positive number";
}

let message = checkNumber(-5);
console.log(message); // Output: "Negative number"

type coercion

Type coercion in JavaScript is the process of converting a value from one type to another (e.g., string to number, object to boolean etc..) .

Implicit Type Coercion

Implicit type coercion occurs when JavaScript automatically converts types during operations.

let result = "5" + 3; // "53"
let result = "5" - 3; // 2 (string "5" is coerced to number 5)
let result = "5" * 2; // 10 (string "5" is coerced to number 5)
if ("") {
  console.log("This will not log"); // Empty string is coerced to false
}

if ("non-empty string") {
  console.log("This will log"); // Non-empty string is coerced to true
}
let result = 5 == "5"; // true (string "5" is coerced to number 5)
let result = 5 === "5"; // false (no coercion, different types)

Explicit Type Coercion

Explicit type coercion occurs when you manually convert a value from one type to another using built-in functions or operators.

let num = Number("123"); // 123
let num = Number(undefined) //NaN
let num = Number(null) // 0
let num = Number("lakshmi prasanna") //NaN
let num = parseInt("123"); // 123
let num = parseFloat("123.45"); // 123.45
let str = String(123); // "123"
let str = (123).toString(); // "123"
let str = String(true); // "true"
let bool = Boolean("non-empty string"); // true
let bool = Boolean(""); // false
let bool = Boolean(1); // true
let bool = Boolean(0); // false

To avoid unintended type coercion, use strict equality (===) and strict inequality (!==) operators, which do not perform type coercion.

Automatic Type Conversion Rules

  • Number to String: When a number is added to a string, the number is converted to a string.

  • String to Number: When a string is involved in a subtraction, multiplication, or division operation, JavaScript tries to convert it to a number.

  • Boolean to Number:true is converted to 1, and false is converted to 0.

console.log([] == false);  // true
/*type coercion happend and [] is converted to empty string "" , empty string is a falsy
"" == false evaluated to true */

console.log([] === false); // false
/*no type coercion empty array is not a falsy ,so output is false*/

falsy values in js

In JavaScript, a falsy value is a value that is considered false when encountered in a Boolean context. The falsy values in JavaScript are:

  1. false

  2. 0 (zero)

  3. -0 (negative zero)

  4. 0n (BigInt zero)

  5. "" (empty string)

  6. null

  7. undefined

  8. NaN (Not-a-Number) β€”- represents a computational error

  9.   alert("not a number" / 2); // NaN, such division is erroneous
    
     // NaN is sticky. Any further mathematical operation on NaN returns NaN:
     alert(NaN + 1); // NaN
     alert(3 * NaN); // NaN
    
     // only one exception to above stmt : NaN ** 0 is 1
    

remember empty object,array β€”- are truthy values

  • NaN === NaN is false because NaN is a special floating-point value that isn't equal to itself. so use

  •   console.log(Number.isNaN(NaN)); // true
    

If the string is not a valid number, the result of such a conversion from string to number is NaN. For instance:

let age = Number("an arbitrary string instead of a number");

alert(age); // NaN, conversion failed

functional programming in js

involves writing code using functions as the primary building blocks. This approach emphasizes the use of pure functions, immutability, and higher-order functions. JS, being a versatile language, supports functional programming alongside other paradigms like object-oriented programming

pure functions

Pure functions always produce the same output for the same inputs and have no side effects.

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

console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5 (same output for same input)

First-Class Functions/Higher-Order Functions

Functions are treated as first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned from other functions

map,filter,reduce are also higher order fns

Avoiding Side Effects

Functions should avoid side effects like modifying external variables or states. Instead, they should only depend on their input parameters and return new values.

let counter = 0;

// Impure function with side effect
function increment() {
  counter++;
}

// Pure function without side effect
function incrementPure(count) {
  return count + 1;
}

console.log(incrementPure(counter)); // 1
console.log(counter); // 0 (original state remains unchanged)

array

let fruits = ["Apple", "Banana", "Mango"];
let fruits = new Array("Apple", "Banana", "Mango");
Array.of(1, 2, 3); // [1, 2, 3]
/*we can create array using above ways*/

methods on arrays

Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
Array.isArray([1, 2, 3]); // true

let fruits = ["Apple", "Banana"];
fruits.length; // 2

/*push(): Adds one or more elements to the end of an array 
and returns the new length.*/
let fruits = ["Apple"];
fruits.push("Banana"); // ["Apple", "Banana"]

/*pop(): Removes the last element from an array and returns that element.*/
let fruits = ["Apple", "Banana"];
fruits.pop(); // "Banana"

/*unshift(): Adds one or more elements to the 
beginning of an array and returns the new length.*/
let fruits = ["Banana"];
fruits.unshift("Apple"); // ["Apple", "Banana"]

/*shift(): Removes the first element from an array and returns that element.*/
let fruits = ["Apple", "Banana"];
fruits.shift(); // "Apple"

/*indexOf(): Returns the first index at which a given element can be found.*/
let fruits = ["Apple", "Banana", "Mango"];
fruits.indexOf("Mango"); // 2 -- gives -1 if asked item is not found

/*lastIndexOf(): Returns the last index at which a given element can be found.*/
let fruits = ["Apple", "Banana", "Mango", "Banana"];
fruits.lastIndexOf("Banana"); // 3

/*includes(): Checks if an array includes a certain element.*/
let fruits = ["Apple", "Banana", "Mango"];
fruits.includes("Banana"); // true

/*reverse(): Reverses the order of the elements in an array.*/
let numbers = [1, 2, 3];
numbers.reverse(); // [3, 2, 1]

/*sort(): Sorts the elements of an array.*/
let numbers = [3, 1, 2];
numbers.sort(); // [1, 2, 3]

/*concat(): Merges two or more arrays.*/
let fruits = ["Apple"];
let moreFruits = ["Banana", "Mango"];
let allFruits = fruits.concat(moreFruits); // ["Apple", "Banana", "Mango"]

/*join(): Joins all elements of an array into a string.*/
let fruits = ["Apple", "Banana", "Mango"];
fruits.join(", "); // "Apple, Banana, Mango"

/*fill(): Fills all the elements of an array from a start index to an 
end index with a static value.*/
let numbers = [1, 2, 3];
numbers.fill(0); // [0, 0, 0]

numbers = [1,2,3,4,5,6,7];
numbers.fill("p",2,5); 
// [1,2,"p","p","p",6,7] --- fills from position 2[inclusive] to 5[exclusive]

/*flat(): Creates a new array with all sub-array elements concatenated 
into it recursively up to the specified depth.*/
let nested = [1, [2, [3, [4]]]];
nested.flat(2); // [1, 2, 3, [4]]

/*every(): Checks if all elements in the array pass the test implemented 
by the provided function.*/
let numbers = [1, 2, 3];
let allEven = numbers.every(n => n % 2 === 0); // false

/*some(): Checks if at least one element in the array passes the test 
implemented by the provided function*/
let numbers = [1, 2, 3];
let someEven = numbers.some(n => n % 2 === 0); // true
/*in above arrow fn if you are using curly braces, you must use return
keyword (n => {return n%2 === 0})*/

/*filter(): Creates a new array with all elements that pass the test 
implemented by the provided function.*/
let numbers = [1, 2, 3, 4];
let even = numbers.filter(n => n % 2 === 0); // [2, 4]

/*forEach(): Executes a provided function once for each array element.*/
let fruits = ["Apple", "Banana", "Mango"];
fruits.forEach(fruit => console.log(fruit));

/*map(): Creates a new array with the results of calling a 
provided function on every element.*/
let numbers = [1, 2, 3];
let doubled = numbers.map(n => n * 2); // [2, 4, 6]

/*find(): Returns the first element that satisfies the provided 
testing function. */
let numbers = [1, 2, 3];
let even = numbers.find(n => n % 2 === 0); // 2

/*findIndex(): Returns the index of the first element that satisfies 
the provided testing function.*/
let numbers = [1, 2, 3];
let evenIndex = numbers.findIndex(n => n % 2 === 0); // 1

slice and splice

/*splice(): Adds, removes, or replaces elements in an array.*/
/*(startindex,count,replacevalue)*/
let fruits = ["Apple", "Banana", "Mango","Orange","guava"];
fruits.splice(1, 1, "Cherry"); // ["Apple", "Cherry","Mango","Orange","guava"]
fruits.splice(1,3,"kiwi","dragonfrt"); //["Apple","kiwi","dragonfrt","guava"];

/*slice(): Returns a shallow copy of a portion of an array into a new array.*/
/*(startindex,endindex)*/
let fruits = ["Apple", "Banana", "Mango","orange"];
let citrus = fruits.slice(1, 2); // ["Banana"]
let frts = fruits.slice(1); // ["Banana","Mango","orange"]

objects

/*accessing object properties can be done via bracket notation, dot notation*/
//dot notation
let person = {
  firstName: "John",
  lastName: "Doe",
  age: 30
};

console.log(person.firstName); //john

//bracketnotation
console.log(person["firstName"]); // John

let propertyName = "firstName";
console.log(person[propertyName]); // John

let property = "lastName";
person[property] = "Smith";
console.log(person[property]); // Smith
let students = [
  { name: "Alice", age: 20, grade: "A" },
  { name: "Bob", age: 22, grade: "B" },
  { name: "Charlie", age: 23, grade: "C" }
];

console.table(students);
/*displays above object in table format*/
(index)nameagegrade
0Alice20A
1Bob22B
2Charlie23C
//interview qn on for loop
var i = 0;

for(;;){
    if(i>3) break;
    console.log(i);
    i++;
}
/*above code works same as the for loop i++ inside block does increment,
if condition acts as checker, we declared a global variable i*/

this keyword

var user = {
firstName:'Lakshmi',
lastName: 'Prasanna',
courseList: [],
buyCourse: function (courseName) {
                this.courseList.push(courseName); 
            //this helps us to access properties in current 'user' object
                function sayHi(){
                    console.log('HI',this);
                }
                sayHi(); 
                // here this prints window obj coz this is regular fn call
           },
getCourseDetails: function () {
return `${this.firstName} has bought ${this.courseList.length} courses`
 //this helps us to access properties in current 'user' object
}
}

console.log(user.buycourse('nodejs'));
console.log(user.getCourseDetails()); 
//for all regular function calls this points to global object
//above 2 are not regular fn calls they are called from an obj using 'obj.fn'

DOM

Here are some of the most commonly used DOM (Document Object Model) APIs in JavaScript:

//Selecting Elements
//document.getElementById(id)

<div id="example">Hello World</div>

<script>
  const element = document.getElementById('example');
  console.log(element.textContent); // Output: Hello World
</script>

//document.getElementsByClassName(className)
<div class="example">Hello</div>
<div class="example">World</div>

<script>
  const elements = document.getElementsByClassName('example');
  console.log(elements[0].textContent); // Output: Hello
  console.log(elements[1].textContent); // Output: World
</script>

//document.querySelector(selector)
<div class="example">Hello World</div>

<script>
  const element = document.querySelector('.example');
  console.log(element.textContent); // Output: Hello World
</script>

//document.querySelectorAll(selector)
<div class="example">Hello</div>
<div class="example">World</div>

<script>
  const elements = document.querySelectorAll('.example');
  elements.forEach(el => console.log(el.textContent));
  // Output: Hello
  // Output: World
</script>
//Manipulating Elements
//element.textContent
<div id="example">Hello</div>

<script>
  const element = document.getElementById('example');
  element.textContent = 'Hello World';
  console.log(element.textContent); // Output: Hello World
</script>

//element.innerHTML
<div id="example"><p>Hello</p></div>

<script>
  const element = document.getElementById('example');
  element.innerHTML = '<p>Hello World</p>';
  console.log(element.innerHTML); // Output: <p>Hello World</p>
</script>

//element.setAttribute(name, value)
<div id="example"></div>

<script>
  const element = document.getElementById('example');
  element.setAttribute('data-example', 'value');
  console.log(element.getAttribute('data-example')); // Output: value
</script>

//element.removeAttribute(name)
<div id="example" data-example="value"></div>

<script>
  const element = document.getElementById('example');
  element.removeAttribute('data-example');
  console.log(element.getAttribute('data-example')); // Output: null
</script>
//Creating and Inserting Elements
//document.createElement(tagName)
<div id="parent"></div>

<script>
  const newElement = document.createElement('p');
  newElement.textContent = 'Hello World';
  document.getElementById('parent').appendChild(newElement);
  console.log(document.getElementById('parent').innerHTML); // Output: <p>Hello World</p>
</script>

//parentNode.insertBefore(newNode, referenceNode)
<div id="parent">
  <p>First</p>
  <p>Third</p>
</div>

<script>
  const newElement = document.createElement('p');
  newElement.textContent = 'Second';
  const parent = document.getElementById('parent');
  parent.insertBefore(newElement, parent.children[1]);
  console.log(parent.innerHTML);
  // Output:
  // <p>First</p>
  // <p>Second</p>
  // <p>Third</p>
</script>


//parentNode.removeChild(childNode)
<div id="parent">
  <p>First</p>
  <p>Second</p>
</div>

<script>
  const parent = document.getElementById('parent');
  parent.removeChild(parent.children[1]);
  console.log(parent.innerHTML); // Output: <p>First</p>
</script>
//Event Handling
//element.addEventListener(type, listener)
<button id="example">Click me</button>

<script>
  const button = document.getElementById('example');
  button.addEventListener('click', () => {
    alert('Button clicked!');
  });
</script>

//element.removeEventListener(type, listener)
<button id="example">Click me</button>

<script>
  function handleClick() {
    alert('Button clicked!');
  }

  const button = document.getElementById('example');
  button.addEventListener('click', handleClick);
  button.removeEventListener('click', handleClick);
</script>
//CSS Manipulation
//element.style.property
<div id="example">Hello</div>

<script>
  const element = document.getElementById('example');
  element.style.color = 'red';
  console.log(element.style.color); // Output: red
</script>

//element.classList.add(className)
<div id="example">Hello</div>

<script>
  const element = document.getElementById('example');
  element.classList.add('highlight');
  console.log(element.classList.contains('highlight')); // Output: true
</script>
//Form Elements
//inputElement.value
<input type="text" id="example" value="Hello">

<script>
  const input = document.getElementById('example');
  console.log(input.value); // Output: Hello
  input.value = 'Hello World';
  console.log(input.value); // Output: Hello World
</script>

//formElement.submit()
<form id="example" action="/submit" method="post">
  <input type="text" name="name" value="John Doe">
  <button type="submit">Submit</button>
</form>

<script>
  const form = document.getElementById('example');
  form.submit();
</script>
//to get styles of an element always use below syntax
window.getComputedStyle(item).backgroundColor

//to change styles of an element use below syntax
colorCircle.style.backgroundColor

self executable anonymous function

A self-executing anonymous function, also known as an Immediately Invoked Function Expression (IIFE), is a function that is defined and executed simultaneously

(function() {
    console.log('This is an IIFE');
})();

(function(name) {
    console.log('Hello, ' + name);
})('Alice');

bind,call,apply

these are used to borrowing methods from other objects or setting context dynamically.

// Example object
const person = {
  name: "Alice",
  greet: function(town) {
    console.log("Hello, my name is " + this.name + town);
  }
};

// If we call greet directly from the person object, it works as expected
person.greet(); // Output: Hello, my name is Alice

/* However, if we assign the greet function to a variable and call it,
 the context is lost*/
const greetFunction = person.greet;
greetFunction(); // Output: Hello, my name is undefined

/* To fix this, we can use bind to create a new function with the
 correct context */
const boundGreetFunction = person.greet.bind(person,'hyderabad');
boundGreetFunction(); // Output: Hello, my name is Alice hyderabad
//function borrowing via call
const person1 = {
  firstName: "John",
  lastName: "Doe",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

const person2 = {
  firstName: "Jane",
  lastName: "Smith"
};

// Using call to borrow fullName method from person1
console.log(person1.fullName.call(person2)); // Output: Jane Smith
/*call example with arguments*/
function greet(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

const person = {
  name: "Alice"
};

greet.call(person, "Hello", "!"); // Output: Hello, Alice!
//giving context to a fn using call
const person1 = {
  firstName: "John",
  lastName: "Doe",
};

 function fullName() {
    return this.firstName + " " + this.lastName;
  }

const person2 = {
  firstName: "Jane",
  lastName: "Smith"
};

console.log(fullName.call(person2)); // Output: Jane Smith

only difference b/w call and apply method is the way we pass arguments

//for above example using apply -- we pass arguments in an array here 
console.log(fullName.apply(person2,['hyderabad']));

when we use call and apply, function will be called directly, but bind returns a function and we have to call it

polyfill for bind [vvv imp] -- own implementation of bind

const person1 = {
  firstName: "John",
  lastName: "Doe",
};

const person2 = {
  firstName: "Jane",
  lastName: "Smith"
};

function fullName(town,street) {
    return this.firstName + " " + this.lastName + " " + town + " " + street;
}

/* see here we are passing arguments at 2 levls [when binding and also when
calling returned function as well] */
let printJohnName = fullName.bind(person1,'hyderabad');
printJohnName("dwarakapuri");

/*bind polyfill, we have to make our bind polyfill available for every 
function so place it in prototype of function*/
Function.prototype.myBind = function (...args1) {
    let context = args1[0];
    let arguments1 = args.slice[1]; 
    //this points to fn on which we called myBind
    let functionUsed = this; 
/*bind always returns a function so lets return a function*/
/*args2 are args provided by us when calling function returned from bind*/
    return function(...args2){
        /*in below log we are concatenating all arguments and giving
        context and arguments to 'functionUsed'*/
        console.log(functionUsed.apply(context,[...arguments1 , ...args2]));
    }
}

/*so myBind just works as bind here*/
let printSmithName = fullName.myBind(person2,'uttarakhand');
printSmithName('RaghavStreet');

prototype and prototypical inheritance in js

one object trying to access properties and methods of another object is inheritance

js engine automatically attaches lot of properties and methods to objects,arrays,functions we create , those attached properties and methods combined into an object is called a prototype

the Prototype which is an object is attached to our object/array/fn and can be accessed like below

let arr = [1,2]
arr._proto_ //this proto gives us array prototype === Array.prototype
arr._proto_._proto_ 
//this gives us object protoype as every array is an obj === Object.prototype
arr._proto_._proto_._proto_ // gives null

the above shown concept in code is called as prototypical chain, same happens with fns also

const person1 = {
  firstName: "Prasanna",
  lastName: "Doe",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

const person2 = {
  firstName: "Jane",
};

//never do this in real life coding it leads to performace issues
person2.__proto__ = person1

/*an object we create searches for asked property/method in itself, if not
found , it searces in its proto, if not found it searches in its proto's p
proto --- this process is prototypical inheritance*/
console.log(person2.lastName); //prints Doe
console.log(person2.fullName()); //Jane Doe

scope

var name = 'prasanna';

if(true){
    var lastName = 'lakshmi';
}

console.log(lastName); //lakshmi 
/* as var is function scoped, the var in if block is also attached to window
object as there is no function over here*/

Strings

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

let str = 'Hello';
console.log(str[0]); // Output: H
console.log(str.charAt(1)); // Output: e

let str = 'Hello';
console.log(str.toUpperCase()); // Output: HELLO
console.log(str.toLowerCase()); // Output: hello

let str = 'Hello, world!';
console.log(str.indexOf('world')); // Output: 7
console.log(str.indexOf('JavaScript')); // Output: -1

console.log(str.includes('world')); // Output: true
console.log(str.includes('JavaScript')); // Output: false

console.log(str.substring(0, 5)); // Output: Hello
console.log(str.substring(7)); // Output: world!

let str = 'Hello, world!';
let newStr = str.replace('world', 'JavaScript');
console.log(newStr); // Output: Hello, JavaScript!

let str = 'Hello, world!';
let arr = str.split(', ');
console.log(arr); // Output: ['Hello', 'world!']

let str = '   Hello, world!   ';
console.log(str.trim()); // Output: 'Hello, world!'

let str = 'Hello, world!';
console.log(str.endsWith('world!')); // Output: true
console.log(str.endsWith('world')); // Output: false

maps in js

A Map is a collection of key-value pairs where both the keys and values can be of any type. This is different from plain objects, which only allow strings or symbols as keys.

Size:

  • Map: Has a size property to get the number of key-value pairs.

  • Object: To get the number of properties, you must use Object.keys(obj).length.

const map = new Map();
const mapWithValues = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]);

map.set('name', 'Alice');
map.set(42, 'Answer to the Ultimate Question');

console.log(map.get('name')); // Alice
console.log(map.get(42)); // Answer to the Ultimate Question

console.log(map.has('name')); // true
console.log(map.has('nonexistent')); // false

map.delete('name');
console.log(map.has('name')); // false

map.clear();
console.log(map.size); // 0

console.log(map.size); // 2 (before clear)

// Iterating over keys
for (let key of map.keys()) {
  console.log(`Key: ${key}`);
}

// Iterating over values
for (let value of map.values()) {
  console.log(`Value: ${value}`);
}

// Iterating over entries
for (let [key, value] of map.entries()) {
  console.log(`Entry: ${key} = ${value}`);
}

// Using forEach
map.forEach((value, key) => {
  console.log(`forEach: ${key} = ${value}`);
});

destructure arrays,objects in js

When you destructure an object, you create variables that correspond to the property names of the object.

const person = {
  name: 'Alice',
  age: 25,
  city: 'Wonderland'
};

// Destructuring the object
const { name, age, city } = person;

console.log(name); // Alice
console.log(age); // 25
console.log(city); // Wonderland

// Renaming the variables
const { name: personName, age: personAge, city: personCity } = person;

console.log(personName); // Alice
console.log(personAge); // 25
console.log(personCity); // Wonderland


const person = {
  name: 'Alice',
  age: 25
};

// Assigning default value for city
const { name, age, city = 'Unknown' } = person;
console.log(city); // Unknown
const person = {
  name: 'Alice',
  age: 25,
  address: {
    street: '123 Main St',
    city: 'Wonderland'
  }
};

// Destructuring nested object
const { name, address: { street, city } } = person;

console.log(street); // 123 Main St
console.log(city); // Wonderland
function printPerson({ name, age, city }) {
  console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}

const person = {
  name: 'Alice',
  age: 25,
  city: 'Wonderland'
};

printPerson(person); // Name: Alice, Age: 25, City: Wonderland

//Q: What does the rest operator (...) do in object destructuring? Provide an example.
const obj = { a: 1, b: 2, c: 3 };
const { a, ...rest } = obj;
console.log(a); // 1
console.log(rest); // { b: 2, c: 3 } 
//ans: It collects the remaining properties of an object into a new object.

arrays destructure

const colors = ['red', 'green', 'blue'];

// Destructuring the array
const [firstColor, secondColor, thirdColor] = colors;

console.log(firstColor); // red
console.log(secondColor); // green
console.log(thirdColor); // blue

// Skipping the second item
const [firstColor, , thirdColor] = colors;

console.log(firstColor); // red
console.log(thirdColor); // blue


const colors = ['red'];

// Assigning default values
const [firstColor, secondColor = 'green', thirdColor = 'blue'] = colors;

console.log(secondColor); // green
console.log(thirdColor); // blue
const colors = ['red', 'green', 'blue', 'yellow'];

// Using rest operator
const [firstColor, ...restColors] = colors;

console.log(firstColor); // red
console.log(restColors); // ['green', 'blue', 'yellow']
const nestedArray = [1, [2, 3], 4];

// Destructuring nested arrays
const [first, [second, third], fourth] = nestedArray;

console.log(second); // 2
console.log(third); // 3


function printColors([firstColor, secondColor, thirdColor]) {
  console.log(`First: ${firstColor}, Second: ${secondColor}, Third: ${thirdColor}`);
}

const colors = ['red', 'green', 'blue'];

printColors(colors); // First: red, Second: green, Third: blue
  • Object Destructuring: Extracts properties from an object and assigns them to variables.

  • Array Destructuring: Extracts elements from an array and assigns them to variables.

  • Rest Operator: Collects the remaining elements in an array.

spread and rest in js

The rest (...) and spread (...) operators in JavaScript are powerful tools that simplify working with arrays, objects, and function parameters. Though they use the same syntax, their functionality depends on the context in which they are used.

Spread Operator (...)

The spread operator allows you to expand an array or object into its individual elements. This is particularly useful for array and object manipulations.

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

//Copying Arrays:
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
console.log(copiedArray); // [1, 2, 3]

//passing args
const numbers = [1, 2, 3];
function add(a, b, c) {
  return a + b + c;
}
console.log(add(...numbers)); // 6
//spread in objects
//combining
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combinedObj = { ...obj1, ...obj2 };
console.log(combinedObj); // { a: 1, b: 2, c: 3, d: 4 }

//Copying Objects:
const originalObj = { a: 1, b: 2 };
const copiedObj = { ...originalObj };
console.log(copiedObj); // { a: 1, b: 2 }

//update obj
const originalObj = { a: 1, b: 2 };
const updatedObj = { ...originalObj, b: 3 };
console.log(updatedObj); // { a: 1, b: 3 }

Rest Operator (...)

The rest operator Collects multiple arguments into an array or obj. It is primarily used in function parameters and destructuring assignments.

//collecting args to array
function sum(...numbers) {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15


function concatenate(separator, ...strings) {
  return strings.join(separator);
}
console.log(concatenate(', ', 'apple', 'banana', 'cherry')); // 'apple, banana, cherry'
//destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]

const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // 1
console.log(b); // 2
console.log(rest); // { c: 3, d: 4 }

//copying
const nestedArray = [1, [2, 3], 4];
const flatArray = [...nestedArray];
console.log(flatArray); // [1, [2, 3], 4]

const nestedObj = { a: { b: 1 } };
const copiedObj = { ...nestedObj };
console.log(copiedObj); // { a: { b: 1 } }
console.log(copiedObj.a === nestedObj.a); // true (shallow copy)
//empty rest
function noRest(first, second, ...rest) {
  console.log(rest);
}
noRest(1, 2); // []

freezing objects

Object.freeze in JavaScript is used to make an object immutable. Once an object is frozen, you cannot add, delete, or modify its properties.

const obj = {
  name: "John",
  age: 30
};

Object.freeze(obj);

obj.name = "Doe"; // This will not change the name property
obj.gender = "male"; // This will not add the gender property
delete obj.age; // This will not delete the age property

console.log(obj); // Output: { name: "John", age: 30 }

/*Shallow freeze: with freeze Only the immediate properties are frozen.
 Nested objects are not frozen.*/
const obj = {
  name: "John",
  address: {
    city: "New York"
  }
};

Object.freeze(obj);

obj.address.city = "Los Angeles"; // This will change the city property
console.log(obj.address.city); // Output: "Los Angeles"

/*To deeply freeze an object, you need to recursively freeze each 
nested object.*/

Sealing objects

What does Object.seal(obj) do?

  1. βœ… Allows:

    • Changing existing property values.

    • Calling methods, updating object state.

  2. ❌ Prevents:

    • Adding new properties.

    • Deleting existing properties.

    • Changing property descriptors (like making a property non-writable).

const person = {
  name: "Prasanna",
  age: 30
};

Object.seal(person);

person.age = 31;       // βœ… Allowed: modifying existing property
person.city = "Hyd";   // ❌ Not allowed: won't be added
delete person.name;    // ❌ Not allowed: won't be deleted

console.log(person); 
// { name: "Prasanna", age: 31 }

//checking if an object is sealed
Object.isSealed(person); // true
  • Object.seal() works only on the top-level of an object. If the object has nested objects, those are not sealed automatically.

  • You can write a custom function that recursively seals all nested objects

🧠 Summary:

  • Use freeze() when you want a completely read-only object.

  • Use seal() when you want to prevent structure changes, but still allow modifying values.

classes in js

JavaScript classes are a blueprint for creating objects with shared properties and methods. They simplify the process of creating objects, especially when you need to create multiple objects with similar properties and behaviors.

here function keyword is not used for declaring fns

let,var,const are not used to declare variables in class

class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

const dog = new Animal('Rex', 'Dog');
dog.speak();  // Rex makes a noise.

module.exports = Animal;
/*In this example, we defined an Animal class and exported it using 
module.exports. This makes the class available to other files.*/

/*To use the Animal class in another file, you would import it using require.*/
//other file
const Animal = require('./Animal');

const dog = new Animal('Rex', 'Dog');
dog.speak();  // Rex makes a noise.

//============================
//ES6 exporting style
export default Animal;

//ES6 import style in other file
import Animal from './Animal.js';

const dog = new Animal('Rex', 'Dog');
dog.speak();  // Rex makes a noise.
class MathUtil {
  static add(a, b) {
    return a + b;
  }
}

console.log(MathUtil.add(2, 3));  // 5
//Static methods are called on the class itself, not on instances of the class.
//getters and setters
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  get area() {
    return this.width * this.height;
  }

  set area(value) {
    this.width = Math.sqrt(value);
    this.height = Math.sqrt(value);
  }
}

const square = new Rectangle(4, 4);
console.log(square.area);  // 16
square.area = 25;
console.log(square.width);  // 5
console.log(square.height);  // 5
//private fields and methods
class Person {
  #privateField = 'Private';

  #privateMethod() {
    return 'Accessing private method';
  }

  getPrivateField() {
    return this.#privateField;
  }

  accessPrivateMethod() {
    return this.#privateMethod();
  }
}

const person = new Person();
console.log(person.getPrivateField());  // Private
console.log(person.accessPrivateMethod());  // Accessing private method
console.log(person.#privateField);  // Syntax Error
console.log(person.#privateMethod());  // Syntax Error

inheritance

Classes can inherit from other classes using the extends keyword. This allows the derived class to use the properties and methods of the base class.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const john = new Person('John', 'Doe');
console.log(john.fullName());  // John Doe

class Employee extends Person {
  constructor(firstName, lastName, jobTitle) {
    super(firstName, lastName);  // Call the parent class constructor
    this.jobTitle = jobTitle;
  }

  fullDetails() {
    return `${this.fullName()} is a ${this.jobTitle}`;
  }
}

const jane = new Employee('Jane', 'Doe', 'Software Engineer');
console.log(jane.fullDetails());  // Jane Doe is a Software Engineer

/*inheritance of static variables, methods is not possible in js*/

How do you define a method in JavaScript?

  • A method is just a function tied to an object or class.

  •     const obj = {
          greet: function() {
            console.log("Hello, World!");
          }
        };
    
        obj.greet(); // Output: Hello, World!
    
        // ES6 way
        const obj2 = {
          greet() {
            console.log("Hello, World!");
          }
        };
    
        obj2.greet(); // Output: Hello, World!
    
        // using classes
        class MyClass {
          greet() {
            console.log("Hello from the class!");
          }
        }
    
        const instance = new MyClass();
        instance.greet(); // Output: Hello from the class!
    

\================================================================ =================================================================

pageload time

Reducing page load time is essential for :

user experience:

  • Faster load = happier users: Users are more likely to stay and interact with your site.

  • Lower bounce rates: Users won’t leave the site out of frustration.

  • Higher conversions: Especially important for e-commerce or lead generation.

Reduces Server Load & Costs

  • Optimized pages send fewer or smaller requests to the server.

  • Less bandwidth and lower cloud or CDN costs.

  • Improves scalability when handling many users.

Critical for Mobile Users

  • Many users access your app on slower 3G/4G connections.

  • Reducing load time gives them better access, especially in rural or low-bandwidth areas.

boosts SEO:

  • Google uses page speed as a ranking factor.

  • Faster sites get better rankings, more visibility, and more traffic

\=========================================================================

ways to reduce pageload time especially in angular:

1. Lazy Load Modules (Most Important)

πŸ‘‰ Load feature modules only when user navigates to them.

// app-routing.module.ts
{
  path: 'dashboard',
  loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
}

βœ… Benefit: Only loads what's needed β†’ faster initial load

\======

2.lazyloading and compressing images at server

Without Lazy Loading:

  • All <img> elements start loading immediately when the page loads β€” even if they are far down the page.

  • This causes:

    • ⏱️ Longer load time

    • πŸ“Ά More network usage

    • πŸ“‰ Slower First Contentful Paint (FCP)

βœ… With Lazy Loading:

  • Only visible images (or about to become visible) are loaded.

  • Rest of the images are loaded as you scroll.

How to Implement image Lazy Loading in Angular

<img src="assets/image.jpg" loading="lazy" alt="My Image">
  • The loading="lazy" attribute is built into modern browsers (Chrome, Edge, Firefox).

  • Works without JavaScript, very lightweight.

we can us AWS to compress images

Auto Compression of images with AWS:

  • Store original in S3

  • Use AWS Lambda to compress on upload

  • Serve via CloudFront CDN for fast global delivery

\===========

3.Tree-Shakable Modules & Standalone Components (Angular 15+)

1. Tree-Shakable Services with providedIn: 'root'

πŸ” Traditional Way (Before Angular 6):

You had to manually register services in the module like this:

@NgModule({
  providers: [UserService]
})
export class AppModule {}

This made services always included in the final bundle β€” even if unused πŸ˜•


βœ… Modern Way (Angular 6+):

Use providedIn: 'root' to make services tree-shakable:

@Injectable({
  providedIn: 'root'
})
export class UserService {}

πŸ’‘ Meaning:

  • Angular will include UserService only if used in your app.

  • If it's not used anywhere, it will be removed (tree-shaken) from the final bundle during ng build --prod.

2. Standalone Components (Angular 14+ Stable in Angular 15+)

πŸ” Traditional Way:

You had to define every component in a module:

@NgModule({
  declarations: [CardComponent]
})
export class SharedModule {}
  • This made apps heavier and hard to scale.

  • Even unused components might stay in the bundle if a module imported them.


βœ… New Way: Standalone Components

  • Define components without a module using standalone:true metadata in component decorator

  • Now, you can directly import and use those standalone components into any other component

  • Benefit:

    • No need for NgModule

    • Makes components independent, lazy-loadable, and tree-shakable

    • Reduces bundle size and speeds up app load time

\==================

4. Use ng build --configuration production

This command tells Angular to build your app for production use. It enables a series of optimizations automatically. Here's what each one does

It does:

  • βœ… Ahead-of-Time (AOT) Compilation

    • Normally, Angular uses Just-in-Time (JIT) compilation during development.

    • In AOT, your HTML templates and TypeScript code are compiled into JavaScript before the browser loads the app.

      • Faster first load – The browser doesn't need to compile anything.

      • Angular does NOT need to ship its compiler to the browser anymore.

      • So the Angular compiler package (β‰ˆ 150 KB) is excluded from the bundle.

      • More secure – your templates are already compiled, so no one can easily inspect them

  • βœ… Minification (removes spaces, comments)

    • Long variable names (e.g., customerDetails becomes a)

    • Harder to reverse-engineer your source code by others from browsers

  • βœ… Tree shaking (removes unused code):

    • If you import a library but use only one function from it, the rest is not included in the final bundle.
  • βœ… Compression:

    • Although Angular doesn't compress files directly, it produces compression-friendly output (e.g., .js, .css files).

    • Your server (like Apache, NGINX, or Firebase) can apply Gzip/Brotli compression.

    • Gzip reduces file sizes by up to 70%, helps in Faster file delivery

  • Bundle Files:

    • Reduce the number of HTTP requests

    • Bundling means combining multiple files (like JS, CSS, images, etc.) into one or a few files.

Angular (via Webpack) bundles your files like below so tat instead of 10 JS files + 5 CSS files = 15 HTTP requests we can just have 5 HTTP requests β€”β€” this will only happen in prod build not development build, in development build this much of optimisation is not done

\==============

5. using Async , Defer on script tags also helps in page load time

Normally, when a browser sees a <script> tag in HTML:

<script src="app.js"></script>

It will:

  1. Pause HTML rendering

  2. Download the JS file

  3. Run the JS

  4. Then continue rendering the page

πŸŸ₯ This blocks page loading β€” slowing down the initial display.

1. defer Attribute

<script src="app.js" defer></script>

βœ… How it Works:

StageWith defer
HTML parsingβœ… Continues immediately
Script downloadβœ… Happens in background
Script executionβœ… Happens after HTML is fully parsed

Use When:

  • Scripts depend on the DOM (e.g., accessing elements)

  • You want scripts to run in order (like Angular scripts)

βœ… Benefits:

  • Non-blocking: Doesn’t delay rendering

  • Safe: DOM is fully ready before script runs

  • Ordered: Scripts run in the order they appear

  • Angular scripts depend on each other, like building blocks:

    βœ… Correct order:

      1. runtime.js  β†’ loads Webpack logic
      2. polyfills.js β†’ ensures browser compatibility
      3. main.js      β†’ starts the app
      /*If main.js runs before runtime.js, the Angular app can't even start, because the environment 
      it needs isn’t ready.*/
    

2. async Attribute

<script src="analytics.js" async></script>

βœ… How it Works:

StageWith async
HTML parsingβœ… Continues immediately
Script downloadβœ… Happens in background
Script executionπŸŸ₯ Runs as soon as it's ready (may pause rendering)

πŸ“Œ Use When:

  • Script is independent (e.g., analytics, ads, maps)

  • Doesn't depend on DOM or other scripts

βœ… Benefits:

  • Faster load if script is small

  • No wait for full HTML before execution

πŸ“Œ In Angular (by default)

When you build with Angular (ng build --configuration production):

  • Angular automatically uses defer for its scripts in the index.html.

  • You don’t need to manually add it β€” Webpack injects scripts like:

<script src="main.abcd123.js" type="module"></script>

β†’ Modern browsers treat type="module" scripts like defer βœ…

critical rendering path :

this is the minimum work the browser needs to do before it can show anything useful (like text, layout, styles) to the user.

things to do for Optimization of critical rendering path

  • Reduce the time from when the user requests a page to when they see something on screen

  • for this to optimisation to happen minify and compress CSS,JS files

  • lazy load images

  • split code to modules, and load modules only tat are reqd

  • If you use a normal <script> tag in the <head>, the browser will stop parsing HTML until the script is downloaded and executed β†’ this blocks rendering.

    βœ… Solution:

    Use async or defer attributes.

\========================================================================================================================================================

difference b/w textContent and innerText

textContent

Both are properties used to get or set the text inside an HTML element β€” but they work differently.

  • gives us Raw Text from the DOM β€”- it gives you ALL the text, even if it’s hidden using CSS (display: none).

  • It ignores styling and reads whatever is in the HTML.

  •    <div id="demo" style="display:none;">Hello <span>World</span></div>
    
      <script>
      const elem = document.getElementById('demo');
      console.log(elem.textContent); // πŸ‘‰ "Hello World"
      //βœ… It shows the text even though the element is hidden!
      </script>
    

innerText

  • It gives you only the visible text (what the user can see).

  • It respects CSS (like display: none, visibility: hidden)

  •    <div id="demo" style="display:none;">Hello <span>World</span></div>
      <script>
      const elem = document.getElementById('demo');
      console.log(elem.innerText); // πŸ‘‰ "" (empty string)
      // Because the element is hidden, you get nothing.
      </script>
    

    When to Use textContent:

    • βœ… When you want all text regardless of visibility

    • βœ… When you want fast performance

    • βœ… When you’re extracting data from the DOM

When to Use innerText:

  • βœ… When you only want what the user can actually see

  • βœ… When doing UI testing

  • βœ… When considering styles, layout, and visibility

JS sets

A Set is a built-in object in JavaScript that lets you store unique values of any type β€” whether primitive or object references.

  • No duplicates allowed.

  • Order is insertion-based, but it's not indexed like arrays.

  • You can store any type of value β€” numbers, strings, objects, etc.

usage:

const fruits = new Set();

//inserting values into set
fruits.add("mango");
fruits.add("apple");
fruits.add("banana");

console.log(fruits); // Set(3) {"mango", "apple", "banana"}

//removing value from set
fruits.delete("apple");
console.log(fruits); // Set(2) {"mango", "banana"}

//checking if a value is in set
console.log(fruits.has("banana")); // true
console.log(fruits.has("apple"));  // false

//clearing a set
fruits.clear();
console.log(fruits); // Set(0) {}
//stores unique values
const ids = new Set([1, 2, 3, 3, 4]); // Set(4) {1, 2, 3, 4}

//removing duplicates from array using set
const arr = [1, 2, 2, 3, 4, 4];
const uniqueArr = [...new Set(arr)];

βœ… When to Use Sets

  • You need to store unique values

  • You need fast lookups (O(1) complexity) β€” using has

  • You want to remove duplicates from an array

When Not to Use Set

  • If you need to store values with duplicates (e.g., shopping cart with multiple quantities).

  • If you need index-based access like arr[0].

  • If order of insertion is not enough and sorting is needed.