{a = 1; {a = 1; function a(){} }; Console.log (a) and {function b(){}; b = 1 }; What console.log(b) outputs. In both cases, the output is the first one in the code block. So let’s start exploring

This paper is based on Chrome research. Warning ahead: This is a boring exploration note

Review the old and learn the new — variable promotion

I’m sure most of you already know this, so let me repeat it here. Js is parsed execution, and variable promotion is how the execution context in JS works. Variable and function declarations are brought forward at compile time.

console.log(a); // undefined
a = 1;
console.log(a); / / 1
var a;

/ / equivalent to
var a;
console.log(a); // undefined
a = 1;
console.log(a); / / 1
Copy the code

Unexpected global variable, but console will report an error if the first sentence is not removed

a = 1;
console.log(a); / / 1
Copy the code

Enhancement of function declarations

console.log(a); // Print function a
function a() {}

/ / equivalent to
function a() {}
console.log(a);
Copy the code

Function declarations are promoted more than variable declarations

console.log(a);
var a;
function a() {}

// both print functions a
console.log(a);
function a() {}
var a;
Copy the code

Note that the declaration in if is also promoted, even if it is not executed

console.log('a' in window); // true
if (false) {
  function a(){}}Copy the code

What happens to var, let, const

Life cycle: declare (scope registers a variable), initialize (allocate memory, initialize to undefined), assign

  • Var: In a scope with var, the declaration and initialization are completed before any statement is executed. This is why variables are promoted and undefined
  • Function: declaration, initialization, and assignment are all completed at the beginning, so variable promotion of a function has a higher priority
  • Let, const: The parser enters a block-level scope and finds the let keyword. The variable is declared first, not initialized. Cannot access ‘a’ before Initialization. This is an indication of a temporary dead zone. The initialization phase starts when the let line is parsed. If that row of the let is an assignment, initialization and assignment occur simultaneously

Note: variable promotion only promotes declaration, not initialization

The code block

As you can see, this problem is a little different from the usual variable promotion routine, with a curly bracket. Here the curly braces mean code blocks. Functions can actually be understood as “reusable code blocks”. The for loop is followed by a block of code, and the while loop is followed by a block of code that repeats the code inside the block:

while(1) {
    / / code block
}
Copy the code

You can even write a code block for no reason:

{
    console.log(123);
};
Chrome does not need to use a semicolon. Some other browsers (Safari) need to use a semicolon to avoid errors
// To be on the safe side, add a semicolon
Copy the code

Block-level scope

There is no block-level scope for var, so the following code prints 2

var a = 1;
{
  var a = 2;
};
console.log(a); / / 2
Copy the code

Let, const, is block-scoped and prints 1

// Const is the same
let a = 1;
{
  let a = 2;
};
console.log(a); / / 1
// Note: If there is no {} block, two lets (const) in the same scope will report an error
// Identifier 'a' has already been declared
Copy the code

Can you interrupt to see what’s going on

debugger;
var a = 1;
{
  debugger;
  var a = 2;
  debugger;
};
debugger;
Copy the code

The first point, variable promotion, var a; And since you’re executing directly from the console, A incidentally becomes A under window. Script expansion is unchanged, right

This is a boring thing to do, but let’s replace var with let

The first point is let, so we don’t see a in global. Let a = 1, window.a = undefined, ‘a’ in window === false

On the second point, we can see an extra block, which represents the block-level scope. It can also be seen that there is variable promotion in A!! If you access it, an error will be reported. In this case, any code in the code block above let a = 2 is a temporary dead zone. One other thing, now that we have let script unfold you can see that we have an A, and that a is the A outside of the block

The third point, you don’t have to think about it, the a in the block is definitely 2, and then the A in the script is still the one outside

And then it goes back out again, a is still a of script, no blocks

Practice is the sole criterion for testing truth

With that in mind and some of the breakpoint debugging tips we’ve covered, let’s get down to business. {a = 1; function a(){} }

{ a = 1; function a(){} }

So this is going to be 1, which is the conventional wisdom, so the function is brought forward, and then a is assigned 1. In the first point, Global, a is not a function but undefined

// Start counting
debugger; // Global => a: undefined
  {
    debugger;  // Block => a: function, Global => a: undefined
    a = 1;
    debugger; // Block => a: 1, Global => a: undefined
    function a(){}
    debugger;  // Block => a: 1, Global => a: 1
  };
debugger;// Global => a: 1
Copy the code

The a in the Block behaves like a regular variable promotion: the function declaration is greater than the variable declaration, so the a in the second Block is a function. Then a is assigned, and the third point, A, is 1, where Global’s a is undefined instead of 1. Number four, the a of Global is 1.

Function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){} = function a(){}

In fact, Chrome’s enhancements to function declarations in this case are undefined at first. Safari, on the other hand, does not use undefined. And {a = 1; Function a(){}} and {function a(){}; A = 1} is output 1. In Safari, the case is the same as if the code block were not added, which is equivalent to executing a = 1; function a(){}

{ function a(){}; a = 1 }

Let’s do {function a(){}; A = 1} a = 1} The answer to this question is, what is declared first in the block of code, is the result of a. The question becomes: why is the outer a the first declared A of the code block?

// Start counting
debugger; // Global => a: undefined
  {
    debugger;  // Block => a: function, Global => a: undefined
    function a(){};
    debugger; // Block => a: function, Global => a: function
    a = 1;
    debugger;  // Block => a: 1, Global => a: function
  };
debugger;// Global => a: function
Copy the code
  • whya = 1Does nothing for Global A?

The problem summary

{
    a = 1;
    // Question 1: why is Global a undefined instead of 1
    function a() {};
};


{
    function a() {};
    // 错 误 : A = 1 is a function of a
    a = 1;
};
Copy the code

Try more

Multiple function declarations, take the last one

  {
    function a(){}
    function a(b){return 1}
    a = 1;
  };
  // >> function a(b){return 1}
Copy the code

Multiple assignments, take the last one

  {
    a = 1;
    a = 2;
    function a(){}};/ / > > 2
Copy the code

Inside the code block, function is more like an “assignment statement”

debugger;
{
  debugger; // global的a: undefined
  function a() {}debugger; // ac
  a = 1;
  debugger; // ac
  function a(b) {}debugger;  / / 1
  a = 2;
  debugger;  / / 1
  a = 3;
  debugger;  / / 1
  function a(c) {}debugger;  / / 3
};
Copy the code

As you can see, the way Chrome behaves in code blocks has these characteristics:

  • In the code block, a variable promotion, a assignment, function declaration a is the same as normal
  • The function declaration of all a functions in the code block is promoted as usual, taking the last one
  • The a function declaration statement in the code block, in addition to the promotion, has another magic performance: it “passes” the value of the assignment statement (a = XXX) related to the previous a statement in the code block.function a(){}More like an “assignment statement.” Why is that? Maybe it’s the way the browser implements code blocks internally
  • Only the first a function declaration “passes”. Subsequent a function declarations only “pass” the value of the previous assignment statement (a = XXX) globally

We can try and guess the output using these rules:

{
  console.log(a, window.a);
  function a() {}console.log(a, window.a);
  a = 1;
  console.log(a, window.a);
  function a(b) {}console.log(a, window.a);
  a = 2;
  console.log(a, window.a);
  a = 3;
  console.log(a, window.a);
  function a(c) {}console.log(a, window.a);
  function a(ca) {}console.log(a, window.a);
};
Copy the code
Click to expand for an explanation
{
  console.log(a, window.a);
  // function a(ca) is undefined
  function a() {}console.log(a, window.a);
  Function a(ca) = function a(ca) = function a(ca) = function a(ca
  a = 1;
  console.log(a, window.a);// 1, function a(){}
  function a(b) {}console.log(a, window.a);
  // 1, 1 because the last assignment was 1, 1 is passed out
  a = 2;
  console.log(a, window.a);
  1 / / 2
  a = 3;
  console.log(a, window.a);
  / 1/3
  function a(c) {}console.log(a, window.a);
  // 3, 3, because last time a=3, 3 was passed on
  function a(ca) {}console.log(a, window.a);
  Function a(){}; // function a(){}; // function a(){}
};
Copy the code

For Safari, however, all this is the same as it would be without the code block {}. After a while, it turned out that the browser process is not the same. Breakpoint debugging proficiency +10, experience +3 😊