Lecture-4 Notes_ Working of JS-3291
Lecture-4 Notes_ Working of JS-3291
Execution Context in JS
● Execution context is a fundamental concept in JavaScript that describes the
environment in which a piece of code is executed.
● It includes variables, functions, and other elements that are necessary for the
code to run.
● In other words, an execution context in JavaScript is a container that holds
information about the current state of code being executed. The concept of
execution context is important in understanding how JavaScript code is
executed.
1) Variable Environment:
a) The Variable Environment is a fundamental component that organizes
and holds all the variables, functions, and parameters accessible within
a given scope. However, we will delve into the concept of scope and its
significance in greater detail later on.
b) It is created when a new function is executed and contains information
about all the variables that are declared within that function and the
values assigned to them.
c) The variable environment also includes a reference to the outer
environment, which is the variable environment of the parent scope.
2) Thread of Execution:
a) The Thread of Execution is the sequence of code execution that is
currently being executed.
b) It is responsible for running the code one line at a time and keeping
track of where the execution is at any given moment.
c) When a new function is called, a new thread of execution is created,
and the execution continues within that thread until the function returns.
1. Creation Phase:
a. During the creation phase, the JavaScript engine creates a new execution
context and sets up the environment for executing the code.
b. This involves establishing a fresh variable environment, configuring the scope
chain, and establishing a reference to the outer environment, which will be
further explored when we cover the concept of scope chain."
2. Execution Phase:
a. During the execution phase, the JavaScript engine executes the code line by
line within the thread of execution.
b. It uses the variable environment to look up variables and functions as needed
and updates the values of variables as they are changed in the code.
1. Defining Window Object: The engine defines the global window object, which
serves as the outermost object in the environment.
2. Creating this Variable: The this variable is created and assigned to the
window object. It represents the context in which the current code is
executing.
3. Hoisting: Hoisting takes place, where variable and function declarations are
moved to the top of their respective scopes. This allows you to use variables
and functions before they are formally declared in the code.
We will cover the concept of hoisting in more detail in a dedicated section
later on.
4. Memory Allocation: After hoisting, the engine allocates memory for variables
and functions, preparing them for later use during the execution phase. It's
important to note that the way variables are allocated and their initial values
can vary depending on the type of declaration.
a. var Variables: When a variable declared with var is encountered during
memory allocation, it is assigned the default value of undefined. This means
that although the variable exists in memory, it holds the value undefined until
a value is explicitly assigned to it.
b. let and const Variables: Variables declared with let and const also go
through memory allocation. However, their behavior is more nuanced and will
be discussed in a later section when we cover the Temporal Dead Zone
(TDZ). It's during this phase that let and const variables are assigned the
initial value of undefined within the TDZ.
Consider the example given below to understand the workflow of Execution Context
in JavaScript.
The detailed explanation of the phases of Execution Context for the given code is as
follows:
● The global and local execution context for the given code snippet can be
described as shown below:
● The workflow for the execution context of this code snippet will be as follows:
1. Creation Phase:
● During the creation of the global execution context, the JavaScript
engine declares three variables (userName, userAge, and
birthYear) and initializes them to undefined. It also declares a
function named greetUser, which is stored in memory but not
executed yet.
● This work-flow is depicted in the figure below:
2. Execution Phase:
a. The JavaScript engine assigns 'Tom' to the userName variable and 10
to the userAge variable. It then executes console.log() twice,
outputting the values of userName and userAge to the console as
"username: Tom" and "userAge: 10" as shown below:
b. When a function is invoked, a new Execution Context is built all
together to carry out the same procedures for that function call/invoke.
The JavaScript engine creates a new execution context (as shown
below) for the greetUser function following the same procedure
discussed above.
● The further workflow of the Local Execution Context is described in the
following figure, where the variables within the greetUser function are
assigned values.
To access and inspect the execution context in Google Chrome Dev Tools
specifically, you can use the following steps:
1. Open Google Chrome browser.
2. Navigate to the webpage or web application where the JavaScript code is
running.
3. Right-click on the page and select "Inspect" from the context menu.
Alternatively, you can use the keyboard shortcut:
a. Windows: Ctrl + Shift + I
b. Mac: Command + Option + I
Once the Dev Tools panel is open, follow these steps to access the execution
context:
I. In the Dev Tools panel, locate and click on the "Sources" tab.
II. In the left-hand sidebar, expand the file or snippet containing the JavaScript
code you want to inspect.
III. Set breakpoints at desired locations by clicking on the line number next to the
code or using shortcut F9.
IV. Refresh the webpage or trigger the execution of the JavaScript code.
V. The execution will pause at the breakpoints, allowing you to inspect the
execution context.
VI. Use the "Scope" section in the right-hand sidebar to explore the variables and
their values within the execution context.
VII. You can also use the "Call Stack" section to see the sequence of function
calls that led to the current execution context.
Hoisting in JS
Hoisting is a behavior in JavaScript where variable and function declarations are
relocated to the beginning of their code blocks during the compilation phase, no
matter where they are actually written in the code. This means that they can be
accessed before they are declared.
● For example, the following code will work without any errors:
This is because the `var x;` declaration is hoisted to the top of its code block,
which in this case is the global block, and is executed before the `x = 5;`
assignment.
● Function declarations are also hoisted in a similar way.
For example:
This code will also work without errors because the greet() function
declaration is hoisted to the top of its code block (in this case, the global
block) before it is called.
● However, it's important to note that only the declarations themselves are
hoisted, not their assignments.
For example:
In this code, the var x; declaration is hoisted to the top of its code block, but
the assignment x = 5; is not. So when console.log(x); is executed, x is
still undefined.
Generally, it's best practice to always declare variables and functions before using
them to avoid unexpected behavior due to hoisting.
Call Stack
● In JavaScript, the call stack is a data structure that tracks the execution of
functions during the runtime of a program.
● Every time a function is called, a new frame is created on top of the call stack
to hold information about the function call, such as its arguments and local
variables.
● The call stack operates on a "last in, first out" (LIFO) basis, meaning that the
most recent function is the first to be completed and removed from the stack.
● The call stack is essential for understanding how JavaScript executes
functions, and it plays a crucial role in identifying and debugging errors that
occur during program execution.
Here is how the call stack builds up for the given code:
figure- 3(a)
2. The greetUser() function is called with userName as an argument, and a
new execution context is created as shown in figure-3(b)
figure-3(b)
figure-3(c)
4. Once the operations of the birthYear function have been completed, it will
be popped out of the stack as shown in figure-3(d).
figure-3 (d)
5. Similarly, the greetUser function is popped out after its operation has been
performed. This operation is depicted in figure-3(e).
figure-3 (e)
6. Finally, in the end, the Global Execution Context (General Execution Context)
is popped out of the Call Stack as depicted in figure-3(f).
figure-3 (f)
Scope in JS
● In JavaScript, scope refers to the accessibility of variables and functions in
your code. Understanding scope is crucial for writing clean and efficient code.
● There are three types of scopes: global scope, functional/local scope, and
block scope.
1. Global Scope:
Variables declared outside of any function or block are in the global scope. They can
be accessed from anywhere in the code, including inside functions or blocks.
For example:
2. Functional/Local Scope:
Variables declared inside a function or block are in the functional/local scope. They
can only be accessed from within that function or block.
For example:
3. Block Scope:
Variables declared using let or const inside a block (e.g., inside a for loop or if
statement) are in the block scope. They can only be accessed from within that block.
For example:
In JavaScript, let, var, and const are used to declare variables, but they differ in
terms of scoping and mutability.
1. let:
● let is used to declare block-scoped variables.
● It allows you to declare a variable inside a block and use it only within that
block.
● let variables can be re-assigned but not re-declared within the same scope.
● let is more strict than var and helps avoid bugs caused by variable
hoisting.
2. var:
● var is used to declare function-scoped variables.
● It can be declared and re-declared multiple times within the same scope.
● var declarations are hoisted to the top of the function or global scope, which
means that they are processed before any code is executed.
● Because of hoisting, var can lead to bugs in code if not used properly.
● However, it's important to note that var has two scopes: global scope and
functional scope. Variables declared with var in the global scope are
accessible throughout the entire program, while variables declared with var
inside a function are only accessible within that function.
3. const:
● const is used to declare read-only variables.
● It can be used to declare a variable once and cannot be re-assigned within
the same scope.
● const variables are also block-scoped.
● const is useful for declaring constants that should not be changed throughout
the program.
Scope Chaining
Lexical environment
● Lexical environment is a fundamental concept in JavaScript that refers to the
specific context in which code is executed.
● It encompasses variables, functions, and objects that are accessible and in
scope at a particular point during the execution of code.
● A fresh lexical environment is generated whenever a function is called or
invoked in JavaScript. This lexical environment encompasses all the variables
and functions that are in scope and can be accessed within that particular
function call.
Scope chaining
● Scope chaining, also known as lexical scoping, is a mechanism in JavaScript
that allows a function to access variables from its outer (enclosing) lexical
environment as well as from the global scope.
● This means that functions can access variables defined in their parent
functions, grandparent functions, and so on, all the way up to the global
scope.
Here's an example to illustrate how scope chaining works in JavaScript:
It's important to note that scope chaining only works in one direction, from inner
to outer, and not the other way around. That means variables defined in an inner
scope cannot be accessed from an outer scope.
● not-defined is a state of a variable that has not been declared at all. If you try
to access such a variable, JavaScript throws a ReferenceError exception.
For example:
In this example, y is not defined anywhere in the code. Therefore, JavaScript throws
a ReferenceError exception.
Strict mode
● Strict mode in JavaScript is a feature introduced in ECMAScript 5 (ES5) that
allows developers to opt into a stricter set of rules and best practices for
writing JavaScript code.
● When strict mode is enabled, the JavaScript interpreter enforces a more
stringent set of rules, catches common mistakes, and disables certain silent
errors.
To enable strict mode for a specific function, add the same line at the
beginning of the function's body.
2. Benefits: Strict mode helps developers write more reliable and maintainable
code by addressing some of the quirks and pitfalls of JavaScript. It prevents
the use of undeclared variables, eliminates the automatic creation of global
variables, prohibits the use of duplicate function parameter names, and
disallows certain unsafe language features.
In this example, we try to access the variable ‘a’ before it is declared. This results
in a ReferenceError because the variable is in the TDZ and cannot be accessed
until it has been declared.
● It occurs only for variables declared with let and const keywords and not
for variables declared with var. This is because var is function-scoped and
hoisted to the top of the function, making it accessible throughout the function,
even before it is declared.
In this example, we try to access the variable ‘b’ before it is declared, but we
get undefined instead of a ReferenceError. This is because var variables
are hoisted to the top of their function and initialized with undefined by default.
● To avoid TDZ errors, it's best practice to declare variables at the beginning of
their scope, before they are accessed. This will ensure that they are not in the
TDZ and can be accessed without throwing a ReferenceError.
Closures in JS
● In JavaScript, a closure is created when a function accesses variables outside
of its immediate lexical scope.
● The closure retains a reference to the environment in which it was created,
allowing the function to access and manipulate variables in that environment,
even after the outer function has returned.
a. Here's an example of a closure in JavaScript:
● In this example, the outer function creates a variable count and a nested
function inner that increments the count variable and logs it to the console.
The outer function then returns the inner function.
● We then assign the returned inner function to the counter variable. We can
now call the counter function multiple times, and each time it will increment
and log the count variable.
● The important thing to note here is that the inner function retains a reference
to the count variable in the environment where it was created (inside the
outer function). This is possible because JavaScript functions are closures,
and they maintain a reference to the environment in which they were
created.
b. Here's another example to illustrate the concept of closures:
● In this example, the createCounter function returns an object with two
methods: increment and decrement. Both methods have access to the
count variable, which is created in the createCounter function's
environment.
● We then assign the returned object to the counter variable, and we can call
the increment and decrement methods on it. Each time we call a method, it
will update and log the count variable.
● The heap is the memory space the JavaScript engine uses to store objects
and values created by the code.
● Web APIs are interfaces provided by the browser environment that allows
JavaScript to interact with browser features. These APIs include the alert(),
confirm(), prompt(), and setTimeout() and setInterval() methods.
● Callback queues and the event loop are used to manage asynchronous code
execution in JavaScript. We will be covering it in the upcoming lectures.
The JavaScript runtime environment is the foundation for executing JavaScript code
in the browser environment. It provides a set of tools and interfaces that allow
developers to create complex and interactive web applications.
Summarizing it
Let’s summarize what we have learned in this Lecture:
● Execution Context in JS
● Hoisting in JS
● Call Stack
● Scope and Scope Chaining
● Strict modes and Temporal Dead Zone
● Closures in JS
● JavaScript Runtime Environment
References
● Closures in JS: Link