The JS engine performs a lot of initialization at the implementation layer from the definition to the execution of javascript. Therefore, before learning the working mechanism of the JS engine, we need to introduce several related concepts: the execution environment stack, global object, execution environment, variable object, activity object, scope and scope chain are the core components of JS engine work. The purpose of this article is not to explain every concept in an isolated way, but to analyze it through a simple demo to explain every detail of the JS engine from definition to execution, and the roles of these concepts.
Var x = 1; // define A global variable xfunction A (y) {var x = 2; // define A local variable x function B (z) {// define an internal function B console. log (x + y + z);} return B; // return function B reference} var C = A (1); // execute A and return BC (1 ); // Execute function B
This demo is a closure and the execution result is 4.Global Initialization,Execute function,Execute function BThree phases to analyze the working mechanism of the JS engine:
1. Global Initialization
When entering a piece of executable code, the JS engine needs to complete the following three initialization tasks:
First, create a Global Object. This Object exists only one copy globally and its attributes can be accessed anywhere. Its existence is accompanied by the entire life cycle of the application. When creating a global object, common JS objects such as Math, String, Date, and document are used as their attributes. Because this global object cannot be directly accessed by name, there is another property window and points the window to itself, so that you can access this global object through the window. The general structure of a simulated Global Object using pseudo-code is as follows:
// Create a Global Object var globalObject = {Math :{}, String :{}, Date :{}, document :{}, // DOM operation... window: this // point the window property to itself}
Then, the JS engine needs to build an Execution environment Stack (Execution Context Stack). At the same time, it also needs to create a global Execution environment (Execution Context) EC, and press the global execution environment EC into the execution environment stack. The execution environment stack is used to ensure that the program can be executed in the correct order. In javascript, each function has its own execution environment. When a function is executed, the execution environment of the function is pushed to the top of the execution environment stack and the execution permission is obtained. After the function is executed, its execution environment is deleted from the top of the stack and the execution right is returned to the previous execution environment. We use pseudo code to simulate the relationship between the execution environment stack and EC:
Var ECStack = []; // defines an execution environment stack, similar to the array var EC ={}; // creates an execution space, // The ECMA-262 specification does not have a clear definition of the EC Data Structure, you can understand it as a space allocated in the memory ECStack. push (EC); // enter the function and press it into the execution environment ECStack. pop (EC); // Delete the execution environment after the function returns
Finally, the JS engine creates a global variable Object (Varibale Object) VO associated with the EC and points VO to a global Object. VO not only contains the original attributes of the global Object, the global variables x and function A are also included. At the same time, when defining function A, an internal attribute scope is added for function A and scope is directed to VO. When defining a function, each function creates a scope attribute associated with it. scope always points to the environment in which the function is defined. The ECStack structure is as follows:
ECStack = [// execution environment stack EC (G) = {// global execution environment VO (G): {// defines the global variable object... // contains the original Global Object Property x = 1; // defines the variable x A = function (){...}; // define the function A [[scope] = this; // define the scope of A and assign the value to VO itself}];
Ii. Execute function
When the execution enters A (1), the JS engine needs to do the following:
First, the JS engine will create the execution environment EC of function A, and then the EC will push to the top of the execution environment stack and obtain the execution right. The execution environment Stack has two execution environments: Global execution environment and function A execution environment. The execution environment of A is at the top of the stack, and the global execution environment is at the bottom of the stack. Then, create the Scope Chain of function A. In javascript, each execution environment has its own Scope Chain for identifier parsing. When the execution environment is created, its scope chain is initialized to the objects contained in the scope of the currently running function.
Then, the JS engine creates an activity Object (AO) of the current function. The activity Object here plays the role of the variable Object, it's just that the name of a function is different (you can think that a variable object is a general concept, and an activity object is a branch of it ), AO contains function parameters, arguments objects, this objects, and definitions of local variables and internal functions. Then, AO will be pushed to the top of the scope chain. Note that when defining function B, the JS engine also adds a scope attribute for function B and points scope to the environment where function B is defined, the environment that defines function B is the activity object AO of function A, while AO is located at the front end of the linked list. Because the linked list has the characteristics of first-end connection, the scope of function B points to the entire scope chain of function. Let's take a look at the ECStack structure at this time:
ECStack = [// execution environment stack EC (A) = {// The execution environment of A [scope]: VO (G), // VO is the global variable object AO (): {// create the activity object y: 1, x: 2 of function A, // define the local variable x B: function (){...}, // define function B [[scope] = this; // this refers to AO itself, and AO is located at the top of scopeChain, therefore, B [[scope] points to the entire scope chain arguments: [], // the arguments we access in the function at ordinary times is the arguments this in AO: window // this In the function points to the caller's window object}, scopeChain: // The linked list is initialized to A [scope], and then AO is added to the top of the scope chain, at this time, the scope chain of A is: AO (A)-> VO (G)}, EC (G) ={// global execution environment VO (G ): {// create a global variable object... // contains the original Global Object Property x = 1; // defines the variable x A = function (){...}; // define the function A [[scope] = this; // define the scope of A, A [[scope] = VO (G)}];
Iii. Execute function B
After function A is executed, it returns B's reference and assigns the value to variable C. Executing C (1) is equivalent to executing B (1). the JS engine needs to do the following:
First, create the execution environment EC for function B, and then push the EC to the top of the execution environment stack to obtain the execution right. In this case, the execution environment Stack has two execution environments: Global execution environment and function B execution environment. The execution environment of B is at the top of the stack, and the global execution environment is at the bottom of the stack. (Note: When function A returns, the execution environment of function A will be deleted from the stack, leaving only the global execution environment.) then, create the scope chain of function B, and initialize the objects contained in the scope of function B, that is, the scope chain of function. Finally, create the activity object AO of function B and take the form parameter z, arguments object and this object of function B as the attributes of AO. In this case, ECStack will become: ECStack = [// execution environment Stack
EC (B) = {// create the execution environment of B, and be at the top of the scope chain [scope]: AO (A), // point to the scope chain of function, AO (A)-> VO (G) var AO (B) = {// create function B's activity Object z: 1, arguments: [], this: window} scopeChain: // The linked list is initialized to B [[scope], and AO (B) is added to the table header. At this time, the scope chain of B is: AO (B)-> AO () -VO (G)}, EC (A), // The execution environment of A has been deleted from the top of the stack, EC (G) = {// global execution environment VO: {// defines the global variable object... // contains the original Global Object Property x = 1; // defines the variable x A = function (){...}; // define the function A [[scope] = this; // define the scope of A, A [[scope] = VO (G)}];
When function B executes "x + y + z", it needs to parse the three identifiers x, y, and z one by one. The parsing process follows the Variable Search rules: first, find whether this attribute exists in your activity object. If so, stop searching and return. If not, continue searching from the top of its scope chain until it is found, if the variable is not found in the entire scope chain, "undefined" is returned ". From the above analysis, we can see that the scope chain of function B is as follows:
AO(B)->AO(A)->VO(G)
Therefore, the variable x will be found in AO (A), rather than in VO (G), and the variable y will also be found in AO (, the variable z is found in its AO (B. Therefore, the execution result is 2 + 1 + 1 = 4.
Summary
After learning about the working mechanism of the JS engine, we should not just stay at the level of understanding the concept, but use it as a basic tool to optimize and improve our code in actual work, improving execution efficiency and generating practical value is our real purpose. Take the variable search mechanism as an example. If your code is deeply nested and every time a global variable is referenced, The JS engine will look for the entire scope chain, for example, at the bottom of the scope chain window and the document object, this problem exists. Therefore, we can do a lot of performance optimization work around this problem. Of course there are other optimizations, which will not be described here, this article is just for reference!