Execution context
The execution context can be understood as the execution environment of the current code. The same function executed in different environments will produce different results due to different data access.
There are three execution contexts:
- Global execution context: Only one, created the first time the program runs, which creates a global object in the browser (
window
Object), sothis
Point to this global object - Function execution context: A function is created when it is called, and a new execution context is created for that function with each call
- Eval function execution context: run
eval
The execution context created when code in a function is used sparsely and is not recommended
Execution context stack
An Execution Context stack (ECS), also known as a call stack, is a stack with LIFO (LIFO) data structures used to store the Execution context created during code Execution
Since JS is single-threaded and can only do one thing at a time, by this mechanism we can keep track of which function is executing while other functions queue up in the call stack.
When the JS engine executes the script for the first time, it will create a global execution context and push it to the top of the stack. Then, with each function call, it will create a new execution context and put it into the top of the stack. After the function is executed, it will be popped up by the execution context stack until it returns to the global execution context.
Code example 🌰
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();
console.log(color); // red
Copy the code
The execution can be seen in devTool’s Call Stack, where Anonyomus is the global context stack; The rest is the function context stack
Illustration:
Execution process:
- I first created
Global execution context
, pushed into the execution stack, where the executable code begins execution. - And then call
changeColor
The JS engine stops executing the global execution context and activates the functionchangeColor
Creates its own execution context, and puts the function context at the top of the execution context stack, where the executable code begins execution. changeColor
Call theswapColors
Function, pausedchangeColor
The execution context of theswapColors
The new execution context of the function, and puts the function execution context at the top of the execution context stack.- when
swapColors
When the function completes execution, its execution context is pushed off the stack and back upchangeColor
Execution continues in the execution context. changeColor
No executable code, no other execution context encountered, pushed its execution context off the stack and backGlobal execution context
To continue the execution.- Once all code has been executed, the JS engine is removed from the current stack
Global execution context
.
Note: when a return is encountered, it terminates the execution of the executable code, and therefore pops the current context directly off the stack.Copy the code
Using ECStack to simulate the call stack:
ECStack=[]
Copy the code
The ECStack is empty only when the entire application terminates, so there is always a globalContext at the bottom of the ECStack:
ECStack.push(globalContext)
Copy the code
Using pseudocode to simulate the above code behavior:
ECStack.push(<changeColor> functionContext);
ECStack.push(<swapColors> functionContext);
/ / swapColors stack
ECStack.pop();
/ / changeColor stack
ECStack.pop();
Copy the code
To reinforce our understanding of the execution context, let’s plot the evolution of a simple closure example.
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
f1()() / / 999
Copy the code
Using pseudocode to simulate the above code behavior:
ECStack.push(<f1> functionContext);
/ / f1 stack
ECStack.pop();
ECStack.push(<f2> functionContext);
/ / f2 stack
ECStack.pop();
Copy the code
Because the function f2 in F1 is not called to execute in f1’s executable code, f2 does not create a new context when f1 is executed, and a new context is not created until F2 is executed. The specific evolution process is as follows.
Es3 version
There are three important properties in the ES3 release execution context:
- VO (Variable Object)
- Scope chain
- this
Each execution context can be abstracted as an object.
Examples of constituent code for an execution context:
executionContextObj = {
scopeChain: { /* variableObject + all parent execution context variable objects */ },
[variableObject | activationObject]: {
/* Function arguments/ internal variables and function declarations */
arguments. },this: {}}Copy the code
The variable object
A variable object is a data scope associated with the execution context that stores variable and function declarations defined in the context.
Variable objects vary from execution context to execution context:
- The variable object in the global context is the global object, which in the browser is the Window object. In top-level JavaScript code, you can reference the global object with the keyword this. All global variables and functions exist as properties and methods of the window.
console.log(this) //window
var a=1 // The property attached to the window
console.log(window.a) / / 1
console.log(this.a) / / 1
Copy the code
- In the context of function execution, we use the activation object AO (AO) to represent a variable object. Because a variable object is a specification or engine implementation, it cannot be accessed directly in the JavaScript environment, only when the function is called and the variable object is activated as an active object. We can access the properties and methods in it.
An active object is a variable object, but in a different state and phase.Copy the code
The scope chain
For JavaScript, variable queries for scopes and scope chains are implemented through an execution context stored in browser memory. When a variable is searched, it is first searched from the variable object in the current context. If there is no variable object in the parent scope, it is searched up to the variable object in the global context. If there is no variable object, an error is reported. Thus a linked list of variable objects in multiple execution contexts is called a scoped chain.
You can refer to my other article on scopes and scope chains: Mastering JavaScript Interviews: What is a Closure?
What is the difference between scope and execution context 🎉 :
A function execution context is created when a function is called, before the function body code executes, and is automatically released when the function call ends. Because different calls may have different arguments:
var a = 10;
function fn(x) {
var a = 20;
console.log(arguments)
console.log(x)
}
fn(20)
fn(10) // Different calls may have different arguments
Copy the code
JavaScript, on the other hand, uses lexical scope. The scope created by the FN function is defined when the function is defined.
Associated 🎉 :
A scope is just a “territory” where there are no variables and the values of the variables are obtained through the execution context corresponding to the scope, so the scope is statically conceptualized, while the execution context is dynamic. That is, a scope is only used to define the valid range of variables that you define in that scope.
In the same scope, different calls to the same function will produce different execution contexts, resulting in different values of variables. Therefore, the value of a variable in a scope is determined during execution, while the scope is determined at the time of function creation.
The life cycle
The lifecycle of an execution context consists of three phases:
- Create a stage
- Generating variable objects
- Create the arguments
- Scanning function declaration
- Scanning variable declaration
- Establish scope chains
- Make sure this points to
- Generating variable objects
- Execution phase
- Variable assignment
- Function reference
- Execute other code
- Destruction of phase
Create a stage
Generate the variable object 🎉
- Create arguments: If it is a function context, it is created first
arguments
Object to add the parameter name and value to the variable object. - Scan function declarations: For found function declarations, store the function name and function reference (pointer)
VO
, if theVO
Override (overwrite the reference pointer) if a function with the same name already exists in. - Scan variable declarations: For each variable declaration found, store the variable name
VO
, and initialize the value of the variable toundefined
. If the variable name already exists in the variable object, no action is taken and the scan continues.
Let’s cite a chestnut to illustrate 🌰 :
function person(age) {
console.log(typeof name); // function
console.log(typeof getName); // undefined
var name = 'abby';
var hobby = 'game';
var getName = function getName() {
return 'Lucky';
};
function name() {
return 'Abby';
}
function getAge() {
return age;
}
console.log(typeof name); // string
console.log(typeof getName); // function
name = function () {};
console.log(typeof name); // function
}
person(20);
Copy the code
When person(20) is called, but the code has not yet been executed, the created state looks like this:
personContext = {
scopeChain: {... },activationObject: {
arguments: {
0: 20.length: 1
},
age: 20.name: pointer, // reference to function name(),
getAge: pointer, // reference to function getAge(),
hobby: undefined.getName : undefined,},this: {... }}Copy the code
Before the function is executed, it will create a function execution context first, first point out the reference of the function, then define the variables in order, initialize them as undefined and store them in VO. When the variable name is scanned, it is found that there is an attribute with the same name in VO (function declaration variable), so it is ignored.
Global execution context creation does not have the create Arguments stepCopy the code
Establish the scope chain 🎉
During the creation phase of the execution-time context, the scope chain is created after the variable object. The scope chain itself contains variable objects.
- When writing a piece of function code, we create a lexical scope. This scope is the property inside the function that we use
[[scope]]
It holds the parent variable object, so[[scope]]
It’s a chain of levels.
person.[[scope]] = [
globalContext.variableObject
]
Copy the code
- When the function is called, it means that the function is activated. Create the function context and push it onto the execution stack. Then copy the function [[scope]] property to create the scope chain:
personContext = {
scopeChain:person.[[scope]]
}
Copy the code
- Create the active object (the generate variable object step above), and then push the active object (AO) to the front of the scope chain.
personContext = {
activationObject: {
arguments: {
0: 20.length: 1
},
age: 20.name: pointer, // reference to function name(),
getAge: pointer, // reference to function getAge(),
hobby: undefined.getName : undefined,},scopeChain:[activationObject,[[scope]]]
}
Copy the code
Make sure this points to 🎉
If the current function is called as an object method or a delegate call is made using the BIND, call, apply API, etc., the caller information of the current code block is put into the current execution context, otherwise it defaults to a global object call.
For details on the implementation of this, please refer to my other article: Unlocking this, Apply, Call, bind new positions 🍊
Execution phase
In the execution phase, the execution stream enters the function and runs/interprets the code in the context. The JS engine starts assigning values to defined variables, starts accessing variables along the scope chain, creates a new execution context if there are internal function calls, pushes it onto the execution stack and cedes control
When the code is executed from top to bottom, the activation phase goes like this:
- First execution
console.log
; At this timename
在VO
Where is the function.getName
No value is specified inVO
The value isundefined
. - Execute to the assignment code,
getName
Is assigned to a function expression,name
To be an assignmentabby
- Second execution
console.log
; At this timename
Because the function is overridden by string assignment, sostring
typegetName
是function
Type. - Third execution
console.log
; At this timename
It’s covered again so it’sfunction
type
As a result, understanding the execution context provides a nice explanation for variable promotion: the actual position of variable and function declarations in code does not change, but is instead put into memory by the JavaScript engine at compile time
This explains why we can access the name before it is declared, why the type value of the name after it changes, and why getName is undefined when first printed.
✨ ES6 introduces the let and const keywords, allowing JavaScript to have block-level scope like other languages, which solves a number of problems associated with variable promotion.
The function execution context for the final console execution:
personContext = {
scopeChain: {... },activationObject: {
arguments: {
0: 20.length: 1
},
age: 20.name: pointer, // reference to function name(),
getAge: pointer, // reference to function getAge(),
hobby: 'game'.getName:pointer, pointer to function getName(),},this: {... }}Copy the code
Destruction of phase
In general, when the function completes, the current execution context (local environment) will be ejected from the execution context stack and wait for the virtual machine to reclaim, and control will be returned to the execution context on the execution stack.
Complete sample
Example 1 🌰 :
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
Copy the code
Execute the global code, generate the global context, and push the execution stack
ECStack=[
globalContext
]
Copy the code
2. Global context initialization
globalContext={
variableObject: [global,scope,checkscope],
this:globalContext.variableObject,
scopeChain:[globalContext.variableObject]
}
Copy the code
3, Create checkScope function generate internal attribute [[scope]], and store the global context scope chain in it
checkscope.[[scope]] = [
globalContext.variableObject
]
Copy the code
4, call the checkScope function, create the function context, press the stack
ECStack=[
globalContext,
checkscopeContext
]
Copy the code
5. At this time, the checkScope function has not been executed, and the execution context is entered
- The copy function [[scope]] property creates the scope chain
- Create an active object with the Arguments property
- Initializes a variable object, adding variable declarations, function declarations, and parameters
- The live object is pressed into the top of the scope chain
checkscopeContext = {
activationObject: {
arguments: {
length: 0
},
scope: undefined.f: pointer, // reference to function f(),
},
scopeChain: [activationObject, globalContext.variableObject],
this: undefined
}
Copy the code
6, checkscope function execute, set the value of the variable scope
checkscopeContext = {
activationObject: {
arguments: {
length: 0
},
scope: 'local scope'.f: pointer, // reference to function f(),
},
scopeChain: [activationObject, globalContext.variableObject],
this: undefined
}
Copy the code
The f function is created to generate the [[scope]] property and hold the scope chain of the parent scope
f.[[scope]]=[
checkscopeContext.activationObject,
globalContext.variableObject
]
Copy the code
7, F function call, generate F function context, stack
ECStack=[
globalContext,
checkscopeContext,
fContext
]
Copy the code
8. Initialize the execution context before f function is executed
- The copy function [[scope]] property creates the scope chain
- Create an active object with the Arguments property
- Initializes a variable object, adding variable declarations, function declarations, and parameters
- The live object is pressed into the top of the scope chain
fContext = {
activationObject: {
arguments: {
length: 0}},scopeChain: [fContext.activationObject, checkscopeContext.activationObject, globalContext.variableObject],
this: undefined
}
Copy the code
9, f function executes, along the scope chain to find the scope value, return scope value
10. After f function is executed, f function context pops up from execution context stack
ECStack=[
globalContext,
checkscopeContext
]
Copy the code
11, The checkScope function is executed, the checkScope execution context is popped from the execution context stack
ECStack=[
globalContext
]
Copy the code
Example 2 🌰 :
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
Copy the code
- Executes the global code, generates the global context, and pushes the execution stack
- Global context initialization
- create
checkscope
Function to generate internal properties[[scope]]
And store the global context scope chain into it - call
checkscope
Function, create function context, push - At this time
checkscope
The function has not been executed. The execution context is entered- Copy the function
[[scope]]
Property creates a chain of scopes - with
arguments
Property to create an active object - Initializes a variable object, adding variable declarations, function declarations, and parameters
- The live object is pressed into the top of the scope chain
- Copy the function
checkscope
Function execution, on variablesscope
Set value,f
The function is created and generated[[scope]]
Property and saves the scope chain of the parent scope- Return function f, then the checkScope function is completed, popstack
f
Function call, generatef
Function context, pushing- At this time
f
The function has not been executed, initializes the execution context- Copy the function
[[scope]]
Property creates a chain of scopes - with
arguments
Property to create an active object - Initializes a variable object, adding variable declarations, function declarations, and parameters
- The live object is pressed into the top of the scope chain
- Copy the function
f
Function executes, looking up along the scope chainscope
Value, returnscope
值f
When the function completes,f
The function context pops up from the execution context stack
🚀 the only difference is that the checkScope function completes the stack unstack, and then f is executed. The steps are the same as in example 1
fContext = {
scopeChain: [activationObject, checkscopeContext.activationObject, globalContext.variableObject],
}
Copy the code
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * F functions can still be found through the scope chain of f functions. The reason why checkScopecontext. AO is not destroyed is because f refers to the value in checkscopecontext. AO, and because JS does not destroy the variables in the parent context when the child context refers to them.
Es5 version
The ES5 specification removes variable and active objects from ES3 and replaces them with LexicalEnvironment Component and VariableEnvironment Component.
The life cycle
The life cycle of es5 execution context also includes three stages: create stage → execute stage → reclaim stage
Create a stage
The creation phase does three things:
-
Determine the value of this, also known as this Binding
-
The LexicalEnvironment component is created
-
The VariableEnvironment component is created
The pseudocode might look like this:
ExecutionContext = {
ThisBinding = <this value>// Make sure this LexicalEnvironment = {... }, // lexical environment VariableEnvironment = {... }, // Variable environment}Copy the code
This Binding
ThisBinding is executed context binding, i.e. every execution context has a this, no different from es3’s this, this value is checked at execution time, not at definition
Create a lexical environment
The lexical environment is structured as follows:
GlobalExectionContext = { // Global execution context
LexicalEnvironment: { // lexical context
EnvironmentRecord: { // Environment record
Type: "Object".// Global environment
// The identifier is bound here
outer: <null> // A reference to the external environment
}
}
FunctionExectionContext = { // Function execution context
LexicalEnvironment: { // lexical context
EnvironmentRecord: { // Environment record
Type: "Declarative".// Function environment
// The identifier is bound here // a reference to the external environment
outer: <Global or outer function environment reference>}}Copy the code
You can see that there are two types of lexical environments 👇 :
- The global environment: is a lexical environment without an external environment, whose external environment is referenced as
null
. Having a global object (window object) with its associated methods and properties (such as array methods) and any user-defined global variables,this
The value points to the global object. - Function of the environment: Variables defined by the user in the function are stored in the environment record, containing
arguments
Object. A reference to an external environment can be a global environment or an external function environment that contains internal functions.
The lexical environment has two components 👇 :
- Environment logger: The actual location where variable and function declarations are stored.
- A reference to the external environment: it points to the next object in the scope chain and has access to its parent lexical environment (scope), similar to es3’s scope chain
There are also two types of environment loggers 👇 :
- Use declarative environment loggers in functional environments to store variables, functions, and parameters.
- Object environment loggers are used in the global environment to define the relationship between variables and functions that appear in the global context.
🎉 accordingly:
- Create lexical environment use for global contextObject environment logger ,
outer
A value ofnull
; - Used when creating the lexical environment for the function contextDeclarative environment logger ,
outer
Value is a global object, or a parent lexical environment (scope)
Create variable environment
The variable environment is also a lexical environment, so it has all the attributes of the lexical environment defined above.
In ES6, the difference between lexical and variable environments is that the former is used to store function declarations and variable (let and const keyword) bindings, while the latter is only used to store variable (var) bindings, so the variable environment implements function-level scope and the lexical environment implements block-level scope on top of function scope.
🚨 global variables declared with let/const are bound to Script objects instead of Window objects and cannot be used as window.xx; Global variables declared using var are bound to the Window object; Local variables declared using var/let/const are bound to Local objects. Note: Script objects, Window objects, Local objects are parallel.
Arrow functions have no context of their own, no arguments, and no variable promotionCopy the code
Use examples to introduce
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20.30);
Copy the code
When the multiply function is called, the function execution context is created:
GlobalExectionContext = {
ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Object", // the identifier is binding here < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: C: undefined,} outer: <null>}} FunctionExectionContext = {ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Declarative", // 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: G: undefined, outer: <GlobalLexicalEnvironment>}}Copy the code
Why variables are promoted: During the creation phase, function declarations are stored in the environment and variables are set to undefined (in the case of var) or remain uninitialized (in the case of let and const). So this is why it is possible to access variables defined by var (albeit undefined) before the declaration, but if you access variables defined by let and const before the declaration, you will be prompted with reference errors. This is called variable lifting.
Graphical variable promotion:
var myname = "Geek Time"
function showName(){
console.log(myname);
if(0) {var myname = Geek Bang
}
console.log(myname);
}
showName()
Copy the code
ShowName = myname; showName = myname; showName = myname; showName = myname So the value of myname is undefined.
Execution phase
At this stage, all of these variables are allocated, and finally the code executes. If the JavaScript engine cannot find the let variable’s value in the actual location declared in the source code, it is assigned undefined
Recovery phase
The execution context goes out of the stack and waits for the VM to reclaim the execution context
Process to summarize
- Create a stageCreate the lexical environment for the global context first: create first
Object environment logger
“, and then creates his external environment referenceouter
The value is null - Create a variable environment for the global context: same procedure as above
- Make this a global object (in the browser case, window)
- The function is called, creating the lexical environment for the function context: first
Declarative environment logger
“, and then creates his external environment referenceouter
, the value is null, the value is a global object, or the parent lexical environment - Create variable environment for function context: same procedure as above
- To determine this value
- Enter the execution phase of the function execution context
- After the execution is complete, the collection phase is started
Instance to explain
Lexical environmentouter
The execution context structure is as follows:
Let’s analyze the creation and execution of an execution context using the following example:
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
Copy the code
Step 1: before calling foo function to compile and create the execution context and the compile phase will var statement variables stored to the environment, let statement variables to store the lexical environment, it is important to note at this point in the function body let the internal block scope statement variables will not be deposited to the lexical environment, as shown in the figure below 👇 :
Step 2: Continue to execute the code. When executing inside the code block, the value of A in the variable environment has been set to 1, and the value of B in the lexical environment has been set to 2. At this point, the execution context of the function is shown in the figure below:
As can be seen from the figure, when entering the scoped block of a function, variables declared by let in the scoped block are stored in a separate area of the lexical environment. Variables in this area do not affect variables outside the scoped block. So in the example, the variable B declared in the function body block scope and the variable B declared in the function scope exist independently.
Inside the lexical environment, a small stack structure is actually maintained. The bottom of the stack is the outermost variable of the function. After entering a scope block, the variables inside the scope are pushed to the top of the stack. When the execution of the block-level scope is complete, the information for that scope is popped from the top of the stack, which is the structure of the lexical environment.
Step 3: When the code executes to console.log(a) in the scope block, we need to look up the value of variable A in the lexical and variable environments by: Search down the stack of the lexical environment. If it finds something in a block in the lexical environment, it returns it directly to the JavaScript engine. If it doesn’t find something, it continues in the variable environment.
This completes the variable lookup process, as you can see below:
Step 4: When the function’s inner block scope finishes execution, its internal variables are popped from the top of the lexical environment, and the execution context is as follows:
Step 5: When foo completes, the execution stack pops up the execution context of foo.
So, block-level scope is implemented through the stack structure of the lexical environment, and variable promotion is implemented through the variable environment. By combining the two, the JavaScript engine supports both variable promotion and block-level scope.
The outer reference
Outer is an external reference to the external execution context, which is specified by lexical scope
function bar() {
console.log(myName)
}
function foo() {
var myName = Geek Bang
bar()
}
var myName = "Geek Time"
foo()
Copy the code
When a piece of code uses a variable, the JavaScript engine first looks for the variable in the current execution context. For example, if the myName variable is not found in the current context, The JavaScript engine then continues to look in the execution context that Outer points to. To get an idea, take a look at this:
As you can see from the figure, the outer of both bar and foo points to the global context, which means that if an external variable is used in bar or foo, the JavaScript engine will look in the global execution context. We call this chain of lookup the scope chain. Now that you know that variables are looked up through the scope chain, one question remains: why is the external reference to the bar function called by foo the global execution context, and not foo’s execution context?
This is because during JavaScript execution, its scope chain is determined by lexical scope. Lexical scope means that the scope is determined by the position of the function declaration in the code, and therefore is static scope
Combining the variable environment, lexical environment, and scope chain, let’s look at the following code:
function bar() {
var myName = "Geek World."
let test1 = 100
if (1) {
let myName = "Chrome"
console.log(test)
}
}
function foo() {
var myName = Geek Bang
let test = 2
{
let test = 3
bar()
}
}
var myName = "Geek Time"
let myAge = 10
let test = 1
foo()
Copy the code
For the above code, when executing into the if block inside the bar function, the call stack looks like this:
Explain the process. The first step is to look in the execution context of the bar function, but since the test variable is not defined in the execution context of the bar function, according to the rules of lexical scope, the next step is to look in the outer scope of the bar function, that is, the global scope.
reference
- JavaScript performs context parsing
- Understand the execution context and execution stack in JavaScript
- Detailed diagrams of execution context
- Talk about execution context