Scope

Scope is the area in the program where variables are defined, and the location determines the lifetime of the variables. Commonly understood, scope is the accessible scope of variables and functions, that is, scope controls the visibility and life cycle of variables and functions.

There are two main working models for scopes:

  • The first is the lexical scope adopted by most programming languages.
  • The second is still used by some programming languages (e.gBashScript) in useDynamic scope.

Lexical scope

Simply put, lexical scope is the scope of a definition at the lexical stage.

The lexical analysis phase of compilation basically knows where and how all identifiers are declared, and thus can predict how they will be looked up during execution.

As you can see from the figure, the lexical scope is determined by the position of the code, where the main function contains the bar function, and the bar function contains the foo function. Because the JavaScript scope chain is determined by the lexical scope, the order of the entire lexical scope chain is: Foo function scope – > bar function scope – > main function scope – > global scope.

Dynamic scope

Dynamic scopes are determined at execution time and live until the code snippet is executed.

Dynamic variables exist in dynamic scope, and the value of any given binding is unknown until it is determined to call its function.

For example, save the following script as scope.bash, then go to the corresponding directory and execute bash./scope.bash from the command line.

value=1
function foo () {
    echo $value;
}
function bar () {
    local value=2;
    foo;
}
bar
Copy the code

The difference between

  • Lexical scope is determined at code writing or definition time, whereas dynamic scope is determined at run time. (This too!)
  • Lexical scopes focus on where functions are declared, while dynamic scopes focus on where functions are called from.

JavaScript does not have dynamic scope; it only has lexical scope. But this mechanism is somewhat like dynamic scope.

Scope classification

There are three JavaScript scopes:

  • Objects in a global scope can be accessed anywhere in the code, and their life cycle follows that of the page.
    • Outermost function and outermost defined variable;
    • Not used in any locationvar.let.constDeclared variables;
    • allwindowObject properties.
  • A function scope is a variable or function defined inside a function and can only be accessed inside the function. After the function is executed, the variables defined inside the function are destroyed.
  • A block-level scope is a block of code, such as a function, a judgment statement, a loop statement, or even a single one, wrapped in a pair of braces{}Can be thought of as a block-level scope.
    • ES6 introducedletandconstKeyword, thus enabling JavaScript to have block-level scope like other languages.
The global scope contains the count variable and the main function
let count = 1 
function main(){ 
    // Function scope
    // block scope
    let count = 2 
    function bar(){
        // Function scope
        // block scope
        let count = 3
        function foo(){
            let count = 4}}}// block scope
/ / if block
if(1) {}/ / while block
while(1) {}/ / function block
function foo(){
 
/ / a for loop
for(let i = 0; i<100; i++){}

// A single block
{}
Copy the code

Why introduce block-level scopes

Before we answer that question, let’s take a look at what variable promotion is.

Variable promotion (as)

The so-called variable promotion refers to the “behavior” in which the JavaScript engine promotes the declaration part of variables and functions to the beginning of the code during the execution of JavaScript code. When a variable is promoted, it is given a default value, which is known as undefined.

Take a look at the following code:

showName()
console.log(myname)
var myname = 'JavaScript'
function showName() {
    console.log('Function showName executed')}Copy the code

Analyze the above code:

  • Lines 1 and 2, because they are not declarative operations, the JavaScript engine does nothing;
  • Row 3, because this is the pathvarDeclared, so the JavaScript engine will create an environment object namedmynameProperty and useundefinedInitialize it;
  • At line 4, the JavaScript engine finds a passfunctionDefined function, so it stores the function definition in the HEAP and creates one in the environment objectshowNameProperty, and then point the value to the position of the function in the heap. This generates the variable environment object.

After compilation, two parts are generated: the Execution Context and the executable code.

// The execution context's variable environment holds the contents of the variable promotion, namely the myname variable and showName().
var myname = undefined
function showName() {
    console.log('Function showName executed')}// Executable code
showName()
console.log(myname) // undefined
myname = 'JavaScript'
Copy the code

The JavaScript engine starts executing executable code, line by line, in sequence.

  • When performing theshowNameFunction, the JavaScript engine starts to look for the function in the variable environment object. Since there is a reference to the function in the variable environment object, the JavaScript engine starts to execute the function and prints the result “function showName was executed”.
  • Next, the “myName” information is printed, and the JavaScript engine continues to look for that object in the variable environment object, as the variable environment existsmynameVariable, and its value isundefined, so this is outputundefined;
  • Next line 3 assigns the JavaScript tomynameVariable, in the variable environment after the assignmentmynameProperty value changed to “JavaScript”.

Problems with variable promotion

1. Variables can easily be overwritten without being noticed

var myname = "JavaScript"
function showName(){
  console.log(myname)
  if(0) {var myname = "CSS"
  }
  console.log(myname)
}
showName() //undefined is used because the function declaration var myname is preferred, and undefined overwrites the global variable with the same name.
Copy the code

2. The variables that should have been destroyed were not destroyed

function foo(){
  for (var i = 0; i < 7; i++) {
  }
  console.log(i)
}
foo() //7, I is not destroyed at the end of the for loop because the variable is promoted.
Copy the code

With block-level scope

Or the above two examples:

var myname = "JavaScript"
function showName(){
  console.log(myname)
  if(0) {let myname = "CSS"
  }
  console.log(myname)
}
showName() // JavaScript JavaScript

function foo() {
    for (let i = 0; i < 7; i++) {
        console.log(i)
    }
}
foo()// 0 1 2 3 5 6
Copy the code

The result is very much in line with our programming conventions: variables declared inside a block do not affect variables outside the block.

Variable lookup in block-level scope

function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a) / / 1
      console.log(b) / / 3
    }
    console.log(b) / / 2
    console.log(c) / / 4
    console.log(d) // Uncaught ReferenceError: d is not defined because the d variable declaration is in the block scope and cannot be accessed externally.
}   
foo()
Copy the code

  1. Internal of a functionvarDeclared variablea = undefined.c = undefinedPut it in a variable environment,letDeclared variableb = undefinedPlaced in lexical context;
  2. Next, we continue to execute the code, when executed inside the code block, in the variable environmenta = 1In the lexical environmentb = 2;
  3. After entering a block scope, a variable inside the block scopeb = undefined.d = undefinedPushed to the top of the lexical environment stack;
  4. When executed to the block scopeconsole.log(a)This line of code is in the variable environmentc = 4, a variable inside the lexical environment’s block scopeb = 3.d = 5;
  5. This is where you need to look in the lexical environment and the variable environmentaIf it is found in a block in the lexical environment, it is returned directly toJavascriptEngine, if not found, then continue to look in the variable environment.
  6. This completes the variable lookup process. When a block scope completes execution, its internally defined variables are popped from the top of the lexical environment’s stack.

Scope chain

The lexical (variable) environment of each execution context contains an external reference to the external execution context, which we call outer.

Take a look at this code:

function bar() {
    console.log(myName)
}
function foo() {
    var myName = " CSS "
    bar()
}
var myName = " JavaScript "
foo() // JavaScript
Copy the code

As you can see from the figure, the outer of both bar and foo points to the global context, which means that if an external variable is used in bar or foo, the JavaScript engine will look in the global execution context. We call this chain of lookup the scope chain.

Function foo calls function bar, so why is the external reference to function bar the global execution context, and not foo’s execution context?

This is because the parent scopes of foo and bar are both global scopes according to lexical scope, so if foo or bar functions use a variable they don’t define, they will look it up in the global scope. That is, the lexical scope is determined at the code stage, regardless of how the function is called.

Execution Context

In short, an execution context is an abstraction of the context in which the current JavaScript code is being parsed and executed, and any code that runs in JavaScript runs in an execution context.

There are three types of execution contexts:

  • When JavaScript executes global code, the global code is compiled and the global execution context is created, and there is only one global execution context for the lifetime of the page.
  • When a function is called, the code inside the function is compiled and the function execution context is created. Generally, the function execution context is destroyed when the function is finished.
  • When usingevalDelta function,evalThe code is also compiled and createdExecution context.

Create a stage

Before any JavaScript code is executed, the execution context is created. Two things happen in the creation phase altogether:

  1. The LexicalEnvironment component is created.
  2. The VariableEnvironment component is created.

Thus, the execution context can be conceptually expressed as follows:

ExecutionContext = { LexicalEnvironment = { ... }, VariableEnvironment = { ... }},Copy the code

Lexical Environment

The official ES6 documentation defines the lexical environment as:

A lexical environment is a canonical type that defines the associations of identifiers with 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 references, as well as this Binding.

In short, a lexical environment is a structure that contains a mapping of identifier variables. The identifier here represents the name of a variable/function, which is a reference to an actual object (including a function type object) or the original value.

Such as:

var a = 20;
var b = 40;

function foo() {
    console.log('bar');
}
Copy the code

The lexical environment above looks like this:

lexicalEnvironment = {
    a: 20.b: 40.foo: <ref. to foo function>
  }
Copy the code

In a lexical context, there are three components:

  1. Environment Record;
  2. References to Outer environments;
  3. This binding.

Environmental records

Is the actual location where variable and function declarations are stored.

There are also two types of environmental records (as shown below) :

  • Declarative environmental recordsStore variable and function declarations.function codeContains a declarative environment record.
  • Object Environment Record global codeThe lexical environment contains an object environment record. In addition to variable and function declarations, object environment records store oneglobal binding object(The window object in the browser). Therefore, for each binding object property (which in the browser contains the properties and methods provided by the browser window object), a new entry is created in the record.

For function code, the environment records that the object contains the mapping between the index and the parameters passed to the function and the length (number) of the parameters passed to the function. For example, the arguments object for the following function looks like this:

function foo(a, b) {
  var c = a + b;
}
foo(2.3);
// argument object
Arguments: {0: 2.1: 3.length: 2},
Copy the code

References to the external environment

A reference to an external environment means that it has access to its parent lexical environment (scope). This means that if the variable is not found in the current lexical environment, the JavaScript engine will look for it in the parent lexical scope.

This Binding

In the context of global execution, the value of this points to the global object (in browsers, the value of this points to the window object).

In the context of function execution, the value of this depends on how the function is called. If it is called by an object reference, the value of this is set to that object, otherwise the value of this is set to global or undefined (in strict mode). Such as:

const person = {
  name: 'peter'.birthYear: 1994.calcAge: function() {
    console.log(2018 - this.birthYear);
  }
}
person.calcAge(); 
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
const calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given
Copy the code

In the abstract, the lexical environment looks like this pseudo-code:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object".// Identifier bindings go here
    }
    outer: <null>,
    this: <global object>
  }
}
FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
    }
    outer: <Global or outer function environment reference>,
    this: <depends on how function is called>
  }
}
Copy the code

For more details, see [JavaScript scope and closure -4]this and its use in several different scenarios

Variable Environment

It is also a lexical environment whose EnvironmentRecord contains bindings created by VariableStatements in this execution context.

As mentioned above, the variable environment is also a lexical environment, so it has all the attributes of the lexical environment defined above.

In ES6, the difference between the LexicalEnvironment component and the VariableEnvironment component is that the former is used to store function declarations and variable (let and const) bindings, while the latter is only used to store variable (var) bindings.

In ES2018, the execution context is again like this, subsumed by the lexical Environment, but with a few additions.

  • Lexical environment: lexical environment, used when fetching variables or this values.
  • Variable environment: used when declaring variables.
  • Code Evaluation State: Used to restore the code execution location.
  • Function: used when the task being executed is a Function.
  • ScriptOrModule: used when executing a ScriptOrModule, indicating the code being executed.
  • Realm: Base libraries and built-in object instances to use.
  • Generator: Only the Generator context has this property, indicating the current Generator.

Execution phase

At this stage, all variable assignments are completed, and then the code executes.

example

Let’s look at a few examples to understand the above concepts:

let a = 20;
const b = 30;
var c;
function multiply(e, f) {
 var g = 20;
 return e * f * g;
}
c = multiply(20.30);
Copy the code

When the above code is executed, the Javascript engine creates a global execution context to execute the global code. So the global execution context will look like this during the creation phase:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object".// Identifier bindings go here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}
Copy the code

During the execution phase, variable assignments are completed, so the global execution context will look like this during the execution phase:

GlobalExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object".// Identifier bindings go here
      a: 20.b: 30.multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}
Copy the code

When multiply(20, 30) is called, a function execution context is created for the function, which looks like this during the creation phase:

FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative".// Identifier bindings go here
      Arguments: {0: 20.1: 30.length: 2}},outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}
Copy the code

The execution context then enters the execution phase, where the variable assignment is complete. The function context at execution time looks like this:

FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative".// Identifier bindings go here
      Arguments: {0: 20.1: 30.length: 2}},outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}
Copy the code

After the function completes, the return value is stored in variable C, at which point the global lexical environment is updated. After that, global code execution is complete and the program ends.

Note: You may have noticed in the code above that variables a and b defined by let and const are not assigned at the creation stage, but variables declared by var are assigned to undefined from the creation stage.

This is because, during the creation phase, variable and function declarations are scanned in the code and then stored in the environment, but the variables are initialized to undefined(in the case of var declarations) and left uninitialized(in the case of let and const declarations).

This is why variables declared using var can be called before the variable declaration, but accessing variables declared using let and const before the variable declaration gives an error (TDZ).

This is what we often hear about variable declaration enhancement.

Note: At execution time, if the Javascript engine cannot find the values of the variables declared by let and const, it will also assign undefined.

Execution Stack

The execution stack, also known as the Call stack in other programming languages, has a LIFO (last in, first out) structure for storing all execution contexts created during code execution.

When the JavaScript engine first reads your script, it creates a global execution context and pushes it onto the current execution stack. Each time a function call occurs, the engine creates a new execution context for that function and pushes it to the top of the current execution stack.

The engine will run the function whose execution context is at the top of the execution stack. When this function is completed, its corresponding execution context will be ejected from the execution stack, and the control of the context will be moved to the next execution context of the current execution stack.

Let’s use the following code example to understand this:

let a = 'Hello World! ';

function first() {  
  console.log('Inside first function');  
  second();  
  console.log('Again inside first function');  
}

function second() {  
  console.log('Inside second function');  
}

first();  
console.log('Inside Global Execution Context');
Copy the code

When the code is loaded in the browser, the JavaScript engine creates a global execution context and pushes it onto the current execution stack. When the first() function is called, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack.

When second() is called within the first() function, the Javascript engine creates a new execution context for the function and pushes it to the top of the current execution stack. When second() completes, its execution context is ejected from the current execution stack, and control of the context is moved to the next execution context on the current execution stack, that of the first() function.

When the first() function completes execution, its execution context pops out of the current execution stack, and context control moves to the global execution context. Once all code has been executed, the Javascript engine removes the global execution context from the execution stack.

How to view the call stack information using the browser

When you execute a complex piece of code, it may be difficult to analyze the call relationship from the code file. In this case, you can put breakpoints in the function you want to view, and then see the call stack of that function when it is executed.

This may sound a bit abstract, but to demonstrate this, you can open developer Tools, click the “Source” TAB, select the page of your JavaScript code, add a breakpoint at line 3, and refresh the page. You can see that when the add function is executed, the execution process stops. At this point, you can check the current call stack through the “Call Stack” on the right, as shown below:

As can be seen from the figure, the function call relationship is displayed under the right “Call stack” : At the bottom of the stack is anonymous, which is the global function entry; In the middle is the addAll function; At the top is the add function. This clearly reflects how functions are called, so the call stack can be useful when analyzing complex structured code or checking for bugs.

In addition to using breakpoints to view the call stack, you can use console.trace() to print the current function call relationship. For example, add console.trace() to the add function in the sample code, and you can see the console output as shown below:

Reference:

JavaScript deep lexical scope and dynamic scope

Understand Javascript execution context and execution stack

Understanding Execution Context and Execution Stack in Javascript

Understand the execution context and execution stack in Javascript

Javascript Advanced Programming version 4

Javascript you Don’t Know (Volume 1)

Working principle and practice of browser

Javascript dynamic scope you don’t know about