Deep Understanding of JavaScript Scope and scope chain

  Front end, html, javascript, node.js, Programmer

Preface

JavaScript has a feature called Scope. Although the concept of scope is not easy to understand for many novice developers, I will try my best to explain scope and scope chain in the simplest way I can in this article. I hope everyone can get something!

If you want to read more excellent articles, please stamp them fiercely.GitHub blog

Scope

1. What is scope

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

function outFun2() {
 Var inVariable = "inner variable 2";
 }
 outFun2();  //Execute this function first, otherwise you have no idea what is in it.
 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 the value taken in the global scope will report an error. We can understand it this way:The scope is an independent site to prevent variables from leaking out and being exposed.. That is to sayThe greatest use of scopes is to isolate variables. Variables with the same name will not conflict under different scopes.

Before ES6, JavaScript had no block-level scope, only global scope and function scope.. The arrival of ES6 provides us with’ block-level scope’, which can be embodied by adding new commands let and const.

2. Global scope and function scope

Objects accessible anywhere in the code have a global scope. Generally speaking, the following situations have a global scope:

  • The outermost function and the variables defined outside the outermost function have a global scope
Var outVariable = "i am the outermost variable";  //outermost variable
 Functionoutfund () {//outermost function
 Var inVariable = "inner variable";
 Functioninnerfund () {//inner function
 console.log(inVariable);
 }
 innerFun();
 }
 console.log(outVariable);  //I am the outermost variable
 outFun();  //inner variable
 console.log(inVariable);  //inVariable is not defined
 innerFun();  //innerFun is not defined
  • All variables that do not define direct assignment are automatically declared to have global scope
function outFun2() {
 Variable = "No directly assigned variable defined";
 Var inVariable2 = "inner variable 2";
 }
 outFun2();  //Execute this function first, otherwise you have no idea what is in it.
 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 the window object have global scope, such as window.name, window.location, window.top, etc.

Global scope has a disadvantage: if we write many lines of JS code and the variable definitions are not included by functions, then they are all in the global scope. This will pollute the global namespace and easily lead to 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 jQuery, Zepto and other libraries source code, all the code will be placed in the(function(){....})()China. Because all the variables placed inside will not be leaked or exposed, will not pollute the outside, and will not affect other libraries or JS scripts. This is an embodiment of the scope of the function.

Function scope refers to variables declared within a function. Contrary to global scope, local scope is generally only accessible within a fixed code fragment, most commonly within a function, for example.

function doSomething(){
 Var blogName= "sailing in the waves";
 function innerSay(){
 alert(blogName);
 }
 innerSay();
 }
 alert(blogName);  //Script Error
 innerSay();  //Script Error

Scope is hierarchical, inner scope can access variables of outer scope, otherwise it is not. Let’s look at an example, using bubbles to describe the scope may be a little better understood:

The final output is 2, 4, 12

  • Bubble 1 is a global scope with identifier foo; ;
  • Bubble 2 is scope foo and has 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, do not create a new scope. Variables defined in block statements will remain in their existing scope.

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

Beginners of JS often need to spend some time getting used to variable promotion, but 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

Block-level scope can be declared by adding commands let and const, and declared variables cannot be accessed outside the scope of the specified block. A block-level scope is created when:

  1. Within a function
  2. Inside a code block (enclosed by a pair of curly braces)

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

  • Declaring variables will not be promoted to the top of the code block

The let/const declaration is not promoted to the top of the current code block, so you need to manually place the let/const declaration to the top to make variables available inside the entire 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
 }
  • Repetitive declaration is prohibited

If an identifier has already been defined within a code block, using the same identifier to declare a let 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 example, the count variable is declared twice: once var and once let. Because let cannot repeatedly declare an existing identifier in the same scope, let declaration here will throw an error. However, if a new variable with the same name is declared using let within a nested scope, no error will be thrown.

var count = 30;
 //No error will be thrown
 if (condition) {
 let count = 40;
 //Other codes
 }
  • The Wonderful Use of Bound Block Scope in Loop

Developers may want to implement the block-level scope of the for loop most, because declared counter variables can be limited to loops, such as:

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

In the above code, counter I is only valid in the for loop, and an error will be reported if it is quoted 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. Every cycle, the value of variable I changes, and the console.log(i) inside the function assigned to array a in the cycle points to the global I. In other words, I in all members of array a point to the same I, resulting in the runtime outputting the value of I in the last round, that is, 10.

If let is used, the declared variable is only valid in 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 round of cycles, 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 re-declared, how does it know the value of the previous cycle so as to calculate the value of this cycle? This is because the JavaScript engine will remember the value of the previous round, and when the variable I of this round is initialized, it will be calculated on the basis of the previous round.

In addition, the for loop has another special feature, that is, the part where the loop variable is set is a parent scope, and the inside 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 has output abc 3 times. This indicates that the variable I inside the function is not in the same scope as the loop variable I, and has its own scope.

scope chain

1. What is free variables

First of all, let’s know what is calledfree variables. In the following code,console.log(a)To get the A variable, but there is no definition of A in the current scope (compare B). There are no variables defined in the current scope, which becomes free variables. How do you get the value of free variables-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
 Log (a)//A here is a free variables here.
 console.log(b)
 }
 fn()

2. What is scope chain

What if there is no parent? Look up layer by layer until the global scope is found or not found, then announce to give up. This layer-by-layer relationship is scope chain.

var a = 100
 function F1() {
 var b = 200
 function F2() {
 var c = 300
 Console.log(a) // free variables, looking for parent scope along scope chain
 Console.log(b) // free variables, looking for parent scope along scope chain
 Log (c)//variables for this scope
 }
 F2()
 }
 F1()

3. The value of free variables

As for the value of free variables, it is mentioned above that it should be taken from the parent scope. In fact, sometimes this explanation may give rise to 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 the value of free variables X is taken, which scope should it be taken? -Take it from the scope in which the fn function was created.No matter where fn function will be called.

So, don’t use it to say anything. In contrast, it would be more appropriate to use this sentence: * * to the domain where this function was created “.
The value in scope is “create” instead of “call” * *. Remember-this is actually 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, assigned to x. Execute x (), i.e. execute bar function code. When the value of B is taken, it is directly taken out in the scope of fn. When taking the value of a, I tried to take it in the scope of fn, but I couldn’t get it. I had to turn to the scope where fn was created to find it. the result was found, so the final result was 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 explanatory language, and its execution can be divided into two stages: interpretation and execution. The two stages do different things:

Interpretation phase:

  • Lexical analysis
  • syntax analysis
  • Scope rule determination

Implementation phase:

  • Create execution context
  • Execute function code
  • Garbage collection

The JavaScript interpretation phase determines the scope rules, so the scope is 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 execution context is that the direction of this is determined at the time of 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 defined and will not change..

A scope may contain several context environments. It is possible that there has never been a contextual environment (functions have never been called); It is possible that once the function is called, the context environment is destroyed. It is possible to have one or more (closures) at the same time.Under the same scope, different calls will produce different execution context environment, and then produce different variable values..

To recommend a useful BUG monitoring toolFundebug, welcome to try free!

Welcome to pay attention to the public number:Front end craftsmanWe witness your growth together! If you feel fruitful, please give me a reward to encourage me to output more high-quality open source content.

Reference articles and books