Do you know the lexical environment? What does it have to do with closures?

Lexical Environment

Official definition

Official ES2020 The Lexical Environment is defined as follows:

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.

Lexical environment is a specification type, which defines the association between identifiers and specific variables and functions based on the lexical nesting structure of ECMAScript code. The lexical environment consists of an environment record and an external lexical environment that may be null.

It's very detailed, but it's hard to understand 🤔

Next, we will explain more intuitively through the compilation process of JS in V8.

The compilation process of JS in V8 can be explained more intuitively

It is roughly divided into three steps:

  • Step 1 lexical analysis: when V8 just gets the execution context, it will perform word segmentation / lexical analysis (Tokenizing/Lexing) from top to bottom line, such as var a = 1, Will be divided into VaR, a, 1; Such atomic token. Lexical analysis = refers to the formal parameters of registered variable declaration + function declaration + function declaration.
  • Step 2 syntax analysis: after lexical analysis, syntax analysis will be done. The engine will parse the token into an abstract syntax tree (AST). In this step, it will detect whether there are syntax errors. If so, it will directly report errors and will not proceed
var a = 1;
console.log(a);
a = ;
// Uncaught SyntaxError: Unexpected token ;
// The code did not print out 1, but directly reported an error, indicating that lexical analysis and syntax analysis were carried out before code execution
  • Note: lexical analysis and grammatical analysis are not completely independent, but staggered. In other words, not all tokens are generated before they are processed by the parser. Generally, every time a token is obtained, it starts to be processed by the parser
  • Step 3 code generation: the last step is to convert AST into machine instruction code that can be recognized by the computer

In the first step, we see lexical analysis, which is used to register variable declarations, function declarations and formal parameters of function declarations. When the subsequent code is executed, we can know where to obtain variable values and functions. The place of registration is the lexical environment.

The lexical environment consists of two parts:

  • Environment record: the actual location where variables and function declarations are stored and where variables are actually registered
  • Reference to external environment: it means that it can access its external lexical environment, which is the key to the connection of scope chain

The set of identifiers that each environment can access, which we call "scope". We nested the scope layer by layer to form a "scope chain".

There are two types of lexical environments:

  • Global environment: it is a lexical environment without external environment, and its external environment reference is null. It has a global object (window object) and its associated methods and properties (such as array method) as well as any user-defined global variables. The value of this points to this global object.
  • Function environment: the variables defined by the user in the function are stored in the environment record, including the arguments object. A reference to an external environment can be a global environment or an external function environment containing internal functions.

There are also two types of environmental records:

  • Declarative environment records: store variables, functions, and parameters. A function environment contains declarative environment records.
  • Object environment record: used to define the association of variables and functions in the global execution context. The global environment contains object environment records.

If expressed in the form of pseudo code, the lexical environment is as follows:

GlobalExectionContext = {  // Global execution context
  LexicalEnvironment: {          // Lexical Environment
    EnvironmentRecord: {           // Environmental records
      Type: "Object",                 // Global environment
      // ...
      // The identifier is bound here 
    },
    outer: <null>                    // Reference to external environment
  }  
}

FunctionExectionContext = { // Function execution context
  LexicalEnvironment: {        // Lexical Environment
    EnvironmentRecord: {          // Environmental records
      Type: "Declarative",         // Function environment
      // ...
      // Identifier is bound here / / reference to external environment
    },
    outer: <Global or outer function environment reference>  
  }  
}

For example:

let a = 20;  
const b = 30;  
var c;

function multiply(e, f) {  
 var g = 20;  
 return e * f * g;  
}

c = multiply(20, 30);

Corresponding execution context and Lexical Environment:

GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // The identifier is bound here  
      a: < uninitialized >,  
      b: < uninitialized >,  
      multiply: < func >  
    }  
    outer: <null>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // The identifier is bound here  
      c: undefined,  
    }  
    outer: <null>  
  }  
}

FunctionExectionContext = {  

  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // The identifier is bound here  
      Arguments: {0: 20, 1: 30, length: 2},  
    },  
    outer: <GlobalLexicalEnvironment>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // The identifier is bound here  
      g: undefined  
    },  
    outer: <GlobalLexicalEnvironment>  
  }  
}

The Lexical Environment corresponds to the structure of our own code, that is, what our own code looks like, the lexical environment is what it looks like. The lexical environment is determined when the code is defined, which has nothing to do with where the code is called. Therefore, JS adopts lexical scope (static scope), that is, its scope is statically determined after the code is written.

Static scope vs dynamic scope

The stack top variable and the stack top variable are dynamic variables, so they are both based on the stack execution context. The static scope means that the value of a variable is determined when it is created, and the location of the source code determines the value of the variable.

var x = 1;

function foo() {
  var y = x + 1;
  return y;
}

function bar() {
  var x = 2;
  return foo();
}

foo(); // Static scope: 2; Dynamic scope: 2
bar(); // Static scope: 2; Dynamic scope: 3

In this example, the execution structure of static scope and dynamic scope may be inconsistent. Bar essentially executes foo function. If it is a static scope, the variable x in bar function is determined when foo function is created, that is, the variable x is always 1 and the two outputs should be 2. The dynamic scope returns different results according to the x value at run time.

Therefore, dynamic scope often brings uncertainty. It cannot determine which scope the value of a variable comes from.

Most programming languages now use static scope rules, such as C/C + +, c#, Python, Java, JavaScript, etc. languages with dynamic scope include Emacs Lisp, Common Lisp (both static scope) and Perl (both static scope). The name used in C/C + + macros is also a dynamic scope.

Lexical Environment and closure

A function is bound with a reference to its surrounding state (lexical environment) (or the function is surrounded by a reference). Such a combination is a closure

-MDN

In other words, a closure is a combination of a function and the Lexical Environment in which it is declared

var x = 1;

function foo() {
  var y = 2; // Free variable
  function bar() {
    var z = 3; //Free variable
    return x + y + z;
  }
  return bar;
}

var test = foo();

test(); // 6

Based on our understanding of lexical environment, the above example can be abstracted into the following pseudo code:

GlobalEnvironment = {
  EnvironmentRecord: { 
    // Built in identifier
    Array: '<func>',
    Object: '<func>',
    // wait..

    // User-defined Identifier 
    x: 1
  },
  outer: null
};

fooEnvironment = {
  EnvironmentRecord: {
    y: 2,
    bar: '<func>'
  }
  outer: GlobalEnvironment
};

barEnvironment = {
  EnvironmentRecord: {
    z: 3
  }
  outer: fooEnvironment
};

As mentioned earlier, lexical scope is also called static scope. Variables are determined in lexical stage, that is, during definition. Although it is called in bar, foo keeps its scope even if it is executed outside its defined lexical scope because it is a closure function. The so-called closure function, that is, this function closes its own definition environment and forms a closure, so foo does not look for variables from bar, which is the characteristic of static scope.

In order to implement closures, we cannot use the dynamic stack of dynamic scope to store variables. If so, when the function returns, the variable must be out of the stack and no longer exist, which is contrary to the original definition of closure. In fact, the closure data of the external environment is stored in the "heap", so that the internal variable still exists even after the function returns (even if its execution context has been out of the stack).

last

This article starts from the "three minute learning front-end". Reply to "communication" and automatically join the front-end three minute advanced group. One programming algorithm interview question (including solutions) every day will help you become a better front-end developer!

Added by wilded1 on Fri, 04 Mar 2022 02:32:47 +0200