How JavaScript Works
Everything inside JavaScript happens inside an execution context. This will help you to
understand what happens when we run a JavaScript program and how the code gets
executed.
Below we have a code that simply calculates the square of a given number and returns the
result. We will take help from this example to understand the internal working of JavaScript.
When we run this code at first a Global Execution Context will be created. It has two
components: memory and code. This context is created in two phases:
1. Memory Allocation Phase
2. Code Execution Phase
Memory Allocation Phase - Global
1. allocate memory to variable n and assigns undefined
2. allocate memory to square function and stores entire function
3. allocate memory to variable square2 and assigns undefined
4. allocate memory to variable square4 and assigns undefined
Code Execution Phase - Global
1. n gets the assigned value i.e. 2
2. nothing to execute from line2 to line5
3. function call thus a New Execution Context is created for the function
invocation
Memory Allocation Phase - square(n)
1. allocate memory to variable num and assigns undefined
2. allocate memory to ans and assigns undefined
Code Execution Phase - square(n)
1. nums gets the value 2
2. JS executes the num * num and stores in ans
3. return encountered which means end of function code.
4. The return keyword returns the value of the ans as 4 from the local execution
context to the function square(n).
5. The value of square2 becomes 4 and control moves back to this line because this
was the place from where the function was invoked.
6. Along with these the local execution context which was created for
square(n) also gets removed from the Global Execution Context.
After this the control will move to the line number 7 which again has a function invocation
square(4) and the same process repeats.
1. new local execution context is created
2. variables and functions are allocated memory
3. next code execution phase happens and control moves back to the place from where
the function was called.
4. the local execution context created gets removed from the Global Execution
Context and there are no further lines to be executed.
5. Once all the execution part is done the Global Execution Context also gets
deleted and the program ends.
Call Stack
In JavaScript, the call stack is a fundamental mechanism used to manage execution
contexts in a single-threaded environment.
When the JavaScript engine runs code, it first creates the Global Execution Context
(GEC), which represents the global scope and serves as the base of the call stack. The
GEC stores global variables, functions, and objects, and it remains at the
bottom of the stack throughout the program’s lifetime.
Whenever a function is invoked, the engine creates a Function Execution
Context (FEC) for that function, which includes its lexical environment
(variables declared inside the function, function parameters, and the scope chain linking it
to outer contexts) and the thread of execution for the function body.
This FEC is then pushed onto the top of the call stack, temporarily pausing the
execution of the code that invoked the function until the current function completes. Inside
the FEC, JavaScript evaluates expressions, executes statements, and handles local
variables.
Once the function finishes execution and returns a value, its execution
context is popped off the stack, and control returns to the context below
it—often another function or the global context.
This stack-based mechanism ensures that JavaScript executes code synchronously
and in order, while maintaining the proper scope chain for variable lookup, allowing functions
to access both their local variables and variables in their outer lexical environments.
By using the call stack together with execution contexts, JavaScript efficiently manages both
global and local scopes, handles nested function calls, and maintains the flow of execution in
a predictable manner.
How to Visualize the Call Stack
Go to the browser and open the inspect window and select snippets from the place
given in the below image. Sources >> Snippets
Create a New Snippet and add the following code. To run the code right-click on the file
name and select run.
After running the code use the debugger to move a step ahead and observe the Call
Stack option. It will show something like the below image.
You can put multiple debuggers to visualize values at different places. Hover your mouse
on the variables to visualize the value of it at the current time.
---------------------------------------------------------------------------------------------------------------------------
Hoisting in JavaScript
This is a very basic program that simply has a variable and a function and they are
being printed and called and obviously it prints the expected results.
However, the below code contains the twist. Tweak the same code in the following way
and now try to guess the output.
This may look like something unexpected but in JavaScript we can do it. Not only this, this
code will also provide an output undefined and Name: Prashant in console.
We were able to access the function and variable name even before initializing them. This
phenomenon is termed as hoisting.
Important Note
The word “hoisting” in JavaScript is not an official technical term from the
ECMAScript specification—it’s more of a teaching metaphor that developers/community
coined to describe JavaScript’s behavior with variable and function declarations.
"Hoist" in English means to raise or lift something up (like hoisting a flag or hoisting cargo).
In JavaScript, when the engine parses code, declarations (variables, functions, classes) are
conceptually “moved up” to the top of their scope before execution. Since it feels like
JavaScript is “lifting” declarations to the top, early developers and educators started using
the word “hoisting” to explain this behavior.
But Why This Behaviour
This part will be well understood only if we know Execution Context i.e. the internal
working of JavaScript.
If we apply that logic then we will be able to recall what is actually happening behind the
scenes.
Now on the left you can see what we have after the Memory Allocation Phase. Let us
analyze what happens next.
JS will now move to Code Execution Phase and will see that there is a function call at
2nd line and thus will create a New Execution Context and will again repeat the process
and will run the code like illustrated in the image below.
And after running the function it will get removed from the Global Execution Context
and it will also get removed from the Call Stack and this is how hoisting works in
JavaScript.
In short, variables and functions are already allocated the memory and in case of functions
the entire function signature with the body sits in the memory, thus during the execution
phase JS is able to pull up that code and is able to run it.
---------------------------------------------------------------------------------------------------------------------------
Understanding Functions in JavaScript
In programming functions are a piece of code that performs some specific tasks which
are assigned to them. It helps to make our code more clean, readable and easy to debug in
case of errors.
If I tell you to print Hello World 10 times, then you may say that it’s an easy job that can be
done using loops.
I agree. However, if I tell you that I want the job to be repeated again and again, in that case
you need to write the repeated loop logic to achieve that and it’s simply code repetition.
This can be avoided with the help of functions. We just need to put this logic inside a
function and then simply call it. A function won't work unless called.
I can just simply call this function as many times as I want and it will do my job and will also
help me to avoid code repetition.
So functions just do the task which we define. A function can help other functions as well if
they are related. A function doesn’t work on its own and explicitly needs to be called.
There are various ways to create a function in JavaScript and their working is also a little
different from one another.
Creating a Simple Function
We can use the function keyword to declare a function in JavaScript. We need to provide
a name for that function followed by parentheses (). After this we declare a block where we
write our code. Block here doesn’t mean block scope. Our function can have multiple
lines of code so we need to wrap it inside a code-block.
Passing Parameters to a Function
We can pass parameters to function inside the () and this helps to get the different values
when the function is called making it reusable.
This function always gives you a result as 30 and will make the function not reusable. It’s like
hard coding the values.
We want if we provide any 2 values my function will always give me the sum of those values.
It can be done by passing parameters to a function.
This function is more reusable. It takes any 2 values and tries to sum them. You haven’t
defined the type so it can take any values. Think about the type-coercion when
passing other values apart from numbers.
Passing Default Parameters
In the above example we were getting output results as NaN. This was because in
JavaScript if you declare a variable then it automatically has the value as undefined.
We want to restrict this situation. If nothing is passed to my addNumber() function then its
result must be 0. Again this is completely up to the requirement what we want function to do
in case of invalid inputs.
Note: 0 may not represent something here but 0 degree celsius is a valid temperature.
In such cases we want to print or return something which represents empty in JavaScript i.e.
null. The null represents that a particular value is actually empty and it’s assigned
intentionally by the developer.
It won’t print undefined here and will take up default values from the parameter if nothing is
passed. However, this doesn’t also handle all the edge cases. We still can pass strings to
this function and whenever we pass arguments the default parameters get overwritten.
return Keyword in Function
By default a function in JavaScript always returns a value. If you are explicitly returning
something then it returns that value. If nothing is returned then JavaScript returns
undefined. The value is passed back to the line from where the function was called and we
need to store that.
Whatever lines of code you write after the return keyword is not reachable and never
executed so use it carefully.
With the help of all this knowledge we can design our addNumber() function in such a way
that it only performs addition when parameters received are either empty or numbers. For
the remaining cases we return giving the feedback that inputs are invalid.
We can still make some changes by providing a little bit of flexibility to our code. What if I call
the function in this way addNumber(“10”, “20”). They are passed as a String but they
are numbers. So we can perform a check using parseInt() or Number() to convert it into
a number type then perform the task.
Still you will be left with a bug i.e. NaN. The type of NaN is a number so make sure you make
the comparison correctly and don’t mess up the code. You can use the isNan() function to
make proper checks.
------------------------------------------------------------------------------------------------------------------------
Lexical Environment and Scope in JavaScript
If we consider the above example and think about the execution context then the
variable b doesn’t exist in the local execution context of function b but it will
still be able to access the value of b.
The same thing will happen in this case and the value of b will be printed as 10. This
somewhat gives us an idea that JavaScript is trying out to find the variable first in the
local context and if it is not found it keeps on going to the next outer context
unless it finds it.
In this case the variable b won’t be available outside of the function a and here the
Scopes comes into picture.
Scopes simply means where can I access a particular declared variable inside my code.
Thus, when someone asks What is the Scope of a Variable he means to say where all can
I access that variable inside the code.
With Scope one more thing comes into picture i.e. Lexical Environment but What is it?.
If we take a look at the third picture and try to remember the Execution Context then
with creation of each Execution Context a Lexical Environment is also created.
Lexical Environment = Local Memory + Lexical Environment of Parent
In this image if you focus there are some blocks that state local memory and the
lexical environment.
So from the above code a Global Execution Context is created and JS sees a
function called a() and creates a Local Execution Context but along with it you can
see that it also has access or reference to global memory space.
When the body of function a executes it calls the function c() and creates a Local
Execution Context along with the reference to a and global, thus it is termed as
Local Memory + Lexical Environment of Parent as there can be multiple
hierarchy.
The Global Execution Context has no parent thus its lexical points to null.
Now you may argue that if we have access to all of the parents lexical environment then we
are not able to access b outside.
It’s simply because after the functions are done executing they are removed from the
execution context thus b is not available.
In very basic terms if a variable is not present inside the local scope then JS keeps on going
to its parent level unless it reaches null. If found then it uses that value else it gives you the
error that particular variable is not defined.
This process of finding the value by going a level up until null is found, is known as the
Scope Chain.
If you just try to run the code by putting a debugger then in the scope section you will be
able to visualize that all scopes can be accessed.
The current image demonstrates that when we are inside the function c there we have
access to the Local, Closure(a), Script, and Global. The variable b is found in
the Closure (a) scope and the value is taken up from there.
------------------------------------------------------------------------------------------------
Types of Functions
Functions in JavaScript are a bit tricky to understand but you should always keep a few
things in mind. First is the Execution Context and second is they are objects behind
the scenes.
Function Statement
In JavaScript when we use the above syntax to create a function then it is known as a
function statement and they are hoisted. It's the same as a function declaration.
Function Expression
When a function is assigned to a variable then that function is known as function
expression and they are not hoisted.
Function Declaration
In JavaScript when we use the above syntax to create a function then it is known as a
function statement and they are hoisted. It's the same as a function statement.
Anonymous Function
If we don’t give a name to a function statement or function declaration then
they are termed as anonymous functions. However, we can’t create a function like this
because of the ECMA specification. According to ECMA it must have a name.
We usually use it when we want to use a function as values. It simply means that we
can assign it to a variable. If we do this it becomes the same as function expression.
Named Function Expression
This function is like a function expression but the function assigned is not an
anonymous function it has a function name, thus known as a named function
expression.
We can’t call this function by printName because it won’t be available in the current scope,
however we can access it inside the function.
First Class Functions | First Class Citizens
In JavaScript we can pass a function as an argument to another function. In the
above example the addNums function is passed as argument to the function addNumbers
and inside that function we are calling the function addNums and since the function is
returning a value we can simply log the returned value;
We can also return a function from a function in JavaScript as shown in the above
example. The addNumbers function is returning a doubleSum function so the value of
returnedVal will be equal to the returned function and when we call the returned
function i.e. doubleSum it executes the code-block inside it where we are first calling
addNums and then doubling the value by twice and returning it.
In short we were able to use functions as values. This particular thing is known as
first class functions where functions are treated as normal values.
---------------------------------------------------------------------------------------------------------------------------
let, const and Temporal Dead Zone
let and const are hoisted in JavaScript. However there is a But, which we will
be covering in this section.
We know that var is hoisted and has a special placeholder value undefined. You may
argue if let and const are also hoisted then why accessing it gives an error like the
below code.
If you put a debugger at the top of this code then you will notice something like the below
code. There is Script scope which contains the variable b but says value
unavailable.
This simply means that the variable was allocated memory in the Memory Allocation
Phase, but present inside a special scope i.e. Script. This scope cannot be accessed
unless some values have been put into them.
The Temporal Dead Zone
The Temporal Dead Zone is the time between when a let or const variable is hoisted
into scope and when it is initialized with its value. Variables declared with let and const are
hoisted just like var.
But unlike var, they are not initialized to undefined immediately. Instead, they stay in the
TDZ until the code execution reaches their declaration line. Trying to access them in this
TDZ causes a ReferenceError.
You can test out the same things with const declaration as well and you will get the same
results.
------------------------------------------------------------------------------------------------------------------------
The window Object
In a browser, the window object is the global object that represents the current browser
window or tab in which your JavaScript is running. It acts as the container for all globally
available functionality, such as browser APIs (document, navigator, location,
localStorage), utility functions (setTimeout, setInterval), and even user-defined
global variables when they are declared with var.
When JavaScript starts executing, it first creates the Global Execution Context (GEC). This
is the default context in which all top-level code (code not inside a function) runs. During the
creation phase of the GEC, the JavaScript engine sets up the global object, which in a
browser is a window, and binds the, this keyword to that global object.
That is why, at the top level in a browser, this === window evaluates to true. Variables
declared with var in the global scope are automatically added as properties of the window
object, whereas variables declared with let and const are not attached to the window
even though they still belong to the global scope.
In short, the window object is the concrete representation of the global object in
browsers, and the Global Execution Context is the environment that makes this object
available and ties top-level code to it.
Global Variable Declaration
A global variable declaration means defining a variable that is accessible throughout
your entire program, no matter which function or block you are in. It lives in the global
scope, which is the outermost scope of a JavaScript program.
In a browser environment, global variables are tied to the global object (window) if
declared using var. With let and const, they are still global in scope but are not added
as properties of window.
---------------------------------------------------------------------------------------------------------------------------
Block Scope & Shadowing
The above image represents a block in JS and also known as compound statements,
i.e. we can combine multiple JS statements and group them together.
Why Do We Do It? This is because at certain places JS only wants 1 statement but we have
multiple-things to write, thus we end up using blocks so that the compiler or interpreter
understands it and treats it as a single statement.
This code prints How Are You and skips the if block because of a falsy value. But I
wanted the 2nd log to be part of the same if. So JS basically picked up the first statement
right after if and didn’t care whether any other statements were part of it or not.
However, introducing a block fixes the issue and in this case it only prints Hi.
Block Scope
Block Scope simply means the functions and variables that can be accessed inside
a particular block.
In this example (above image) if you take a look at the Scope section then b and c goes to
Block and a goes to Global and it’s hoisted.
This code will log the value of a but will give error on Line 9, because let and const are
accessible only inside that particular block but a is accessible because it’s currently in the
global object.
Shadowing
In the given image the var a which is inside the block shadows the outer var a and logs
the value as 20. However, it also modifies the original value of a because they both point to
the same memory location, thus the outer log also prints 20. Use a debugger to visualize
the changes.
The same thing won’t work with let and const as they are not accessible outside of a
Block Scope. They goes into a special location i.e. Script Scope, as shown in above
picture.
Function Behaviour
In the case of functions, variables declared inside the function block go to local
scope and this points to the window object as seen in the image above.
In short let and const are block scoped and var is function scoped, this is the 1
line answer in context of scoping. And don’t forget about lexical scoping, they all
follow it.
---------------------------------------------------------------------------------------------------------------------------
Closure in JavaScript
closure => function + lexical scope
In the above image we have an outer function and an inner function and this is the
snapshot when the program pauses on the first debugger.
On the second debugger we are able to see something in the scope i.e.
closure(outer), but What is it?
You might be thinking that every time you write a nested function it will form closure but
that is not the case. Instead of printing a if you try to print other things which are not present
in any lexical scope then it won’t show this scope.
Closure simply means a function + lexical environment. The variable a was not
found in the inner function thus it JS tried to find it in the lexical scope and found it in
the outer function.
Complicating Closure
As per the above example and the knowledge about the lexical scope and execution
context we know that a local execution context is removed the moment a
function finishes execution. There is something more associated with it when it comes to
closure.
The above image might question your knowledge that if the outer function will finish
executing the variable b will also be removed then how it is getting printed.
If you think in a general way then it’s like somehow the inner was able to remember and
preserve itself along with the lexical scope. This is actually what happens also, the
inner remembers where it was present in the scope chain.
In this case the function + closure was returned from the outer function thus it is still
able to access the variables.
---------------------------------------------------------------------------------------------------------------------------