Hello everyone, the editor is here to answer the following questions for you, explain in detail the execution process of js and the execution principle of javascript. Now let us take a look together!
Foreword
JavaScript
The execution process is divided into two stages, the compilation stage and the execution stage. During the compilation phase, the JS
engine mainly does three things: lexical analysis, syntax analysis and code generation; after the compilation is completed, the JS
engine begins to create an execution context (JavaScript
code running environment), and execute the JS
code python for statement usage.
Compilation phase
For common compiled languages (for example: Java
), the compilation steps are divided into: lexical analysis -> syntax analysis -> semantic checking -> code optimization and bytecode generation
For interpreted languages (for example: JavaScript
), the compiler stage can interpret and execute the code through lexical analysis -> syntax analysis -> code generation.
Lexical analysis
The JS
engine will treat the code we write as a string and decompose it into tokens. For example, var a = 2
, this program will be broken down into: “var, a, =, 2,;” five token
. Each lexical unit token
cannot be subdivided. You can try this website address to view token
: Esprima: Parser
Syntactic analysis
The syntax analysis stage will convert the lexical unit stream (array), which is the token
mentioned above, into a tree-structured “Abstract Syntax Tree (AST)”
Code generation
The process of converting AST
into executable code is called code generation, because computers can only recognize machine instructions, and a certain method needs to be used to convert the AST of var a = 2;
A set of machine instructions used to create a variable of a
(including allocating memory) and store the value in a
.
Execution phase
Executing a program requires an execution environment. Java
requires the Java
virtual machine. Similarly, parsing JavaScript
also requires an execution environment. We call it “execution context” .
What is execution context
In short, an execution context is an abstraction of the JavaScript
code execution environment. Whenever JavaScript
is run, it is run in the execution context.
Execution context type
There are three execution contexts for JavaScript
:
-
Global execution context – When the
JS
engine executes global code, it will compile the global code and create an execution context. It will do two things: 1. Create a globalwindow
object (in browser environment), 2. Set the value ofthis
to the global object; the global context is valid throughout the page life cycle, and there is only one copy. -
Function execution context – When a function is called, the code in the function body will be compiled and a function execution context will be created. Generally, after the function execution ends, the created function execution context will be destroyed. .
-
eval execution context – Calling the
eval
function also creates its own execution context (the eval function is prone to malicious attacks and runs code slower than the corresponding alternative, so Not recommended)
Execution stack
The concept of execution stack is relatively close to us programmers. Learning it allows us to understand the working principles behind the JS
engine. It helps us debug the code during development and can also cope with interviews about the execution stack during interviews. question.
The execution stack, known as the “call stack” in other programming languages, is a LIFO (last in, first out) stack data structure that is used to store all execution contexts created while the code is running.
When the JS
engine starts executing the first line of JavaScript
code, it creates a global execution context and pushes it onto the execution stack. Whenever the engine encounters a function call , which creates a new execution context for the function and pushes it onto the top of the stack.
The engine will execute those functions whose execution context is at the top of the stack. When the function execution ends, the execution context is popped from the stack and control flow reaches the next context on the current stack.
Combine the following code to understand:
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context');
When the above code is loaded in the browser, the JS
engine creates a global execution context and pushes it onto the current execution stack. When encountering first()
JS
the engine creates a new execution context for the function and pushes it onto the top of the current execution stack.
When calling second()
from within the first()
function, the JS
engine creates a new execution context and pushes it onto the top of the current execution stack. When the second()
function completes execution, its execution context is popped from the current stack, and the control flow reaches the next execution context, which is the execution context of the first()
function.
When first()
completes execution, its execution context is popped from the stack and control flow reaches the global execution context. Once all code has been executed, the JavaScript engine removes the global execution context from the current stack.
How to create an execution context
Now that we have understood how the JS
engine manages execution context, how is the execution context created?
The creation of execution context is divided into two phases:
- creation stage;
- execution stage;
Creation phase
The execution context creation phase will do three things:
- Bind
this
- Create a lexical environment
- CreateVariable Environment
So the execution context is conceptually represented as follows:
ExecutionContext = { // Execution context Binding This, // this value binding LexicalEnvironment = { ... }, // Lexical environment VariableEnvironment = { ... }, // Variable environment }
Bind this
In the global execution context, the value of this
points to the global object. (In a browser, this
refers to the Window
object).
In a function execution context, the value of this depends on how the function was called
- Calling a function through an object method,
this
points to the calling object - After declaring the function, use the function name to call it normally.
this
points to the global object. In strict mode, the value ofthis
isundefined
- Use the
new
method to call the function, andthis
points to the newly created object - Using
call
,apply
, andbind
to call a function will change the value ofthis
to point to the first passed in parameters, for example
function fn () { console.log(this) } function fn1 () { 'use strict' console.log(this) } fn() // Ordinary function call, this points to the window object fn() // In strict mode, this value is undefined let foo = { baz: function() { console.log(this); } } foo.baz(); // 'this' points to 'foo' let bar = foo.baz; bar(); // 'this' points to the global window object because no reference object is specified let obj { name: 'hello' } foo.baz.call(obj) // call changes this value to point to the obj object
Lexical environment
Each lexical environment consists of the following two parts:
- Environment record: variable object => stores declared variables and functions (let, const, function, function parameters)
- External environment references: scope chain
The official documentation of ES6 defines the lexical environment as:
Lexical Environments (Lexical Environments) are a type of specification used to define the association of identifiers with specific variables and functions based on the lexical nesting structure of ECMAScript code. A lexical environment consists of an Environment Record and a possibly empty reference to the outer Lexical Environment.
Simply put, the lexical environment is a structure of identifier-variable mapping (where identifier refers to the name of the variable/function, variable strong> is a reference to an actual object [objects containing function and array types] or an underlying data type).
As an example, consider the following code:
var a = 20; var b = 40; function foo() { console.log('bar'); }
The lexical environment of the above code looks like this:
lexicalEnvironment = { a: 20, b: 40, foo: <ref. to foo function> }
Environmental Records
The so-called environment record is the place where variable and function declarations are recorded in the lexical environment
There are also two types of environment records:
Declare class environment records. As the name suggests, it stores variable and function declarations. The lexical environment of the function contains a declaration class environment record.
Object environment records. The lexical environment in the global environment contains an object environment record. In addition to variable and function declarations, object environment records also include global objects (the browser’s window
object). Therefore, for each new property of the object (which, to the browser, contains all properties and methods provided by the browser to the window object), a new entry is created in this record.
Note: For functions, the environment record also contains an arguments object, which is an array-like object that contains parameter indexes and parameter mappings, as well as a length attribute of the parameters passed into the function. For example, an arguments object looks like this:
function foo(a, b) { var c = a + b; } foo(2, 3); //The argument object is similar to the following Arguments: { 0: 2, 1: 3, length: 2 }
Environment record objects are also called variable objects (VO) during the creation phase and active objects (AO) during the execution phase. It is called a variable object because at this time the object only stores the variable and function declarations in the execution context. After the code starts to execute, the variables will be gradually initialized or modified, and then this object is called an active object.
External environment reference
A reference to an external environment means that the external lexical environment is accessible within the current execution context. That is, if a variable cannot be found in the current lexical environment, the Java
engine will try to find it in the upper lexical environment. (The Java engine will use this attribute to form what we often call a scope chain)
The lexical environment is abstracted into pseudocode similar to the following:
GlobalExectionContext = { // Global execution context this: <global object> // this value binding LexicalEnvironment: { //Global execution context lexical environment EnvironmentRecord: { // Environment record Type: "Object", //Identifier is bound here } outer: <null> // external reference } } FunctionExectionContext = { // Function execution context this: <depends on how function is called> // this value binding LexicalEnvironment: { // Function execution context lexical environment EnvironmentRecord: { // Environment record Type: "Declarative", //Identifier is bound here } outer: <Global or outer function environment reference> // Reference the global environment } }
Variable environment
It is also a lexical environment whose environment recorder holds the bindings created by variable declaration statements in the execution context.
As mentioned above, the variable environment is also a lexical environment, so it has all the properties of the lexical environment defined above.
In ES6, one difference between lexical environment and variable environment is that the former is used to store function declarations and variable (let and const) bindings, while the latter is only used to store var variable binding.
Take a look at the sample code to understand the above concepts:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
Execution looks like this:
GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Bind identifier here a: < uninitialized >, b: < uninitialized >, multiply: <func> } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Bind identifier here c: undefined, } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Bind identifier here Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Bind identifier here g: undefined }, outer: <GlobalLexicalEnvironment> } }
Note – The function execution context is only created when the function multiply is called.
You may have noticed that variables defined by let
and const
do not have any value associated with them, but variables defined by var
are set to undefined
.
This is because during the creation phase, the engine examines the code for variable and function declarations, and although the function declarations are stored entirely in the environment, the variables are initially set to undefined
(var
case below), or uninitialized (in the case of let
and const
).
That’s why you can access variables defined by var
before declaration (albeit undefined
), but let
and before declaration Variables that are const
will get a reference error.
This is what we call variable declaration hoisting.
Execution phase
After the execution context is created above, the execution of the JavaScript
code begins. During execution, if the JavaScript
engine cannot find the value of the let
variable at the actual location declared in the source code, it will be assigned the value undefined
.
Execution stack application
Use the browser to view stack call information
We know that the execution stack is a data structure used to manage execution context call relationships, so how do we use it in actual work.
The answer is that we can use the browser “Developer Tools” source
tag, select the JavaScript
code and put a breakpoint, then we can view the calling relationship of the function, and switch to view each function variable value
When we put a breakpoint inside the second
function, we can see that the Call Stack
call stack on the right displays second
and first
, (anonymous)
calling relationship, second
is on the top of the stack (anonymous
is equivalent to the global execution context at the bottom of the stack), execute second
function we can view the values of the function scope Scope
local variables a
, b
and num
, by looking at the call relationship of the call stack we can quickly locate the execution of our code.
If an error occurs during code execution and you don’t know where to break the debugging point, how can you check the call stack where the error occurred? Let me tell you a trick, as shown below
We don’t need to break points. By performing the above two steps, we can automatically set breakpoints where the code executes abnormally. After knowing this trick, you no longer have to worry about code errors.
In addition to viewing the call stack through breakpoints above, you can also use console.trace() to output the current function call relationship. For example, console.trace() is added to the second
function in the sample code. , you can see the results output by the console, as shown below:
Summary
JavaScript
execution is divided into two phases, the compilation phase and the execution phase. The compilation phase will go through lexical analysis, syntax analysis, and code generation steps to generate executable code; the JS
engine will create an execution context when executing the executable code, including binding this
, creating Lexical environment and variable environment; the lexical environment creates external references (scope chain) and record environments (variable objects, let, const, function, arguments). JS
engine creation and execution start from single thread after completion Go to the next line and execute the JS
code line by line.
Finally, some application tips of the call stack during the development process were shared.
Citation link
JavaScript syntax parsing, AST, V8, JIT
[Translation] Understanding execution context and execution stack in JavaScript
Understand execution context and execution stack in Java