1

This article introduces scope in JavaScript. Let’s start with what scope is, introduce JavaScript’s lexical scope, and then talk about lexical scope to dynamic scope, and compare the two. After talking about the classification of scopes, we use an example to explain the role of scopes. Then talk about the classification of scopes and how to write functions because of features

What is the scope?

Compilation principle

A piece of source code in a program goes through three steps before being executed, collectively referred to as "compiling"

  • Tokenizing/Lexing

    • var a = 2; 31f607245323272d45c78d827a0f182e---拆解成最基本的词法单元vara=2
  • Parsing/Parsing (Parsing)
  • Converts a stream (array) of lexical units into a number that represents the program's grammatical structure, consisting of natively nested levels. This tree is called the "Abstract Syntax Tree" (AST, which is also the key to modern front-end frameworks)
  • code generation

    • The process of converting an AST into executable code is called code generation

- "You Don't Know JavaScript Volume Up"

In short: any JavaScript code snippet is compiled before execution

understand scope

The assignment of a variable performs two actions, first the compiler declares a variable in the current scope (if it has not been declared before), then at runtime the engine looks for the variable in the scope, and if it can find it, it will assign

scope nesting

Scope is a set of rules for finding variables by name

Nesting of scopes occurs when a block or function is nested within another block or function. Therefore, when a variable cannot be found in the current scope, the engine will continue to search in the outer nested scope until the variable is found, or the outermost scope (that is, the global scope) is reached.

Let's understand scope with an example

 var a = 1;
function foo() {
    var a = 2;
    console.log(a);
    var myFunction = (function () {
        var a = 3;
        console.log(a);
    })();
}
function bar() {
    var a = 4;
    foo();
}
bar(); // 2 3

Translate the function into an image as follows:

作用域示意图

Each scope is a domain. In this domain, your variables can be customized, and they can be the same as the outside ones, or they can be used arbitrarily. However, when the code is executed, first look for variables in the execution domain, and then go to the outer layer if you can't find them. , layer by layer until the global scope

Note that where your code is written, your scope is located. In the example, the foo function and the bar function are at the same level (same level), and their internal variables do not affect each other. This is the scope of JavaScript

Lexical and dynamic scoping

There are two main modes of working with scopes. The first is the most common, lexical scoping used by most programming languages, and the other is called dynamic scoping, such as Bash scripting.

Lexical scoping is a set of rules for how and where the engine looks for variables. The most important feature of lexical scope is that its definition process occurs in the writing phase of the code (assuming you do not use eval or with), that is, your scope is determined after you write it.

JavaScript does not have dynamic scoping. It only has lexical scope, which is straightforward, but the this mechanism is somewhat like dynamic scope

The main difference: lexical scope is determined at the time of writing code or definition, while dynamic scope is determined at runtime (so is this!)

Lexical scoping concerns where the function is declared, while dynamic scoping concerns where the function is called from

lexical scope

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

function bar() {
    var a = 3;
    foo();
}

var a = 2;

bar(); // a = 2

dynamic scope

PS: Assuming there is dynamic scope in JavaScript, there really isn't

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

function bar() {
    var a = 3;
    foo();
}

var a = 2;

bar(); // a = 3

You can visually understand that the dynamic scope is dynamic. My bar is called globally, and foo is called in bar, and foo is called to print console.log(a). In this case, it can be understood as this way of thinking:

 - function foo() {
-    console.log(a)
- }

function bar() {
    var a = 3;
    + function foo() {
    +    console.log(a)
    + }
}

var a = 2;

bar()

Move the foo function to the bar function, then when I call bar, in the bar function, a is naturally 3. This is a way of thinking that is different from lexical scoping, and is the same as this (whoever calls me, I will point to. Very dynamic)

This is also what makes people anxious in JavaScript. There are two modes in a language. After you learn scope, when you understand this in the thinking mode of scope, you are often confused. Why does this point to and fro? Obviously you can write this.name = name in the subfunction, why should you assign it to that first? Now I see if the dynamic scope is a little confusing. It turns out that the scope is the scope, and this exists in the object in a dynamic form.

The role of scope

The author has a callback function here. When you understand this example, your understanding of the scope is already Entering the room getting Started:

 var a = 1;
console.log(a);
var myFunction = (function () {
    var a = 2;
    console.log(a);
    var myNextFuntion = (function () {
        var a = 3;
        console.log(a);
        var myNextNextFunction = (function () {
            var a = 4;
            console.log(a);
        })();
    })();
})();

In each function, there must be a scope, and the scope is the scope. In our scope, the printed a is the a in my scope. The scope chain is that if there is no scope in my scope, go to my superior to find this variable (if not found, it is undefined)

Another angle: the variables in the function are private variables, which can only be accessed by this function. The upper scope cannot access the variable in the lower scope

Classification in scope

In JavaScript, a scope is the context in which code is executed. There are four types of scope: global scope, function scope (also called "local scope"), block scope, and eval scope

Code defined with var inside a function is local in scope and "visible" only to other expressions of that function, including code in nested/subfunctions. A variable defined in the global scope is accessible from anywhere because it is the highest/last in the scope chain.

The following code, each declaration of foo is unique because of the scope

 var foo = 0; // 全局作用域
console.log(foo); // 0
var myFunction = (function () {
    var foo = 1; // 函数作用域
    console.log(foo); // 1
    var myNestFunction = (function () {
        var foo = 2; // 函数作用域
        console.log(foo); // 2
    })();
})();
eval('var foo = 3; console.log(foo);'); // eval() 作用域
// let/const 块作用域,变量无法提升
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i)
    });
}

Because the global scope and function scope have already been introduced when the scope is introduced, and the concept is relatively simple, I won't repeat them here. The eval() function will execute the incoming string as JavaScript code, that is, a sentence has a scope, which is easy to understand, and the author will not talk about it. Let's look at block scope

block scope

Before ES6, we did not have block scope. In ES6, let关键字 , const关键字 can form block scope

Let's first think about what happens when there is no block scope:

 for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i); // 55555
    });
}

What do we want it to be like? In the for loop, each i is independent, even if setTimeout has a delay, each i is entered into the event loop queue, and then printed out one by one

But the actual situation is that var i = 0 is exposed in the global scope, because setTimeout has a lag (as long as it is setTimeout, put the function in it into the macro task), so the for loop is executed first, the for loop's The result is 1,2,3,4,5. But because i is a global variable, the i in setTimeout is unified to 5

How to break? Before ES6, the function in the for loop was changed to an immediate execution function (forming a scope), and each time the loop, the IIFE would generate a new scope, so that the callback of the delayed function could enclose the new scope in each loop. Inside the loop, each iteration will contain a variable with the correct value for us to access

 for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        });
    })(i);
}
// 1 2 3 4 5

The function is executed each time an i is passed in, and the scope of each i is independent

Later, with ES6, you only need to change var to let

 for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    });
}

The reason is very simple, because let has its own block-level scope. The author explained the details in the Promise interview question extension , so I won't introduce it too much here. Just need to know where there are let and const, the variable i defined by it is wrapped in block-level scope (the field has absolute field, and the variable is self-contained)

So we have another way to solve the problem of changing the argument of setTimeout in the for loop. Here I hope you can understand a point of knowledge. Before ES6, JavaScript did not have the concept of block-level scope. The effect of block-level scope can only be achieved by executing the function immediately, or because the variables in the function scope cannot be accessed from the outside world, which is equivalent to ensuring that the variables in the function will not be accessed. Change. After ES6, let, const keywords can play the effect of block-level scope

Next, let's talk about the meaning of executing the function immediately

IIFE (immediately execute function)

When we develop a website, we often introduce some libraries, such as JQuery, etc. When we use these libraries, assuming that these libraries are written happily and do not hide their internal scope, then we will face naming conflicts.

In order to solve this problem, we have the concept of modularity, and this problem has also found the answer in ES6, import export

Before there is no modularization, our common method is to execute the function immediately (IIFE)

 var a = 1;
(function () {
    var a = 2;
    console.log(a); // 2
})()
(function (name) {
    console.log(name); // johnny
})('johnny');
// jquery
(function(global, factory){
})(typeof window !== "underfined" ? window: this, function(window, noGlobal){
});

Each referenced library is imported and executed. The variable exists in the scope where it is located. Because the functions of the library and the library are independent, the naming methods do not affect each other and do not disturb the whole world.

Another way to understand it: First of all, it is a function, so it has a scope. The scope can play a role in the existence of variables in the function and will not be exposed in the global, so that the variables will not be polluted; immediate execution is to directly call the function, so you introduce The library can be used directly (generally the library will be mounted on the window object)

anonymous function

No function name, easy to understand, just a "tool man"

 var foo = function () {
    console.log('hello world');
};
foo();
setTimeout(function () {
    console.log('hello,setTimeout');
});

execute function immediately

Just call it directly, how to call the function, add ()

 (function () {
    console.log('hello world');
})();

The anonymous function is called directly, which ensures that the variables in the anonymous function are independent. Because the variables in the function scope cannot be accessed from the outside world, the variables are independent

Therefore, the immediately executed function is a function, the function has a (function) scope, and the variables in the scope will not be affected by the outside world

scope design

The following function:

 function doSomething(a) {
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}

function doSomethingElse(a) {
    return a - 1;
}
var b;

doSomething(2); // 15

Do you think there is something wrong with this way of writing?

Although we can write functions like this, because of scope (lexical scope: where the definition is, the scope is formed), the scope of doSomethingElse is implicitly built globally, that is to say, doSomethingElse and doSomething are the same the scope of the class

作用域-隐形作用域

But is this what we mean?

No, we want doSomethingElse to be in the doSomething function and its scope in the doSomething scope

 function doSomething(a) {
    function doSomethingElse(a) {
        return a - 1;
    }

    var b;

    b = a + doSomethingElse(a * 2);

    console.log(b);
}

doSomething(2); // 15

The specific content is privatized by design, whether it is the variable b or the function doSomethingElse belongs to the private variable (or function) of doSomething. That is, the global scope cannot access doSomethingElse, and doSomethingElse can only be called in the doSomething function

Summarize

Wherever a function is called, and no matter how it is called, its lexical scope is determined only by where the function is declared

Lexical scope means that the scope is determined by the location of the function declaration when the code is written. The lexical analysis phase of compilation basically knows where and how all the identifiers are declared, so it can predict how they will be looked up during execution

Lexical scope is the scope defined in the lexical phase. In other words, lexical scoping is determined by where you write variable and block scoping when you write your code, so when the code is processed by the lexer, the scoping remains unchanged (most of the time)

The scope is determined when the function is defined. The function saves a [[scope]] property, which holds the parent scope object

References

  • JavaScript Rollup You Don't Know

series of articles


山头人汉波
391 声望554 粉丝