JavaScript has a feature called Scope. Although the concept of scope is not easy to understand for many novices, I will do my best to explain scopes and scope chains in the simplest way possible. Let’s take a look

1. Scope

A scope is a separate area, specifically the area in which variables are defined in our program. It determines the access to variables for the currently executing code.

There are two types of scope in JavaScript:

  • Global scope
  • Local scope

If a variable is declared outside of a function, or outside of a code block (curly braces {}), then a global scope is defined. Before ES6, local scopes consisted only of function scopes. The block-level scopes provided by ES6 were also local scopes.

Var innerVariable = "inner"} console.log(innerVariable) // Uncaught ReferenceError: innerVariable is not definedCopy the code

In the example above, the innerVariable is declared in the local scope of the function. The innerVariable is not declared in the global scope. Therefore, the output error is reported in the global scope.

In other words, a scope is a separate area where variables are kept from being exposed. The greatest use of a scope is to isolate variables. Variables of the same name in different scopes do not conflict.

function fun1(){
    var variable = 'abc'
}
function fun2(){
    var variable = 'cba'
}
Copy the code

In the example above, there are two functions, each with the same name as variable, but they are in different functions, that is, in different scopes, so they do not conflict.

Prior to ES6 JavaScript did not have block-level scopes, only global and function (local) scopes. Unlike functions, block statements (statements in the middle of {}), such as the if and switch conditional statements or the for and while loops, do not create a new scope.

if(true) {
    var a = 1
}
for(var i = 0; i < 10; i++) {
    ...
}
console.log(a) // 1
console.log(i) // 9
Copy the code

2. Global scope

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

  1. Outermost functions and variables defined outside the outermost function have global scope
Var outVariable = "I am the outer variable "; Var inVariable = "inVariable" {// the inVariable function console.log(inVariable)} InnerFun ()} console.log(outVariable) // I am the outer variable outFun() // the inner variable console.log(inVariable) //inVariable is not defined innerFun() //innerFun is not definedCopy the code

In the example above, the innerFun function executes after we call outFun(), which outputs the inVariable variable because the inner scope has access to the outer scope. But the next line outputs the inVariable which is in the global scope and cannot access the local scoped variable, so that variable will not be accessible.

  1. All variables that do not have a direct assignment defined (also known as unexpected global variables) are automatically declared to have global scope
Function outFun2() {variable = "undefined "; Var inVariable2 = "inner variable 2"; } outFun2(); Console. log(variable); // Execute this function first, otherwise you won't know what console.log(variable) is; // "Variable not defined by direct assignment" console.log(inVariable2); //inVariable2 is not definedCopy the code
  1. All properties of window objects have global scope

In general, the built-in properties of window objects have global scope, such as window.name, window.location, window.document, window.history, and so on.

The downside of global scope is that if we write many lines of JS code and the variable definitions are not included in functions, then they are all in the global scope. This will pollute the global namespace and cause naming conflicts.

Var data = {A: 1} var data = {B: 2}Copy the code

That’s why jQuery, Zepto, etc., all the code is placed in (function(){…. })() (execute function immediately). Because all variables placed inside will not be leaked and exposed, will not pollute the outside, will not affect other libraries or JS scripts. This is a representation of the scope of the function.

3. Local scope

In contrast to global scopes, local scopes are generally accessible only within fixed snippets of code. Local scopes are divided into function scopes and block scopes.

3.1 Function scope

A function scope is a variable or function declared inside a function.

function doSomething(){
    var name="Rockky";
    function innerSay(){
        console.log(name);
    }
    innerSay();
}
console.log(name); //name is not defined
innerSay(); //innerSay is not defined
Copy the code

Scopes are layered, with inner scopes having access to variables in outer scopes and not vice versa. Let’s look at an example where the scope analogy is easier to understand by using bubbles:The final output is 2, 4, 12

  • Bubble 1 is global scoped and has identifier foo;
  • Bubble 2 is scoped foo and has identifiers A,bar,b;
  • Bubble 3 is scoped bar and has only identifier C.

3.2 Block-level scope

ES5 has only global and functional scopes, not block-level scopes, which leads to a lot of irrational scenarios.

In the first scenario, the inner variables may override the outer variables.

var tmp = new Date();
function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}
f(); // undefined
Copy the code

The if block uses the TMP variable in the outer layer and the TMP variable in the inner layer. The TMP variable in the inner layer overwrites the TMP variable in the outer layer. The initialization of the temp variable is not promoted. That is, the variable is declared but not initialized, so the value of temp is undefined.

In the second scenario, loop variables used to count are exposed as global variables.

var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); / / 5Copy the code

In the code above, the variable I is used only to control the loop, but when the loop ends, it does not disappear and leaks out as a global variable.

ES6’s block-level scope addresses these issues to some extent.

Block-level scopes can be declared with the new let and const commands. The declared variables cannot be accessed outside the scope of the specified block. Block-level scopes are created when:

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

The syntax of the let declaration is the same as that of var. You can basically use let instead of VAR to declare variables, but you limit the scope of the variable to the current code block. Block-level scopes have the following characteristics:

  • Declared variables are not promoted to the top of the code block, that is, there is no variable promotion
  • Do not declare the same variable repeatedly
  • Nifty use of bound block scope in loops
3.2.1 Variable promotion

The “variable promotion” phenomenon occurs when the var command is used, that is, the variable can be used before the declaration and its value is undefined. This is somewhat strange, as normal logic dictates that variables should be used only after the statement has been declared. To correct this, the let command changes the syntactic behavior so that the variables it declares must be used after the declaration or an error will be reported.

// var case console.log(foo); Var foo = 2; // let's case console.log(bar); // ReferenceError let bar = 2;Copy the code

In the above code, variable foo is declared with the var command, and variable promotion occurs. That is, when the script starts running, variable foo already exists, but has no value, because the JS engine only improves the declaration of variables, not the initialization of variables. Equivalent to the following code:

Var foo; console.log(foo); // undefined foo = 2;Copy the code

So it prints undefined. The variable bar is declared with the let command, and no variable promotion occurs. This means that the variable bar does not exist until it is declared, and an error will be thrown if it is used.

If both functions and variables are declared, which will be promoted?

console.log(foo); 
var foo = 'abc'; 
function foo(){}
Copy the code

The output is function foo(){}, which is the function content. What if it’s the other way around?

console.log(foo);
var foo = 'abc';
var foo = function(){}
Copy the code

The output result is undefined.

  • The first is function declaration. It’s the first one above,function foo(){}This form
  • The second kind: function expression. The second one above,var foo=function(){}This form

The second form is the declaration definition of the var variable, so it should be understood that the second form is undefined. The first form of function declaration, when promoted, is promoted in its entirety, including the function definition! So the first form is equivalent to the following!

var foo = function(){}
console.log(foo);
var foo ='abc';
Copy the code
  • Function declarations are promoted to the top;
  • The declaration is only made once, so latervar foo='abc'“Is ignored.
  • Function declarations take precedence over variable declarations and are promoted along with definitions (unlike variables).

As long as the let command exists in the block-level scope, the variables it declares are “binding” to the region, no longer subject to external influence.

var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
Copy the code

In the above code, there is a global variable TMP, but in the block-level scope, let also declared a local variable TMP, causing the latter to bind the block-level scope. Therefore, before let declared the variable, TMP assignment will report an error.

ES6 explicitly states that if there are let and const commands in a block, the variables declared by the block to those commands form a closed scope from the start. Any time these variables are used before declaration, an error is reported.

In short, the variable is not available within the code block until it is declared using the let command. This is grammatically known as a “temporal dead zone” (TDZ).

If (true) {// Temporary dead zone start TMP = 'ABC '; // ReferenceError console.log(tmp); // ReferenceError let tmp; // Temporary dead zone end console.log(TMP); // undefined tmp = 123; console.log(tmp); / / 123}Copy the code

In the above code, the TMP variable is in the “dead zone” of TMP until the let command declares it.

A “temporary dead zone” also means that Typeof is no longer a 100 percent secure operation.

typeof x; // ReferenceError
let x;
Copy the code

In the above code, variable X is declared using let command, so before declaration, it belongs to the “dead zone” of X, and an error will be reported whenever this variable is used. Therefore, the Typeof runtime throws a ReferenceError.

By comparison, typeof does not generate an error if a variable is not declared at all.

typeof undeclared_variable // "undefined"
Copy the code

In the above code, undeclared_variable is a nonexistent variable name and returns “undefined”. So, before let, typeof operators are 100 percent safe and never report errors. That’s no longer true. This is designed to encourage good programming practice. Variables must be used after declaration, otherwise an error will be reported.

Some “dead zones” are hidden and hard to spot.

function bar(x = y, y = 2) { return [x, y]; } bar(); / / an errorCopy the code

In the above code, calling bar fails (and some implementations may not) because the default value of parameter x is equal to another parameter y, which has not yet been declared and is a “dead zone.” If y defaults to x, no error is reported because x is already declared.

function bar(x = 2, y = x) { return [x, y]; } bar(); / / (2, 2)Copy the code

In addition, the following code also reports an error, which behaves differently from var.

Var x = x; // let x = x; // ReferenceError: x is not definedCopy the code

The above code error is also due to temporary dead zone. When declaring a variable with let, an error is reported whenever the variable is used before the declaration is complete. The above line is an example of this situation, where the value of x is fetched before the declaration of variable x is completed, causing an error “x undefined”.

ES6 makes it clear that temporary dead zones and let and const statements do not promote variables. The main purpose of ES6 is to reduce runtime errors and prevent the variable from being used before it is declared, resulting in unexpected behavior. Such errors are common in ES5, and with this provision, it’s easy to avoid them.

In short, the essence of a temporary dead zone is that the variable you want to use already exists as soon as you enter the current scope, but you can’t get it until the line of code that declared the variable appears.

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 at the top to make variables available within the entire code block.

function getValue(condition) { if (condition) { let value = "blue"; return value; } else {// value cannot return null here; } // value is not available here}Copy the code

The emergence of block-level scopes virtually eliminates the need for widely used anonymous instant-execution function expressions (anonymous IIFE).

Function () {var TMP =... ; . } ()); // block level scope {let TMP =... ; . }Copy the code
3.2.2 Repeated Statements

If an identifier is already defined within a code block, let declarations using the same identifier within that code block will result in an error being thrown. Such as:

Function func() {let a = 10; var a = 1; Function func() {let a = 10; let a = 1; }Copy the code

However, if you declare a new variable with the same name using let in a nested scope, no error is thrown.

var count = 30; if (condition) { let count = 40; // Will not throw an error}Copy the code

Also, arguments cannot be redeclared inside functions.

function func(arg) { let arg; Function func(arg) {{let arg; }} func(Copy the code

There is another caveat. ES6 block-level scopes must have braces, and if there are no braces, the JavaScript engine assumes that there is no block-level scope.

If (true) let x = 1; If (true) {let x = 1; }Copy the code
3.2.3 for loop

Developers probably want to implement block-level scope for the for loop the most, because you can restrict declared counter variables to the loop, for example:

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

In the code above, the counter I is only valid inside the for loop, and references outside the loop report an error.

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

In the code above, variablesiIt is declared by the var command and is valid globally, so there is only one variable globallyi. Each time through the loop, the variableiIs changed, and the loop is assigned to the arrayaInside the function ofconsole.log(i)The I in there is going to be globali. In other words, all arraysaOf the members of theiThey all point to the same thingi, resulting in the run-time output being the last roundiThat’s 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](); / / 6Copy the code

In the code above, variablesiisletDeclared, currentiIt’s valid for this cycle, so it’s valid for every cycleiIt’s actually a new variable, so the final output is6. You might ask if each turn of the loop is variableiIt’s all redeclared, so how does it know the value of the last cycle, so it can figure out the value of this cycle? This is because the JavaScript engine internally remembers the value of the previous loop and initializes the variables for this roundiIs calculated on the basis of the previous cycle.

Another special feature of the for loop is that the part that sets the variables of the loop is a parent scope, and the body of the loop is a separate child scope.

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

The above code works correctly, printing three ABCs. This indicates that the variable I inside the function and the loop variable I are not in the same scope and have separate scopes (the same scope cannot be repeatedly declared with let).

4. Scope chain

In the following code, console.log(a) retrievalvariable A, but it is not defined in the current scope (compare b). Variables that are not currently defined in scope become free variables. How do you get the value of the free variable? It looks through the parent scope layer by layer until it finds the global Window object, which is the global scope, and returns undefined if it doesn’t already exist. It’s like looking up variables from the inside out, layer by layer, along a chain, which we call the scope chain. The inner environment can access all the outer environments through the scope chain, but the outer environment cannot access any of the variables and functions of the inner environment.

Var a = 100 function fn() {var b = 200 console.log(a)} fn() {var b = 200 console.log(b)} fn()Copy the code

Let’s do another example

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

In f sub n, what scope do I go to when I take the value of the free variable x? — to the scope in which the fn function was created, regardless of where the fn function will be called.

The value of a free variable is evaluated in the scope in which the function was created. The value is “created”, not “called” (similar to the arrow function this points to). Remember that this is what is called 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()
Copy the code

Fn () returns the function bar, which is assigned to x. By executing x(), the bar function code is executed. When taking the value of b, fetch it directly from fn’s scope. The value of a is 30, and the value of a is 30. The value of a is 30, and the value of a is 30

reference

  • ES6 tutorial by Yifeng Ruan: es6.ruanyifeng.com/#docs/let
  • An in-depth understanding of JavaScript scopes and scope chains: juejin.cn/post/684490…
  • An in-depth understanding of JavaScript starts with scopes and scope chains :juejin.cn/post/684490…