Still confused about execution context and scope?

As a front-end worker, we must know how JavaScript is executed internally. That’s crucial to understanding execution context and scope, a step that cannot be skipped, whether it’s a job or an interview.

Execution context

Let’s look at the code first

var foo = function () {<!-- -->
  console.log("foo1")
}

foo() // foo1

var foo = function () {<!-- -->
  console.log("foo2")
}
foo() // foo2

What about this code?

function foo() {<!-- -->
  console.log("foo1")
}
foo() // foo2

function foo() {<!-- -->
  console.log("foo2")
}
foo()// foo2

Are you a little confused? The first piece of code is easy to understand, but why does the second piece of code print two “foo2”?

This is because the JavaScript engine does not analyze and execute programs line by line. When executing a piece of code, there will be some preparatory work. What kind of work does the JavaScript engine prepare for?

Let’s do a little analysis

console.log(a) // undefined
var a = 10

In this code, we printed a before defining a, but no error was reported, indicating that when executing console.log(a), a has already been declared, which is what we often call variables Ascension, that’s the preparation.

var a
console. log(a)
a = 10

First, the definition of a will be declared in advance instead of assignment.

Let’s take a look at how the JavaScript engine prepares for function declarations and function expressions.

console.log(add2(1, 2)) // 3
function add2(a, b) {<!-- -->
  return a + b
}

console.log(add1(1, 2)) // error: add1 is not a function
var add1 = function (a, b) {<!-- -->
  return a + b
}

We found that for add2 created with a function statement, both the function name and the function body are advanced, using it before declaring it. The function expression is just that the variable declaration is advanced, and the variable assignment is still in the previous position. Now go back to the code at the beginning, do you understand it?

So what preparations have the JavaScript engine made?

  • Variables, function expressions – variables declared in advance, the default is undefined
  • Function declaration – early declaration and assignment

In fact, there is another this that is prepared in advance and assigned a value.

When a function is executed, preparations will be carried out. The “preparation” here is the “execution context”

Execution context stack

The execution context stack manages the execution context. JavaScript code has two execution contexts: global execution context and function execution context, and one is eval (we don’t consider it for now). There is only one global execution context, and a new function execution context is created every time a function executes a call.

Each execution context has three attributes:

  • Variable object (Variable object, VO)
  • Scope chain (Scope chain)
  • this

Variable object

A variable object is a data scope associated with an execution context, storing variable and function declarations defined in the context.

The variable objects of different execution contexts are different. Let’s take a look at the variable objects of the global context and the variable objects of the function context

Global context

  • Global objects are predefined objects that serve as placeholders for JavaScript’s global functions and global properties. All other predefined objects, functions and properties can be accessed by using the global object
  • In top-level JavaScript code, you can use the keyword this to refer to the global object. Because the global object is the head of the scope chain, it means that all unqualified variable and function names will be queried as properties of the object
  • For example, when JavaScript code refers to the parseInt() function, it refers to the parseInt property of the global object.

Function context

In the function context, we use the activation object (activation object, AO) to represent the variable object.

The active object and the variable object are actually the same thing, but the variable object is on the specification or the engine implementation, and cannot be accessed in the JavaScript environment. Only when entering an execution context, the variable object of this execution context will be accessed. Activation, so it is called activation object, and only the activated variable object, that is, various attributes on the active object, can be accessed.

The active object is created when entering the function context, and it is initialized through the arguments property of the function. The arguments attribute value is an Arguments object.

Execution process

The code of the execution context is processed in two phases:

  • Enter the execution context
  • code execution
Enter execution context

When the function is called, the execution context is entered, and before the code is executed, the variable object will contain:

  1. all parameters of the function
    • Properties of a variable object consisting of names and corresponding values are created
    • No actual parameter, attribute value is set to undefined
  2. function declaration
    • An attribute of a variable object consisting of a name and a corresponding value (function object) is created
    • If a property with the same name already exists on the variable object, this property is completely replaced
  3. variable declaration
    • A property of a variable object consisting of a name and a corresponding value (undefined) is created
    • If the variable name is the same as an already declared formal parameter or function, the variable declaration does not interfere with already existing properties of this type. For example:
function foo(a) {<!-- -->
  var b = 2
  function c() {<!-- -->}
  var d = function () {<!-- -->}
  b = 3
}

foo(1)

After entering the execution context, the value of AO:

AO={<!-- -->
    arguments: {<!-- -->
        0:1,
        length: 1
    },
    a: 1,
    b:undefined,
    c: reference to function c(){<!-- -->},
    d:undefined
}
Code execution

In the code execution stage, the code will be executed in order, and the value of the attribute of the variable object will be modified according to the code

AO={<!-- -->
    arguments: {<!-- -->
        0:1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){<!-- -->},
    d: reference to FunctionExpression "d"
}

A small summary of variable objects:

  • The variable object initialization of the global context is the global object
  • The variable object initialization of the function context includes the Arguments object
  • When entering the execution context, initial attribute values such as formal parameters, function declarations, and variable declarations will be added to the variable object
  • In the code execution phase, the property values of the variable object will be modified again.

Let’s take a look at how the execution context stack works

function fun3() {<!-- -->
  console.log("fun3")
}

function fun2() {<!-- -->
  fun3()
}

function fun1() {<!-- -->
  fun2()
}

fun1()

We use an array to simulate the execution context stack. The first thing we encounter is the global code. When it is initialized, the global execution context globalContext will be pushed into the execution context stack.

Stack=[
    globalContext
]

When a function is executed, an execution context is created and pushed onto the execution context stack. When the function is executed, the execution context of the function is popped from the stack. The context where all its code is executed will be destroyed.

// execute fun1
Stack.push(<fun1>functionContext);

// fun2 is called in fun1
Stack.push(<fun2>functionContext);

//fun3 is called in fun2
Stack.push(<fun3>functionContext);

//fun3 is executed and pops up
Stack. pop()

//fun2 is executed and pops up
Stack. pop()

//fun1 is executed and pops up
Stack. pop()

Finally, there is always a global execution context globalContext at the bottom of the Stack.

Scope

Scope refers to the area in the program source code where variables are defined. The scope specifies how to find the variable, that is, determine the access rights of the currently executing code to the variable. JavaScript uses lexical scope, also known as static scope.

Static scope and dynamic scope

JavaScript uses lexical scope, and the scope of a function is determined when the function is defined. The opposite of lexical scope is dynamic scope, and the scope of a function is determined when the function is called.

Scope chain

When searching for a variable, it will first search from the variable object of the current context. If it is not found, it will search from the variable object of the parent execution context, and always find the variable object of the global context, that is, the global object. Such a linked list composed of variable objects of multiple execution contexts is called a scope chain.

Function creation

As mentioned above, the scope of a function is determined when the function is defined. This is because the function has an internal attribute [[scope]]. When the function is created, all parent variable objects will be saved in it. It can be understood that [[scope]] is the hierarchical chain of all parent variable objects, but [[scope ]] does not represent the complete scope chain. Let’s look at a code:

function foo(){<!-- -->
    function bar(){<!-- -->
    }
}

When the function is created, the respective [[scope]] is

foo.[[scope]] = [
    globalContext.VO
]

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
]

When the function is activated, enters the function body, and creates VO/AO, the active object will be added to the front end of the action chain.

Summary

The difference between execution context and scope:

  1. Except for the global scope, each function creates its own scope, and the scope is determined when the function is defined, not when the function is called.

    The global execution context is created immediately before the js code is executed after the global scope is determined.

    The function execution context is created when the function is called, before the code of the function body is executed.

  2. The scope is static, as long as the function is defined, it will always exist and will not change.

    The execution context is dynamic, it is created when a function is called, and the context is released when the function call ends.