Relearn precompile

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:

  1. Create a GO object (Global Object)
  2. 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
  3. 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)
  1. Create a GO object, find variable declarations

    GO: {
        a: undefined
    }
    
  2. Find the function declaration (will overwrite the same name)

    GO: {
        a: function a() {
            console.log(222)
        },
        b: function b() {
            console.log(555)
        } 
    }
    
  3. 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)
    
  4. 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:

  1. Create AO object (Activation Object)
  2. Find formal parameters and variable declarations, use variables and formal parameters as AO properties, the value is undefined
  3. Unification of actual parameters and formal parameters (assigning the value of the actual parameter to the formal parameter)
  4. 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)
  1. Create AO object

  2. Finding parameters and variable declarations

    AO: {
        a: undefined,
        b: undefined,
        c: undefined
    }
    
  3. Unification of actual participation and formal parameters

    AO: {
        a: 123,
        b: 456,
        c: undefined
    }
    
  4. find function declaration

    AO: {
        a: function a() {
            console.log(333)
        },
        b: 456,
        c: undefined
    }
    
  5. 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)
    
  6. 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:

  1. There will be two variable declarations a, one inside the block and one outside the block

  2. 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¹);
    
  3. 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¹);
    
  4. 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.

  5. 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()

Tags: Javascript Front-end

Posted by wolfrat on Fri, 13 May 2022 01:40:45 +0300