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

  1. Function declaration

  2. 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 toThe currentScope (global/function) top
  • Difference: function declaration enhancements are implemented during precompilationThe function and the body of the functionAre 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

  1. Declare variable sum

  2. 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:

  1. Due to the promotion of var, define variable A in the global scope with the value undefined
  2. Since the function declaration is promoted, we define variable A in function b with the value undefined
  3. Function () {} function() {}

Code execution phase:

  1. Assign the variable a in the global scope to 1
  2. Execute function B
  3. Enter function B and assign 10 to a in function scope
  4. Exit the function and destroy the scope of the function. At this point, the variable defined under the scope of the function is destroyed
  5. 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