Execution Context allows the Javascript engine to manage the complexity of interpreting and running the code. Every execution context has 2 phases: Creation phase and Execution phase.
The first execution context, the Global Execution Context, gets created when the Javascript engine runs your code. In this execution context,
- Global object gets created, which is the window object.
this
object gets created, pointing to the window object.- We set up memory space for variables and functions.
- All the variables declarations are assigned a default value of undefined (this is hoisting), while the function declaration is put entirely into memory.
Invoking a function creates the Function Execution Context. It is precisely the same as the global execution context except that instead of creating a global object, an arguments object gets created. Take, for example, the code below:
function fn() {
console.log(arguments);
}
fn(x, y);
Here arguments is an array-like object for all of the arguments passing inside of this function, and a keyword in Javascript.
Now anytime a function is invoked, a new execution context is created and added to the execution stack. Whenever a function is finished running through both the creation phase and the execution phase, it gets popped off the execution stack.
Anytime you pass a variable to a function, that variable during the creation phase of a function execution context is going to be put in the variable environment of the current execution context as if they are local variables to the execution context. So for the code below
var name = 'John';
var handle = '@johndoe';
function getURL (handle) {
var url = 'https://facebook.com/';
return url + handle;
}
getURL(handle);
during the creation phase of getURL function, the value of url is undefined, while the value of handle is
@johndoe
as the variable is passed to a function. The value of arguments object during creation phase is {0:"@johndoe", length:1}
.If the Javascript engine can't find a variable local to the function's execution context, it will look to the nearest parent execution context for that variable. This process will continue all the way up until the engine reaches the global execution context. If the global execution context doesn't have the variable, then it will throw a reference error because that variable doesn't exist anywhere up to the scope chain or the execution context chain.
var name = 'John';
function logName() {
console.log(name);
}
logName();
Now let's look at the code below:
var count = 0;
function makeAdder(x) {
return function inner(y) {
return x + y;
};
}
var add5 = makeAdder(5);
count += add5(2);
Here we have a function nested inside a function (inner inside of makeAdder), and whenever that happens, the inner function is going to create a closure over the outer function's execution context. This is because later on when inner function is invoked, it needs to have access to any of the variables declared in the parent function's execution context even though that parent function's execution context has been removed from the stack. So the variables inside of this closure execution context are same as the variables that were in the makeAdder execution context. This whole concept is known as closure.