From definition to execution, the JS engine does a lot of initialization work in the implementation layer. Therefore, before learning the working mechanism of the JS engine, we need to introduce several related concepts: execution environment stack, global objects, execution environment, variable objects, active objects, scope and scope chains, etc. These concepts are the core components of the work of the JS engine. The purpose of this article is not to explain each concept to you in isolation, but to analyze it through a simple demo, explaining the details of the JS engine from definition to execution, and the role these concepts play in it.
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 a reference to function B}var C = A(1); //Execute A, return BC(1); //Execute function BThis demo is a closure, and the execution result is 4. Below we will analyze the working mechanism of the JS engine in three stages: global initialization, execution function A, and execution function B :
1. Global initialization
When the JS engine enters an executable piece of code, it needs to complete the following three initialization tasks:
First, create a global object (Global Object). There is only one global copy of this object, its properties can be accessed anywhere, and its existence will accompany the entire life cycle of the application. When creating a global object, commonly used JS objects such as Math, String, Date, document are used as their properties. Since this global object cannot be accessed directly by name, there is another property window, and it points the window to itself, so that the global object can be accessed through window. The general structure of using pseudo-code to simulate global objects is as follows:
//Create a global object var globalObject = { Math:{}, String:{}, Date:{}, document:{}, //DOM operation... window:this //Let the window attribute point to itself}Then, the JS engine needs to build an Execution Context Stack. At the same time, it also needs to create a global Execution Context EC and push this global Execution Environment EC into the execution environment stack. The function of the execution environment stack is to ensure that the program can be executed in the correct order. In JavaScript, each function has its own execution environment. When executing a function, the execution environment of the function will be pushed to the top of the execution environment stack and obtain execution rights. When this 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 pseudocode to simulate the relationship between the execution environment stack and EC:
var ECStack = []; //Define an execution environment stack, similar to array var EC = {}; //Create an execution space, //ECMA-262 specification does not clearly define the EC's data structure, you can understand it as a piece of space allocated in memory ECStack.push(EC); //Enter the function and push the execution environment ECStack.pop(EC); //After the function returns, delete the execution environmentFinally, the JS engine also creates a global variable object (Varibale Object) VO associated with EC and points VO to the global object. VO not only contains the original properties of the global object, but also includes the variable x and function A defined globally. At the same time, when defining function A, an internal attribute scope is also added to A and points scope to VO. When each function is defined, a scope attribute associated with it will be created, and scope always points to the environment in which the function is defined. The ECStack structure at this time is as follows:
ECStack = [ //Execution environment stack EC(G) = { //Global execution environment VO(G):{ //Define global variable object... //Contain the original attributes of the global object x = 1; //Define variable x A = function(){...}; //Define function AA[[scope]] = this; //Define scope of A and assign value to VO itself} }];2. Execute function A
When 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 it to the top of the execution environment stack and obtain the execution rights. At this time, there are two execution environments in the execution environment stack, namely the global execution environment and the 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 resolution. When the execution environment is created, its scope chain is initialized as the object contained in the scope of the currently running function.
Next, the JS engine will create an active object (Activation Object) AO of the current function. The active object here plays the role of a variable object, but it is called differently in the function (you can think that a variable object is a general concept, and the active object is a branch of it). AO contains the formal parameters of the function, arguments object, this object, as well as the definitions of local variables and internal functions, and then AO will be pushed to the top of the scope chain. It should be noted that when defining function B, the JS engine will also add a scope attribute to B and point scope to the environment where function B is defined. The environment where function B is defined is A's active object AO, and AO is located at the front end of the linked list. Since the linked list has the characteristics of end connection, the scope of function B points to the entire scope chain of A. Let's take a look at the ECStack structure at this time:
ECStack = [ //Execution environment stack EC(A) = { //A's execution environment [scope]:VO(G), //VO is the global variable object AO(A): { //Create the active object y:1, x:2, //Define the local variable x B:function(){...}, //Define the function BB[[scope]] = this; //This refers to AO itself, and AO is at the top of scopeChain, so B[[scope]] points to the entire scope chain arguments:[],//The arguments we access in the function are arguments in AO this:window //This in the function points to the caller window object}, scopeChain:<AO(A),A[[scope]]> //The linked list is initialized as A[[scope]], and then AO is added to the top of the scope chain. At this time, A's scope chain: AO(A)->VO(G) }, EC(G) = { //Global execution environment VO(G):{ //Create global variable object... //Contain the original attributes of the global object x = 1; //Define the variable x A = function(){...}; //Define the function AA[[scope]] = this; //Define the scope of A, A[[scope]] == VO(G) } }];3. Execute function B
After function A is executed, the reference to B is returned and assigned to the variable C. Execution of C(1) is equivalent to executing B(1). The JS engine needs to complete the following tasks:
First, like above, create the execution environment EC of function B, and then push the EC to the top of the execution environment stack and obtain the execution rights. At this time, there are two execution environments in the execution environment stack, namely the global execution environment and the execution environment of function B. 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, A's execution environment will be deleted from the stack, leaving only the global execution environment) Then, create the scope chain of function B and initialize it into the object contained in the scope of function B, that is, the scope chain of A. Finally, create the active object AO of function B, and use the parameters z, arguments object and this object of B as properties of AO. At this time, ECStack will become like this:
ECStack = [ //Execution environment stack EC(B) = { //Create B's execution environment and is at the top of the scope chain [scope]:AO(A), //Point to the scope chain of function A, AO(A)->VO(G) var AO(B) = { //Create the active object of function B z:1, arguments:[], this:window } scopeChain:<AO(B),B[[scope]]> //The linked list is initialized to B[[scope]], and then AO(B) is added to the header of the linked list. At this time, B's scope chain: AO(B)->AO(A)-VO(G) }, EC(A), //A's execution environment has been deleted from the top of the stack, EC(G) = { //Global execution environment VO:{ //Define global variable object... //Contain the original attributes of the global object x = 1; //Define variable x A = function(){...}; //Define function AA[[scope]] = this; //Define scope of A, A[[scope]] == VO(G) } }];When function B executes "x+y+z", it is necessary to parse the three identifiers x, y, and z one by one. The parsing process adheres to the variable search rules: first find whether the attribute exists in your active object. If it exists, stop searching and return; if it does not exist, continue to search from the top along its scope chain, until it is found. If the variable is not found on the entire scope chain, return "undefined". 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), and will not look for x in VO(G), the variable y will be found in AO(A), and the variable z will be found in its own AO(B). So the execution result: 2+1+1=4.
Simple summary
After understanding the working mechanism of the JS engine, we cannot 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, improve execution efficiency, and generate actual value. Take the variable search mechanism as an example. If your code is deeply nested and every time you refer to a global variable, the JS engine will look for the entire scope chain. For example, there is this problem with window and document objects at the bottom of the scope chain. Therefore, we can do a lot of performance optimization work around this problem, and of course there are other aspects of optimizations. I won’t go into details here. This article is just regarded as a tip-off!
by @一竞2015
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.