It’s a very old topic indeed. The discussion in this article is based on the ES5 standard, with a little reference to ES6. Nitro will perform slightly differently with Chrome V8 as the JavaScript engine involved.

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

From this example, there are at least two oddities:

  1. We’re defining a functionfooIt was executed before without an error.
  2. We’re defining variablesaI visited them earliera, but returnedundefinedNo error is reported, indicating executionfoowhenaIt’s already there, it’s just not assigned.

This involves the promotion of JavaScript.

Promotion is just a description

Although the above code can be seen as:

var a;
function foo(){
    console.log(a)
};
foo(); 
a = 1;  
Copy the code

But this is a figurative description of how the JavaScript engine handles things, as if variable and function declarations have been elevated to the top of the current code block. The code didn’t actually change, but the engine did something else behind the scenes.

At compile time before the code executes, the JavaScript engine scans our code and collects all function declarations and variable declarations, then adds them to a data structure called the context logger under the Lexical Environment.

Lexical scope consists of two parts:

  1. Environment Record
  2. Possibly null reference to an outer Lexical Environment (instantiation)

The environment logger does what is called identity-variable mapping, and its data structure can be understood as some sort of set of key-value pairs. It is important to note that a “identifier” is a variable name or function name, and a “variable” refers to a primitive value or reference to a reference type. Conceptually it can be described as follows:

// lexicalEnvDemo
lexicalEnvironment = {
    outerLexicalEnvironment: null.environmentRecord: {
        a:  <value>,
        funcA:  <reference to funcA>,
        objB:  <reference to objB>,
    }
}
Copy the code

The lexical scope is created before execution and lives through the execution phase, which explains why it looks like foo can be used before function declaration, and variable a can be accessed before definition. This is the whole process of ascension. Throughout the execution, variables and functions needed by the code are searched in lexical scope, and two results naturally occur:

  1. To find theidentifier, returns a value or reference, or if there is no assignmentundefined
  2. Can’t findidentifier, report an error and print itUncaught ReferenceError: <identified> is not defined

The process of ascension

When the JavaScript engine finds a declaration at compile time, it adds the declaration to the environment logger. This process is divided into 2 steps and involves only creation and initialization:

  1. Create:identifierAdd to the environment logger and open oneidentifier: <uninitialized>location
  2. Initialization:
    • Function:identifier: <reference to aFunction>
    • Variables:identifier: undefined(After all, the engine has no way of knowing what our code will do at this pointaA what type)

From the first example, we can also see that the engine treats variable A differently from function foo. After the variable a is promoted, the value is not promoted, but given undefined. The function foo, on the other hand, is comprehensively promoted so that foo() executes correctly.

Finally, when the engine encounters an assignment to identifier at runtime, it will eventually add the value or reference to the environment logger, Identifier:

or A:

Many tutorials on the web suggest that there is a third assignment step when upgrading variable declarations, changing identifier: undefined to identifier:

. I’m not so sure about this, because of the code at the beginning:

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

Foo () has reached the execution stage, and a is still undefined. So I don’t think assignment exists in ascension. I could be dead wrong. Please correct me.

Promotion function declaration (function)

The promotion of function declarations for function statements can be considered to have the highest priority and is not disturbed by variable declarations of the same name. This explains why foo works at the top.

Promotion variable declaration (var)

var a = 1;
Copy the code

It gets broken down by the engine

var a;  // compile time processing
a = 1;  // Return to the execution period
Copy the code

Function expressions and class expressions

var a = function (){};
var b = new Object(a);Copy the code

The engine’s handling of functional and class expressions can be thought of as variables.

/* compile time processing */
var a;
var b;
/* Execute */
a = function (){};
b = new Object(a);Copy the code

Promotion and assignment occur at two different times and do not affect each other.

The nuptial processing

  1. If a function with the same name appears, the last one overwrites the previous one.
foo(); / / 2
function foo(){
    console.log(1)};function foo(){
    console.log(2)};Copy the code
  1. If there are variables with the same name, they do not affect each other, assuming that their initialization results are allundefined.
  2. If a function or variable with the same name appears, since the function has the highest priority of promotion,identifierIt must be pointing to a function,undefinedReferences to functions are not overridden.

Is there a variable promotion priority?

There’s a lot of talk on the Internet about prioritizing. Mabishi Wakio’s tutorial, for example, benefited me a lot, but I found the part about Order of Precedence to be less rigorous. In this paper, promotion declaration is considered to have priority, and variable assignment > function declaration, while function declaration > variable declaration. The reason is shown in the following two pieces of code:

var double = 22;
function double(num) {
  return (num * 2);
}
console.log(typeof double); // Output: number
Copy the code

Variable assignment > function declaration, so double is of type number.

var double;
function double(num) {
  return (num * 2);
}
console.log(typeof double); // Output: function
Copy the code

Function declaration > variable declaration, so double is function. But such priorities do not explain the output below

double(4);
var double = 22;
function double(num) {
  console.log(num * 2.typeof double); // 8, "function"
}
Copy the code

Obviously function declaration takes precedence over variable assignment. So I think it’s not that complicated, and there should be only one priority, which is that declarations of funciton statements take precedence over declarations of var statements recorded by the environment logger. Double declarations of the same name do not use undefined to override references to function double(){} of the same name. Assignment to double does overwrite the function reference, and that is an execution-phase matter, not a promotion phase.

// Improve the placeholder
function double(num) {
  return (num * 2);
}
// promote without overwriting
var double;
double = 22;
console.log(typeof double); // Output: number
Copy the code

I think it’s a little more succinct. I could be quite wrong.

letThere is variable promotion

var a = 1;
function foo(){
    // beginning of TDZ
    console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
    / *... * /
    // end of TDZ
    let a;
}
foo();
Copy the code

The example here shows that a variable declared by let must be promoted in its scope but not promoted in initialization and assignment. Otherwise a in foo would be global 1. However, this variable is not available from the start of the code block until a is declared by let. This is grammatically known as a “temporal dead zone” (TDZ).

Limited to the promotion differences, let can be approximated as var in strict mode, which cannot be used without being declared. Const is more strict. Variables declared by const must be initialized directly.

const a; // Uncaught SyntaxError: Missing initializer in const declaration
Copy the code

conclusion

With the launch of ES6, let and const applications have largely ended the myth of whether to improve or not, and they are no longer as ambiguous as var. It’s like saying stop it. You can’t do it without a statement.

The resources

  1. Hoisting
  2. JavaScript Hoisting
  3. Demystifying JavaScript Variable Scope and Hoisting
  4. Annotated ECMAScript 5.1
  5. Javascript execution environment, lexical environment, variable environment
  6. Lexical environment and function scope
  7. In Modern JavaScript — let, const, and var
  8. In ES6, let has variable declaration enhancement