Execution Context in Javascript
How Javascript works behind the scenes or How Javascript code is being executed ?
First, we have to know what is the execution context in javascript ?
Execution context is an important concept in JavaScript that helps in managing the execution of code. It defines the environment in which JavaScript code is evaluated and executed. An execution context consists of variables, functions, and objects that are available during the execution of a particular segment of code.
Types of Execution Context in js :
There are three types of execution contexts in JavaScript:
Global Execution Context:
The global execution context is created when the JavaScript code starts running.
It represents the default or outermost context in which the global variables and functions are defined.
There is only one global execution context in a JavaScript program.
Function Execution Context:
Whenever a function is invoked, a new function execution context is created.
Each function has its own execution context, which contains its local variables, function arguments, and references to the outer variables and functions (lexical environment).
Multiple function execution contexts can exist simultaneously, depending on how many functions are called.
Eval Execution Context:
The eval() function in JavaScript can dynamically evaluate code.
When eval() is called, a new eval execution context is created.
The eval execution context behaves similarly to a function execution context but has its own scope and variables.
Execution Contexts in Detail: Creation and Execution Phases and Hoisting
In the above section, we talked about when the execution context is created and now we will talk about how it is created. An execution context can be considered as an object and this object has three properties.
Variable Object(VO):
It will contain a function argument, function declaration, inner function declaration, and inner variable or variable declaration. Function declaration, pointing to the function, and variable declaration are commonly called Hoisting.
Scope Chain:
It contains its own variable objects as well as its parent’s variables object.
“this” variable:
Based on the scope chain the value of this is initialized. If this is a bit confusing don’t worry once I explain the scope chain “this” will be a lot clearer.
When a function is called inside a function a new execution context is created for it and is pushed on top of the execution stack as discussed before this step happens in two phases the Creation phase and the Execution phase.
All three properties VO, Scope Chain, and “determining the value of this” is done in the Creation phase. After that in the Execution phase, the code generated in the current execution context is run line by line.
Hoisting
Function and variables are available before the execution phase starts. Functions and variables are hoisted in a different way, functions are already defined before the execution phase starts while variables are set to be undefined and will only be defined in the execution phase.
Let’s look into hoisting by looking into practical examples.
testHoisting()
function testHoisting() {
console.log('function is hoisted')
}
The above code will work, in the creation phase of the execution context in this its a Global Execution Context(GEC) the function declaration testHoisting is stored in a Variable Object (VO) before the code is executed, so when we enter the execution phase the function testHoisting is already available. So we can declare the function later and use it first in our code.
This only works for function declaration as mentioned before but not for variables, there is another thing called function expressions.
testHoisting()
var testHoisting = function() {
console.log('function is not hoisted')
}
The above code will throw an error, because this is not a function declaration but a function expression and hoisting will only work with function declarations.
Above we worked on functions now lets try it out on variables.
console.log(a) var a = 23 console.log(a)
The above code will print undefined and 23. This happens because in the creation phase of the variable object the variables are scanned for variable declarations and are set to undefined.
console.log(a) // var a = 23
The above code will give us a ReferenceError of a is not defined, because we don’t have any definition javascript won’t know about the variable.
var a = 23
function something() {
var a = 62
console.log(a)
}
something()
The above code will print 62 because something function has its own execution context and if write console.log(a) above line 3 we will get undefined.
That will be all for hoisting and an advantage for hoisting is that we can use function declaration before we declare them in our code.
Scoping and Scope Chain
Scoping is where can the variable be accessed. Each function has its own scoping space where the variables are defined. Typically a scope is created by if for blocks but not in Javascript. New scope is created by only writing new functions. In Javascript we also have lexical scoping for example a function inside another function will access to its outer function.
var a = 'apple'
one()
function one() {
var b = 'ball'
two()
function two() {
var c = 'cat'
console.log(a+b+c)
}
}
The above code will print appleballcat because of lexical scoping that is the inner function function has access to outer function but not the other way round.
one()
function one() {
var a = 'apple'
function third() {
var c = 'cat'
console.log(b)
}
two()
function two() {
var b = 'ball'
third()
}
}
The above code will throw an error of RefrenceError of b is undefined but wait why can we even call third function inside two function and the answer is scope chain the two function has access to variables and functions of one function. Another thing is why are we getting undefined error and the answer again is scope chain function three is not an inner function of function two so it can’t access variable b.
“this” variable
Each and every new execution context gets a this variable. In a regular function call “this” keyword points to window or browser object which is a global object. Important thing to remember is that the this keyword is not assigned a value until a function where a function where it is defined is called.
Lets look into some examples
console.log(this)
function one() {
console.log(this)
}
obj_a = {
a: 'apple',
one: function() {
console.log(this)
}
}
obj_a.one()
one()
At Line 1 it will print a window object because the global execution context over here is the window object. At line 3 it will again print a window object because it’s a function call not a method call. At line 8 it will print obj_a instead of a window object it makes sense because it’s a method call.
obj_a = {
a: 'apple',
one: function() {
console.log(this)
two()
function two() {
console.log(this)
}
}
}
obj_a.one()
We have extended our obj_a and added another function inside one method now on line 4 it will print obj_a and line 7 it will print a window object. At line 7 the result is unexpected that is because the this keyword over here is back to being the window. This is a bit counter intuitive but it makes sense because it’s not a method but a regular function call.