Scope, scope chain, free variable

Scope

1. What is scope

A scope is the accessibility of variables, functions, and objects in certain parts of the runtime code. In other words, the scope determines the visibility of variables and other resources in the code block. Maybe these two sentences are not easy to understand. Let's take a look at an example:

function outFun2() {
    var inVariable = "Inner variable 2";
}
outFun2();//You have to execute this function first, or you don't know what's inside
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined

From the above example, we can understand the concept of scope. The variable inVariable is not declared in the global scope, so an error will be reported when taking a value in the global scope. We can understand it this way: the scope is an independent territory, so that variables will not be leaked and exposed. In other words, the greatest use of scopes is to isolate variables. Variables with the same name under different scopes will not conflict.

Before ES6, JavaScript had no block level scope, only global scope and function scope. The arrival of ES6 provides us with a 'block level scope', which can be reflected by adding the commands let and const.

2. Global scope and function scope

2.1 global scope

Objects that can be accessed anywhere in the code have a global scope. Generally speaking, the following situations have a global scope:

  • The outermost function and variables defined outside the outermost function have global scope
var outVariable = "I'm the outermost variable"; //Outermost variable
function outFun() { //Outermost function
    var inVariable = "Inner variable";
    function innerFun() { //Inner function
        console.log(inVariable);
    }
    innerFun();
}
console.log(outVariable); //I'm the outermost variable
outFun(); //Inner variable
console.log(inVariable); //inVariable is not defined
innerFun(); //innerFun is not defined
  • All variables that are not defined and directly assigned are automatically declared to have a global scope
function outFun2() {
   variable = "No directly assigned variable is defined";
   var inVariable2 = "Inner variable 2";
}
outFun2();//You have to execute this function first, or you don't know what's inside
console.log(variable); //No directly assigned variable is defined
console.log(inVariable2); //inVariable2 is not defined
  • The properties of all window objects have global scope
    In general, the built-in properties of window objects have global scope, such as window name,window.location,window.top, wait.
    There is a disadvantage of global scope: if we write many lines of JS code and the variable definitions are not included in the function, then they are all in the global scope. This will pollute the global namespace and easily cause naming conflicts.
// In the code written by Zhang San
var data = {a: 100}

// In the code written by Li Si
var data = {x: true}

This is why the source code of jQuery, Zepto and other libraries will be placed in (function() {...}) (). Because all variables placed inside will not be leaked and exposed, will not pollute the outside, and will not affect other libraries or JS scripts. This is an embodiment of function scope.

2.2 function scope (local scope)

Function scope refers to variables declared inside a function. Contrary to the global scope, the local scope is generally accessible only in fixed code fragments, such as inside a function.

function doSomething(){
    var blogName="Boating in the waves";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //Script error
innerSay(); //Script error

The scope is hierarchical. The inner scope can access the variables of the outer scope, and vice versa.
It is worth noting that block statements (statements between braces "{}"), such as if and switch conditional statements or for and while loop statements, unlike functions, do not create a new scope. Variables defined in block statements will remain in their existing scope.

if (true) {
    // The 'if' conditional statement block does not create a new scope
    var name = 'Hammad'; // name is still in the global scope
}
console.log(name); // logs 'Hammad'

JS beginners often need to take some time to get used to variable promotion. If they do not understand this unique behavior, it may lead to
bug . Because of this, ES6 introduces block level scope to make the life cycle of variables more controllable.

  • There is no block scope in JavaScript

3. Block level scope

The block level scope can be declared through the new command let and const. The declared variables cannot be accessed outside the scope of the specified block. A block level scope is created when:

a. Inside a function
b. Inside a code block wrapped in a pair of curly braces

The syntax of let declaration is consistent with that of var. You can basically use let to declare variables instead of VaR, but it will limit the scope of variables to the current code block. Block level scopes have the following characteristics:

  • Declared variables are not promoted to the top of the code block
    The let/const declaration will not be promoted to the top of the current code block, so you need to manually place the let/const declaration to the top to make the variables available within the whole code block.
function getValue(condition) {
  if (condition) {
    let value = "blue";
    return value;
  } else {
    // value is not available here
    return null;
  }
  // value is not available here
}
  • No duplicate declaration
    If an identifier is already defined within a code block, using the same identifier for let declaration within the code block will cause an error to be thrown. For example:
var count = 30;
let count = 40; // Uncaught SyntaxError: Identifier 'count' has already been declared

In this case, the count variable is declared twice: once with var and once with let. Because a let cannot repeatedly declare an existing identifier in the same scope, the let declaration here will throw an error. However, if you use let to declare a new variable with the same name within a nested scope, no error will be thrown.

var count = 30;
// No errors will be thrown
if (condition) {
  let count = 40;
  // Other codes
}
  • Clever use of binding block scope in loop
    Developers may most want to implement the block level scope of the for loop, because the declared counter variables can be limited to the loop. For example, the following code is often seen in JS:
<button>Test 1</button>
<button>Test 2</button>
<button>Test 3</button>
<script type="text/javascript">
   var btns = document.getElementsByTagName('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log('The first' + (i + 1) + 'individual')
      }
    }
</script>

We want to realize such a requirement: click a button and prompt "the nth button is clicked". Here we do not consider the event agent. Unexpectedly, clicking any button will pop up the "Fourth" in the background. This is because i is a global variable. When the event is clicked, the value of i is 3. How to modify it? The simplest is to declare i with let

 for (let i = 0; i < btns.length; i++) {
    btns[i].onclick = function () {
      console.log('The first' + (i + 1) + 'individual')
    }
  }

Scope chain

1. What is a free variable

First, let's know what free variables are. In the following code, console Log (a) wants to get the a variable, but there is no a defined in the current scope (compare with b). Variables that are not defined in the current scope become free variables. How to get the value of free variables - look for the parent scope (Note: this statement is not strict and will not be explained in detail here).

var a = 100
function fn() {
    var b = 200
    console.log(a) // A here is a free variable here
    console.log(b)
}
fn()

2. What is scope chain

The scopeChain (scope chain) in the execution environment EC is a one-way linked list of variable objects VO in the current memory. Each time a function is executed on the stack, the corresponding VO is added to the head of the scope chain. The previous VO can freely access the variables on the next VO, but not vice versa. That is, the VO(innerTest) in the following example can access VO(test) along the one-way chain, So you can access the b defined in test. Conversely, VO(test) in the chain cannot access VO(innerTest), so test cannot access c defined in innerTest.

In addition, innerTest needs to access the global a through the path of VO (innerTest) > VO (test) > VO (global). This is why we say that variables that cannot be found in innerTest will be found in test first, and then in global. We will find them along this chain.

It is also to avoid performance problems caused by too many nested function s that lead to too long chains.

var a = 20;
 
function test() {
    var b = a + 10;
 
    function innerTest() {
        var c = 10;
        return b + c;
    }
 
    return innerTest();
}
 
test();


innerTestEC = {
    VO: {...},  // Variable object
    scopeChain: [VO(innerTest), VO(test), VO(global)], // Scope chain
    this: {}
}

That is, search upward along the parent level until the global scope is found or not found, and then announce to give up. This layer by layer relationship is the scope chain.

var a = 100
function F1() {
    var b = 200
    function F2() {
        var c = 300
        console.log(a) // Free variable, find the parent scope along the scope chain
        console.log(b) // Free variable, find the parent scope along the scope chain
        console.log(c) // Variables in this scope
    }
    F2()
}
F1()

3. Values of free variables

As for the values of free variables, it is mentioned above that they should be taken from the parent scope. In fact, sometimes this interpretation will produce ambiguity.

var x = 10
function fn() {
  console.log(x)
}
function show(f) {
  var x = 20
  (function() {
    f() //10, not 20
  })()
}
show(fn)

In the fn function, when taking the value of the free variable x, which scope do you want to take it from—— To go to the scope where the fn function is created, no matter where the fn function will be called.
So don't use the above statement again. In contrast, this sentence is more appropriate * *: go to the domain where the function is created ".
The value in the scope emphasizes "create" rather than "call" * *. Remember - in fact, this is the so-called "static scope"

var a = 10
function fn() {
  var b = 20
  function bar() {
    console.log(a + b) //30
  }
  return bar
}
var x = fn(),
b = 200
x() //bar()

fn() returns the bar function, which is assigned to X. Execute x(), that is, execute the bar function code. When taking the value of b, it is directly taken out in the scope of fn. When getting the value of a, you try to get it in the fn scope, but you can't get it. You can only turn to the scope where fn was created. The result is found, so the final result is 30

4. Scope chain and closure

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() {
        console.log(a);
    }
    fn = innnerFoo; // Assign the reference of innnerFoo to fn in the global variable
}
 
function bar() {
    fn(); // References to reserved innerFoo here
}
 
foo();
bar(); // 2

In the above example, after foo() is executed, according to common sense, its execution environment life cycle will end and the occupied memory will be released by the garbage collector. However, through fn = innerFoo, the reference of the function innerFoo is retained and copied to the global variable FN. This behavior leads to the variable object of foo, which is also retained. Therefore, when the function fn is executed inside the function bar, it can still access the retained variable object. So you can still access the value of variable a at this moment.
In this way, we can call foo closure.

The following figure shows the scope chain of closure fn:

5. Closure thinking:

Let's take setTimeout as an example. Modify the following code with closures so that the output of the loop is 1, 2, 3, 4 and 5 in turn.

for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
        console.log(i);
    }, i*1000 );
}

Scope and execution context

Many developers often confuse the concepts of scope and execution context and mistake them for the same concept, but this is not the case.
We know that JavaScript is an interpretive language. The execution of JavaScript is divided into two stages: Interpretation and execution. The things done in these two stages are different:

Interpretation stage:

  • lexical analysis
  • Syntax analysis
  • Scope rule determination

Execution phase:

  • Create execution context
  • Execute function code
  • garbage collection

The scope rules will be determined in the JavaScript interpretation stage, so the scope has been determined when the function is defined, not when the function is called, but the execution context is created before the function is executed. The most obvious thing about the execution context is that the direction of this is determined during execution. The variables accessed by the scope are determined by the structure of the code.

The biggest difference between scope and execution context is:
The execution context is determined at runtime and may change at any time; The scope is determined at the time of definition and will not change.
A scope may contain several contexts. It is possible that there has never been a context environment (the function has never been called); it is possible that after the function is called, the context environment is destroyed; there may be one or more (closures) at the same time. Under the same scope, different calls will produce different execution context environments, and then produce different variable values.

For details about execution context, see the chapter "execution context".

Keywords: Javascript

Added by jmcc on Mon, 03 Jan 2022 06:21:00 +0200