Relearn precompile
Foreword: I originally planned to give up CSDN completely and enter the Nuggets, but seeing that there are still small partners paying attention, I finally decided to spend a little more time and update it synchronously. If this article is helpful to you, please don't be stingy with your likes. 😃
Past situation review
Questions I saw in the group when I participated in the Nuggets Daily New Program (renovated)
var a; if (true) { console.log(a) a = 111 function a() { } a = 222 console.log(a) } console.log(a)
Basics review
variable promotion
In fact, the promotion of variables is actually a criticism of JS, so after es6 came out with let and const, it is recommended to use let and const.
Before executing the function, it is precompiled, and the declaration of the variable declared with var is hoisted to the front.
First of all, if we only have one statement console.log(a), this will directly report the error a is not defined.
If you just declare the variable, but don't assign a value, you get undefined.
var a; console.log(a)
So, what if you print a first and define a later?
console.log(a) var a
First of all? JS is single-threaded, so JS theoretically executes code from top to bottom, so it stands to reason that an error a is not defined will be reported.
However, in fact, before the code is executed, a precompile is performed, and the declaration of the var variable is hoisted to the front.
So the above code is actually also equivalent to
var a console.log(a)
Variable hoisting only hoists declarations of variables to the front, assignments do not.
console.log(a) var a = 123 console.log(a)
will output undefined first, then 123
The precompiled code is as follows,
var a console.log(a) a = 123
function hoisting
Function declarations are hoisted as a whole, function calls are not hoisted
console.log(mytest) console.log(111) function mytest() { console.log(222) } console.log(mytest) console.log(333) mytest() console.log(444)
After precompiling:
function mytest() { console.log(222) } console.log(test) console.log(111) console.log(mytest) console.log(333) mytest() console.log(444)
Declaring a function using a variable takes the variable promotion route instead of the function declaration route
console.log(mytest) // undefined mytest() // mytest is not a function var mytest = function () { console.log(123) }
There will also be variable promotion inside the function. At this time, the global will be preprocessed first, and then the function will be preprocessed, and the variables and function promotion in the function cannot be promoted outside the function.
function mytest1() { console.log(a) // undefined b() // 456 var a = 123 function b() { console.log(456) } } mytest1() console.log(a) // a is not defined
Precompiled code:
function mytest1() { function b() { console.log(456) } var a console.log(a) b() a = 123 } mytest1() console.log(a)
If the variable inside the function is not defined and assigned directly, it will directly become a global variable (it should be regarded as a leftover bug, don't use it like this)
function mytest1() { b() // 456 a = 123 function b() { console.log(456) } } mytest1() console.log(a) // 123
So, is variable promotion first, or function promotion first?
Comments with different opinions are welcome.
From the result, the function takes precedence, but from the process point of view, the variable takes precedence
precompile step
What's going on here?
global precompile
First, let's take a look at the 3 steps of global precompilation:
- Create a GO object (Global Object)
- Find the variable declaration, use the variable as a GO attribute (in the browser, it is actually mounted on the window object), the value is undefined
- Find the function declaration, as the GO attribute value is the function body
case analysis:
console.log(111) console.log(a) function a() { console.log(222) } var a = 333 console.log(a) function b() { console.log(444) } console.log(b) function b() { console.log(555) } console.log(b)
-
Create a GO object, find variable declarations
GO: { a: undefined }
-
Find the function declaration (will overwrite the same name)
GO: { a: function a() { console.log(222) }, b: function b() { console.log(555) } }
-
The global precompilation process ends, and the real compilation process begins (remove the promotion first)
console.log(111) console.log(a) a = 333 console.log(a) console.log(b) console.log(b)
-
Combining properties of GO objects
console.log(111) console.log(a) // f a() { console.log(222) } a = 333 console.log(a) // 333 console.log(b) // f b() { console.log(555) } console.log(b) // f b() { console.log(555) }
Local (function) precompilation
GO objects are precompiled globally, so they are created and executed in preference to AO objects.
First, let's take a look at the 4 steps of partial precompilation:
- Create AO object (Activation Object)
- Find formal parameters and variable declarations, use variables and formal parameters as AO properties, the value is undefined
- Unification of actual parameters and formal parameters (assigning the value of the actual parameter to the formal parameter)
- Find the function declaration, assign the value to the function body
case analysis:
function mytest(a, b) { console.log(a) console.log(b) console.log(c) var a = 111 console.log(a) function a() { console.log(222) } console.log(a) function a() { console.log(333) } console.log(a) var b = 444 console.log(b) var c = 555 console.log(c) } mytest(123, 456)
-
Create AO object
-
Finding parameters and variable declarations
AO: { a: undefined, b: undefined, c: undefined }
-
Unification of actual participation and formal parameters
AO: { a: 123, b: 456, c: undefined }
-
find function declaration
AO: { a: function a() { console.log(333) }, b: 456, c: undefined }
-
The local pre-compilation process ends, and the real compilation process begins (remove the promotion first)
function mytest(a, b) { console.log(a) console.log(b) console.log(c) a = 111 console.log(a) console.log(a) console.log(a) b = 444 console.log(b) c = 555 console.log(c) } mytest(123, 456)
-
Combine the properties of the AO object
function mytest(a, b) { console.log(a) // f a() { console.log(333) } console.log(b) // 456 console.log(c) // undefined a = 111 console.log(a) // 111 console.log(a) /// 111 console.log(a) // 111 b = 444 console.log(b) // 444 c = 555 console.log(c) // 456 } mytest(123, 456)
From the result, the function takes precedence, but from the process point of view, the variable takes precedence, because the variable is promoted and overwritten by the subsequent function promotion.
Back to the point
After preparing the basic knowledge, it is natural to not forget the original intention and start to solve the first problem
refer to: Function declaration in block moving temporary value outside of block?
var a; if (true) { console.log(a) a = 111 function a() { } a = 222 console.log(a) } console.log(a)
analyze:
-
There will be two variable declarations a, one inside the block and one outside the block
-
Function declarations are hoisted and bound to internal block variables
var a¹; if (true) { function a²() {} console.log(a²) a² = 111 a² = 222 console.log(a²) } console.log(a¹);
-
Looking at it this way, this is not the same as local variable hoisting. However, when the original function declaration is reached, the block variable is assigned to the external variable
var a¹; if (true) { function a²() {} console.log(a²) a² = 111 a¹ = a² // When the original function declaration is reached, the block variable is assigned to the external variable a² = 222 console.log(a²) } console.log(a¹);
-
After that, the block variable and the external variable are no longer connected, that is, the change of the block variable will not lead to the change of the external variable.
-
Output f a() {}, 222, 111 in sequence
Why is the block variable assigned to the external variable when it reaches the original function declaration?
the spec says so. I have no idea why. – Jonas Wilms
Don't use block-level declarative functions
Don't use block-level declarative functions
Don't use block-level declarative functions
if (true) { function b() { console.log(111) } console.log(b) // f b() { console.log(111) } } console.log(b) // f b() { console.log(111) }
According to the above analysis:
if (true) { function b²() { console.log(111) } b¹ = b² // No definition, direct assignment, become a global variable console.log(b²) // f b() { console.log(111) } } console.log(b¹) // f b() { console.log(111) }
After we change the condition of the if statement to false:
- The content of the if statement is no longer executed, which is reasonable
- function not being hoisted outside
- But considering that the if condition is false, the content may not be precompiled
- But the outside b does not report the error b is not defined, but outputs undefined
Why? I don't know, I can't think of the reason, if anyone knows, let me know in the comments. (Won't use it like this, just curious why)
In fact, if you want to switch functions based on conditions, you can use the following form
let fn if (true) { fn = function () { console.log(111) } } else { fn = function () { console.log(222) } } fn()