Life is not like cooking, all the ingredients are ready to cook.

preface

I was on the verge of getting confused when it came to learning about scopes and scope chains until the other day when someone in a group asked me an interview question about scopes and I closed my eyes wondering what I would say if asked. These two days to check the data, do a few interview questions, think to output a bit of my own understanding, this article will be through a few simple code snippet and surface questions of JavaScript scope and closure related knowledge to explore. If you think it’s ok, please light up 👍🙈 on the left

1. Birth of a variable

var name = 'Jake Zhang'
Copy the code

Var name = ‘Jake Zhang’; var name = ‘Jake Zhang’;

1. When var name is encountered, the compiler asks the scope if a variable with that name already exists in the collection of the same scope. If so, the compiler ignores the declaration and continues compiling. Otherwise it requires the scope to declare a new variable in the current scope’s collection and name it name (error in strict mode).

2. The compiler then generates the runtime code for the engine to handle the name = ‘Jake Zhang’ assignment. The engine first asks the scope if there is a variable called name in the current set of scopes. If so, the engine will use this variable; If not, the engine continues to look for the variable.

To prove the above statement:

console.log(name); Var name ='Jake Zhang'; 

Copy the code

Var name = ‘Jake Zhang’; var name = ‘Jake Zhang’; From these two steps, a variable named name is created.

The core word of this article is scope. What is scope? Let’s take a look.

2. What is scope

First look at this code:

function fun() {
  var name = 'Jake Zhang'; console.log(name); } fun(); / / output"Jake Zhang"
Copy the code

When fun() executes, it prints a name variable. Where does the name variable come from? So you can see that the first line of the function has the code that defines the variable name var name = ‘Jake Zhang’

Let’s look at another piece of code:

var name2 = 'Jake Zhang2';
function fun() { console.log(name2); } fun(); / / output"Jake Zhang2"
Copy the code

Similarly, if name2 is not found inside the function itself, it will look in the outer global, stop looking and output the result.

Notice that both pieces of code have lookup variables. The first code is to find the name variable in the function fun, and the second code is to find the name2 variable globally. Now add scope to the two boldface words, and read again: The first code finds the name variable in the function scope fun, and the second code finds the name2 variable in the global scope.

In fact, we can see that scope is, in essence, a set of rules for determining where and how to find variables (identifiers). The key is to find the variable (or identifier).

From this we can derive

(1) Definition of scope:

A scope is the area where variables are defined, and it has a set of rules for accessing variables that govern how the browser engine looks for variables based on variables (identifiers) in the current scope and in nested scopes.

(2) Lexical scope

In the scope introduction above, we defined scope as a set of rules that govern how the browser engine performs variable lookups based on variables (identifiers) in the current scope as well as in nested scopes.

Now we put forward a concept, “lexical scope” is a kind of work scope of the model, there are two kinds of scope work model of the lexical scope in JavaScript (static scope) is one of the more mainstream, another dynamic scope (it is not concerned about the functions and variables declared how, and where, only care about where they call). Lexical scope is determined by where variables and blocks are scoped as you write code. In other words, lexical scope is a static scope, determined as you write code.

Look at the following code:

function fn1(x) {
	var y = x + 4;
	functionfn2(z) { console.log(x, y, z); } fn2(y * 5); } fn1(6); / / 6 10 to 50Copy the code

This example has three nested scopes, as shown in the figure below:

  • A is global scope and has an identifier: fn1
  • B is the scope created by fn1 and has three identifiers: x, y, and fn2
  • C is the scope created by fn2 and has an identifier: z

Scope is determined by where its code is written and is included at a level by level.

(3) Block-level scope

Before ES6 JavaScript did not have the concept of block-level scope. Let’s look at some code:

for(var i=0; i<5; i++){ console.log(window.i) } //0 1 2 3 4Copy the code

If you don’t use a for loop inside a function, you might be surprised to find that var is not equal to white var, because it is a global variable, and we can only search from the bottom up, not the other way around. So JavaScript has no concept of block-level scope. Block-level scope is a new concept in ES6. It often refers to statements in {}, such as if and switch conditions or for and while loops. Unlike functions, they do not create a new scope. Block-level scope is usually represented by let or const.

for(letj=0; j<5; j++)(console.log(window.j)); //undefined *5Copy the code

Look at the code above to compare this to the previous var I loop. In fact, when it comes to let and const, there are also related knowledge points such as variable promotion, temporary dead zone and so on. (Limited by space, I will not expand it here, but I will write about it later.)

Ok, now if you’re asked again what a scope is, it should be clear. Remember to mention lexical scope in passing. Let’s continue exploring the chain of scopes.

3. Scope chain

Let’s go back to the code that started with scopes:

var name2 = 'Jake Zhang2';
function fun() { console.log(name2); } fun(); / / output"Jake Zhang2"
Copy the code

When we look for the name2 variable, we first look in the function scope, and then look in the global scope. You’ll notice that this is an outer search, a search up a chain of variables. This chain is called the chain of scope.

This leads us to the concept of a scope chain: the process of finding free variables in multiple levels of nested scopes is the access mechanism of the scope chain. Nested scopes form relationships by accessing free variables called scope chains.

Here are two images to help you understand:

4. Analyze the scope and scope chain from the interview questions

1, decryption principle

  • Every time a function in a scope is executed, it enters a new scope (usually from the bottom up).
  • When you use a variable or assign a value to a variable, the variable is found first from the current scope and then from the upper scope.

Question 1: The output of the following code

var a = 1
function fn1() {function fn2(){
    console.log(a)
  }
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2   
  returnFn3} var fn = fn1() fn() // output a=2 // execute fn2, fn2 cannot find a variable, then go up and find a=2 in fn1 where the current fn2 was createdCopy the code

Question 2: The output of the following code

var a = 1        
function fn1() {function fn3(){  
    var a = 4
    fn2()        
  }
  var a = 2
  return fn3    
}

function fn2(){console.log(a)} var fn = fn1() fn() fn() fn()Copy the code

Question 3 (key) : The output of the following code

var a = 1
function fn1() {function fn3() {function fn2(){
      console.log(a)
    }
    var a
    fn2()
    a = 4
  }      
  var a = 2
  returnFn3} var fn = fn1() fn(); fn3} var fn = fn1() fn(); fn3} fn = fn1() fn(); A is declared but not initialized to undefinedCopy the code

Let’s look at a set of pseudocode for finding procedures in the scope chain:

1 problem

var x = 10
bar() 
function foo() {
   console.log(x) 
}
function bar(){var x = 30 foo()} /* (){var x = 30 foo()} /* (){var x = 30 foo()} I found the var x is equal to 10. The output of foo() is 10. * /Copy the code

The second problem is


var x = 10;
bar()  //30
function bar(){
  var x = 30;
  function foo(){ console.log(x) } foo(); } /* In line 2, bar() calls the function bar; in line 3, bar is the function foo; in line 4, foo looks for x in its local environment, but does not find it. Foo looks for x in its local environment, and finds var x = 30. So on line 2 bar() output is 30 */Copy the code

The third problem is

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function(){console.log(x)})()} /* Line 2, bar () calls bar functionfunction() looks for x in his local environment, but doesn't find itfunction() Look for x in the local environment of bar, and find var x =30, so the result is displayed as 30 */Copy the code

5, closures

The scope and lexical scope mentioned above are preparation for closure. Lexical scope is also a prerequisite for understanding closures, so if you are a little vague about scope, you can go back and look at it again.

(1) Resolve the closure from the instance

Closures are a phenomenon that occurs when writing code based on lexical scope. A closure is a function that can read the internal variables of other functions. As you’ll see by the following practice, closures are everywhere in your code, you don’t have to create them for yourself, and as you get deeper into your project, you’ll be using them in your code.

Example 1:

function a(){
    var n = 0;
    function add(){
       n++;
       console.log(n);
    }
    return add;
}
var a1 = a(); // Note that the function name is only an identifier (a pointer to the function), and () is the execution function;
a1();    / / 1
a1();    / / 2

Copy the code

Analysis is as follows:

  • addIs accessible to the lexical scope ofaThe scope of. Conditional executionaThe code inside the function,addReturn as a value;
  • addAfter execution, willaIs assigned toa1;
  • performa1, output 1,2 respectively

By reference, a1 is the function a itself (A1 =a). Executing A1 normally outputs the value of the variable n, which is not “a remembers and accesses its lexical scope”, whereas a (called by A1) runs outside the current lexical scope.

When the add function is finished, the scope is destroyed, and the garbage collector frees the closure, but the closure magically keeps add’s scope alive, and A still holds a reference to that scope.

Why is that? The reason is that a is the parent function of Add, and add is assigned to a global variable, which causes add to always be in memory. The existence of ADD is dependent on A, so A is always in memory and will not be collected by garbage collection after the call is completed. So, in essence, closures are the bridge that connects the inside of a function to the outside.

Summary: A closure is a function that references a variable of another function. Because the variable is referenced, it is not reclaimed and can therefore be used to encapsulate a private variable.

(2) The purpose of closures

Closures can be used in many ways. It is useful both for reading variables inside a function, as mentioned earlier, and for keeping the values of those variables in memory.

(3) Practical application of closures

There are many things we can do with closures. For example, simulating object-oriented code style; More elegant, more concise expression of code; Improve code execution efficiency in some ways.

Example 2: Timers everywhere

function waitSomeTime(msg, time) {
	setTimeout(function () {
		console.log(msg)
	}, time);
}
waitSomeTime('hello', 1000);

Copy the code

The timer has an anonymous function that has a closure covering the scope of the waitSomeTime function, so that after 1 second, the anonymous function can output MSG.

Example 3: Problem with the for loop to output function values

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

Through the for loop, the expected result is 0 to 9, but the final result of execution, shown on the console, is 10 10s in the global scope.

This is because when we execute fnArr[3], we first look for the variable I in its current scope. If we don’t find the variable I, we look for it in the global scope. The for loop ends when I is greater than or equal to 10. Then function is executed. When I is equal to [0,1,2,3,4,5,6,7,8,9], I then executes the function code, and the output value is always I and the final value at the end of the loop is: 10, so it prints 10 times.

Result: I was declared in the global scope, the function anonymous functions and implementation in the global scope, that is, of course, every time the output of 10.

Extension:

So, let I generate a private scope for each iteration, and hold the current value of I in that private scope

var fnArr = [];
for (var i = 0; i < 10; i++) {
  fnArr[i] = (function(){
    var j = i
    return function() {return j
     }  
  })()
}
console.log(fnArr[3]()) //3
Copy the code

In a more concise and elegant way:

Pass the I of each iteration as an argument to the self-executing function, which uses the variable to receive the output value


var fnArr = []
for (var i = 0; i < 10; i ++) {
  fnArr[i] =  (function(j){
    return function() {return j
    } 
  })(i)
}
console.log( fnArr[3]() ) // 3
Copy the code

Example 4: Traversal abstraction in an array

Here I first talk about the concept of abstraction through Java abstract classes:

If you have learned Java, you will not be unfamiliar with abstract ideas. Let’s first look at an abstract class in Java:

public abstract class SuperClass {
  public abstract void doSomething();
}
Copy the code

So this is a class in Java that has an abstract method doSomething in it, and now we don’t know what the subclass wants the doSomething method to do, so we’re going to define that method as an abstract method, and let the subclass implement the logic itself. Create subclasses to implement SuperClass:

public class SubClass  extends SuperClass{
  public void doSomething() {
    System.out.println("say hello"); }}Copy the code

Copy the doSomething output string “say Hello” in the SubClass of the code, and other subclasses will have other implementations, which are abstract classes and implementations in Java.

I think the callback function should be a:

function createDiv(callback) {
  let div = document.createElement('div');
  document.body.appendChild(div);
  if (typeof callback === 'function') {
    callback(div);
  }
}
createDiv(function (div) {
  div.style.color = 'red';
})
Copy the code

In this example, we have a function called createDiv, which is responsible for creating a div and adding it to the page, but the function createDiv doesn’t know what to do with the div after that, so give it to the person who called createDiv, and let the caller decide what to do next, The div is given to the caller as a callback.

This is also abstract, since you don’t know what the div will do next, you can give it to the caller and let the caller do it. This is also a basic idea that we are learning about vUE and other framework component development.

Ok, so to summarize the concept of abstraction: abstraction is hiding the more concrete implementation details and looking at the problem at a higher level.

The traversal abstraction in an array

During programming, not all functions are ready-made. For example, in the example above, several divs can be created, and each div may be treated differently. Unknown operations need to be abstracted and entry for operations reserved.

Let’s take a look at a few JavaScript array manipulation methods to get a deeper understanding of the abstract idea:

var arr = [1, 2, 3, 4, 5];
for (var i = 0; i < arr.length; i++) {
  var item = arr[i];
  console.log(item);
}
Copy the code

This code uses a for loop, and then values in order, so if you think that’s a little bit unelegant, it leaves open the possibility of something going wrong, like writing length wrong, accidentally reusing I. So in that case, can you extract a function? Most importantly, all we want is each value in the array, and then we can manipulate that value, so we can hide the traversal:

function forEach(arr, callback) {
  for(var i = 0; i < arr.length; i++) { var item = arr[i]; callback(item); }}forEach(arr, function (item) {
  console.log(item);
});
Copy the code

The forEach method above hides the details of the traversal and returns the item that the user wants to operate on. You can also return the I and arr themselves in the callback: callback(item, I, arr). The forEach method provided natively by JS looks like this:

arr.forEach(function (item) {
  console.log(item);
});
Copy the code

Similar methods to forEach include map, some, every, and so on. The idea is the same: this abstract approach makes it easier for the user while making the code clearer.

Well, the abstract simple introduction to this, and then back is the knowledge of higher order functions, I am still ignorant of higher order functions, when I grow up this matter, I will update.

The latter

This lying on my draft box for approximately two weeks, before wrote half just write not bottom go to, today finally finished the 😪 ~ to write again scope: scope is to define a variable area, it has a variable access rules, the rules of how to manage the browser engine in the current scope and nested scopes according to variables (identifier) for search. Finally, niche is the front small white one, wrote one of the most original intention is to let oneself have more profound impression on the knowledge and understanding, writing is also very small white, in this paper, if there is any wrong, welcome to correct me ~ then hope can point like watching a friend, if you don’t abandon, can focus on a wave of ~ I will continue to output!

Personal Blog links

GitHub

CSDN personal home page

Nuggets personal page

Brief book personal home page