Deep understanding of JavaScript scope and scope chain

Time:2021-10-22

preface

There is a feature in JavaScript called scope. Although the concept of scope is not easy to understand for many novice developers, I will try my best to explain the scope and scope chain in the simplest way. I hope you can gain something!

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 this:The scope is an independent domain, so that variables will not be leaked and exposed。 in other wordsThe 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

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 am 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 = "variable with direct assignment is not 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

Generally, the built-in properties of window objects have global scope, such as window.name, window.location, window.top, etc.

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(){....})()Yes. 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.

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。 Let’s take an example. It may be easier to understand the scope by using bubbles:

The final output results are 2, 4 and 12

  • Bubble 1 is a global scope with identifier foo;
  • Bubble 2 is the scope foo, with identifiers a, bar, B;
  • Bubble 3 is the scope bar and has only identifier C.

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, they do not create a new scope。 Variables defined in a block statement 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.

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:

  1. Inside a function
  2. 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:

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

In the above code, the counter I is only valid in the for loop, and an error will be reported if it is referenced outside the loop.

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

In the above code, the variable I is declared by the VaR command and is valid in the global scope, so there is only one variable I in the global scope. In each loop, the value of variable I will change, and in the loop, the console.log (I) assigned to the function of array a, in which I points to the global I. In other words, the I in all members of array a points to the same I, resulting in the output of the last round of I value at runtime, that is, 10.

If let is used, the declared variables are valid only within the block level scope, and the final output is 6.

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

In the above code, the variable I is declared by let, and the current I is only valid in this cycle, so the I of each cycle is actually a new variable, so the final output is 6. You may ask, if the variable I of each cycle is redeclared, how does it know the value of the previous cycle and calculate the value of this cycle? This is because the JavaScript engine will remember the value of the previous cycle. When initializing the variable I of this cycle, it will calculate based on the previous cycle.

In addition, a special feature of the for loop is that the part where the loop variable is set is a parent scope, and the interior of the loop body is a separate child scope.

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

The above code runs correctly and outputs ABC three times. This indicates that the variable I inside the function and the loop variable I are not in the same scope and have their own separate scopes.

Scope chain

1. What is a free variable

First of all, let’s know what is calledFree variable。 In the following code,console.log(a)To get the a variable, but there is no a defined in the current scope (compare with B). There is no variable defined in the current scope, which becomes a free variable. How to get the value of a free variable — look for the parent scope (Note: this statement is not rigorous, and will be explained in detail below).

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

2. What is scope chain

What if there’s no parent? Look up layer by layer until the global scope is found or not found, and 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. Follow the scope chain to find the parent scope
        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 instead of 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. In comparison, this sentence is more appropriate:Go to the field where this function is created.
Value in the scope. The emphasis here is on “create” rather than “call”
, remember — this is actually what we call a “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

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 (the function has never been called); There may have been. Now, after the function is called, the context is destroyed; It is possible that one or more (closures) exist at the same time.Under the same scope, different calls will produce different execution context environments, and then produce different variable values

Postscript

If you need front-end guidance, front-end materials, Java guidance and Java materials, please contact me and thank you for your support.

WECHAT:xzsj07
remarks: please indicate the source.

Author: boating in the waves
Link:Deep understanding of JavaScript scope and scope chain
Source: GitHub
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.