Location>code7788 >text

Does JavaScript Execute Sequentially? Talking about variable elevation in JavaScript

Popularity:545 ℃/2024-12-17 21:19:05

As a front-end developer, we often hear the phrase:"JavaScript is executed in sequential order from top to bottom."There is nothing wrong with that statement. But it seems as if it's not quite right. Let's look at the following piece of code. What do you think the result will output?

1 showName()
2 (myName)
3 
4 var myName = 'Xiuqian'
5 function showName() {
6     (''My name is Xiu Qian'')
7 }

If you follow the logic of top-down execution as stated before, then the output should be:

1, because the function showName execution, it is not defined, so it will report errors

2. Similarly, the variable myName is not defined, so it will also report an error

However, when we execute it in the browser console, the actual result is shown below.

 

Surprisingly, the code doesn't report an error! The first line outputs"My name is Xiu Qian."The second line outputs“undefined”, at this point do you wonder:"How is this a bit different from the previously imagined sequence of execution! How could it turn out like this?"

By this point, I think you should have thought of a little something. That is:"Functions and variables are available before they are defined."But what would be the result if we execute undefined functions and variables?

Let's try deleting the third line of the previous code and executing it.

1 showName()
2 (myName)
3 
4 function showName() {
5     (''My name is Xiu Qian'')
6 }

After running the code, as shown below, this time we see that the result is that the function has executed, but theconsolefunction output has reported an error, outputting the“myName is not defined”

 

By this point, can you get some new insights about the above two results? In fact, with the above two code executions, we can at least get the following conclusions:

1、JavaScript in the process of execution, if the use of undefined variables, it will report errors

2、Used before a variable is defined, it will not report an error, only its value is undefined.

3. Using a function before it is defined does not report an error, but executes it correctly

The first conclusion is easy to understand, since the variable is undefined, it must not be found when it is used, and therefore an error is bound to be reported. But the second and third conclusions are really puzzling: how can variables and functions be used before they are defined? This seems to indicate that the JS code is not executed sequentially from top to bottom as previously stated.

Another point is that in the same way, why is the result of processing variables and functions different? For example, the result of the above execution, the advance use of theshowNamefunction prints out the full result, but the advance use of themyNameThe value of the variable isundefinedinstead of the one we used when defining"Xiuqian."of this value. To explain this, we have to talk about a very important concept in JavaScript:Variable promotion

1, what is the JavaScript variable lifting (Hoisting)

Before we talk about variable lifting in JavaScript, we have to talk about declaration and assignment operations in JavaScript, for the following line of code

1 var myName = 'Xiuqian'

In fact, this code you can look at it in two parts, namelyheraldcap (a poem)assign a value to something

1 var myName //  variable declaration
2 myName = 'Xiuqian' // variable assignment

The above is the declaration and assignment of variables in JavaScript, let's take a look at the function declaration and assignment operation in JavaScript is what kind of, we still look at the following piece of code

1 function showName() {
2     (''My name is Xiu Qian'')
3 }
4 
5 var showName = function() {
6     (''My name is Xiu Qian'')
7 }

We can see that the first functionshowNameis a complete function declaration, which does not involve assignment operations; the second function starts by declaring the variableshowNameThen put thefunction(){('My name is Xiuqian')}The value is assigned to showName. by this point you should know what variable declaration and assignment in JavaScript is all about.

Having said what's going on with variable declaration and assignment in JavaScript, let's talk about variable elevation in JavaScript.

In JavaScript, the so-called variable elevation: refers to the JavaScript engine elevating the declaration part of a variable and the declaration part of a function to the beginning of the code during the execution of JavaScript code."Behavior.". When a variable is promoted, a default value is set for the variable, and the default value it sets is the one we're most familiar withundefined. Taking the concept literally."Variable lifting" means that variable and function declarations are moved to the top of the code at the physical level.

But that's actually not accurate. Because in reality, in JavaScript, variable and function declarations don't change their position in the code. Why? Because in JavaScript, a piece of code is first compiled by the JavaScript engine, and only after the code is compiled does it go to the execution stage of the code (as shown in the following figure). The reason that the position of variable and function declarations in the code does not change is that the code is put into memory by the JavaScript engine at the compilation stage (since it is put into memory, its position is fixed).

So why the elevation when the location is fixed in memory during the compilation phase? What is the relationship between the compilation phase and variable elevation? Here we come to another concept:Execution context

2. Implementation context(Execution context)

what is calledexecution contextWe can simply understand that it isThe environment in which JavaScript runs when executing a piece of codeFor example, when we call a function in a JavaScript file, we enter the execution context of that function, which identifies things like this, variables, objects, and functions used during the execution of that function. And in the execution context there is also aObjects of Variable Environment (Viriable Environment)This is very important. This is because the object holds the contents of the variable lifting, such as the variable in the code abovemyNamesum function (math.)showName, all of which will be saved in that object (we'll simulate it with this code below first, and explain it in detail later).

1 ViriableEnvironment
2     myName -> undefined
3     showName -> function: {(myName)}

In JavaScript, execution contexts are generally categorized into the following three types:

1. Global execution context:When JavaScript executes global code, it compiles the global code and creates a global execution context, and there is only one copy of the global execution context for the entire lifetime of the page.

2. function execution context:When a function is called, the code within the function is compiled and a function execution context is created, and in general, the created function execution context is destroyed after the function execution is finished.

3、eval :When the eval function is used, the eval code is also compiled and an execution context is created.

However, we are now often exposed to or generally refer to the first two. After understanding the concept and categorization of execution context, let's learn about the other two:Function execution (call)respond in singinga wooden or bamboo pen for sheep or cattle

3. Function execution (call)

The concept of a function call is very simple. To put it simply, you run a function, using the function name followed by a pair of parentheses. Let's take an example

1 var myName = 'Xiuqian'
2 function showName() {
3     (''My name is Xiu Qian'')
4 }
5 
6 showName() // fulfillment

This code is very simple. First we create a file namedmyNamevariable, followed by the creation of ashowNamefunction. The method is executed immediately after the last call. Here's how the function call works in this simple code.

When executing the functionshowName()Before that, the JavaScript engine creates a global execution context for the above code, containing the declared functions and variables, as shown below:

As you can see from the figure, the global variables and functions in the code above are stored in the variable environment of the global context. Once the execution context is ready, the JavaScript engine starts executing the global code, and when it gets to theshowNamefunction, JavaScript determines that it is a function call and begins the following:

1. First, take the showName function code from the global execution context.
2. Next, compile this code for the showName function and create the execution context and executable code for the function.
3. Finally, execute the code and output the results.

We can describe it in a relatively complete diagram 

When executing theshowNamefunction, we've got two execution contexts - theGlobal Execution Contextcap (a poem)showName The execution context of the function itself (function execution context)This means that there are multiple execution contexts when JavaScript is executed. This means that when JavaScript is executed, there are multiple execution contexts. So how does the JavaScript engine manage when there are multiple contexts? That's what we're going to talk about in the next section, a data structure - thea wooden or bamboo pen for sheep or cattle

4. Stack (Stack)

Stackis a linear data structure that followscome late and leave first(Last In, First Out, LIFO) principle. This means that the last element to enter the stack is the first to be removed. As shown in the following figure, the first inABut the first one to come out wasE. The JavaScript engine utilizes this structure of the stack to manage the execution context. After the execution context is created, the JavaScript engine presses the execution context into the stack and then executes it, and the stack that is used to manage the execution context is often referred to as theexecution context stack (computing)orJavaScriptcallstack

4. Execution context stack (JavaScriptcallstack

Below we will be specific with code and diagrams to simulate how the JS execution context stack is executing code, such as the following piece of code (to ES5 to demonstrate)

 1 var a = 2
 2 function add(b,c){
 3   return b+c
 4 }
 5 function addAll(b,c){
 6   var d = 2
 7   result = add(b,c)
 8   return a+result+d
 9 }
10 addAll(3,3)

Step 1: Create the global top and bottom and press it into the bottom of the stack (as shown)At this point the variableaFunctionsadd as well asaddAll are saved to the global context's variable environment object.

 Immediately after the global execution context is pressed onto the call stack, the JavaScript engine begins executing the global code. It first executes thea=2After the assignment operation, the value of a was previously removed from theundefinedIt's turning into2. Because at this point in time theaddfunctions andaddAllNone of the functions have been executed yet, so the state is still the same as before. Once this step is complete, let's look at the state of the global context, as shown below:

Step 2: Execute the addAll function.At this point the JavaScript engine compiles the function, creates an execution context for it, and then presses its execution context onto the stack, as shown here:

Similarly, whenaddAllOnce the execution context of the function has been created, it's time to move on to the execution of the function's code, because the function has a variabledand therefore still performs the assignment operation first, i.e., thedvalue from the previousd=undefinedset tod=10 . Then proceed to the next execution.

Step 3: Execute the add function.When executing theaddWhen the function call statement, the JavaScript engine will also create an execution context for it and press it into the call stack, the state of the call stack at this time is shown in the following figure:

after thataddThe function executes, assigning the returned result to the variableresultat this timeresultThe value of theundefinedIt's turned into6. The execution context of the function is then popped off the top of the stack. The call stack at this point is shown below:

followingaddAllAfter performing the last sum operation and returning, after completing theaddAllThe execution context is also popped off the top of the stack, leaving only the global context on the call stack. The final result is shown in the following figure. At this point, the entire JavaScript execution is complete.

Through the above analysis, we can know that it is due to the existence of JavaScript variable lifting this feature, which leads to our daily learning or work, we can always see a lot of intuition does not match or with our habits of thought is not the same as the code, and this is also an important design flaw of JavaScript. For this reason, the ECMAScript6pull intoblock scope (computing)concepts and to work with the let、const keyword to bypass this design flaw (which we'll talk about next). But before we get to that, let's move on to the two remaining problems with variable boosting:Why does variable lifting occur in JS? What are the disadvantages of variable lifting?

5. The reason for variable elevation in JS

We all know that JavaScript did not support block-level scoping before ES6. This is because when the language was designed, it was designed in the simplest way possible. That is, it was designed with onlyglobal scopecap (a poem)function scopeIn order to simplify the process of parsing and executing JavaScript code. What wasn't expected was that JavaScript would be so hot behind the scenes that eventually itsThe lack of block-level scopes is slowly exposed

Now that the problem has been exposed, it's time to fix it. But you can't just add block-level scopes right away! After all, you've already developed so many applications in JavaScript. So a not particularly radical approach was taken - theLift variables inside the scope uniformly. This was the quickest and easiest way to do it in his time.

Of course, there are two sides to everything. One of the major drawbacks of this approach is that it directly results in the variables in the function, no matter where they were declared, being extracted into the execution context's variable environment at compile time, so that they are accessible from anywhere inside the entire body of the function, which is what we commonly refer to as variable lifting in JS.

6. The problem of variable lifting in JS

1. Variables are overwritten without realizing it

Let's look at the following piece of code, what do you think the output will be? YesmodestYes, sir. Yes, sir.Wu Men Mountaineer

 1 var myName = "Xiuqian."
 2 
 3 function showName(){
 4   (myName);
 5   if(0){
 6    var myName = "Wu Men Mountain Man"
 7   }
 8   (myName);
 9 }
10 
11 showName()

In fact, if you execute the code, you will find that its output is neither. Instead, it outputsundefined. Why is this so? You can try to draw the process diagram yourself by referring to that JS execution example given earlier. Here we'll just paste the final execution stack diagram.

(coll.) fail (a student)showNameOnce the function's execution context is created, the JavaScript engine starts executing its internal code. The first thing executed is the(myName). And executing this code requires the use of the variablemyName, and as we can see from the diagram, here are theTwo myName variables: one is in the global execution context and its value is"Xiuqian."; the other is in the context of the execution of the showName function and has the value ofundefined. Which output should JS actually use at this point? As a front-end developer, I think the vast majority of people would say the correct answer:"The showName function must be used first to execute the variables inside the context!"

This is indeed the case, because during function execution, JavaScript prioritizes the execution of functions from theFinding variables in the current execution context, but because of variable lifting, the current execution context contains the variablemyNameand its value isundefinedSo get themyNameThe value ofundefined. Instead, as in other languages, it will output"Xiuqian."

2. Variables that should have been destroyed were not destroyed

So since variable elevation in JavaScript causes all the problems mentioned above? And what was the final solution? The answer was the release of a new JS standard back in 2015 - theECMAScript6(ES6 for short).In this standard, the formal introduction ofblock scope (computing)of the concept. And it also introduces the let cap (a poem) const keyword to declare block-level scopes, JavaScript can now have block-level scopes like other languages.

7, let and const in ES6

with respect toletrespond in singingconst. Let's start with the following code

1 let myName = 'Xiuqian'
2 const myAag = 35
3 myName = 'Mountain Man'
4 (myName)
5 
6 myAag = 18
7 (myAag)

This code outputs results that I think anyone who has written JavaScript should know what the results are. The first output is"Mountain Man."while the second one outputs an error. From here we can see that although both are used to declare block-level scopes, there is a difference between the two, using thelet The variables declared with the keyword can be changed, whereas the variables declared with theconst The value of a declared variable cannot be changed. With that said let's also drop in a question that is often asked in interviews:What is a temporary dead zone in JavaScript?

Let's start with the code.

1 function example() {
2   (x); 
3   let x = 10;
4 }
5 
6 example();

When we copy this code to the browser console it reports this error:“ReferenceError: Cannot access 'x' before initialization”. This error translates to:Reference error: unable to access "x" before initialization(The translation may not be accurate, but the meaning is similar). We know from this error that in ES6, when we use theletrespond in singingconst A declared variable is in a"Uninitialized"state, and this state is known as a temporary dead zone (officially defined as:In JavaScript, a Temporal Dead Zone (TDZ) is a block-level scope (e.g., the block of code in which a variable declared by let and const is located) where accessing the variable before it is declared will result in a ReferenceError)。

after all is said and doneletcap (a poem)constLet's look at the following two simple lines of code

1 var myName = 'Xiuqian'
2 let myAag = 35

These two lines of code aren't really anything special, and I'm using them just to beg the question, ie:How does JavaScript support the variable lifting feature while also supporting block-level scoping?Because we are in the project, sometimes you will find people in the code namely will use thevarkeyword to declare variables, while at the same time using theletcap (a poem)constto declare variables. Although this approach is not recommended, it is always unavoidable. We've already talked about the variable lifting feature. So the next thing we'll focus on is how JavaScript supports block-level scoping.

8、How does JavaScript support block-level scoping?

As we mentioned earlier, function-level scoping is implemented in the JavaScript engine through variable environments, so how does ES6 build on that and implement support for block-level scoping? Let's take a look at the following code

 1 function showName(){
 2     var myName = 'Xiuqian'
 3     let myAag = 35
 4     {
 5       let myAag = 18
 6       var heName = 'Wah'
 7       let heAge = 63
 8       (myName)
 9       (myAag)
10     }
11 }   
12 showName()

When executing this code, the JavaScript engine compiles it and creates an execution context before executing the code in the order in which it was executed in our example without the ES6 keywordlet. But now the introduction of let keyword, which creates block-level scopes, so how does it affect the execution context? Here we have to mention a term - thelexical environment. You'll remember from the previous example that there's been an empty block on the right called lexical environment. And the reason JavaScript supports block-level scoping has to do with it.

Let's sort out the execution of this code in the same way as before.

Step 1: Compile and create a global execution context 

Step 2: Execute the showName function to create a function execution context for it

until (a time)showNameThe function performs this step. We can tell from the call stack:

1. The variables declared by var inside the function are all stored in the variable environment at the compilation stage (this is the same as the previous one).
2. Variables declared by let are stored in the Lexical Environment at the compilation stage.
3. Inside the function's scope block, variables declared via let are not stored in the lexical environment

Step 3: Continue to execute the code down the line.When executing inside a code block, the variable environment of themyNameThe value of has been set to"Xiuqian."and the lexical environmentmyAagis set to the value of35The execution context of the function at this point is shown below:

As we can see from the diagram in step 3, when entering a scope block inside a function, the scope block is passed through the let The declared variable (myAagcap (a poem)heAag), is stored in a separate area of the lexical environment and does not affect variables outside the scope block (the previousmyAag). So they all exist independently. Also we can see from this that in factInside the lexical environment, a small stack structure is also maintained, with the outermost variable of the function at the bottom of the stack(i.e., variables on the outside of the internal scope block, in this case themyAag), when a particular scope block is entered, the variables inside that scope block are pressed to the top of the stack (myAagrespond in singingheAag); when the execution of the scope is complete, the information for that scope is popped off the top of the stack, and this is the structure of the lexical environment (The prerequisite is that you must use theletorconstKeyword Definition)。

Step 4: Continue to execute the code down the line.To set the value of themyAagrespond in singingheAagare assigned the values of16,63and also sets the environment variable in theheNameThe value of the"Wah". As shown

Step 5: Continue to execute the code down the line.When executing the(myName)When this line of code is used, it is then necessary to add thelexical environmentrespond in singingvariable environmentFinding variables inmyNamevalue now, and the exact way to find it is:Look up along the top of the stack of the current lexical environment, and if it's found in one of the blocks in the lexical environment, it's returned directly to the JavaScript engine, and if it's not found, then it continues to look up in the variable environment (the same rule applies to (myAag) in the scope block). At this point the following is shown: because it is not found in the lexical environmentmyNamefor this variable, so it goes to the variable environment and eventually finds themyName(indicated by the yellow arrow), so the output"Xiuqian."equal(myAag)Because it is found in the lexical environmentmyAag(indicated by the dark blue arrow), so the output18. And executing the above code in a browser results in this as well

 

Once the function's internal scope block has finished executing, its internally defined variables are popped off the top of the lexical environment's stack, and the final execution context is shown below:

Through the above analysis, we have basically understood the structure and working mechanism of the lexical environment:Block-level scoping in ES6 is implemented through the stack structure of the lexical environment, while variable elevation was previously implemented through the variable environment, and through the combination of the two, the JS engine also supports both variable elevation and block-level scoping.At this point, I think you should have a better impression about variable lifting. Of course, what I wrote above may not be completely correct. We welcome your comments and criticisms.