JavaScript - Execution Context

Execution Context
The execution context in JavaScript is an OBJECT that gathers environmental information to provide the code to be executed. When a certain execution context is activated, JavaScript performs the following actions:
It hoists declared variables to the top.
It sets up external environmental information.
It sets the value of
this.
Due to these behaviors, JS exhibits unique characteristics that differentiate it from other programming languages.
Understanding Call Stack
A call stack is a mechanism for an interpreter (like the JavaScript interpreter in a web browser) to keep track of its place in a script that calls multiple functions — what function is currently being run and what functions are called from within that function, etc.
Execution Contexts = Global, eval(), and functions -> these contexts will be stacked up in the call stack
// ---- 1
var a = 1;
function outer() {
function inner() {
console.log(a); //undefined
var a = 3;
}
inner(); // ---- 2
console.log(a);
}
outer(); // ---- 3
console.log(a);
Execution Order: Run code -> Global (in) -> Global (stop) -> outer() (in) -> outer() (stop) -> inner() (in) -> inner() (out) -> outer() (resume) -> outer() (out) -> Global (resume) -> Global (out) -> Code finished
What Does Execution Context Include?
VariableEnvironment (VE)
It holds information about identifiers in the current context (=record).
var a = 3In the above example,
var ais referred to as the identifier.
It also holds information about the outer environment (=outer).
The snapshot of the LexicalEnvironment at the time of declaration.
LexicalEnvironment (LE) -> we use this
- It's similar to the VariableEnvironment, but it reflects changes in real-time.
ThisBinding
- The object that the
thisidentifier should refer to.
- The object that the
VE vs. LE
both are exactly the same where they have record + outer (environmentRecord + outerEnvironmentReference)
but, VE keeps the snapshots, whereas LE does not (reflecting changes in real-time)
LE - environmentRecord & Hoisting
Record(=environmentRecord) collects...
the parameter identifiers specified in a function (when function expression is used)
function itself (when function declaration is used)
variable identifier
from the first line to the last line of the code, but not executing
Hoisting
Even if all variable information has been collected, the code that the execution context will handle is still in the pre-execution state. (The JS engine already knows all variable information before executing the code.)
'Virtual concept' is explained to make it easier to understand the process of collecting variable information.
console.log(sum(1, 2));
console.log(multiply(3, 4));
var x = 3;
function sum (a, b) {
return a + b;
}
var multiply = function (a, b) {
return a + b;
}
// After Hoisting
var x; // 1. variable identifier
function sum (a, b) { // 2. function declaration - hoisting the whole function
return a + b;
}
var multiply; // 3. function expression - hoisting the function identifier only (not the whole func)
console.log(sum(1, 2));
console.log(multiply(3, 4));
x = 3;
multiply = function (a, b) {
return a + b;
};
So, it is IMPORTANT to use function expression instead of the function declaration.
LE - outerEnvironmentReference & Scope & Scope Chain
Scope
Refers to the scope of validity of an identifier.
It exists in most languages, and of course, also in JavaScript.
Scope Chain
Refers to the process of searching for the scope of validity of an identifier from inside to outside in order.

Outer(= outerEnvironmentReference) enables the scope chain
- Outer refers to the LE when the current function was declared
var a = 1;
var outer = function() {
var inner = function() {
console.log(a); // 1
var a = 3;
};
inner();
console.log(a); // 1
};
outer();
console.log(a); // 1
// After hoisting
var a;
var outer;
a = 1;
outer = function() {
var inner;
inner = function() {
var a;
console.log(a); // undefined
a = 3;
};
inner();
console.log(a); // 1
};
outer();
console.log(a); // 1
For outer(), the outer it stores is Global.
For inner(), the outer it stores is the outer().
Each execution context has (record + outer) in LE.
Outer contains the LE info of the context at the time of its declaration, so the record of the upper context can be read through the scope chain.
ThisBindings
In most cases, the value of this is determined by how a function is called (runtime binding). It can't be set by assignment during execution, and it may be different each time the function is called.
this in the Global environment
Runtime: the environment that the code is running
Node
Browser
under node,
this === global(global object)under browser,
this === window(window object)
this in the method vs. this in the function
function vs. method
function: this -> global object
method: this -> the subject of calling
// CASE1 : function
// this === global
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
// CASE2 : method
// this ==== obj
var obj = {
method: func,
};
obj.method(2); // { method: func } 2
// this inside the method
var obj = {
methodA: function () { console.log(this) },
inner: {
methodB: function() { console.log(this) },
}
};
// this = contains who called this
obj.methodA(); // this === obj
obj['methodA'](); // this === obj
obj.inner.methodB(); // this === obj.inner
obj.inner['methodB'](); // this === obj.inner
obj['inner'].methodB(); // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner
// this inside the inner function in method
var obj1 = {
outer: function() {
console.log(this); // (1) this === obj1
var innerFunc = function() {
console.log(this); // (2) this === global, (3) this === obj2
}
innerFunc(); // execute (2)
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // execute (3)
}
};
obj1.outer(); // execute (1)
How to use this inside the method's inner function
Use variable (not used anymore)
var obj1 = { outer: function() { console.log(this); // (1) outer // AS-IS var innerFunc1 = function() { console.log(this); // (2) local object } innerFunc1(); // TO-BE var self = this; // use variable to save this var innerFunc2 = function() { console.log(self); // (3) outer }; innerFunc2(); } }; obj1.outer();IMPORTANT: Use Arrow Function - this is the reason why we use the arrow function (no
thisbinding)var obj = { outer: function() { console.log(this); // (1) obj var innerFunc = () => { console.log(this); // (2) obj (this !== global) }; innerFunc(); } } obj.outer();
this inside the callback function
the callback function is a function!
thus, inside the callback function,
this === global
setTimeout(function () { console.log(this) }, 300); // this === global
[1, 2, 3, 4, 5].forEach(function(x) { // this === global
console.log(this, x);
});
// EXCEPTION: inside the addListener, this is to return the element of the calling subject
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e); // this === button
});
this inside the constructor
var Cat = function (name, age) {
this.bark = 'Meow';
this.name = name;
this.age = age;
};
var choco = new Cat('choco', 7); // this : choco
var nabi = new Cat('nabi', 5); // this : nabi
Explicit ThisBindings
Call
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3); // Window{ ... } 1 2 3
// explicit binding
// this in func binding {x: 1}
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6
// How to use in practice
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender);
// this.name = name;
// this.gender = gender;
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]);
// this.name = name;
// this.gender = gender;
this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');
Apply
- Exactly the same as
call(), but use[]to bind other arguments
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6
// How to use in practice
// Inefficient
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
if (number > max) {
max = number;
}
if (number < min) {
min = number;
}
});
console.log(max, min);
// Efficient by Using apply()
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers); // no this binding, so just use null
var min = Math.min.apply(null, numbers);
console.log(max, min);
// We can just use spread operator as well
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min);
Array-like-object
The length is required
The index should start from 0 and increment by 1
// this is array-like-object
// but we can't use the actual array methods
// but by using call() or apply(), we can utilize array methods
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
Array.from()
From ES6, we do not need to use
call()orapply()for array-like-objectWe can just convert array-like-object into the actual array by using
Array.from()
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// Object -> Array
var arr = Array.from(obj);
console.log(arr); // ['a', 'b', 'c']
Bind
Unlike
call()andapply(),bind()does not call right awayPurpose
To apply
thisto the function in advancePartially applied function
name property
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // global object
// apply this in advance
var bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
// Partially applied function
var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9
// name property
// functions with bind() have bound prefix - good to track
console.log(func.name); // func
console.log(bindFunc1.name); // bound func
console.log(bindFunc2.name); // bound func
// we use bind() a lot because of its advantage that applied this in advance
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this); // this binding
innerFunc();
}
};
obj.outer();
Actually, just using the arrow function is the best (way better than
call(),apply(), orbind())However, it is important to know these methods to understand the legacy (previously built code by others)
var obj = { outer: function () { console.log(this); // obj var innerFunc = () => { console.log(this); // obj (not global) }; innerFunc(); }; }; obj.outer();
![[코테] 그리디 문제 - 무지의 먹방 라이브](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1712215455263%2F1ac1f35a-8862-4e42-8d0c-e2bea01e04c0.png&w=3840&q=75)
![[코테] Bfs 토마토](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1709032619170%2F70056896-c857-444b-9c99-45bfcb466806.png&w=3840&q=75)
![[코테] Dfs 문제 유형 - 그래프 내에서 구분하여 카운트 하기](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1709019361383%2Fb0585d72-c808-4169-83a9-2724f312e927.png&w=3840&q=75)
![[코테] DFS vs BFS](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1708971211123%2F71f9386c-6a62-43b2-a602-4d084c24d6cf.png&w=3840&q=75)
![[코테] 여행경로](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1708971251412%2F27ce72ed-8ee7-4d13-a02f-ff4bbe50c4be.png&w=3840&q=75)