MEMEPh. ideas that are worth sharing...

Deep understanding of JavaScript scope and scope chain

Foreword


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

 

Scope


1. What is scope

Scope is the accessibility of variables, functions and objects in some specific part of the code at runtime. In other words, scope determines the visibility of variables and other resources within a block of code. Maybe these two sentences are not easy to understand, let's look at an example first:

function outFun2() {
var inVariable = "Inner Variable 2";
}
outFun2();//You must execute this function first, otherwise you don't know what's in it at all
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined

From the above example, you 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 site, so that variables will not be leaked and exposed . That is to say , the biggest use of scope is to isolate variables, and variables with the same name in different scopes will not conflict.

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

 

2. Global scope and function scope

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

var outVariable = "I am the outermost variable"; //The outermost variable
function outFun() { //outermost function
var inVariable = "Inner Variable";
function innerFun() { //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
function outFun2() {
variable = "undefined variable for direct assignment";
var inVariable2 = "Inner Variable 2";
}
outFun2();//You must execute this function first, otherwise you don't know what's in it at all
console.log(variable); //The variable that is directly assigned is not defined
console.log(inVariable2); //inVariable2 is not defined

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

The global scope has a disadvantage: if we write many lines of JS code, and the variable definitions are not included with functions, 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 libraries such as jQuery, Zepto, etc., all the code will be placed (function(){....})() in. Because all the variables placed in it will not be leaked and exposed, will not be polluted to the outside, and will not affect other libraries or JS scripts. This is a manifestation of function scope.

Function scope refers to variables declared inside a function. In contrast to the global scope, the local scope is generally only accessible within a fixed code segment, the most common being inside a function.

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

Scopes are hierarchical, inner scopes can access variables in outer scopes, but not vice versa . Let's look at an example. It may be easier to understand using a bubble as a metaphor for scope:

 

The final output is 2, 4, 12

It's worth noting that block statements (statements between curly braces "{}"), such as if and switch conditionals or for and while loops, unlike functions, do not create a new scope . Variables defined within a block statement will remain in the scope where they already exist.

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

Beginners to JS often take a while to get used to variable hoisting, and failure to understand this peculiar behavior can lead to

bugs. Because of this, ES6 introduces block-level scope to make the life cycle of variables more controllable.

 

3. Block scope

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

The syntax of a let declaration is the same as that of a var. You can basically use let instead of var for a variable declaration, but it will limit the scope of the variable to the current block of code. Block-level scope has the following characteristics:

let/const declarations are not hoisted to the top of the current block, so you need to manually place the let/const declaration at the top to make the variable available throughout the block.

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

Do not repeat declarations

If an identifier is already defined inside a code block, a let declaration with the same identifier inside the code block will cause an error to be thrown. E.g:

var count = 30;
let count = 40; // Uncaught SyntaxError: Identifier 'count' has already been declared

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

var count = 30;
// no error will be thrown
if (condition) {
let count = 40;
// other code
}

The Magical Use of Binding Block Scope in Loops

Developers may want to implement block-scoped for loops, because it is possible to limit the declared counter variable 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 body of the for loop, and an error will be reported 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 globally. Each time the loop, the value of the variable i will change, and the console.log(i) inside the function assigned to the array a in the loop, the i in it points to the global i. That is to say, the i in all the members of the array a point to the same i, which causes the output of the runtime to be the value of i in the last round, which is 10.

If let is used, the declared variable is only valid in block scope, and the last 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 is re-declared in each loop, how does it know the value of the previous loop to calculate the value of this loop? This is because the JavaScript engine will internally remember the value of the previous cycle, and when the variable i of this cycle is initialized, it will be calculated on the basis of the previous cycle.

In addition, there is a special feature of the for loop, that is, the part that sets the loop variable 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 outputs abc 3 times. This indicates that the variable i inside the function and the loop variable i are not in the same scope, but have separate scopes.

 

Scope Chain


1. What is a free variable

First of all, let's understand what a free variable is . In the following code, console.log(a)the a variable is to be obtained, but a is not defined in the current scope (compare b). A variable that is not defined in the current scope, this becomes a free variable. How to get the value of the free variable - look to the parent scope (note: this statement is not rigorous, and it will be explained below).

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

 

2. What is a scope chain

What if the parent doesn't have one? Look up layer by layer until you find the global scope and still don't find it, then 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. About the value of free variables

Regarding the value of the free variable, it is mentioned above that it needs to be taken from the parent scope. In fact, sometimes this interpretation will cause 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 should it be taken from? - to go to the scope where the fn function was created, no matter where the fn function will be called .

So, stop using the above statement. In contrast, it would be more appropriate to describe it in this sentence: to go to the field where the function was created". The

value in the scope, the emphasis here is "create", not "call" , remember to remember - in fact, this It's 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, take it directly in the fn scope. When taking the value of a, I tried to take it in the scope of fn, but I couldn't get it, so I could only turn to the scope where fn was created to find it, and 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, mistakenly thinking they are the same concept, but this is not the case.

We know that JavaScript is an interpreted language. The execution of JavaScript is divided into two phases: interpretation and execution. These two phases do different things:

 

Interpretation stage:

Execution stage:

The scoping rules are determined during the JavaScript interpretation phase, 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 executes. The most obvious execution context is that the pointer to this is determined at execution time. The variables accessed by scope are determined by the structure of the written code.

The biggest difference between a scope and an execution context is that the

execution context is determined at runtime and may change at any time; the scope is determined at the time of definition and does not change .

A scope may contain several contexts. It is possible that there has never been a context (the function has never been called); it may have been, and now after the function is called, the context is destroyed; there may be one or more (closures) at the same time. Under the same scope, different invocations will generate different execution contexts, and then generate different variable values .