Execution context and execution context stack
Variable promotion and function promotion
Variable ascension
The variable declared by the var keyword, which can be accessed before the statement is defined, has the value undefined
console.log(a) // undefined
var a = 'Fitz'
// The actual process of the above code is:
var a
console.log(a)
a = 'Fitz'
Copy the code
Function increase
A function declared with the function keyword can be successfully executed completely before the function is defined
// called before the function declaration
sayName('Fitz') // 'Fitz'
function sayName (myName) {
console.log(myName)
}
// The actual process of the above code is:
function sayName (myName) {
console.log(myName)
}
sayName('Fitz') // 'Fitz'
Copy the code
Note: a function promotion must be declared by the function keyword, not by a direct variable declaration, because this declaration is actually a normal variable declaration, which is consistent with the case of variable promotion
func() Func is not a function
var func = function () {
console.log('hello')}Copy the code
Priority of variable promotion and function promotion
After introducing variable promotion and function promotion, one might ask: If my variable name and function name are the same, which one will be overridden? (Who will be the final object)
On this question of exploring the priority of the two, I will directly say the conclusion: perform function promotion first, then perform variable promotion
With this conclusion in mind, here are two questions that scream WTF from the heart
Let’s start with topic 1:
var a = 123
function a () {
console.log('a()')}console.log(a) // Guess what the result is
a() // Guess what the result is
Copy the code
Correct answers to the questions above
var a = 123
function a() {
console.log('a()')}console.log(a) / / 123
a() // error: a is not a function
Copy the code
According to the conclusion: should function promotion be performed first, then variable promotion be performed
According to the results, this conclusion is totally wrong. Hold on
The complete conclusion about variable promotion and function promotion is as follows:
- Function promotion is performed before variable promotion
- Variable declarations do not override function declarations and are ignored if they have the same name
- Variable assignments override function declarations (assignments)
So according to the full conclusion, the actual flow of the problem code is like this:
function a() { // The function is promoted
console.log('a()')}var a // Variable promotion does not override the above function
a = 123 // Function A is reassigned to a Number (123), because assignment overrides function declarations (123).
console.log(a) / / 123
a() // error: a is not a function
Copy the code
Feeling good? Let’s have a look at title 2:
var a
function a() {}console.log(typeof a) // Guess what the result is
Copy the code
Answers to the questions above
var a
function a() {}console.log(typeof a) // 'function'
Copy the code
Is not the heart of thousands of grass mud horse pentium, in any case follow the conclusion we analyze what happened, according to the conclusion of the topic code execution process should be like this:
function a() {}// The function is promoted first
var a // The compiler ignores the variable declaration because of the same name
console.log(typeof a) // 'function'
Copy the code
So remember the complete conclusion is the most important, otherwise encounter similar to these two questions, although know there is a pit, but the probability is still directly into the jump
But it’s pretty gross to remember this long conclusion, so instead of remembering: perform function promotion first, then variable promotion, etc., just remember: == Perform variable promotion first, then function promotion ==, and you’ll find that everything above works except one sentence
Finally, I will give you another topic to consolidate:
var c = 1
function c(c){
console.log(c)
}
c(2) // What is the result
Copy the code
Follow conclusion walk, these everybody total can’t make a mistake, still wrong of consciousness hair comment at the back of article!
var c = 1
function c(c){
console.log(c)
}
c(2) // error: c is not a function
// The actual execution of the above code
function c(c){
console.log(c)
}
var c // Remember: variable declarations with the same name are ignored
c = 1
c(2) // c is a function of type 1.
Copy the code
A topic on variable promotion introduces the respective characteristics of the var keyword and the let keyword
Without further ado, let’s start with the title
if(! (bin window)) {var b = 1
}
console.log(b) // What is the result
Copy the code
The answer is output undefined
I venture to guess that someone’s answer is: false
The person reporting the error should be thinking the same thing I was thinking at first, but ignoring the var keyword, which has no block-level scope ==
What is block-level scope, as opposed to function scope and global scope, which is the big scope inside the braces of for loops and if statements
Since var has no block-level scope, the curly braces of if do not bind it, so the problem code runs like this
var b
if(! (bin window)) {// window already has a b variable with undefined
b = 1
}
console.log(b) // undefined
Copy the code
The lack of block-level scope for the var keyword raises another classic problem: indexing for loops
for (var i=0; i<10; i++){
setTimeout(
() = >{
console.log(i) // Guess the result
},
2000)}Copy the code
The answer is: after about 2s it directly prints 10 10s
The timer is a hole in the JS system, which can be filled later. For now, we will focus on the index of the for loop
This is because the var keyword has no block-level scope. When the timer is executed asynchronously, the synchronous execution of for has already finished, and I has already become 10. Therefore, the output of all 10 timers is 10
So what’s the solution? Here I introduce another big hole —- closure, which we can solve by using the properties of closures
for (var i = 0; i < 10; i++) {
(function (outerIAsParm) {
setTimeout(
() = > {
console.log(outerIAsParm)
},
2000
)
})(i) // The closure is generated
}
Copy the code
It’s a little bit complicated to solve this problem with closures, and the concept of closures is still very difficult for me, and I’ll give you a summary of the concept of closures later, so you can take a look
So what’s a faster way to solve this problem?
Let has its own block-level scope, which allows the I of each for loop to be read and saved by the timer inside it. There is a concept called let deadfield (this is related to the lexical environment covered in the execution context section below)
for (let i = 0; i < 10; i++) {
setTimeout(
() = > {
console.log(i)
},
2000
}
Copy the code
Execution context
== variable promotion and function promotion are caused by the execution context ==
The execution context is divided into global execution context and function (local) execution context according to where the code is written, as well as scope
Global execution context
The global execution context is the work done automatically by the JS engine before the global code execution. Its work has the following parts
== First :== specifies the window object as a global execution context object, i.e., globalContext is the Window object
console.log(window) // window
Copy the code
== Then :== preprocesses the data in the global scope
-
Collect all variables defined in the global scope using the var keyword, assign the value to undefined, and add it as an attribute of the global execution context object window
-
Collect all functions declared with the function keyword in the global scope and add methods to the global execution context object Window
-
Point the context object to the global execution context object, that is: this = window
== Finally :== begins execution of global code, normal assignment of variable assignments, execution of statements, expressions, etc
So based on the concepts above, the code execution for this example should look like this
console.log(a) // undefined
test() // 'Fitz'
var a = 123
function test () {
console.log('Fitz')}// The actual code execution should look like this
window.test = function () {
console.log('Fitz')}var a
console.log(a) // undefined
test() // 'Fitz'
a = 123
Copy the code
Function (local) execution context
Before a function is called, that is, ready to execute the code in the function body, an execution context object for the function is created, which automatically does the following
== First :== preprocesses variables and parameters inside the function
-
The parameter is assigned to the value of the argument and then added as a property of the function execution context object
function test (parm){} test(1) // parm = 1 in the context of function execution Copy the code
-
The Arguments object is assigned a pseudo-array of arguments, which are then added as properties of the function execution context object
function test (parm){ console.log(arguments) } test(1.2.3) Copy the code
-
The variables declared by the var keyword and the functions declared by the function keyword are similar to the global execution context, but are confined to the function scope
function test () { console.log(a) // undefined innerFunc() // 'hello' function innerFunc() { console.log('hello')}var a = 'haha' } test() Copy the code
== The context object changes dynamically according to the object on which the function is called
== Finally :== starts executing the function body code
So based on the concepts above, the code execution for this example should look like this
function func(a1) {
console.log(a1) / / 666
console.log(a2) // undefined
a3() // a3()
console.log(this) // window
console.log(arguments) // The pseudo-array has elements 666 888
var a2 = 3
function a3() {
console.log('a3()')
}
}
func(Awesome!.888)
// The actual process of the above code is:
function func(a1) {
The /* 'var' keyword declares variables and the 'function' keyword declares functions, similar to the global execution context, but restricted to function scope */
function a3() {
console.log('a3()')}var a2
console.log(a1) // The parameter is assigned a1 = 666
console.log(a2) // undefined
a3() // a3()
console.log(this) // The context object changes dynamically according to the object on which the function is called. This is the default binding case
console.log(arguments) The // arguments object is assigned a pseudo-array of arguments
a2 = 3
}
func(Awesome!.888) // Call the function. Before executing a statement within a function, the function execution context operations described above are automatically performed
Copy the code
Details the process of executing a context
We have already described what happens automatically when a complete execution context and a function execution context are created, but we will also take a closer look at what happens when an execution context object is created (i.e., the steps described above and what the process is like here)
First of all, this part of the knowledge comes from the blogger: Listen to the wind is the wind blogger’s “an article to understand JS execution context”.
The creation of an execution context object is divided into two phases: the creation phase and the execution phase
The execution phase is the execution of the global code or the execution of a function call. Then focus on the creation phase
Create a stage
The creation phase is responsible for three things:
- To determine this
- Create LexicalEnvironment components
- Creating VariableEnvironment components (VariableEnvironment)
Corresponding to the steps described above (first… And then… And finally…).
Refer to the pseudo-code in the reference blog to show the creation process:
ThisBinding = <this value>, // create LexicalEnvironment component = {}, // create a VariableEnvironment component VariableEnvironment = {},}Copy the code
As this has been introduced in other notes, we need to focus on the lexical environment component and the variable environment component
The lexical environment component is composed of two parts: the environment record and the introduction record to the external environment. It is a mapping structure about the identifier (variable name/function name) and the value of the actual object (common data/address value)
var a = 123
var b = {}
Identifier A maps the value of type Number 123 Identifier B maps the address of the heap memory */
Copy the code
The environment record is used to store the actual location of variables and function declarations in the current environment, which is used to mark the actual assignment location
console.log(a)
var a = 123
// The actual execution of the code is
var a
console.log(a)
a = 123
/* The environment record is used to indicate that a is actually assigned after console.log(). Note: this is a personal guess, and the truth is not certain */
Copy the code
The external environment introduces records to store which external environments are accessible to the current environment. This is similar to what variables/values are accessible under the scope chain. The lexical environment is divided into global and function execution contexts
The global lexical environment is shown in pseudocode
// Global environment
GlobalExectionContext = {
// Global lexical environment
LexicalEnvironment: {
// Environment record
EnvironmentRecord: {
Type: "Object".// The type is the object environment record
// The identifier is bound here
},
// The external reference record is null
outer: [null]}}Copy the code
The functional lexical environment is shown in pseudocode:
// Function environment
FunctionExectionContext = {
// function lexical environment
LexicalEnvironment: {
// Environmental record
EnvironmentRecord: {
Type: "Declarative".// The type is declarative environment record
// The identifier is bound here
arguments: {0: 'fitz'.length: 1}},// References to external records may be global objects
// It can also be the environment of another function, such as a nested function that introduces records to the external environment
outer: < Global or outerfunction environment reference >
}
}
Copy the code
The variable environment component is a special lexical environment that only stores variables declared by var
So in conclusion
var a = 123
let b = 'Fitz'
const c = 'Lx'
var d
function e (parm1, parm2) {}
d = e('p1'.'p2')
Copy the code
The execution context object creation process for the above instance is completely represented in pseudocode:
// Global execution context
GlobalExectionContext = {
// This is bound to a global object
ThisBinding: <Global Object>, // <Global Object> => window // LexicalEnvironment: { < initialized >, c: < uninitialized >, e: < initialized > < function >}, outer: <null> // the environment component VariableEnvironment: {EnvironmentRecord: {Type: D: undefined}, d: undefined}, d: undefined}, d: undefined < null >}} / / = = = = = = = = = = = = = = = = = = = = = = = = = = = line = = = = = = = = = = = = = = = = = = = = = = / / function execution context FunctionExectionContext = { This => window ThisBinding: <Global Object>, // LexicalEnvironment: {EnvironmentRecord: Arguments: {parm1: 'p1', parM1: 'p2', length: </Global> outer: <GlobalEnvironment>}, VariableEnvironment: {EnvironmentRecord: {Type: </Global> outer: <GlobalEnvironment>}}Copy the code
Execution context stack
When I introduced the execution context above, both global and function (local) execution contexts, I mentioned the concept of an execution context object. It is now around this execution context object that I introduce what an execution context stack is.
First, the life cycles of global and local execution context objects are different
- The global execution context object is generated before global code execution. And it stays there until the browser closes
- The function execution context object is generated only when the function is called and the statement inside the function is executed. After the function call, the execution context object is destroyed. This concept is like the relationship between function scope and THE JS garbage collector
Second, the execution context object is not a real JS object, it is a virtual concept level object, the reason why it is called an object is to make it easier to understand
Here is a diagram of the Execution context stack of the English website
This topic explains the execution context object and the execution context stack
var a = 10
var bar = function (x) {
var b = 5
foo(x + b)
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10)
Copy the code
My question to the above question is: == how many execution context objects are generated above, and how do they relate to the execution context stack ==
With questions, let’s explore and solve them
First, as I mentioned above, the global execution context object Window is available when the browser is created, whereas the function execution context object is available only when the function is called. So the problem == produces three execution context objects ==
// 1. You are now in the global execution context object
var a = 10
var bar = function (x) {
var b = 5
foo(x + b) // 3. Function call, generate a function execution context
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10) // 2. Function call, which generates a function execution context
Copy the code
How about calling the bar() function twice? The answer is: five execution context objects
// 1. You are now in the global execution context object
var a = 10
var bar = function (x) {
var b = 5
foo(x + b) // 3. Function call, generate a function execution context
// 5. Function call, generate a function execution context
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10) // 2. Function call, which generates a function execution context
bar(10) // 4. Function call, generate a function execution context
/* Note that the function execution context object is destroyed at the end of the function call
Copy the code
From this, we can summarize the formula for the number of execution context objects: Execution context objects = number of function executions + 1
Having said that, how does this execution context object relate to the execution context stack?
Well, before global code execution, the JS engine creates a stack to store and manage all execution context objects
Note: all means both global and local are placed on the stack
Many of my notes refer to the stack data structure several times without explaining it properly. Although it is not a problem, but strive to idiocy knowledge, also no matter long wordy.
As a data structure, the stack has LIFO (last in, first out) property, so much for my humble knowledge. It’s hard to understand just saying it like that
Combined with the things in life, or relatively easy to understand
At this point, we know that the execution context stack is to better manage the execution context object, or the above title, let’s briefly visualize the execution context stack
// 1. You are now in the global execution context object
var a = 10
var bar = function (x) {
var b = 5
foo(x + b) // 3. Function call, generate a function execution context
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10) // 2. Function call, which generates a function execution context
Copy the code
Perform context stack interview questions and parse them
// What is the result of the execution
// How many execution contexts (objects) are generated
console.log('global begin: ' + i)
var i = 1
foo(1)
function foo(i) {
if (i === 4) {
return
}
console.log('foo() begin: ' + i)
foo(i + 1)
console.log('foo() end: ' + i)
}
console.log('global end: ' + i)
Copy the code
Visual parsing:
// What is the result of the execution
// How many execution contexts (objects) are generated
console.log('global begin: ' + i) // Variable promotion output undefined
var i = 1
// This part focuses on the visual analysis of the picture
foo(1)
function foo(i) {
if (i === 4) {
return
}
console.log('foo() begin: ' + i)
foo(i + 1) // recursive call
console.log('foo() end: ' + i)
}
console.log('global end: ' + i) //1, the global scope of I has not changed, do not be affected by the function scope I
Copy the code