This paper tries to be the most clear one about lexical scope. Read the brothers and sisters have doubts about the local comment section, I will improve.

What is the scope?

One of the most basic functions of almost all programming languages is to store the value of a variable so that it can be accessed and modified later. This ability to store and access values of variables brings state to the program.

So with states, we need to think about where do we store variables? How do we find him?

These two problems require a well-designed set of rules for storing variables, and JavaScript has introduced lexical scoping rules.

Understanding scope

JS code needs to be handled by the compiler before execution, and then by the engine.

What does the compiler do?

Suppose the compiler encounters var num = 100.

1. The compiler will first ask if the current scope already has a variable named num

  • If so, the compiler ignores the current declaration and continues compiling
  • If not, the compiler declares a new variable in the current scoped collection, named num

2. The compiler then generates the run-time code for the engine. This code is used to handle assignments such as num = 100.

When num = 100, the engine will ask if the current scope has a num variable

  • If so, the engine references this variable
  • If not, the engine looks for the variable according to the rules (LHS and RHS for variables and attributes)

Dialogue between engine and scope

As mentioned above, the engine looks for variables in scope according to the rules, which in this case are LHS and RHS, which stand for left query and right query. That is, an LHS left query is performed when a variable appears to the left of an assignment, and an RHS right query is performed when a variable appears to the right of an assignment.

The above description of LHS and RHS is not particularly accurate, but it would be more accurate to say that an RHS query is no different from simply looking up the value of a variable, whereas an LHS query is trying to find the variable container itself in order to assign a value.

For example, console.log(a) does an RHS query because there is no assignment to a.

But an operation such as a = 2 will perform an LHS query.

Consider the following code:

function foo(a) {
    console.log(a) / / 2
}
foo(2)
Copy the code

Query process analysis:

  • The last line of codefoo(2)An RHS query is required to find the foo function.
  • Once you find foo, you need toa = 2The assignment operation of. You need to query LHS for A.
  • performconsole.log(a)The RHS operation is performed when.

Given the above analysis, how do engines and scopes talk to each other?

Engine: Scope brother, I’m going to run an RHS query on foo, have you seen him?

Scope: Huh? Ao, I’ve seen it. The compiler just declared it to be a function. Here it is. Here it is.

Engine: Thank you, big brother. That’s great. So I’m going to execute foo.

Engine: That’s right, brother. Have you met this guy “A”? I also need to run an LHS on him.

Scope: Yes yes, this is a parameter to foo, the compiler has lived before. You can have this, too.

Engine: No thanks, I’ll assign 2 to A.

Engine: Hi youyou, sorry brother, I need to perform RHS operation on console. Do you know him?

Scope: no matter, no matter, you don’t say, I really know, this is the built-in object, also give you.

Engine: Sorry, dude, I still need to do RHS operation on log, you see ~

Scope: what is this, it is a function inside the console built-in object…

Engine, please kindly excuse me for the last time. Although I know A before, I still need to perform RHS operation on A to output it.

Scope: Ok, a is in…

Engine: Thank you for helping me so much, I finally finished the task.

Scope nesting

As mentioned earlier, scopes are rules for finding variables by name. In practice, we need to take care of several scopes.

Nesting of scopes occurs when a block or function is nested within another block or function. When a variable is not found in the current scope, the engine looks for that variable in the outer nested scope until it reaches the outermost (global) scope of the scope.

Consider the following code:

function foo(a) {
    console.log( a+b )
}
var b = 2
foo(2) / / 4
Copy the code

A RHS query on b in scope foo does not yield a result; you need to query scope foo above.

The dialogue between the engine and Foo might be:

Engine: Foo scope brother, have you seen B? I need to do an RHS operation on it.

Scope foo: I’ve never heard of this guy b, go ask my big brother.

Engine: Ok, Foo’s superior scope brother, have you seen B? I need to run an RHS check on him.

Foo’s parent scope: buddy, you came to the right place, I have a global scope, if you don’t find it in me you won’t find it in all of my scope brothers. Fortunately, the compiler is declared b here. Here you go.

Simulate the scope as a building

This building represents the nested chain of scopes in the program. The first layer represents the current execution scope and the top layer is the global scope.

Both LHS and RHS will search at the current floor. If not, they will take the elevator to the next floor, and so on until they reach the top floor. After reaching the top floor, they may or may not find the variable you want, but the search process will be terminated.

Huh? Did you think it was over? The good part is just beginning

If you read the above article carefully, I’m sure you’ll get some idea of scope. However, there may be problems in the implementation process.

For example: closure functions:

function f1(){
    var a = 'a';
    function f2(){
        var b = 'b';
        console.log(a)
    }
    return f2;
}
f1()(); // a
Copy the code

If you understand the scope description above, please answer:

  1. Why can f2() be called from f1()() after f1() is unstacked?
  2. After f1() is executed, why can a be accessed through f1()() when f1 is unstacked?

The reason for this confusion is that the memory implementation of the scope is not understood. Let’s go into the world of memory and see how scopes are implemented. The following content comes from a reading of the ES specification.

JS scope is also called lexical scope (in fact, most languages use lexical scope rules beautiful such as Java, C#, etc.), of course, the so-called lexical scope refers to our scope access ability (in the current scope can access the scope) at the time of coding has been determined.

Instead of describing the implementation of Lexical Scope, the specification describes the concrete implementation of Lexical Environment.

Lexical Environments

Different Lexical environments are associated with different data structures. The following types of code are executed to create lexical environments, such as function declaration statements, with statements, and try-catch statements.

Now, you might have a little bit of a problem with metaphor Environment here, and the obvious problem is what is it?

I’m going to try to describe it in more general terms.

Lexical Environment: As mentioned above, the access power of the scope is determined by our code. At which step the engine executes, it looks up variables or attributes using LHS or RHS operations in the scope of that step. If the engine does not find a target in the current scope, it accesses the parent scope layer by layer. Has the reader ever wondered what the hierarchical structure is? Yes, this is Lexical Environment.

So, in short, a lexical environment is a chain of scopes that the compiler has established before it runs, according to built-in rules.

The scope chain is a chain structure. We won’t go into the details of how to implement the scope chain in each browser (different browsers use different engines and probably use different data structures to implement the scope chain), but we will assume that the data structure implemented is an array. Now look at each element in the array.

The ES specification states that each element in an array is an object with a fixed name — LexicalEnvironment or VariableEnvironment. (We’ll discuss the difference between LexicalEnvironment and VariableEnvironment later, as well as the top-level object of scope being GlobalEnvironment.)

We take a look at each LexicalEnvironment/VariableEnvironment what are the details of the specific object? There are two parts in the specification:

  • EnvironmentRecord: is a reference to an object that records variables and properties in the current scope.
  • Outer :outer reference to the external lexical environment that points to the local lexical environment. (Our array is joined together by this reference.)

Pseudo code:

LexicalEnvironment/VariableEnvironment: {
    EnvironmentRecord = undefined; // Record the content that needs to be recorded under the current scope, for example: variables, properties and so on.
    outer = undefined; //outer LexicalEnvironment/VariableEnvironment Reference
}
Copy the code

Ha ha, a layer of the same layer, but fortunately, not particularly complicated.

Let’s look at the components of EnvironmentRecord: The specification states that EnvironmentRecord can be considered as an abstract parent class, with two concrete subclasses declarative Environment Records and Object Environment Records.

  • declarative environment recordsPoints to ECMAScript language syntax elements (such as FunctionDeclarations, VariableDeclarations, and Catch clauses).
  • object environment recordsPoints to ECMAScript elements (such as WithStatement).

End end, their data structure we analyze here ~

GlobalEnvironment

The global environment is a unique lexical environment that is created before the ECMAScript code is executed. The EnvironmentRecord of the global environment is an object EnvironmentRecord bound to the Global Object. The outer reference to the global environment is null. When ECMAScript code is executed, additional properties may be added to the Global Object, and the initial properties may be modified.

In browsers, a GlobalEnvironment is associated with a window, which is a global object.

In nodeJs, GlobalEnvironment is associated with global, which is a global object.

Differences between LexicalEnvironment and VariableEnvironment

LexicalEnvironment is a local lexical scope, for example, for variables defined by lets. If you use let to define a variable in a catch block, it is only visible in the catch block, and to implement this in the specification, we use LexicalEnvironment. Variableenvironments are variable and function declarations such as var definitions. Vars can be thought of as “elevating” to the top of the function. To implement this in the specification, we give the function a new VariableEnvironment, but the block inherits the closed VariableEnvironment.

Refer to the article

  • JavaScript You Don’t Know

  • ECMAScript specification