Keywords: variable declaration, precompilation, scope, strict mode

Variable declarations

Creating variables in JavaScript is often called “declaring” variables, and there are three ways to declare a variable, including var, let, and direct assignment, which is often ignored.

Friendly reminder:If you want to verify the code that appears below, use your browser to visit the About :blank page and copy it to the clean browser console to validate it, in case a dirty web environment has declared some of the test variable names.

The var keyword declares variables

We use the var keyword to declare variables:

var a;
Copy the code

After a variable is declared, the variable is empty (undefined). To assign a value to a variable, use the variable assignment symbol (equal sign) :

var a;
a = 'var_a';
Copy the code

If the value is undefined, there is no difference:

var alm = undefined

We can also declare a variable and assign it a value using the var keyword, like this:

var a = 'var_a';
Copy the code

Null variable declaration

Var var var var var var var var var var var var var var var var var var var var var var var var As the name implies, a direct variable declaration is a direct assignment, without using the var and let keywords. Let’s call it an empty variable declaration, like the following:

a = 'a'
Copy the code

You might look at code like this and wonder, isn’t it variable declaration? There’s no declaration here, just a variable assignment. ! Yes, you are absolutely right, the above code is mostly variable assignment, but only if variable A is already declared. Strictly speaking, it should be called: in the current scope and above, variable A is declared

scope

Here we have to start with the concept of scope, which is standard in almost all programming languages, including JavaScript. Without the distinction of scope, we can imagine how confusing the code we write, we have to be careful whether the variable we declare is already declared, worried that the variable we declare can not declare or overwrite the already declared variable, resulting in all kinds of unpredictable problems.

Scope can be divided into global scope and function scope. The global scope is the area that all function scopes can access. The current scope can access all declared variables and functions of the current and parent scopes, but cannot access any variables and functions of the child scopes. For details, search scope chain by yourself.

The global scope of the browser environment

We all know that the browser is the most common environment for JavaScript code to run, and if we write JS code directly from the browser’s DevTools console, that’s a browser-provided JavaScript global scope. All of the variables we declare directly in there (excluding the variables declared using the let keyword described below) are actually adding a new property to the window object. We can verify this by declaring a variable using the var keyword:

var a = 'var_a'
console.log(a) // var_a
console.log(window.a) // var_a
console.log(window.a === a) // true
Copy the code

A function scope can have a superior scope (one level or multiple levels) and a subscope (one level or multiple levels), so the global scope can also be understood as a root scope, which is the top-level superior scope of all function scopes. The forehead.. Seems to say a bit complicated, in fact very simple, draw a picture is very clear at a glance!

StateDiagram -v2 Global scope --> Function scope 1 Global scope --> Function scope 2 Global scope --> Function scope 3 Function scope 2 --> Function scope 4 function scope 2 --> Function scope 5 function scope 5 --> function scope 6

The tree above can be used to describe the relationship between JavaScript scopes. The global scope is the root node of the tree. The current and above scopes of function scope 6 include: function scope 6, function scope 5, function scope 2, and global scope. If we declare an undeclared variable in function scope 6, then JavaScript will declare a new variable for us at the top of the global scope, as follows:

// console.log(a); // a is not defined

function f1() {
    var a = 'f2_a'
}

function f2() {
    function f4() {
        var a = 'f4_a'
    }

    function f5() {
        function f6() {
            a = 'f6_a'
            console.log(a); // f6_a
        }
        f6()
    }

    f4()
    f5()
}

function f3() {
}

f1();
f2();
console.log(a); // f6_a
Copy the code

To explain the above code, if we typed the above code directly into the browser console, we would have built a scoped environment exactly like the tree above. The body of the f6 function is the figure’s scope 6, where we directly assign ‘f6_a’ to a variable a. The function f6 and console.log(a) in the global scope output the same values as those assigned by f6. The function f6 and console.log(a) in the global scope output the same values assigned by F6. This means that while only variable a is assigned in f6, JavaScript declares a new variable a in the global scope and then assigns the value ‘f6_A’ when the f6 function executes.

JavaScript variable predeclarations and variable scope enhancement

If this line of code is executed, the browser environment will throw an exception because it can’t find variable A. Why is that? JavaScript will declare a new variable a in the global scope.

In fact, this involves a design of the JavaScript language called the precompilation mechanism. What is precompiled? We all know that JavaScript is a scripting language, and the nature of a scripting language is to interpret execution, to have code interpreted line by line in order. JavaScript, however, is a bit different. Before executing the JS code, it will first check whether there are any variables in the current scope declared by the var keyword or any variables directly assigned by the keyword. If there are any variables in the current and above scope, it will declare the names of these variables in the first place. So where do we declare it? This makes a difference. For the VAR keyword, the scope of the variable is only promoted to the top of the current scope, whereas for directly assigned variables, JavaScript promotes it to the global scope.

Strictly speaking, this should not be called precompiled; “preinterpreted” might be more appropriate, since JavaScript is not a compiled language by nature.

It is important to note that variable accessible scope promotion occurs when the code has been executed in this scope, otherwise there will be no variable accessible scope promotion. If you look at the code above, it is clear that the first line of code is in global scope. At this point, the code has not yet been executed to the f6 scope, so variable A in the F6 scope has not been promoted to variable accessibility, so there is no declaration of variable A in the global scope. So if we declare a variable a on line 2 of our global scope, we won’t get an error on line 1? Yes! Do not believe you to execute the following code try (please refresh the page environment under the console execution) :

console.log(a); // undefined
var a = 'global_a'
console.log(a); // global_a
Copy the code

Let’s try variable promotion with direct assignment again:

console.log(a); // a is not defined
a = 'global_a'
console.log(a); // global_a
Copy the code

The first line of the global scope still cannot find variable A. ! Yeah, this is really bad… Directly assigned variables are promoted not at the precompile stage, but at the corresponding line of code execution! So there are two different situations:

// console.log(a); // a is not defined
function f1() {
    console.log(a); // a is not defined
}

f1();
a = 'global_a'
console.log(a);
Copy the code
// console.log(a); // a is not defined
function f1() {
    console.log(a); // global_a
}

a = 'global_a'
f1();
console.log(a); // global_a
Copy the code

Looking at the previous example, if we promoted variable A to global in F1 ahead of time (the f1 function was executed first), the code executed f1, console.log(a), f2(), f4(), f5(), f6(), If a = ‘f6_A’ assignment is encountered at f6, JavaScript will not promote the variable, because there is already a global variable named A, so this is a pure global assignment.

// console.log(a); // a is not defined

function f1() {
    // Variable A is promoted to global
    a = 'f1_a'
    console.log(a);// f1_a
}

function f2() {
    function f4() {
        var a = 'f4_a'
    }

    function f5() {
        function f6() {
            // There will be no variable promotion since f1 has already been promoted once
            a = 'f6_a'
            console.log(a); // f6_a
        }
        f6()
    }

    f4()
    f5()
}

function f3() {
}

f1();
console.log(a); // f1_a
f2();
console.log(a); // f6_a
Copy the code

Ok, we understand the above complicated situation, so the following situation is a piece of cake, let’s practice:

var a ='var_a'
function fn(){
    a = 'fn_a'
    console.log(a); // fn_a
}

fn();
console.log(a); // fn_a
Copy the code

How does the above code work with JavaScript interpretation? According to the theory we analyzed above, first in the global precompilation phase, the variable a declared by var is promoted to the current scope, which is declared at the top of the global scope here. When the first line of code is completed, variable A is assigned to ‘var_a’. When fn is about to be executed, the same precompile operation is performed, and variable A is encountered again, but a is already declared in the upper scope, so the assignment of a on line 3 is only considered as an assignment statement. Variable predeclarations and variable scope promotion are no longer performed. So the answer is pretty straightforward, fn_A will be printed twice after the code executes.

The let, const keywords declare variables

Now you know how bad JavaScript variable declarations are! Declare variables with the var keyword very carefully! So in ES6, it is no longer recommended to use var keywords in favor of let and const keywords! These two keywords are only supported in ES6 syntax (browsers that do not support ES6 syntax cannot use them). The let keyword declaration variable is no different from var in usage:

let a = 'let_a';
Copy the code

Const variables cannot be changed again. We must assign a value to the variable or throw an exception as follows:

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

The let keyword is designed to prevent all of the inexplicable problems mentioned above due to JavaScript precompilation and variable promotion features. Variables declared using let will not be pre-declared and scoped!

But take a look at the code below and you’ll find something new…

let a = 'let_a'
console.log(a) // let_a
console.log(window.a) // undefined
Copy the code

The variable a declared by let is not mounted to the global object Window. Isn’t there a global scope inside the Window object?

In es6, in addition to the let and const variable declaration keywords, the concept of block-level scope was added. What is a block? The area inside the braces “{}” is a code block, such as if, for, etc. The scope of let and const keywords is within this block. Variables declared cannot be accessed outside the scope of the specified block.

Therefore, the function scope mentioned above is actually only a local scope, and the complete local scope should include two kinds of function scope and block level scope. Therefore, the following code execution results are understandable.

function fn() {{// console.log(a)// Cannot access 'a' before initialization
        let a = 'let_a'
        console.log(a) // let_a
    }
    console.log(a) // a is not defined
}

fn()
Copy the code

Block-level scope

Question: What scope is the code written by the console in? Global scope Window? In the Sources/Snippets? The eval?

eval('let a = 1; console.log(a)')/ / 1
console.log(a);// ReferenceError: a is not defined
Copy the code

No, these are block-level scopes, equivalent to the following:

{
  let a = 1
  console.log(a);/ / 1
}

console.log(a);// ReferenceError: a is not defined
Copy the code

closure

Closure is a kind of expression of function scope, we said to the front, unable to get the inner layer under the outer scope under the scope of variables, the different function scope is not visit each other variables, but my inner scope can access itself and the outer scope of variables, it is actually a closure, closure is not most functional languages have some concepts, In itself or because of the scope of the problem.

Memory management

Let temporary dead zone

Variables declared using the LET keyword do not undergo variable promotion and pre-declaration, but there is a concept of temporary dead zones. In theory console.log(a) should print vara without the effect of temporary dead zones, but in reality js will also scan the line before fn is executed, and then set the time before let declaration to TDZ (” temporary dead zone “). Variables cannot be accessed. Syntax errors are not reported in advance when code is written.

var a = 'vara';

function fn(){
    console.log(a);// Cannot access 'a' before initialization
    let a = 'leta';
}

fn();
Copy the code

Strict mode

To prevent developers from accidentally using the var keyword to declare variables without knowing it, resulting in possible exceptions. Es6 provides strict patterns for developers to use “use strict” in different scopes; The scope can be changed to a strict mode scope as follows:

"use strict";
fn();

function fn() {
    a = 'fn_a' // a is not defined
}
Copy the code

With strict schema constraints, the above variable a will no longer be pre-declared and scoped, but you will notice that fn is not defined in the second line.

conclusion

OK, that’s all we have to say about JavaScript’s variable predeclarations and scope promotion. To summarize, JavaScript interprets executing code by precompiling it in the current scope. In the precompilation phase, all variables declared with the var keyword in the current scope are found first, and the variable declaration operation is immediately performed at the top of the current scope. In the code interpretation phase, when you find that you need to execute a direct assignment to a variable whose name has never been previously declared, JavaScript immediately declares a variable with that name at the top of the global scope, and then performs the assignment! It is shown in a flow chart as follows:

Graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> graph TD --> Variable assignment

Statement: the above analysis results only my test analysis, if there are mistakes or unclear description, please criticize and correct! I just learn front-end, JavaScript understanding is not in-depth, such as the article exists in the error to you misled and troubled, please forgive me! Please inform me in time and I will correct the relevant content in time

Refer to the article

  1. In-depth understanding of JavaScript scopes and scope chains
  2. Javascript- Annoying closures
  3. Js closure test