Skip to content

Instantly share code, notes, and snippets.

@yogain123
Last active March 6, 2025 08:20
Show Gist options
  • Save yogain123/0e7000820aadb6f9d8804dec96d32e99 to your computer and use it in GitHub Desktop.
Save yogain123/0e7000820aadb6f9d8804dec96d32e99 to your computer and use it in GitHub Desktop.
Namaste JS
@yogain123
Copy link
Author

Screenshot 2024-08-02 at 11 40 15 AM Screenshot 2024-08-02 at 11 43 12 AM Screenshot 2024-08-02 at 11 44 14 AM

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

Screenshot 2024-08-02 at 11 45 20 AM

here on 1st phase JS parsers the code
Screenshot 2024-08-02 at 11 45 54 AM

After then it execute the code in 2nd phase

Screenshot 2024-08-02 at 11 48 47 AM Screenshot 2024-08-02 at 11 50 20 AM Screenshot 2024-08-02 at 11 51 07 AM

Here's a concise explanation of execution context in JavaScript:

  1. Global Execution Context:

    • Created when the script starts running
    • Represents the global scope
  2. Function Execution Context:

    • Created when a function is called
    • Each function call gets its own execution context
  3. Execution Context Lifecycle:

    • Creation Phase:
      • Creates the Variable Object (VO) / Variable Environment
      • Sets up the Scope Chain
      • Determines the value of 'this'
    • Execution Phase:
      • Executes code line by line
      • Assigns values to variables
      • Executes function calls
  4. Call Stack:

    • Keeps track of execution contexts
    • Global EC is at the bottom of the stack
    • Function ECs are pushed onto the stack when called
  5. Context Deletion:

    • When a function returns, its EC is popped off the stack
    • Once all code is executed, the global EC is removed
  6. Closure:

    • When a function is returned from another function, it retains access to its outer function's scope

This process ensures proper variable scope, function execution, and memory management in JavaScript.

@yogain123
Copy link
Author

Hoisting

Certainly! I'll explain hoisting in JavaScript, relating it to the Execution Context (EC), with some easy-to-understand examples.

Hoisting is a behavior in JavaScript where variable and function declarations are moved to the top of their respective scopes during the compilation phase, before the code is executed. This is closely related to how the Execution Context works.

Let's break it down step by step:

  1. What is Hoisting?

Hoisting makes it appear as if variable and function declarations are moved to the top of their scope. However, only the declarations are hoisted, not the initializations.

  1. How does it relate to Execution Context?

When an Execution Context is created (whether global or function), it goes through two phases:

a. Creation Phase: This is where hoisting happens.
b. Execution Phase: The code is executed line by line.

  1. Examples of Hoisting

Let's look at some examples to illustrate this concept:

Example 1: Variable Hoisting

console.log(x); // Output: undefined
var x = 5;
console.log(x); // Output: 5

What's happening here?

  • During the Creation Phase of the EC:
    • The declaration var x is hoisted to the top.
    • x is initialized with undefined.
  • During the Execution Phase:
    • The first console.log(x) outputs undefined.
    • x = 5 assigns the value 5 to x.
    • The second console.log(x) outputs 5.

It's as if the code was rewritten like this:

var x;
console.log(x); // undefined
x = 5;
console.log(x); // 5

Example 2: Function Hoisting

sayHello(); // Output: "Hello!"

function sayHello() {
    console.log("Hello!");
}

What's happening here?

  • During the Creation Phase:
    • The entire function declaration is hoisted to the top.
  • During the Execution Phase:
    • sayHello() is called and executes successfully.

Example 3: Function Expressions vs. Declarations

sayHi(); // Error: sayHi is not a function
hello(); // Output: "Hello!"

var sayHi = function() {
    console.log("Hi!");
};

function hello() {
    console.log("Hello!");
}

What's happening here?

  • During the Creation Phase:
    • var sayHi is hoisted (but not the function assignment).
    • The entire hello function is hoisted.
  • During the Execution Phase:
    • sayHi() throws an error because at this point, sayHi is undefined.
    • hello() works fine because the entire function was hoisted.
  1. Let and Const

It's important to note that let and const declarations are hoisted but not initialized. This creates a "Temporal Dead Zone" where the variable exists but cannot be accessed before its declaration:

console.log(x); // ReferenceError
let x = 5;

To summarize:

  • Hoisting is part of the Creation Phase of the Execution Context.
  • Variable declarations (var) are hoisted and initialized with undefined.
  • Function declarations are fully hoisted.
  • Function expressions are not hoisted (only the variable declaration is, if using var).
  • let and const are hoisted but not initialized, creating a Temporal Dead Zone.

Understanding hoisting helps in writing more predictable JavaScript code and avoiding common pitfalls related to variable and function declarations.

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

Callstack

The call stack is a data structure that keeps track of the execution context of a program. It works on a Last-In-First-Out (LIFO) principle. Here's how it operates:

  1. Global Execution Context (GEC):
    When a JavaScript program starts running, the Global Execution Context is created and pushed onto the call stack. This is the base context where all global code is executed.

  2. Function Calls:
    When a function is called, a new Execution Context is created for that function and pushed onto the top of the call stack.

  3. Nested Function Calls:
    If a function calls another function, a new EC is created for the inner function and pushed onto the stack, on top of the outer function's EC.

  4. Function Completion:
    When a function completes its execution, its EC is popped off the stack, and control returns to the EC below it.

  5. Program Completion:
    When all code has been executed, the GEC is popped off the stack, and the program ends.

Here's a simple example to illustrate this process:

function outer() {
  console.log('Outer function');
  inner();
}

function inner() {
  console.log('Inner function');
}

console.log('Global scope');
outer();
console.log('Back to global scope');

Let's break down how the call stack changes as this code executes:

  1. GEC is created and pushed onto the stack.
  2. 'Global scope' is logged.
  3. outer() is called:
    • A new EC for outer is created and pushed onto the stack.
    • 'Outer function' is logged.
  4. inner() is called from within outer():
    • A new EC for inner is created and pushed onto the stack.
    • 'Inner function' is logged.
  5. inner() completes:
    • The EC for inner is popped off the stack.
  6. outer() completes:
    • The EC for outer is popped off the stack.
  7. 'Back to global scope' is logged.
  8. Program ends, GEC is popped off the stack.

This process ensures that JavaScript can keep track of where it is in the execution of a program, manage scope, and handle the flow of control between different parts of the code.

Screenshot 2024-08-02 at 12 07 21 PM Screenshot 2024-08-02 at 12 07 58 PM Screenshot 2024-08-02 at 12 08 17 PM hola

so whenever any JS function is run, a EC is created, this variable is created
for global EC this is equal to window in browser
and for nodejs global EC this is equal to global

@yogain123
Copy link
Author

undefined vs not defined

  1. Execution Context (EC):
    The EC is the environment in which JavaScript code is executed. It consists of two phases: the Creation Phase and the Execution Phase.

a) Undefined:

console.log(x);  // Output: undefined
var x = 5;
console.log(x);  // Output: 5

In the Creation Phase:

  • JavaScript sees the var x declaration (due to hoisting).
  • It adds x to the Variable Object and initializes it with undefined.

In the Execution Phase:

  • The first console.log(x) outputs undefined because x exists in the VO but hasn't been assigned a value yet.
  • x = 5 assigns the value 5 to x.
  • The second console.log(x) outputs 5.

b) Not defined:

console.log(y);  // Throws ReferenceError: y is not defined

In the Creation Phase:

  • JavaScript doesn't see any declaration for y.
  • y is not added to the Variable Object.

In the Execution Phase:

  • When trying to access y, JavaScript can't find it in the Variable Object.
  • It throws a ReferenceError because y is not defined in the current scope.

The key difference is that "undefined" variables exist in the EC but haven't been assigned a value, while "not defined" variables don't exist in the EC at all.

@yogain123
Copy link
Author

Scope chaining in JS

Screenshot 2024-08-02 at 12 29 26 PM
  1. Scope

Scope in JavaScript defines the accessibility and visibility of variables, functions, and objects in some particular part of your code during runtime.

Types of scope:
a) Global Scope: Variables declared outside any function or block have global scope.
b) Function Scope: Variables declared within a function are only accessible within that function.
c) Block Scope: Introduced with let and const in ES6, variables declared in a block (inside {}) are only accessible within that block.

Example:

var globalVar = "I'm global";

function exampleFunction() {
    var functionVar = "I'm function-scoped";
    
    if (true) {
        let blockVar = "I'm block-scoped";
        console.log(blockVar);  // Accessible
    }
    
    console.log(functionVar);  // Accessible
    console.log(blockVar);     // ReferenceError
}

console.log(globalVar);      // Accessible
console.log(functionVar);    // ReferenceError
  1. Scope Chaining

Scope chaining is the mechanism JavaScript uses to resolve variables. When a variable is used, JavaScript will look for it in the current scope. If not found, it will look in the outer scope, and continue until it reaches the global scope.

Example:

var x = 10;

function outer() {
    var y = 20;
    
    function inner() {
        var z = 30;
        console.log(x + y + z);  // JavaScript looks for x and y in outer scopes
    }
    
    inner();
}

outer();  // Outputs: 60
  1. Lexical Environment

A lexical environment is a data structure that holds identifier-variable mapping. It has two components:

a) Environment Record: Actual storage for variables and functions
b) Reference to the outer environment: Allows access to outer lexical environment

Each time a function is created, a new lexical environment is created.

Example:

let a = 'Global';

function outer() {
    let b = 'Outer';

    function inner() {
        let c = 'Inner';
        console.log(a + b + c);  // Can access a, b, and c due to lexical environment chain
    }

    inner();
}

outer();  // Outputs: GlobalOuterInner

In this example:

  • Global lexical environment has 'a' and 'outer'
  • 'outer' function's lexical environment has 'b' and 'inner', and a reference to global lexical environment
  • 'inner' function's lexical environment has 'c', and a reference to 'outer' function's lexical environment

This chain of lexical environments forms the scope chain, allowing inner scopes to access variables from outer scopes.

Screenshot 2024-08-02 at 12 37 30 PM

this orange is reference to Lexical Environemnt of its parent

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

Let and const in JS, Temporal Dead Zone

Screenshot 2024-08-02 at 12 42 39 PM

b is sorted,
but why "a" is inside script, not global EC?

it is all same a hoisting, but but but
it is just not in global, but its own space (script)

why?
TDZ is a time since when let/const variable is hoisted and till it is initialised some value

Think of the TDZ like a waiting room for variables. When you use let or const, the variable enters this waiting room as soon as its scope begins, but it's not ready to be used yet.

Here's a simple example:

console.log(x); // Error! x is in the TDZ
let x = 5;
console.log(x); // 5 - This works fine

In this code:

  1. x enters the TDZ at the beginning of its scope.
  2. When we try to use x before its declaration, it's still in the TDZ, so we get an error.
  3. After the let x = 5 line, x leaves the TDZ and we can use it.

Another example to make it clearer:

{
  // TDZ for 'message' starts here
  
  // This would cause an error:
  // console.log(message);
  
  let message = "Hello";
  
  // TDZ ends here
  
  console.log(message); // "Hello" - This works fine
}

The key points about TDZ:

  1. It starts at the beginning of the scope.
  2. It ends when the variable is declared.
  3. Trying to use the variable while it's in the TDZ causes an error.
  4. It only applies to let and const, not to var.

The TDZ helps catch errors by making sure we don't use variables before they're properly set up.

Screenshot 2024-08-02 at 12 50 36 PM

@yogain123
Copy link
Author

shadowing

Here's a concise example:

let x = "outer";

{
    let x = "inner";
    console.log(x);  // Outputs: "inner"
}

console.log(x);  // Outputs: "outer"

In this example:

  1. We declare x in the outer scope and assign it the value "outer".
  2. We then create a block (enclosed by curly braces {}).
  3. Inside this block, we declare another x and assign it the value "inner".
  4. The console.log(x) inside the block outputs "inner" because this x shadows (hides) the outer x.
  5. After the block ends, the inner x goes out of scope.
  6. The final console.log(x) outputs "outer" because we're back in the outer scope where x still has its original value.

This demonstrates how the inner x temporarily shadows the outer x within its block, without affecting the outer x outside of that block.

Screenshot 2024-08-02 at 1 09 12 PM

Certainly. Let me provide a concise explanation of illegal shadowing with a brief example.

Illegal shadowing in JavaScript occurs when you try to declare a var variable in a block scope that already has a let or const variable with the same name in its outer scope. This is not allowed because it would break block-scoping rules.

Here's a short example:

let x = 10;

{
    var x = 20; // SyntaxError: Identifier 'x' has already been declared
}

In this case, trying to declare x with var inside the block is illegal because there's already a let x in the outer scope. The JavaScript engine prevents this to maintain the integrity of block scoping.

Remember:

  • You can shadow let with let
  • You can shadow var with let
  • You cannot shadow let with var

This rule helps prevent accidental overwriting of block-scoped variables with function-scoped ones.

block scope also follow lexical scope chain patter , the one which we learned sometime back

const a = 2;

{
    {
        console.log(a);
        
    }
}

output is:
2
so it looks inside it block first, didn't find, then it looks inside its parent lexical scope, didn't find, then it looks inside its parent-parent scope (global scope), found it and print

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

closures

In simple term - A closure is a function along with its lexical environment

Certainly! I'll explain JavaScript closures in a simple way using lexical scope and execution context (EC). Let's break it down step by step with some easy examples.

  1. Lexical Scope:
    Lexical scope means that a function can access variables from its outer (enclosing) scope. The scope is determined by where the function is defined, not where it's called.

  2. Execution Context (EC):
    When a function is called, it creates a new execution context. This context includes its own variables and has access to its outer scope.

  3. Closure:
    A closure is formed when a function retains access to variables from its outer scope even after the outer function has finished executing.

Now, let's look at some examples:

Example 1: Basic Closure

function outerFunction(x) {
  let y = 10;
  
  function innerFunction() {
    console.log(x + y);
  }
  
  return innerFunction;
}

const closure = outerFunction(5);
closure(); // Output: 15

In this example:

  • outerFunction takes a parameter x and defines a variable y.
  • innerFunction can access both x and y due to lexical scope.
  • outerFunction returns innerFunction.
  • When we call outerFunction(5), it returns innerFunction, which we assign to closure.
  • Even after outerFunction has finished executing, closure still has access to x and y.

Example 2: Practical Use Case

function createCounter() {
  let count = 0;
  
  return function() {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // Output: 1
counter(); // Output: 2
counter(); // Output: 3

In this example:

  • createCounter function creates a count variable and returns an anonymous function.
  • The returned function has access to count even after createCounter has finished executing.
  • Each time we call counter(), it increments and logs the count.

These examples demonstrate how closures work by maintaining access to their lexical scope, even after the outer function has completed execution. Closures are powerful for creating private variables, implementing data encapsulation, and preserving state between function calls.

Screenshot 2024-08-02 at 1 31 44 PM

@yogain123
Copy link
Author

famous interview question

Certainly! Let's go through this step-by-step with a detailed example to really understand what's happening.

Let's use this code as our example:

function x() {
  for (var i = 1; i <= 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 1000);
  }
  console.log("Namaste JavaScript");
}
x();

Now, let's break down what happens when we run this:

  1. We call the function x().

  2. The for loop starts:

    • First iteration: i = 1
      • It sets a timeout to log i after 1000ms (1 second)
    • Second iteration: i = 2
      • It sets a timeout to log i after 2000ms (2 seconds)
    • Third iteration: i = 3
      • It sets a timeout to log i after 3000ms (3 seconds)
    • Fourth iteration: i = 4
      • It sets a timeout to log i after 4000ms (4 seconds)
    • Fifth iteration: i = 5
      • It sets a timeout to log i after 5000ms (5 seconds)
    • The loop tries i = 6, but 6 <= 5 is false, so the loop ends
  3. Immediately after the loop, "Namaste JavaScript" is logged to the console.

  4. Now, the function x() is done, but those timeouts are still waiting to run.

  5. After 1 second, the first timeout runs. It looks for the value of i, but i is now 6.

  6. After 2 seconds, the second timeout runs. It also sees i as 6.

  7. This continues for all five timeouts.

So the output looks like this:

Namaste JavaScript  (immediately)
6  (after 1 second)
6  (after 2 seconds)
6  (after 3 seconds)
6  (after 4 seconds)
6  (after 5 seconds)

The key point is that var i is function-scoped, not block-scoped. There's only one i for the whole function, and by the time the timeouts run, that i has become 6.

To fix this, we can use let instead of var:

function x() {
  for (let i = 1; i <= 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 1000);
  }
  console.log("Namaste JavaScript");
}
x();

With let, each loop iteration gets its own i. So each timeout remembers its own unique value of i. This gives us the output we want:

Namaste JavaScript  (immediately)
1  (after 1 second)
2  (after 2 seconds)
3  (after 3 seconds)
4  (after 4 seconds)
5  (after 5 seconds)

This example illustrates the importance of understanding variable scope and how asynchronous functions (like setTimeout) interact with loops in JavaScript.

Excellent! I'm glad that explanation helped. Let's now look at the solution where we use var but create a closure to achieve the desired result. Here's the code for this approach:

function x() {
  for (var i = 1; i <= 5; i++) {
    (function(j) {
      setTimeout(function () {
        console.log(j);
      }, j * 1000);
    })(i);
  }
  console.log("Namaste JavaScript");
}
x();

Let's break this down:

  1. We're still using var i in the loop, but we've added an Immediately Invoked Function Expression (IIFE) inside the loop.

  2. The IIFE takes a parameter j, and we pass i to it.

  3. Inside this IIFE, we set up our setTimeout, but now it uses j instead of i.

Here's what happens step by step:

  1. The loop starts:

    • First iteration: i = 1
      • The IIFE is called with 1 as the argument
      • Inside the IIFE, j = 1
      • It sets a timeout to log j (1) after 1000ms
    • Second iteration: i = 2
      • New IIFE called with 2 as the argument
      • Inside this new IIFE, j = 2
      • Sets a timeout to log j (2) after 2000ms
    • This continues for i = 3, 4, and 5
  2. "Namaste JavaScript" is logged immediately after the loop.

  3. The timeouts start running:

    • After 1 second: First timeout runs, logs 1
    • After 2 seconds: Second timeout runs, logs 2
    • And so on...

Why this works:

  • Each iteration of the loop creates a new function scope (the IIFE).
  • Each of these function scopes has its own j variable.
  • The setTimeout function in each scope closes over its own j.
  • So each timeout "remembers" its own unique number, even though they all share the same i.

This solution creates a closure for each iteration of the loop. A closure is a function along with its lexical environment, which includes any local variables that were in-scope when the closure was created. In this case, each timeout function closes over its own j variable.

The output with this solution is the same as using let:

Namaste JavaScript  (immediately)
1  (after 1 second)
2  (after 2 seconds)
3  (after 3 seconds)
4  (after 4 seconds)
5  (after 5 seconds)

This method is a bit more complex than using let, but it demonstrates how closures can be used to create private, persisted scopes even when using var. It's a powerful technique in JavaScript, especially when dealing with older codebases or environments where let might not be available.

@yogain123
Copy link
Author

First Class function

First-class functions in JavaScript refer to the language's ability to treat functions as values, just like any other data type (numbers, strings, objects, etc.). This concept allows for some powerful and flexible programming techniques. Here's an explanation in simple terms:

  1. Functions as variables:
    You can assign a function to a variable, just like you would with a number or string.
let greet = function() {
    console.log("Hello!");
};

greet(); // Outputs: Hello!
  1. Passing functions as arguments:
    You can pass functions as arguments to other functions.
function doSomething(callback) {
    callback();
}

doSomething(function() {
    console.log("I'm a callback function!");
});
  1. Returning functions from functions:
    A function can return another function.
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

let double = createMultiplier(2);
console.log(double(5)); // Outputs: 10
  1. Storing functions in data structures:
    You can store functions in arrays or objects.
let mathOperations = {
    add: function(a, b) { return a + b; },
    subtract: function(a, b) { return a - b; }
};

console.log(mathOperations.add(5, 3)); // Outputs: 8

These features make JavaScript more flexible and allow for techniques like higher-order functions, callbacks, and functional programming paradigms.

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

Event loop from scratch

Screenshot 2024-08-02 at 1 49 21 PM Screenshot 2024-08-02 at 1 50 02 PM Screenshot 2024-08-02 at 1 51 38 PM

JS engine is inside browser, but browers itself have many super power, like setTimeout, localStorage etc
there has to be some connection b/w js engine and brower super power for JS to use it... this connection is in form of web apis

Screenshot 2024-08-02 at 1 53 59 PM Screenshot 2024-08-02 at 1 56 33 PM

Certainly. I'll explain the concept of asynchronous JavaScript and the event loop in more detail, using a code example to illustrate the process.

Let's consider this simple JavaScript code:

console.log("Start");

setTimeout(() => {
    console.log("Timeout callback");
}, 0);

document.getElementById("myButton").addEventListener("click", () => {
    console.log("Button clicked");
});

console.log("End");

Here's how this code executes:

  1. Call Stack:
    The call stack is where JavaScript keeps track of what function is currently running. It's a Last-In-First-Out (LIFO) structure.

  2. Execution:

    • console.log("Start") is pushed onto the call stack, executed, and then popped off. "Start" is printed to the console.

    • setTimeout is encountered. Its callback is sent to the Web APIs to handle the timing, even though the delay is 0ms.

    • The event listener is added to the button. The browser's Web APIs handle this, waiting for a click event.

    • console.log("End") is pushed onto the stack, executed, and popped off. "End" is printed to the console.

  3. Event Loop and Callback Queue:

    • After 0ms, the timeout callback is moved to the Callback Queue.
    • The Event Loop constantly checks if the Call Stack is empty and if there's anything in the Callback Queue.
    • Since the call stack is now empty, the timeout callback is moved to the Call Stack and executed. "Timeout callback" is printed.
  4. User Interaction:

    • When the user clicks the button, the click event callback is added to the Callback Queue.
    • The Event Loop sees that the Call Stack is empty and moves this callback to the Call Stack.
    • The callback executes, and "Button clicked" is printed to the console.

The output would be:

Start
End
Timeout callback

And "Button clicked" would appear whenever the button is clicked.

This demonstrates how JavaScript handles asynchronous operations:

  1. Synchronous code runs immediately (the console.logs for "Start" and "End").
  2. Asynchronous operations (setTimeout, event listeners) are delegated to Web APIs.
  3. Callbacks from these operations wait in the Callback Queue.
  4. The Event Loop ensures these callbacks run only when the Call Stack is clear.

This mechanism allows JavaScript to remain non-blocking. It can handle time-consuming operations or wait for user input without freezing the entire application. It's crucial for creating responsive web applications, handling network requests, and managing user interactions smoothly.

Screenshot 2024-08-02 at 2 01 31 PM Screenshot 2024-08-02 at 2 02 18 PM

Certainly. Here's a concise breakdown:

Microtask Queue:

  • Promise callbacks (.then, .catch, .finally)
  • queueMicrotask() callbacks
  • MutationObserver callbacks
  • process.nextTick() (Node.js)

Callback Queue (Macrotask Queue):

  • setTimeout/setInterval callbacks
  • Event listener callbacks (click, keypress)
  • requestAnimationFrame
  • I/O operations
  • UI rendering tasks

Key differences:

  1. Microtasks execute before the next macrotask
  2. Microtasks have higher priority
  3. All microtasks complete before any macrotask starts

This order ensures that promise-based operations and DOM updates are handled promptly, while longer tasks or I/O operations are managed without blocking the execution flow.

You've got the right idea. Callback queue starvation occurs when tasks in the microtask queue prevent tasks in the callback queue (macrotask queue) from executing in a timely manner. Here's a concise explanation:

  1. Definition: Callback queue starvation happens when tasks in the microtask queue continually add more microtasks, delaying the execution of tasks in the callback queue.

  2. Cause: Microtasks have priority over tasks in the callback queue. The event loop processes all microtasks before moving to the next callback queue task.

  3. Effect: Tasks in the callback queue (like setTimeout callbacks or UI updates) can be significantly delayed or appear unresponsive.

  4. Example scenario:

    • A Promise chain keeps adding new microtasks.
    • This prevents the event loop from processing callback queue tasks.
    • UI events or timers in the callback queue are delayed.
  5. Prevention: Avoid long chains of microtasks, and be cautious with recursive Promise usage.

In essence, it's a situation where high-priority microtasks monopolize the JavaScript engine, starving lower-priority callback queue tasks of execution time.


Promise callbacks and I/O operations are different, though they're often used together. Let me explain the key differences:

Here's a practical example to illustrate:

// Promise callback (Microtask)
Promise.resolve('hello').then(msg => console.log(msg));

// I/O operation (Macrotask)
fs.readFile('file.txt', (err, data) => console.log(data));

While we often use Promises to wrap I/O operations (like fetch or filesystem operations), the Promise mechanism itself is separate from the underlying I/O operation. The Promise is just managing the state and callbacks, while the I/O operation is doing the actual external work.

When you use something like:

fetch('https://api.example.com/data')
  .then(response => response.json())
  • The actual network request (I/O) goes to the callback queue
  • The .then callback goes to the microtask queue once the I/O completes

This separation allows the JavaScript engine to optimize performance by handling quick Promise resolutions immediately while deferring heavier I/O operations.

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

JS engine exposed

Example of an AST:
For the code let x = 5 + 3;, a simplified AST might look like:

Program
└─ VariableDeclaration
   ├─ Identifier (x)
   └─ BinaryExpression
      ├─ Literal (5)
      ├─ Operator (+)
      └─ Literal (3)

This process allows JavaScript engines to execute code efficiently while providing the flexibility of an interpreted language. The combination of interpretation and JIT compilation enables both fast startup and high performance for long-running applications.

Screenshot 2024-08-02 at 2 10 58 PM

Certainly! This image shows the architecture of Google's V8 JavaScript engine. Let me break it down in a simple way:

  1. JavaScript Source Code: This is where it all starts - your raw JavaScript code.

  2. Parser: The parser reads your JavaScript code and breaks it down into a structure that the engine can understand.

  3. Abstract Syntax Tree (AST): This is the output of the parser. It's a tree-like representation of your code's structure.

  4. Interpreter (Ignition): This part takes the AST and turns it into bytecode. It runs your code quickly, but not as efficiently as compiled code.

  5. Compiler (TurboFan): This is V8's optimizing compiler. It takes frequently run code (called "hot" code) and turns it into highly optimized machine code.

  6. Bytecode: This is the output of the Interpreter. It's a low-level representation of your code that can be executed quickly.

  7. Optimized Machine Code: This is the output of the Compiler. It's very fast native machine code.

The process works like this:

  • Your JavaScript code is parsed into an AST.
  • The Interpreter (Ignition) quickly converts this to bytecode and starts running it.
  • As the code runs, the Compiler (TurboFan) identifies frequently used parts (Hot codes) and optimizes them into machine code.
  • The engine can switch between running bytecode and optimized machine code as needed.

This architecture allows V8 to start executing code quickly (thanks to the Interpreter) while also achieving high performance for long-running or frequently executed code (thanks to the Compiler).

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

Trust issue with setTimeout

I'll explain the trust issues with setTimeout in a simple way:

  1. JavaScript is single-threaded:
    This means it can only do one thing at a time.

  2. The call stack:
    It's like a to-do list for JavaScript. Tasks are added to the top and executed from top to bottom.

  3. setTimeout and trust issues:
    When you use setTimeout, you're telling JavaScript to do something later. But JavaScript doesn't wait around - it keeps executing other code.

Here's why this can cause trust issues:

  1. Timing isn't guaranteed:
    The time you set in setTimeout is the minimum wait time, not exact. If JavaScript is busy, your function might run later than expected.

  2. Order of execution:
    Code after setTimeout might run before the setTimeout function, which can lead to unexpected results.

  3. Asynchronous nature:
    setTimeout pushes your function to a separate queue (the callback queue). This queue is only checked when the call stack is empty.

Example:

console.log("First");
setTimeout(() => console.log("Second"), 0);
console.log("Third");

You might expect: First, Second, Third
Actual output: First, Third, Second

This happens because even with a 0ms delay, setTimeout pushes the function to the callback queue, allowing other synchronous code to run first.

Understanding these issues helps write more predictable asynchronous code.


if we have a setTimeout of 5000 ms, but we have million of line of code which takes 10 sec to execture, please exmapl in this context

Certainly, let's explore this scenario with a setTimeout of 5000ms (5 seconds) and a large block of code that takes 10 seconds to execute.

Here's what would happen:

  1. JavaScript encounters the setTimeout:
    It sets a timer for 5 seconds and continues executing the rest of the code.

  2. JavaScript starts executing the million lines of code:
    This takes 10 seconds to complete.

  3. After 5 seconds (while still executing the long code):
    The timer for setTimeout expires, but the callback function doesn't run immediately. Instead, it's placed in the callback queue.

  4. JavaScript continues executing the long code:
    It keeps working on this for another 5 seconds.

  5. After 10 seconds:
    The long code finishes executing, and the call stack becomes empty.

  6. Only now, JavaScript checks the callback queue:
    It finds the setTimeout callback and executes it.

So in this case:

  • The setTimeout was set for 5 seconds
  • But the callback actually runs after about 10 seconds

This demonstrates a key point about setTimeout:
The time you specify is the minimum time before execution, not a guaranteed execution time.

Here's a simplified code representation:

console.log("Start");

setTimeout(() => {
    console.log("This was supposed to run after 5 seconds");
}, 5000);

// Simulate long-running code
let startTime = Date.now();
while (Date.now() - startTime < 10000) {
    // This loop runs for 10 seconds
}

console.log("End of long code");

The output would be:

Start
End of long code
This was supposed to run after 5 seconds

This behavior underscores why setTimeout can't always be trusted for precise timing, especially when there's other code running in the same thread.

@yogain123
Copy link
Author

Higher order function in JS
Higher-order functions are a key concept in functional programming. Let me explain them in simple terms:

  1. Definition:
    A higher-order function is a function that does at least one of the following:
  • Takes one or more functions as arguments
  • Returns a function as its result
  1. Simple analogy:
    Think of higher-order functions like a manager in a company. A manager can:
  • Assign tasks to employees (functions as arguments)
  • Create new roles or positions (return functions)
  1. Examples:

a) Function as an argument:

// A simple function
const greet = (name) => `Hello, ${name}!`;

// A higher-order function
const repeatThreeTimes = (func) => {
  return (value) => {
    console.log(func(value));
    console.log(func(value));
    console.log(func(value));
  };
};

// Using the higher-order function
const tripleGreet = repeatThreeTimes(greet);
tripleGreet("Alice");
// Output:
// Hello, Alice!
// Hello, Alice!
// Hello, Alice!

b) Returning a function:

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

const double = multiply(2);
console.log(double(5)); // Output: 10
  1. Common higher-order functions in functional programming:

a) Map: Applies a function to each element in an array

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(x => x * 2);
// doubled is now [2, 4, 6, 8]

b) Filter: Creates a new array with elements that pass a test

const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(x => x % 2 === 0);
// evenNumbers is now [2, 4, 6]

c) Reduce: Reduces an array to a single value

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// sum is now 10
  1. Benefits of higher-order functions:
  • Increased code reusability
  • Improved abstraction
  • More modular and maintainable code
  • Enables composition of functions

Higher-order functions are powerful tools in functional programming that allow for more flexible and expressive code. They help in creating abstractions and promoting code reuse, which are key principles in functional programming.

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

prototype inheritance and chaining

I'd be happy to explain prototype inheritance in JavaScript. This is an important concept in the language, so I'll break it down step-by-step with examples.

Prototype inheritance is a mechanism in JavaScript that allows objects to inherit properties and methods from other objects. It's a way to share functionality between objects without having to duplicate code.

Let's start with a simple example:

// Create an object
let animal = {
  eat: function() {
    console.log("The animal is eating.");
  }
};

// Create another object
let dog = {
  bark: function() {
    console.log("Woof!");
  }
};

// Set the prototype of dog to be animal
Object.setPrototypeOf(dog, animal);

// Now dog can use methods from animal
dog.eat(); // Outputs: "The animal is eating."
dog.bark(); // Outputs: "Woof!"

In this example:

  1. We create an animal object with an eat method.
  2. We create a dog object with a bark method.
  3. We set the prototype of dog to be animal.
  4. Now dog can use both its own bark method and the eat method from animal.

This is the essence of prototype inheritance. The dog object inherits the eat method from its prototype, which is the animal object.

Here's another way to create objects with prototype inheritance, using constructor functions:

// Constructor function
function Animal(name) {
  this.name = name;
}

// Add a method to the prototype
Animal.prototype.eat = function() {
  console.log(this.name + " is eating.");
};

// Create a new object using the constructor
let cat = new Animal("Whiskers");

cat.eat(); // Outputs: "Whiskers is eating."

In this example:

  1. We define a constructor function Animal.
  2. We add an eat method to Animal.prototype.
  3. We create a new object cat using the Animal constructor.
  4. The cat object can use the eat method, which it inherits from Animal.prototype.

One more important concept is the prototype chain. When you try to access a property on an object, JavaScript first looks for that property on the object itself. If it doesn't find it, it looks at the object's prototype, then that object's prototype, and so on up the chain until it either finds the property or reaches an object with a null prototype.

let mammal = {
  warmBlooded: true
};

let human = Object.create(mammal);
human.think = function() {
  console.log("I'm thinking...");
};

let developer = Object.create(human);
developer.code = function() {
  console.log("I'm coding...");
};

console.log(developer.warmBlooded); // true
developer.think(); // Outputs: "I'm thinking..."
developer.code(); // Outputs: "I'm coding..."

In this example, developer inherits from human, which inherits from mammal. This forms a prototype chain.

let address = {
  myName: "yogendra"
};

let human = Object.create(address);

Object.setPrototypeOf(human.__proto__,{age:30})  // or Object.setPrototypeOf(Object.setPrototypeOf(human),{age:30})

console.log(human.__proto__) // {myName:"yogendra"}
console.log(human.__proto__.__proto__) // {age:"30"}

Certainly, I'll provide a detailed explanation of prototype chaining for various JavaScript objects, including arrays, functions, and general objects. I'll also cover Object.create() in depth. This comprehensive overview should help you prepare for interviews.

  1. Prototype Chaining - General Concept:

Prototype chaining is the mechanism by which JavaScript objects inherit features from one another. Every object in JavaScript has an internal property called [[Prototype]], which can be accessed using proto (though this is deprecated in favor of Object.getPrototypeOf()).

  1. Object Prototype Chain:

All objects in JavaScript ultimately inherit from Object.prototype, unless explicitly set otherwise.

let obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.__proto__.__proto__); // null
  1. Array Prototype Chain:

Arrays inherit from Array.prototype, which in turn inherits from Object.prototype.

let arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(arr.__proto__.__proto__.__proto__); // null

This is why arrays have access to both array methods (like push, pop) and object methods (like hasOwnProperty).

  1. Function Prototype Chain:

Functions are objects in JavaScript. They inherit from Function.prototype, which inherits from Object.prototype.

function foo() {}
console.log(foo.__proto__ === Function.prototype); // true
console.log(foo.__proto__.__proto__ === Object.prototype); // true
console.log(foo.__proto__.__proto__.__proto__); // null
  1. Custom Object Prototype Chain:

You can create custom prototype chains using constructor functions or Object.create().

Using constructor functions:

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(this.name + " is eating.");
};

function Dog(name) {
    Animal.call(this, name);
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    console.log(this.name + " is barking.");
};

let myDog = new Dog("Buddy");
myDog.eat(); // "Buddy is eating."
myDog.bark(); // "Buddy is barking."

console.log(myDog.__proto__ === Dog.prototype); // true
console.log(myDog.__proto__.__proto__ === Animal.prototype); // true
console.log(myDog.__proto__.__proto__.__proto__ === Object.prototype); // true
  1. Object.create() in Detail:

Object.create() is a method that creates a new object with the specified prototype object and properties. It's a powerful way to implement inheritance in JavaScript.

Syntax: Object.create(proto[, propertiesObject])

  • proto: The object which should be the prototype of the newly-created object.
  • propertiesObject (optional): Specifies additional properties to add to the new object.

Examples:

a. Basic usage:

let animal = {
    eat: function() {
        console.log("The animal is eating.");
    }
};

let dog = Object.create(animal);
dog.bark = function() {
    console.log("The dog is barking.");
};

dog.eat(); // "The animal is eating."
dog.bark(); // "The dog is barking."

b. Using the optional propertiesObject:

let person = Object.create(Object.prototype, {
    firstName: {
        value: "John",
        writable: true,
        enumerable: true
    },
    lastName: {
        value: "Doe",
        writable: true,
        enumerable: true
    },
    fullName: {
        get: function() {
            return this.firstName + " " + this.lastName;
        },
        enumerable: true
    }
});

console.log(person.fullName); // "John Doe"
  1. Prototype Pollution:

It's important to be aware of prototype pollution, a security vulnerability that can occur when modifying object prototypes:

Object.prototype.polluted = "This is bad!";

let obj = {};
console.log(obj.polluted); // "This is bad!"

This can lead to unexpected behavior and security issues, so it's generally advised to avoid modifying built-in prototypes.

  1. ES6 Classes and Prototype Inheritance:

ES6 introduced class syntax, which is syntactic sugar over prototype-based inheritance:

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

    eat() {
        console.log(this.name + " is eating.");
    }
}

class Dog extends Animal {
    bark() {
        console.log(this.name + " is barking.");
    }
}

let myDog = new Dog("Rex");
myDog.eat(); // "Rex is eating."
myDog.bark(); // "Rex is barking."

Under the hood, this still uses prototype inheritance.

@yogain123
Copy link
Author

yogain123 commented Aug 2, 2024

CORS

previsuly when cors was not there then

Screenshot 2024-08-02 at 3 26 56 PM

but this was needed so that things can be shares b/w services etc

Screenshot 2024-08-02 at 3 28 18 PM

CORS Success

  1. The server responds with an HTTP status code 204 (Success - No Content).
  2. The response also includes specific CORS headers like Access-Control-Allow-Origin, Access-Control-Allow-Methods, and potentially Access-Control-Allow-Headers.

CORS Failure
1.The server might respond with a status code 403 (Forbidden), 401 (Unauthorized), or other error codes depending on the server's configuration.


Certainly, I'll explain CORS with examples to make it clearer. Let's go through some practical scenarios:

  1. Simple CORS Request Example:

Imagine you have a website hosted at https://mywebsite.com, and you want to make a GET request to an API at https://api.externalservice.com.

Frontend JavaScript code:

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

If the API server at externalservice.com has CORS configured correctly, it would send these headers in its response:

Access-Control-Allow-Origin: https://mywebsite.com

This allows the browser to accept the response from the external API.

  1. Preflight Request Example:

Now, let's say you want to make a PUT request with a custom header:

fetch('https://api.externalservice.com/data', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ key: 'value' })
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Before this request is sent, the browser will first send a preflight OPTIONS request:

OPTIONS /data HTTP/1.1
Host: api.externalservice.com
Origin: https://mywebsite.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Custom-Header

The server should respond with:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://mywebsite.com
Access-Control-Allow-Methods: PUT, GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400

This response tells the browser that it's okay to proceed with the actual PUT request.

  1. Server-side CORS Configuration Example:

Here's how you might configure CORS on a Node.js server using Express:

const express = require('express');
const cors = require('cors');
const app = express();

// Allow all origins (not recommended for production)
app.use(cors());

// Or, for more specific control:
app.use(cors({
  origin: 'https://mywebsite.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

app.get('/data', (req, res) => {
  res.json({ message: 'This is your data' });
});

app.listen(3000, () => console.log('Server running on port 3000'));
  1. Handling Credentials Example:

If your API requires credentials (like cookies), you need to enable this on both ends:

Client-side:

fetch('https://api.externalservice.com/data', {
  credentials: 'include'
})
  .then(response => response.json())
  .then(data => console.log(data));

Server-side (Express.js):

app.use(cors({
  origin: 'https://mywebsite.com',
  credentials: true
}));

The server must also include this header in its response:

Access-Control-Allow-Credentials: true
  1. CORS Error Example:

If CORS is not configured correctly, you might see an error like this in the browser console:

Access to fetch at 'https://api.externalservice.com/data' from origin 'https://mywebsite.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the 
requested resource.

This means the server at api.externalservice.com is not configured to allow requests from mywebsite.com.

  1. Wildcard Origin (Not Recommended for Production):

Server configuration:

app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE']
}));

This allows any origin to access your API, which can be useful for testing but is generally not secure for production use.

These examples illustrate how CORS works in practice. In an interview, you might be asked to explain these scenarios, troubleshoot CORS issues, or discuss the security implications of different CORS configurations. Remember, the key is understanding why CORS exists (to provide secure cross-origin access) and how it's implemented (through HTTP headers and browser enforcement).


CORS (Cross-Origin Resource Sharing) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the resource originated. Preflight requests are a part of CORS.

What is a Preflight Request?

A preflight request is an HTTP OPTIONS request sent by the browser to the server before the actual request, to determine if the actual request is safe to send. This is done when the request does not meet certain simple criteria, known as "simple requests".

Simple Requests

A request is considered "simple" if it meets all of the following conditions:

  1. Methods: The HTTP method is one of the following:

    • GET
    • HEAD
    • POST
  2. Headers: The request can only include certain headers:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (with values of application/x-www-form-urlencoded, multipart/form-data, or text/plain)
  3. Content-Type: The content type of the request can only be one of the following:

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

If a request does not meet these criteria, it triggers a preflight request.

Note: CORS preflight requests are not sent for same-domain (same-origin) requests at all, even if they're not "simple" requests. CORS is only for cross-origin requests.

When is a Preflight Request Not Sent?

  1. Simple Requests:

    • If the request method is GET, HEAD, or POST.
    • If the request headers are among the allowed headers (Accept, Accept-Language, Content-Language, Content-Type with allowed values).
    • If the request does not include any custom headers or headers like Authorization.
  2. Requests to the Same Origin:

    • If the request is sent to the same origin (same protocol, domain, and port).

When is a Preflight Request Sent?

  1. Non-Simple Methods:

    • If the request method is PUT, DELETE, PATCH, or any method other than GET, HEAD, or POST.
  2. Non-Simple Headers:

    • If the request includes headers other than the allowed ones (e.g., custom headers, Authorization).
  3. Custom Content-Type:

    • If the request has a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain.
  4. Requests with Credentials:

    • If the request includes credentials (e.g., cookies, HTTP authentication) and the server's response includes the Access-Control-Allow-Credentials header.

Examples

Example of a Simple Request (No Preflight)

fetch('https://example.com/api/data', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Example of a Request Triggering Preflight

fetch('https://example.com/api/data', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token'
  },
  body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

In the second example, the request uses the PUT method and includes custom headers (Content-Type: application/json and Authorization). Therefore, the browser sends a preflight request to check if the server will allow the actual request.

Conclusion

Preflight requests are an essential part of CORS, ensuring that the server is aware of and permits potentially unsafe requests. Understanding the criteria for simple requests helps in designing APIs that minimize the need for preflight requests, thereby improving performance.

@yogain123
Copy link
Author

"this"

"this" key word in global space refers to window

inside function

function abc(){
    console.log(this) // window object only
}

this inside function is window object only. but in "strict mode" it is undefined, this is called this substituation

"use strict"
function abc(){
    console.log(this); // undefined
}

abc()

"this" value depends on how it is called

function abc(){
    console.log(this) // undefined if called like abc(), if called as window.abc() then -> window object
}

abc()
window.abc()

this inside object menthod

const obj = {
    name:"yogendra",
    print:function() {
        console.log(this); // {name"yogendra", print:fn}
    }
}
obj.print()

this inside arror function
Arrow functions do not have their own this. They inherit this from the enclosing scope:

const obj = {
    name:"yogendra",
    print:()=> {
        console.log(this); // window
    }
}
obj.print() 

this inside nested arrow function

const obj = {
    name:"yogendra",
    sum(params) {
        const print= ()=> {
            console.log(this); // {name:"yogendra",sum:fn}
        }
    }
}
obj.sum() 

this inside DOM
it refers to its own eleemt
<button click="alert(this)"></button> // this -> button element


I'd be happy to explain how the "this" keyword behaves in different contexts in JavaScript, particularly focusing on its behavior with the "new" keyword, in classes, constructors, and other scenarios. Let's break it down with examples:

  1. "this" with the "new" keyword:

When you use the "new" keyword to create an object from a constructor function, "this" refers to the newly created object.

Example:

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

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

In this example, "this" inside the Person constructor refers to the new object being created (john).

  1. "this" in classes:

In class methods, "this" refers to the instance of the class.

Example:

class Car {
  constructor(brand) {
    this.brand = brand;
  }
  
  getBrand() {
    return this.brand;
  }
}

const myCar = new Car("Toyota");
console.log(myCar.getBrand()); // Output: Toyota

Here, "this" in the constructor and getBrand method refers to the instance of the Car class (myCar).

  1. "this" in constructors:

In constructors, whether using the class syntax or constructor functions, "this" refers to the new object being created.

Example (using class syntax):

class Animal {
  constructor(species) {
    this.species = species;
    this.makeSound = function() {
      console.log("Some generic animal sound");
    };
  }
}

const dog = new Animal("dog");
console.log(dog.species); // Output: dog
dog.makeSound(); // Output: Some generic animal sound
  1. "this" in different contexts:

a. Global context:
In the global scope, "this" refers to the global object (window in browsers, global in Node.js).

console.log(this === window); // Output: true (in a browser)

b. Function context:
In a regular function, "this" depends on how the function is called.

function showThis() {
  console.log(this);
}

showThis(); // Output: window (in non-strict mode) or undefined (in strict mode)

const obj = { method: showThis };
obj.method(); // Output: obj

c. Arrow functions:
Arrow functions don't have their own "this". They inherit "this" from the surrounding scope.

const arrowFunction = () => {
  console.log(this);
};

const obj = { method: arrowFunction };
obj.method(); // Output: window (or global object) - not obj!

d. Event handlers:
In event handlers, "this" typically refers to the element that triggered the event.

document.getElementById("myButton").addEventListener("click", function() {
  console.log(this); // Output: the button element
});

e. Using bind, call, or apply:
These methods allow you to explicitly set what "this" should refer to.

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

const person = { name: "Alice" };

greet.call(person); // Output: Hello, Alice
greet.apply(person); // Output: Hello, Alice

const boundGreet = greet.bind(person);
boundGreet(); // Output: Hello, Alice

In summary, the behavior of "this" in JavaScript depends on how a function is called, whether it's used with the "new" keyword, in a class, or in different contexts. Understanding these differences is crucial for writing effective JavaScript code.

Would you like me to explain or elaborate on any specific part of this explanation?

@yogain123
Copy link
Author

yogain123 commented Aug 7, 2024

ES6 Modules vs CommonJS

ES6 modules are asynchronous, while CommonJS modules are synchronous. This means ES6 modules use either the defer attribute or type="module" in script tags. The download and parsing of ES6 modules are asynchronous, but their execution, which happens at the end, is synchronous. Using type="module" in a script tag is equivalent to using defer. If you need to use async/await in a script, you should use type="module".

Node.js and Module Systems
Node.js, by default, uses CommonJS. This is because when Node.js was created, ES6 modules didn't exist and weren't standardized yet. This historical context explains why CommonJS became the default module system for Node.js.

Development vs Production
During development, export statements are used across the codebase. However, for production, bundlers like Webpack create a final bundle. This bundle is what gets added to the index.html file, typically with the defer attribute.

The JavaScript ecosystem is gradually shifting towards ES6 modules
Many libraries now offer both CommonJS and ES6 module versions

Browsers don't support CommonJS natively; transpilation/bundling is required


When an ESModule static import is encountered, it is handled asynchronously. The main thread is not blocked and can continue handling other tasks, such as network requests or UI updates. However, script execution pauses and does not proceed to the next line until the imported module is fully fetched, parsed, and evaluated.

It's similar to a hidden async/await mechanism within the module system. Once all static imports are resolved, the script execution resumes.

@yogain123
Copy link
Author

yogain123 commented Jan 12, 2025

Phases

Execution Context Component

  1. Memory
  2. Code

Phases in Execution Context

  1. MEMORY Creation Phase
  2. CODE Exection Phase

JS Engine

  1. Parser
  2. Makes a Token - AST
  3. Interpreter -> ByteCode
  4. Compiler -> Machine Code

HTML File Executing

  1. Parsing
  2. Found JS Script (1. Fetching, 2. Executing) depends on defer/async

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment