If you are not aware of how call stack works, you should definitely check out this article before moving ahead. In this article, I have broken down what exactly happens during the creation phase of the execution context. Each Execution context has three components-
Variable environment
Scope chain
this
keyword binding
Variable environment
It stores the variables and function declarations defined within that Execution Context. In global EC, Each global variable is defined with var
initialized with a special value undefined
while variables defined with let
or const
remains uninitialized while the actual copy of the function body is stored inside the memory space for function
declarations.
This means that the JS engine is aware of all the variables and functions that are inside the file and does not give this error ReferenceError: variable/function is not defined
if one accesses them before their declaration. This is known as Hoisting and you can read more about it in depth here.
Scope Chain
Ever wonder how a piece of JS code is accessible to other parts of the codebase? Each execution context creates its own scope & it is the space or environment in which a certain variable/function can be accessed without any error. Global EC creates global scope while function EC creates block/function scope.
Global Scope
All variables and functions declared in top-level code(the piece of code that is not inside any block/function scope) have global scope.
Function scope
All variables and functions declared in a function can be accessed inside that block only and given reference error
if used outside of their scope.
in ES6, all functions are also block scoped(in strict mode).
Block scope
This concept was introduced in ES6. The variables declared with let
or const
keyword inside a block(bounded by curly braces {} like an if-else statement, switch statement, for loop, etc.) is accessible inside that block only. Note:- If inside a curly braces block, a variable is declared using var
a keyword, it can be accessible outside this scope too. However, if a variable is declared inside the function block using var
a keyword, it cannot be accessed outside that function. That is why, var
variables are function-scoped.
To avoid confusion, avoid declaring variables using
var
keyword. To summarize,let
,const
variables and functions inside a block are block-scoped.
lexical scope
When EC is created, we also get a reference to the lexical env of its parent. It means, that Javascript first looks for a variable in local scope, then it will look up in its parent scope, and so on till the global scope.
For global EC, lexical scope = global scope.
function parent(){ // lexical scope = local env + global scope
let b = 5;
function child(){ // lexical scope = local env + scope of parent fn
console.log(b);
}
child();
}
parent();
lexical env = local scope + lexical env of parent
Scope chain
JavaScript follows lexical scoping. It describes how nested(child) functions have access to the variables defined in their parent's scope. Lexical in general means in a hierarchy or a sequence. So all scopes have access to variables/functions from all parent scopes including the global scope. This is known as scope chaining. The scope chain is the order in which functions are written in the code. It has nothing to do with the order in which functions are called i.e. order of EC in the call stack.
Function along with its lexical scope forms a closure.
Closure scope
If any function is created, that function will continue to remember all the variables that were present at the time when the function was created. In other words, any function has always access to the variable environment of the EC in which the function was created even after that EC is executed i.e. popped from the call stack. This concept is called closure. In JavaScript, closures are created every time a function is created, at function creation time. Thanks to the closure, a function does not lose connection to the variables that existed at the function's creation time.
The closure has priority over the scope chain.
It means while executing a function, JS will first look into the scope of that function. If a certain variable is not found in the block scope of this function, JS will immediately look into the closure(if any) first. If it does not find that variable in closure too, then JS will look it up in the scope chain.
Scope chain = look in local scope -> then in closure -> then in the parent scope -> global scope
this
keyword in JS
It is a special variable that is created for every execution context. It will always take the value of the owner of the function in which it is used.
This points to the owner of the function. Note that the value of
this
is not static. It depends on how the function is called.
You can refer this blog for deeper analysis of this
keyword usage for various use cases including call, bind, apply, prototype .
this
inside a function using object methods
Inside an object method, it will point to the object that is calling the method.
const myData = {
name: 'krishna saini`,
year: '1992',
calcAge: function(){
return 2022 - this.year; // here this will points to myData object
// this.year = myData.year = 1992
}
}
this
Inside the simple function call
Inside function declaration or function expression, this
will point to the global object in non-strict mode else will point to undefined
in strict mode.
"use strict";
function sayHello() {
console.log(this); // Logs undefined
}
sayHello();
function sayHello() {
console.log(this); // Logs the window object
}
sayHello();
this
in global scope
It will point to a window object.
this
inside arrow functions
Arrow functions differ in their handling of this
: they inherit this
from the parent scope at the time they are defined.
arrow functions do not have their own
this
keyword.
For example,
const arrowFunction = () => {
console.log(this); // points to this of parent -> log window object
};
arrowFunction();
const myObject = {
name: "My Object",
regularFunction: function() {
console.log("krishna here ", this); // Refers to myObject
const arrowFunction = () => {
console.log("krishna",this); // Also refers to myObject (inherits from parent)
};
arrowFunction();
},
};
myObject.regularFunction();
this
inside an object method
using arrow function
using regular functions
const myData = { name: 'krishna saini', year: '1992', calcAge2: () => {console.log(this)}, // this will point to parent's this -> global object calcAge: function(){ return 2022 - this.year; // here this will points to myData object } }
use a regular function (not an arrow function) for object method because arrow functions inherit the
this
context from their surrounding scope, which can lead to unexpected behavior. Regular functions, on the other hand, create their ownthis
context, which is bound to the object they're called on.
const obj4 = {
name: "obj4",
getThis() {
return this; // point to obj4
},
};
this
inside event listener
Here, it will point to the DOM element that the handler is attached to.
That's it, folks! That's all about scope chain and closures. Follow me for more in-depth articles. Leave your feedback in the comment section.