0% found this document useful (0 votes)
6 views20 pages

Understanding JavaScript Execution Contexts

Uploaded by

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

Understanding JavaScript Execution Contexts

Uploaded by

Ankit Bali
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

How does JavaScript work?

​ Table of Contents :

​ 1. Introduction
● Overview of JavaScript
● Single-threaded, event-driven environment
● Key concepts

​ 2. Execution Context
● Memory Component or Variable Environment
● Code Component or Thread of Execution
● Two phases of execution context

​ 3. Call Stack
● Managing execution contexts

​ 4. Hoisting
● Variable and function declarations
● Temporal Dead Zone (TDZ)

​ 5. The Window Object


● Predefined this variable
● Variables in the global scope
● Differences with let and const

​ 6. Lexical Environment
● Components of lexical environment
● Scope and variable access
● Searching in the scope chain

​ 7. Temporal Dead Zone (TDZ), let, and const


● Understanding the Temporal Dead Zone
● Variable redeclaration and const behavior
● Best practices and block scope

​ 8. Shadowing
● Legal and illegal shadowing
● Examples of shadowing

​ 9. Closures
● Definition and example
● Creating closures
● Common uses and considerations

​ 10. Functions
● Function Statement (Function Declaration)
● Function Expression
● Function hoisting
● Anonymous Functions
● Named Function Expressions
● Parameters and Arguments
● First Class Functions
● Callback Functions
● Higher-Order Functions

​ 11. Event Listeners and Closures


● Example of using closures with event listeners

​ 12. Asynchronous JavaScript and the Event Loop


● Call Stack, Web API, Event Loop, Queues
● Event Loop Priority
● Starvation
● Memory Heap

​ 13. Summary
● Recap of key concepts
Introduction:

JavaScript is a versatile and dynamic programming language that plays a pivotal role in
web development. It operates in a single-threaded, event-driven environment, which
means it handles tasks one at a time in a sequential order. Understanding how
JavaScript works involves diving into key concepts like execution contexts, hoisting, the
window object, lexical environments, closures, functions, and asynchronous
programming through the event loop. This foundational knowledge is essential for
developers looking to write efficient and responsive code in JavaScript.

● JavaScript is a single-threaded language that operates synchronously, executing


one command at a time in a sequential order. Each line of code is executed only
after the preceding line has been fully processed.

● JavaScript is a loosely typed language, which means that you are not required to
explicitly declare variable types in your code.

● Everything in JavaScript happens inside an execution context. An execution


context in JavaScript can be thought of as a container with two crucial
components :

1. Memory Component or Variable Environment : Inside this part, variables


and functions are stored as key-value pairs. It serves as a storage space
for all the data and functions needed for your code.

2. Code Component or Thread of Execution : This is where your JavaScript


code is carried out step by step, one line at a time, in a sequential fashion.
It’s the “action” part of the execution context where the code is executed.
● An execution context is established in two distinct phases :

1. Memory Allocation Phase : During this phase memory is allocated for all
variables and functions. Variables are assigned a key-value pair with the
key as the variable name and the initial value as ‘undefined’. Functions, on
the other hand, are assigned a key-value pair with the key as the function
name and value as the code of the function.

2. Code Execution Phase : In this phase, the actual values of variables are
allocated in memory, and the JavaScript code is executed within the code
component. When functions are invoked, new execution contexts are
created. These temporary execution contexts are deleted after the
function completes its execution. Additionally, the global execution
context, representing the entire program, is also removed when the code
execution is finished.

● The Call Stack( execution context stack/ program stack/ control stack/ runtime
stack/ machine stack ) is responsible for managing the order of execution of
execution contexts in JavaScript.
Here’s how it works :

○ The Global Execution Context is initially placed at the top of the call stack
when any JavaScript program starts running.
○ When a new execution context is created due to a function invocation, it’s
added to the top of the call stack.
○ After the execution of a function is completed, its execution context is
removed(popped) from the call stack.
○ The call stack is considered empty when the execution of all JavaScript
code is finished.

● Hoisting is a process in JavaScript where the compiler allocates memory for


variable and function declarations before the actual execution of the code. It
means that, during compilation, JavaScript sets up the memory space for these
declarations.

However, it's important to note that declarations made using `let` and
`const` are not initialized during hoisting. Instead they remain in a state known
as the “Temporal Dead Zone (TDZ)” until the line where they are initialized is
executed. If you attempt to access these variables before their initialization line,
JavaScript will throw a ReferenceError.

For example, consider the code :

JavaScript
[Link](x); // undefined
console/log(y); // ReferenceError
var x = 1;
let y = 2; // End of TDZ ( for y )

In this code, `x` is hoisted, so it’s accessible, but it’s initially ‘undefined’. On the
other hand, `y` is also hoisted but remains in TDZ until it’s initialized, causing an
error if accessed before that point.

● The Window Object :

In the context of web browsers, the window is a global object created by the
JavaScript engine alongside the global execution context. It serves as a
fundamental component of the browser’s JavaScript environment.
Here are some key characteristics :

○ A predefined `this` variable is automatically created, pointing to the


global object. In browsers, this global object is referred to as`window` (
e.g. `this === window` is true within a browser environment).
○ In the global scope, all variables and functions are naturally connected to
the window object. For instance, a variable `a` defined in the global
scope can be accessed using various syntax: `window.a`, `a`, or
`this.a`

It’s important to note that this behavior applies specifically to variables declared
with `var`. Variables declared with `let` and `const` in the global scope are
exceptions and not added to the window object.

● Lexical environment :

A lexical environment is a concept defined in the language specifications to


describe how variables and their scope are managed in JavaScript. It’s essential
to understand that this concept exists primarily for language design and
interpretation purposes. You cannot directly access or manipulate a lexical
environment in your code.

Here are key points to grasp :

○ When an execution context is created in JavaScript, a lexical environment


is also established. This lexical environment consists of two main
components :
■ Local Memory : This part stores variables and functions specific to
the current scope.
■ Reference to Parent’s Lexical Environment : It allows the code
to access variables from the parent scope or the surrounding
environment.
○ Scope refers to the specific area within your code where you can access
a particular variable or function.
○ If the JavaScript engine cannot find a variable in the local memory of the
current scope, it searches in the next level of the scope chain. This chain
consists of linked lexical environments, which enables access to variables
defined in outer scopes.
● Temporal Dead Zone (TDZ), let and const :

○ Temporal Dead Zone (TDZ) : This is the period between when a let or
const variable is hoisted ( memory is allocated for it ) and when it is
actually initialized with a value. Accessing a let variable during the TDZ
results in a ReferenceError.

Example :

JavaScript
{ // TDZ starts at the beginning of the scope
[Link](x); // undefined
[Link](y); // ReferenceError
var x = 1;
let y = 2; // End of TDZ (for y)
○ }

○ Re-declaration : Unlike var, which allows re-declaration in the same


scope, let does not. You can only declare a let variable once in a given
scope.
○ const Declarations : const declarations must be initialized when
declared, and any attempt to reassign a value to a const variable after
initialization will result in a TypeError.
○ Best Practice : To avoid TDZ issues, it’s recommended to declare and
initialize variables at the top of the scope or block where they are used.
○ Block Scope : let and const are block-scoped, meaning they are confined
to the block in which they are defined. They are not added to the global
window object. In contrast, var variables are function-scoped and can be
accessed within the function they are created in or globally if not within a
function.

Example :

JavaScript
{
var a = 10;
let b = 20;
const c = 30;
[Link](a); // 10
[Link](b); // 20
[Link](c); // 30
}

[Link](a); // 10
[Link](b); // ReferenceError: b is not defined
[Link](c); // ReferenceError: c is not defined

● Shadowing :

Shadowing occurs when a variable with the same name as one defined in an
outer scope is declared within an inner scope. In this situation, the value
assigned to the inner variable takes precedence and is stored in memory,
effectively shadowing the outer variable.

Here are key points to understand :

○ Legal Shadowing : It’s permissible to shadow a variable in an inner


scope, and the inner variable’s value is separate from the outer variable.
This allows you to use the same variable name for different purposes
within different scopes.
○ Illegal Shadowing : However, it’s essential to avoid shadowing variables
in a way that crosses the scope boundaries. You can shadow a var
variable with a let variable, but attempting the opposite ( shadowing a let
variable with a var variable ) is not allowed and results in an error.

Here’s an example to illustrate shadowing :

JavaScript
function func() {
var a = 1; // An outer var variable
let b = 2; // An outer let variable

if (true) {
let a = 3; // legal shadowing of the 'a' variable in this block
var b = 4; // Illegal shadowing of the 'b' variable in this block
[Link](a); // Outputs: 3 ( inner 'a' variable )
[Link](b); // SyntaxError : b has already been declared

}
}

func();

This example demonstrates legal shadowing of the ‘a’ variable within


the block, while the attempt to shadow the ‘b’ variable with var is not
allowed and results in a SyntaxError.

● Closures :

A closure is a powerful concept in JavaScript that consists of a function along


with the lexical environment in which it was declared. It allows a function to
“remember” and access variables and parameters from its outer scope even after
that outer function has finished executing. Closures are created every time a
function is defined or created.

Here’s an example to demonstrate the concept of closures and how they capture
and remember variables from their outer scope.

JavaScript
function x()
var a = 7; // 'a' is a variable defined in the outer function 'x'
function y() {
[Link](a); // 'y' is an inner function that can access 'a'
from its outer scope

}
return y; // Return the inner function 'y', creating a closure

// At this point, the outer function 'x' has finised executing but 'y' retains
access to 'a' in its closure

}
var z = x(); // Invoke the outer function 'x' and assign the returned inner
function 'y' to 'z'
z(); // Call the inner function 'y' which still has access to the variable 'a'
in it's closure
// Outputs : 7 ( the value of 'a' is retained in the closure even though 'x'
has finished executing )

In this code, the inner function y is returned from the outer function x, creating a
closure. This closure includes the variables and the lexical environment from the
surrounding scope, allowing y to access and use the variable a even after the outer
function x has completed its execution. When we call z(), it, in turn, calls the inner
function y, which still retains access to the variable a, and thus, it can log the value of a
to the console.

Here are some key points to understand about closures :

○ Creating of Closures : Closures are formed at the time a function is


defined, capturing its surrounding lexical environment, including variables
and functions. This captured environment remains accessible to the inner
function even after the outer function has completed execution.
○ Usage of Closures : One common use of closures is to achieve data
privacy or data hiding. By encapsulating data within a closure, you can
create private variables and functions that are not directly accessible from
outside the closure.
○ Function Arguments : In JavaScript, function arguments are always
passed by value. However, when passing objects, you are working with
references. This means that if you modify an object with a function, the
changes are reflected outside the function as well.
○ Common Uses of Closures : Closures have numerous applications in
JavaScript, including :
■ Module Design Pattern : Creating encapsulated modules with
private and public members.
■ Currying : Partially applying functions to create new functions.
■ Memoization : Catching function results to improve performance.
■ Maintaining State : Especially in asynchronous programming,
closures can help preserve state between function calls.
■ Iterators : Closures are often used to maintain the state in iterator
functions.
○ Memory Considerations : Closures capture their lexical environment,
including variables and functions, which can lead to increased memory
consumption. If not managed properly, they can cause memory leaks or
even freeze the browser. It’s essential to be mindful of this when working
with closures.
○ Garbage Collection : Morden JavaScript engines include a garbage
collector that frees up memory used by unused objects and closures. This
helps prevent memory leaks and maintain efficient memory usage.

Here’s an example illustrating a closure used for data privacy :

JavaScript
function counter() {
var count = 0; // 'count' is a private varibale encapsulated in
the clousre
return function incrementCounter() {
count++;
[Link](count);
}
var counter1 = counter(); // 'counter1' captures the closure, including
'count'
counter1(); // Outputs: 1

In this example, the closure created by the counter function allows us to


maintain a private variable count and expose only the incrementCounter
function, which can access and modify count while keeping it hidden from the
outside.

Closures are a fundamental and versatile concept in JavaScript, enabling


powerful patterns and data encapsulation.

● Functions :

1. Function Statement ( or Function Declaration )

JavaScript
function a() {
[Link]('called a');
}
a();

2. Function Expression

JavaScript
var b = function() {
[Link]('called b');
}
b();

Note : Function expression and function statement behave differently during hoisting.

Function statements are hoisted to the top of their containing scope, this means you can
call the function before it’s declared in the code, and it will work as expected. Whereas
function expressions are similar to regular variables and are assigned the value
‘undefined’ during the hoisting process, not the actual function code. You can only call a
function expression after it’s been defined in your code.

Here’s an example to illustrate this behavior :

JavaScript
// Function Declaration (Function Statement)
sayHello(); // This works
function sayHello() {
[Link]('Hello');
}

// Function Expression
sayHi(); // This will result in error ( TypeError: sayHi is not a function )
var sayHi = function() {
[Link]('Hi!');
};

3. Anonymous Function : A function without a name, used as a value in a


function expression.
JavaScript
var c = function() {
[Link]('called c');
};
c(); // Calls the anonymous function c

4. Named Function Expression

JavaScript
var d = function xyz() {
[Link]('called d');
};
xyz(); // ReferenceError: xyz is not defined
d(); // called d

5. Parameters : Parameters are the value that a function can receive.

JavaScript
function sum(param1, param2) {
[Link](param1 +param2);
}
sum(10, 20); // Calls the 'sum' function with arguments 10 and 20

6. Arguments : Arguments are the values that are passed to a function.

JavaScript
sum(10, 20);

7. First Class Functions : The ability to use functions as values, pass them
to other functions and return them from other functions.

JavaScript
function square(x) {
return x * x;
}

function apply(func, value) {


return func(value);
}

var result = apply(square, 5);


[Link](result); // Output: 25

8. Callback Functions : Callback functions are widely used in JavaScript for


various purposes. Here are some good examples of callback functions :

● Asynchronous Operations : Callbacks are commonly used to handle


asynchronous operations, like reading a file, making an HTTP request, or
executing a timer. For example :

JavaScript
function fetchDataFromServer(callback) {
// Simulate an API request
setTimeout(function() {
const data = { message : "Data Received" };
callback(data);
}, 1000);
}

function processServerData(data) {
[Link]("Processing server data:", [Link]);
}

fetchDataFromServer(processServerData);

● Event Handling: Event listeners often use callback functions to respond


to user interactions. For example:
JavaScript
[Link]('muButton').addEventListener('click', function()
{ [Link]('Button Clicked');
});

● Array Methods: Many array methods like map, filter, and forEach
accept callback functions to perform operations on array elements. For
example:

JavaScript
const numbers = [1, 2, 3, 4, 5];

const squaredNumbers = [Link](function(number) {


return number * number;
});

[Link](squaredNumbers);

● Promises: Promises in JavaScript rely heavily on callback functions to


handle asynchronous operations. For example:
JavaScript
function fetchData() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
const data = { message: "Data Received" };
resolve(data);
}, 1000);
});
}

fetchData().then(function(data) {
[Link]("Data:", [Link]);
});

● Higher-Order Functions: Callbacks are essential for creating higher


order functions that manipulate or customize the behavior of other
functions.
Here’s an example of a higher-order function that takes a callback to
customize its behavior:

JavaScript
function calculate(operation, num1, num2) {
return operation(num1, num2);
}

// Callback functions to perform different operations:

function add(x, y) {
return x + y;
}

function subtract(x, y) {
return x - y;
}

function multiply(x, y) {
return x * y;
}

function divide(x, y) {
if(y !== 0) {
return x / y;
} else {
return "Cannot divide by zero";
}
}

// Using the calculate function with different callbacks

const result1 = calculate(add, 5, 3);


[Link]("Addition result:", result1);

const result2 = calculate(subtract, 10, 4);


[Link]("Subtraction result:", result2);

const result3 = calculate(multiply, 6, 2);


[Link]("Multiplication result:", result3);

const result4 = calculate(divide, 10, 2);


[Link]("Division result:", result1);

In this example, the calculate function is a higher-order function that takes an


operation (a callback function) and two numbers as arguments. Depending on the
provided callback (e.g., add, subtract, multiply, or divide), the calculate
function performs the specified operation on the given numbers. This demonstrates how
you can customize the behavior of the calculate function by passing different
callback functions.

● Event Listeners and Closures:

In the following example, we use event listeners and closures to track the number of
clicks on an HTML element with the ID 'clickMe'.

JavaScript
function addEventListeners() {
let count = 0;

[Link]('clickMe').addEventListener('click', function() {
[Link]('Click', count++);
});
}

addEventListeners();

In the above code, we have a function called `addEventListeners( )` that demonstrates


the use of event listeners and closures. Let’s break it down:

1. The `addEventListeners( )` function is responsible for setting up event


handling.
2. Inside this function, a `count` variable is declared and initialized to 0. It serves as
a counter to keep track of the number of clicks on an HTML element.
3. We use the `[Link](‘clickMe’)` method to select
an HTML element with the ID ‘clickMe’. We then attach a click event listener to
this element.
4. Within the event listener, there is an anonymous function defined ( often
referred to as closure ). This function executes whenever the ‘click’ event
occurs on the ‘clickMe’ element.
5. Inside the closure, a message is logged to the console, displaying ‘Click’ along
with the current value of the `count` variable. After logging, the `count`
variable is incremented by 1.
6. Finally, the `addEventListeners( )` function is called to set up the event
listener.

It is important to note that closures are involved in this scenario. The anonymous
function inside the event listener “captures” the count variable, preserving it even after
`addEventListeners( )` has completed execution. This enables the function to
access and update the `count` variable each time the element is clicked, effectively
creating a click counter. Closures are valuable for maintaining state in event-driven
programming, such as handling user interactions with web pages.

Regarding memory, while event listeners do consume some memory, modern


JavaScript engines are efficient at managing these closures and automatically cleaning
up memory when it’s no longer needed. This means that developers can generally focus
on writing clean and functional code without significant concerns about memory
management.

● Asynchronous JavaScript and the Event Loop:

In JavaScript the event loop is a key component that manages asynchronous


operations. To grasp this concept, it’s essential to understand some fundamental
components and their interactions.

Call Stack:

○ The call stack is a part of the JavaScript engine, and it can only handle
one operation at a time.

Web API:
○ JavaScript provides access to various Web APIs, including
`setTimeout( )` ,DOM APIs, `fetch( )`, `localStorage`,
and more. These Web APIs are typically via the global object ( eg.,
`[Link]( )`).

Event Loop and Queues:

○ The event loop continuously monitors two important queues: the callback
queue and the microtask queue.

Callback queue:

■ The Callback queue holds functions that are ready to be executed


but are waiting for the call stack to be empty. Functions from
operations like `setTimeout( )` and DOM API callbacks are
placed in the callback queue.

Microtask queue:

■ The microtask queue is similar to the callback queue but has a


higher priority. It receives callback functions from promises,
mutation observers, and other high-priority tasks.

Event Loop Priority:

■ The event loop prioritizes tasks in the microtask queue. It only


moves tasks from the callback queue to the call stack when the
microtask queue is empty.

Starvation:

■ If a task in the callback queue remains unexecuted for an extended


period due to a backlog of tasks in the microtask queue, it is
referred to as “starvation”. This scenario can impact the timely
execution of tasks in the callback queue.

Memory Heap:

○ The memory heap is the space where JavaScript assigns memory for
variables and functions. It’s where data and code elements are stored.
In summary, asynchronous JavaScript relies on the event loop to manage and
prioritize tasks from various sources, including Web APIs. The event loop
ensures that high-priority tasks from the microtask queue are executed promptly
while preventing starvation of tasks in the callback queue. Understanding these
components is crucial for effective asynchronous programming in JavaScript.

Summary:

JavaScript's inner workings are based on the interplay of execution contexts, which
encompass memory allocation and code execution phases. Hoisting is a crucial concept
that determines variable and function behavior during compilation. The window object
plays a significant role in web browsers, serving as the global environment for
JavaScript. Lexical environments and scopes help manage variable access and
inheritance in your code.

Closures enable functions to remember and access variables from their outer scope,
even after the outer function has completed execution. Functions are the building blocks
of JavaScript, available in various forms such as function statements, function
expressions, and anonymous functions. Callback functions are widely used for
asynchronous operations, event handling, and more. Higher-order functions, like the
example provided, showcase the power of customizing function behavior through
callbacks.

The event loop is a critical component of JavaScript's asynchronous nature, managing


tasks in the call stack, callback queue, and microtask queue. Understanding these
components is crucial for building responsive and efficient web applications. In
summary, mastering the inner workings of JavaScript is essential for becoming a
proficient web developer and harnessing the full potential of this versatile language.

You might also like