author

@Bobby King Kong

If you want to reprint, please indicate the source. If you feel good, don’t be stingy with your star


1. What is scope

Domain, in Chinese, means a range. So the scope is literally the scope of the scope, which is close to the actual scope. But it’s still not quite right. A scope is essentially a set of rules for determining where and how to look for variables (identifiers).

To understand the rules of where and how to look, we need to understand a few concepts.

LHS & RHS

The engine looks for two rules for variable A in scope. This can be easily divided by the assignment operator ‘=’.

Variables are queried LHS to the left of the = sign. The purpose of the query is to assign values. Variables are RHS queries to the right of the = sign. The purpose of a query is to get a value.

This is to better understand the meaning of LHS and RHS, but the ‘=’ division is too absolute. A better understanding is that LHS is to find the target of the assignment. RHS is the source of assignment operations. In other words, RHS is equivalent to getting the value of a variable, LHS is to find the variable itself and assign it a value.

A case to analyze:

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

foo(2)
Copy the code

This simple code snippet Outlines the search process:

  1. RHS query foo in foo(2), the last sentence, because we want to get its value.
  2. The function foo (a) {… }, and assign 2 to it.
  3. Perform an RHS query on console and verify that it has a log() function.
  4. Perform an RHS query on a in log(a) to obtain its value of 2.

With that in mind, we have a general idea of the lookup rules in scope. What about the scope of the search? That’s actually where to look. A scope not only defines a set of rules for finding, but also defines a scope for itself. This range can still be nested.

ReferenceError & TypeError

ReferenceError represents a declaration that the variable was not found in scope; TypeError means that the variable is declared globally, but an incorrect, illegal operation has been performed on its result

eval & with

2. with

The most common use of with() is to facilitate access to an object’s properties

var obj = {
    a:1,
    b:2
}

with(obj){
    a=3;
    b=4;
}
Copy the code

The interesting thing about with is that it creates a completely separate scope for this object. This can be a problem;

function foo(obj){ with(obj){ a = 2; } } var o1 = { a : 1; } var o2 = { b : 1; } foo(o1); console.log(o1.a) //a foo(o2); Console. log(O2.a)//undefined console.log(a) //2 --> Leaked globallyCopy the code

The above code works in non-strict mode, where with is largely disabled.

Inside foo(), with creates a separate scope for obj. The LHS query is performed on A. So if foo(o1) is executed, we can find o1.a in outer foo(o1) and assign to it. If foo(o2) is executed, we can find o2.a and assign to it. This is a RefferenceError. If you can’t find it, it should be RefferenceError. We print the global a and find that it is indeed a=2. There is no way to create one in non-strict mode with this 2 LHS query.

Best practice: Eval and with should both disappear from our code.

Block scope

Block scopes should be familiar to you if you have experience developing in other languages. Although the most common type of scope in JavaScript is function scope, there are still other types of scope. Block scope is one of them.

What are the benefits of block scope? Let’s take a look at a code we wrote badly:

for(var i = 0, len = 10; i < len; i++){
    console.log( i );
}
Copy the code

What we really want is for I to bind to {… } inside the use, the outside can not operate. But the fact is that the I declared by var is still bound to the outer scope.

function foo( a ){ function bar(a){ i = 2; // The LHS query here finds I in the for loop. So... Ha ha... return a + i; } for(var i = 0, len = 10; i < len; i++){ bar( i * 2 ) } }Copy the code

The result above is an endless loop. Because I is not bound to {… } inside, but in the outer scope.

1. The with mentioned earlier creates a separate block scope.

Block scope in ES6

let + for


The let/for loop is a perfect match. Some of the problems with the var+for loop can be solved perfectly. For example:

// for loop for(var i = 0 ; i <= 5 ; i++){ setTimeout(function(){ console.log(i); // Output 6}, I *1000)} console.log(I); // Finally output 6Copy the code

Replace with let, and then:

for(let i = 0 ; i <= 5 ; i++){ setTimeout(function(){ console.log(i); 0,1,2,4,5}, I *1000)} console.log(I); // ReferenceError: i is not definedCopy the code

The comparison of the two demos illustrates a number of let features:

  1. The let is bound to the block-level scope of the for loop and is not promoted, so the outside world cannot access the I.
  2. Var has only one public, promoted declaration, so overrides occur. The output is the last value.
  3. In each iteration, let declares a new I for the iterator as well as an I for the for. Each iteration is a new I, so the closure in the for loop is also closed with the value you expect, as shown in demo3:
// demo3 // for loop with let and closure var arrfunc = []; for(let i = 0 ; i < 5 ; i++){ arrfunc.push(function(){ console.log(i) }) } arrfunc[3](); // 3 If it is var, output 5Copy the code

Dig deeper: in layman’s terms for loops, () and {} correspond to different scopes

Another special aspect of the for loop is that the statement part of the loop is a parent scope, while the body of the loop is a separate child scope

for(var i = 0 ; /* scope a*/ I < 3; console.log('in for expression', i), i++){ let i; // the scope of a is different from that of a. Console. log('in for block', I)} in for block undefined in for expression 0 in for block undefined in for expression 1 in for block undefined in for expression 2Copy the code

for(…) Var I, let I, {… } you can get the value of I directly. If var is declared, it is easier to understand. } can get the external I value, also because there is only one common I, so it will overwrite, but let? How do you send values? My personal understanding is probably as follows (code for Demo3):

{ let i = 0; { let _i = i; arrfunc.push(function(){ console.log(_i); }}})Copy the code

In other words, there’s actually a new I iterating every time. This illustrates the let’s nature of binding new values in the iterations mentioned in the above article. And it’s bound to the previous value.

Block scoped function


Starting with ES6, function declarations can be defined in block scope. Prior to ES6, the specification did not require this, but many implementations did. Norms now meet reality.

{
    foo(); // it works
    function foo(){
        console.log('it works')
    }
}

foo(); //ReferenceError: foo is not defined
Copy the code

The above example illustrates:

  1. Functions can be declared in a block scope and cannot be accessed by the outside world.
  2. Function declarations can be promoted in block scope. Attention should be paid.

So pay attention to some of our writing habits:

if(true){
    function foo(){
        console.log('1')
    }
}else{
    function foo(){
        console.log('2')
    }
}

foo(); //in ES6: ReferenceError: foo is not defined
       //in Pre-ES6: 2
Copy the code