Skip to main content

Command Palette

Search for a command to run...

JavaScript - Execution Context

Updated
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:

  1. It hoists declared variables to the top.

  2. It sets up external environmental information.

  3. 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?

  1. VariableEnvironment (VE)

    1. It holds information about identifiers in the current context (=record).

      1. var a = 3

      2. In the above example, var a is referred to as the identifier.

    2. It also holds information about the outer environment (=outer).

    3. The snapshot of the LexicalEnvironment at the time of declaration.

  2. LexicalEnvironment (LE) -> we use this

    1. It's similar to the VariableEnvironment, but it reflects changes in real-time.
  3. ThisBinding

    1. The object that the this identifier should refer to.
  • 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

  1. 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.)

  2. '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

  1. 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();
    
  2. IMPORTANT: Use Arrow Function - this is the reason why we use the arrow function (no this binding)

     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() or apply() for array-like-object

  • We 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() and apply(), bind() does not call right away

  • Purpose

    1. To apply this to the function in advance

    2. Partially applied function

    3. 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(), or bind())

  • 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();