How JavaScript works? How Scope in JavaScript works? What is Scope Chain?
Javascript is fast growing programming language as it can be used from browser to server & from mobile apps to web apps. There are people who love JS & then there is a whole lot of section who completely hate JS.
But JS is a beautiful language ❤
Through this blog, I will try to explain how JS works behind the scenes and explain you how scope works in JS. So let’s begin.
How JS works in the browser?
All the browsers have an engine (mostly written in C & C++), which converts the JS code into machine code & thereby allow the browsers to understand and run the code. Some popular browser engine are V8 used in Google Chrome while Safari uses Squirrelfish & like so.
Everything in JS happens inside an execution context.
Before going further, let’s understand that JS is synchronous single threaded language. Single threaded means JS can execute only one command at a time. Synchronous single threaded means JS can execute only one command at a time and in a specific order i.e. it can only go to next line once the current line has finished executing.
Now let’s understand what happens inside the execution context. In order to explain what happens in execution context, let’s see some JS code and see how it works.
When the script loads in the browser, a global execution context is created. The execution context runs the code in 2 phases namely Memory component and Code component.
In the first phase, inside the memory component all the variables and functions will be allocated memory as key value pairs. Remember, JS is synchronous single threaded language. So it will run one line at a time. So JS will skim through the whole piece of code line by line and allocate memory to variables and functions.
Line 1: var n = 8; n will be assigned a value of undefined and not 8. The global execution context will look like this —
Line two: function square(){….}. Memory will be allocated to the whole block of code and as it is a function, the whole function will be stored. Now the GEC will look like this —
Line 3, Line 4 will be allocated memory on function invocation as they are inside function.
Line 6: var square8 = square(n); The behavior is similar to line 1.
Line 7: var sqaure10 = square(10); The behavior is similar to line 1.
At the end the execution content, it will look something like this.
Phase 2-Code Execution.
After the memory is allocated, the Phase 2-Code Execution begins. In code execution, JS will skim through the program once again from top to bottom and line by line (remember synchronous single threaded).
Line 1: var n = 8; It actually places 8 inside “n” in the place of undefined. So until memory phase the value of n is undefined and now 8 is allocated to ‘n’.
Line 2: function square Since this is a function, there is nothing to allocate so it jumps to line 6.
Line 6: var square8 = square(n); Here the function square is invoked and when a function is invoked, a new execution phase is created inside the code component. The new execution context created again has 2 phases just like the previous one.
So now in the memory phase, it allocates memory to the variables inside the function starting from the parameter. So now the parameter ‘num’ along with the variable ‘ans’ will be allocated memory and will hold the value of undefined.
In the second phase i.e. the code component phase, the code is executed. So this is the phase where the invoked function will try to find all the assigned values to the variables and complete the return call .
The function is invoked on line 6, with the argument ‘n’ , so n which is assigned 8 will be placed in num. So the execution context will now look like this —
So now the code will move to next line which is line no. 3 where var ans = num * num. Since num = 8, ans will be stored as 64. The execution context will look this —
So now the control will be go to line 4, where it sees the special keyword return ans. So whenever the function sees return keyword, it knows it is the end and it has to return ans (which is 64), to the execution context where the function was invoked (line 6). So it will find the value of ans from the memory component (local memory) which is 64 and will assign 64 to square8 in place of undefined. Now the execution context will look like this —
One last thing which happens after the function has returned is it will delete the execution context of the function invocation. So now it will look like this —
Line 7: var square10 = square(10); Same behavior as it happened in Line 6.
Now you might think that the above process of creation of global execution context, allocation of memory, execution of code component phase, deletion of function instance is very cumbersome and it sure looks that way but JS does it very beautifully using the call stack.
What is a call stack? A call stack is a stack of data structure, which stores information about which line in the code is currently running and what other function is being called from within the function.
How does call stack work? When a script loads, the global execution context is pushed inside the call stack and when a function is invoked, a new execution context is created, that is pushed on top of the global execution context (in our program, line 6 invokes a function and square8 is pushed on top of global execution context).
Upon reaching the return statement, the square8 function invocation is completed and then the call stack pops out the sqaure8 function and the control goes back to global execution context and continues from where it left i.e. at line no. 7. This line creates a new execution context as a function is invoked (square10) and upon reaching the return statement, even this is popped out and then again the control goes back to global execution context. Now there is no more code to run, so the global execution context is also deleted.
So the call stack is only for managing these execution contexts. It maintains the order in which the execution of these execution contexts takes place.
Now let’s see what is scope and how it works? Javascript has a feature called Scope. Scope is a context in which a variable/function can be accessed. It determines the visibility of variables in the code. Which part of the program can have access to certain variable and which part cannot. This helps in maintaining the security of the code. This way we can avoid unintended modification to the code from other parts.
For example, you run a t-shirt store in a mall. You hire a salesman for your store. Now this person should have access only to the role limited to him like attending the prospective buyer, billing, and he should not have access to accounts or manipulate CCTV etc. But you as a owner of the store, can have access to everything. So just like this in coding, we may assign certain variables which can be accessed globally and certain variables whose accessibility is limited.
In Javascript there are two types of scope: Global Scope Local Scope
Global Scope A variable which if declared outside a function or curly braces, then it is said to be a variable defined in global scope. consider the following code:
Now this variable ‘a’ can be accessed anywhere in the code, even in functions.
Local Scope Variables which can be used only in a specific part of the code, or which are declared within the curly braces, then it is said to be declared in local scope.
It is best practice to declare local variables as it doesn’t lead to any unwanted manipulation of variables and also prevents in naming collisions where two or more variables have the same name. Also it is best practice to declare variables with const and let as we would receive an error whenever name collision happens. You can see the below example -
Here we get an error saying n is already defined thereby not allowing for name collisions.
Here there is no respect to name collisions and the code works as if there is no error when we declare variables using var.
So it is best practice to declare variables using let & const and to do it in local scope instead of the global scope.
Now consider the following code:
In the above code, when we invoke the function a(), we get 10 in the console.
Now see this :
Function c() which is inside Function a() also has access to global variable. In this example too, 10 will be printed in the console.
Now see this code:
In this example too, 10 will be put in the console. function c has access to variable b which is declared inside a().
But a local variable cannot be accessed from global scope. Check the code below -
An error will be thrown that b is not defined.
Wouldn’t it be great to know how the code runs? Let’s see how scope works now. But before going further, please note local scope are of two types, namely, function scope and block scope.
Function Scope: All the variables which are declared inside a function, can only be accessed inside a function. We cannot access the variable outside the function. Check Example 4 above.
Block Scope: When we declare a variable with const & let inside a curly brace, we can access the variable only inside this curly brace. Check the below example -
Please note the behavior in the above example, how var can be accessed even outside the block of curly braces whereas variables declared with ‘let’ and ‘const’ cannot be accessed and it throws an error.
Now let’s see how scope works behind the scenes?
Consider the following code:
When this script loads in the browser, a global execution context is created and put onto the call stack. Whenever an execution context is created, a lexical environment is also created.
Lexical environment is local memory along with lexical environment of its parent. The call stack will look like this when the first line runs.
After the first line, there is no assignment of any further variables in global scope. So, next the function is invoked on line 9.
When line 9 is run, a new execution context is created for function a() with memory component and code component on top of the GEC. Now the call stack will look like this :
After the function is invoked, the same steps will be repeated as I mentioned in the beginning of the article.
The variable b will be first defined undefined in the memory component and then when the code component phase begins, it will be defined with their assigned values i.e. b = 10. The whole instance of the function c() will be stored inside the memory component.
Line 3: This is a function invocation. So again a new execution context will be pushed on top of the call stack. Now the call stack will look like this
So this is how the code is executed inside the GEC and new execution context is created whenever there is function execution. Now let us see what is lexical environment.
Lexical environment is the local memory along with the lexical scope of its parent. The word lexical means, in hierarchy or in a sequence. In the above example the function c() is sitting lexically inside the function a(). So this means the function c() can access variables which are declared inside c() by default along with the variables which are declared inside function a().
So whenever the execution context of function c() is created, we also get the reference to the lexical environment of its parent. The same applies for function a(), which gets the reference of the lexical environment of GEC.
Now the revised call stack looks like this:
So the function c() will have reference its own memory along with the reference of lexical environment of its parent (function a()) and GEC.
The function a() will have access to its own memory along with the reference to lexical environment of its parent being GEC.
The GEC will have reference to its lexical parent. At global level, this reference to the outer environment points to null as there is no parent.
If you have come this far, I guess it is safe to assume, you have understood what is lexical environment.
How lexical environment is used? consider the following code:
The call stack will be the same as it is mentioned above. Now when JS engine encounters line 5, it will try to access the variable b in the local memory of function c(). It will not find there.
Since function c() is inside the lexical environment of its parent function a(), it will see that it has the reference of the same and now will access var b = 10 and will print 10 in the console.
Now if suppose we did not have var b = 10; inside the function a(), it would go further up to the parent of function a(), being GEC. There it would search for var b, and if it is there, then the value assigned to b will be printed.
In the example above it is not there in GEC as well. So it will go further to the parent of GEC being null. So after reaching here, it will stop and print b is not defined.
This way of finding the variables and going further to lexical environment of the parent and further until it reaches the parent of the GEC is known as the Scope chain. Scope chain is nothing but all the lexical environment along with the references of the parent references.
Wrapping Up
When the script loads in the browser, a Global Execution Context(GEC) is created. This execution context has 2 components — Memory component and code component. In the first phase, the JS engine skims through the whole program and allocates memory to all the variables and functions.
In the second phase, the code execution phase, the JS engine runs through the whole program again, line by line (Synchronous single threaded) and it executes the code. A new execution context is created every time a function is invocated.
All this is done inside the call stack. Once the code is fully executed, the call stack pops out the GEC and is empty.
When an execution context is created, a lexical environment is also created. lexical environment means the local memory plus the reference to lexical environment of the parent.
When a variable is not present in the local memory, it goes further up to the reference of the lexical environment of the parent and does it until it reaches the lexical environment of the GEC. This process of going further down to GEC is knows as scope chain.
I hope you enjoyed reading this and learnt something new. If you have any questions, feel free to ask. I will reply as soon as I can.
You can learn more on how Javascript works here on Akshay Saini's youtube channel.
All thanks to Akshay Saini for making such great videos and also thanks to Tanay Pratap who’s pushing a lot of people to write blogs and become full stack programmers.