preface
This was my first blog post two years ago, and it is memorable for me
Recently, I encountered a similar problem of variable promotion when USING Vue3. While reading my notes, I sorted out the layout of the article and posted it to the Gold Digging account. I put the actual problems in the last part of the article for analysis
The body of the
I saw a question about function declaration enhancement at dinner
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a) / / 1
Copy the code
Most beginners think the answer is 10 at first sight, and so do I
Function A is a global variable, so when b is executed, a is assigned a value of 10. Finally, return exits the function, and the console prints 10
Then I was shocked to see the answer. After b was executed, a was still 1
After consulting materials for analysis and summary, I decided to write the first blog in my life
Please forgive me for writing bad, if there are mistakes welcome to point out
scope
Before we get to variable declarations, there’s another core element of JavaScript that needs to be explained
scope
The purpose of a scope is to partition variables and add namespaces to variables. Variables defined in scope cannot be used outside of scope
Prior to ES6, JavaScript existed in two scopes
- Global scope
- Function scope
A new block-level scope has been added since ES6. In short, curly bracketed code that exists in let/const is now a separate scope
For example
if (true) {
let color = "blue";
}
console.log(color); // Uncaught ReferenceError: color is not defined
Copy the code
The appearance of let in line 2 makes the curly braces a block-level scope. Printing color outside the block-level scope throws a reference error because the outer scope cannot access the inner scope
Another effect of scope is to avoid the definition of useless variables. Imagine that without a scope, all defined variables could be freely accessed. This would result in a high memory footprint. By destroying a scope, you can destroy all the variables defined inside the scope, freeing up memory
The nice thing about scopes is that they’re static, so you can tell when you’re writing code what scope a variable is in
JavaScript searches for a variable layer by layer in the current scope up to the global scope, and throws a reference error if the variable is still not found
Uncaught ReferenceError: a is not defined
Variable declaration promotion
Declaring a variable with var raises it to the top of the current scope (global/function), for example
if (true) {
var color = "blue";
}
console.log(color); // blue
Copy the code
The variable declaration of the if statement raises the color variable to the top of the current scope
Because the block-level scope is not generated using let/const, the color variable is promoted directly to the topmost global scope
And the above operations are performed in the pre-compilation phase before JavaScript runs, the code essence is as follows
// Precompile phase
var color;
// Execution phase
if (true) {
color = "blue";
}
console.log(color); //"blue"
Copy the code
Here’s another classic interview question
for (var i = 0; i < 10; i++) {
// ...
}
console.log(i); / / 10
Copy the code
The precompilation phase defines the I variable, which is promoted to global scope
The code then runs, and after executing the for loop, the value of I is assigned to 10 of the last loop, while it still exists in global scope, and finally prints 10
// Precompile phase
var i;
// Execution phase
for (i = 0; i < 10; i++) {
// ...
}
console.log(i); / / 10
Copy the code
You can see that the global window object has an additional I attribute
Going back to the first example, if the if statement is false
if (false) {
var color = "blue";
}
console.log(color); //undefined
Copy the code
So the code essence after variable declaration enhancement is as follows
// Precompile phase
var color;
// Execution phase
if (false) {
color = "blue";
}
console.log(color); //undefined
Copy the code
The color variable is declared in the precompile phase, but is not assigned because the if condition is false, and undefined is displayed
But if you simply comment out the if statement, you’re throwing it wrong
// if (false) {
// color = "blue";
// }
console.log(color) // ReferenceError: color is not defined
Copy the code
Because JavaScript tries to find the color variable up the scope until it is not found in the outermost global scope, it eventually throws a variable reference error
JavaScript treats a variable declaration as two parts
- Declaration: var color
- Assignment: color = “blue”
Declaration takes place during the precompilation phase, elevating the variable declaration to the top of the current scope, with a default value of undefined
The assignment operation is left to wait for the code to execute
Variable promotion happens when JavaScript is precompiled, before the code starts running
Function declaration promotion
JavaScript has two ways of creating functions
-
Function declaration
-
Functional expression
Function declaration
function sum(num1, num2) {
returnnum1 + num2; };Copy the code
Functions also have a declaration promotion, and the differences and similarities between variables are:
- Similarities: both will be promoted to
The current
Scope (global/function) top - Difference: function declaration enhancements are implemented during precompilation
The function and the body of the function
Are all advanced to the top of the current scope
This allows us to call the function before it is declared
sum(10.10); / / 20
function sum(num1, num2) {
returnnum1 + num2; };Copy the code
Equivalent to the following code
// Precompile phase
var sum;
sum = function (num1, num2) {
returnnum1 + num2; };// Execution phase
sum(10.10); / / 20
Copy the code
Functional expression
var sum = function (num1, num2) {
return num1 + num2;
}
Copy the code
The operation of assigning an anonymous function to a variable is called a function expression
Functions created through function expressions are not promoted, unlike function declarations
But the sum variable is declared by var, so there is a variable declaration, and the code is as follows
// Precompile phase
var sum;
// Execution phase
sum = function (num1, num2) {
return num1 + num2;
}
Copy the code
An error is thrown if a function is used before a function expression declaration
sum(10.10); // Uncaught TypeError: sum is not a function
var sum = function (num1, num2) {
return num1 + num2;
}
Copy the code
The code essence is as follows
// Precompile phase
var sum;
// Execution phase
sum(10.10); // Uncaught TypeError: sum is not a function
sum = function (num1, num2) {
return num1 + num2;
};
Copy the code
You can think of a functional expression as having two parts
-
Declare variable sum
-
Assign the variable sum to an anonymous function
A function created by a function expression has no function promotion, and sum(10,10) is searched up the scope step by step
Until the outermost global scope finds the sum variable defined by var, which is undefined, and attempts to execute sum as a function and pass in arguments, resulting in an error
Both declare the order of ascension
Look at this example when there are both function declarations and variable declarations
getName(); / / 1
var getName = function () {
console.log(2);
}
function getName() {
console.log(1);
}
getName(); / / 2
Copy the code
When there are both function and variable declarations, the function declaration precedes the variable declaration
The code essence is as follows
// Precompile phase
// The priority function declaration is promoted
var getName;
getName = function () {
console.log(1);
};
// The variable declaration is promoted
// We have already declared the getName variable. If we declare the variable repeatedly through var, subsequent declarations will be ignored
var getName;
// Execution phase
getName(); / / 1
getName = function () {
console.log(2);
};
getName(); / / 2
Copy the code
If a variable of the same name is declared more than once in the same scope, subsequent declarations are ignored
An aside: Using ES6 let/const to declare a variable with the same name is an error
var a = 1
var a = 2 // success
let b = 1
let b = 2 // Uncaught SyntaxError: Identifier 'b' has already been declared
Copy the code
As an aside: Newer versions of Chrome can let multiple times on the console, but are only for debugging use and are not a standard specification
Back to the original interview question
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a); / / 1
Copy the code
And this leads to the second scope, the function scope, where the b function itself will be a function scope
At the same time, function B uses variable A in two places, one is the assignment, the other is the function declaration
Function declarations of A are promoted to the top level of function scope of b, and occur during precompilation
For function B alone, the code is essentially the following
function b(){
/ / the precompiled
var a
a = function(){}
// Code execution
a = 10
return
}
Copy the code
Function a() {} after return is promoted to the top of function B during precompilation
Unlike the outermost global scope, since b is a new scope, the previously mentioned scope has the effect of partition variables, so variables declared in this scope cannot be accessed by the outer layer, and variable A will be redefined in this scope
The complete code is as follows:
/ / the precompiled
var a
// Code execution
a = 1
function b() {
/ / the precompiled
var a;
a = function () {}
// Code execution
a = 10;
return;
}
b();
console.log(a); / / 1
Copy the code
Order as follows
Precompilation phase:
- Due to the promotion of var, define variable A in the global scope with the value undefined
- Since the function declaration is promoted, we define variable A in function b with the value undefined
- Function () {} function() {}
Code execution phase:
- Assign the variable a in the global scope to 1
- Execute function B
- Enter function B and assign 10 to a in function scope
- Exit the function and destroy the scope of the function. At this point, the variable defined under the scope of the function is destroyed
- Prints the global scope variable a with the value 1 defined in the first step
Write in the back
A seemingly simple interview questions, behind the declaration of promotion, var characteristics, scope function, as the front end of the basis of the topic, or more classic
There was a time when I thought there was a better syntax for ES6, why would there still be var
These days, I encountered a problem that the variable promotion caused the call to be used in advance and thrown wrong after transferring the old version Babel ES6 to ES5
Here is a piece of Vue3 source code
export function traverseStaticChildren(n1: VNode, n2: VNode, shallow = false) {
const ch1 = n1.children
const ch2 = n2.children
if (isArray(ch1) && isArray(ch2)) {
for (let i = 0; i < ch1.length; i++) {
// this is only called in the optimized path so array children are
// guaranteed to be vnodes
const c1 = ch1[i] as VNode
let c2 = ch2[i] as VNode
if(c2.shapeFlag & ShapeFlags.ELEMENT && ! c2.dynamicChildren) {if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.HYDRATE_EVENTS) {
c2 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
c2.el = c1.el
}
if(! shallow) traverseStaticChildren(c1, c2) } } } }Copy the code
The webpack compressed plug-in code is shown below
function qe(t, e, n = !1) {
const r = t.children
const o = e.children
if (Object(i.m)(r) && Object(i.m)(o))
for (var t = 0; i < r.length; t++) {
const e = r[t];
let i = o[t];
1& i.shapeFlag && ! i.dynamicChildren && ((i.patchFlag <=0 || i.el = e.el),
n || qe(e,i))
}
}
Copy the code
Finally, Babel ES6 is translated into ES5 to output the final code
function qe(t, e, n = !1) {
var r = t.children
var o = e.children
if (Object(i.m)(r) && Object(i.m)(o))
for (var t = 0; i < r.length; t++) {
var e = r[t];
var i = o[t];
1& i.shapeFlag && ! i.dynamicChildren && ((i.patchFlag <=0 || i.el = e.el),
n || qe(e,i))
}
}
Copy the code
The error occurs in line 4
Although the variable names are unrecognizable, just focus on the fourth and seventh lines
if (Object(i.m)(r) && Object(i.m)(o))
Copy the code
var i = o[t];
Copy the code
The variable I in line 4 and variable I in line 7 are not the same variable, corresponding to isArray and c2 respectively in source code. The WebPack compression plugin has changed the variable name, but because of the let block-level scope isolation, there is no problem
However, in the old version of Babel, let of ES6 was directly converted into VAR of ES5 without special processing, resulting in the loss of scope limit of these two variables with the same name and variable conflict
At the same time, the variable I declared by var has a variable declaration enhancement, which is promoted to the top of the scope of the QE function during precompilation, and assigned to undefined by default
So when the fourth line of code attempts to access the m property, it ends up throwing an error because I is undefined
If you do not master the knowledge of variable improvement, it is difficult to investigate the reasons for errors. I hope you will not go far in the pursuit of new technology and eventually lose the foothold of the front end
‘