Home  >  Article  >  Web Front-end  >  JavaScript summary sharing closure

JavaScript summary sharing closure

WBOY
WBOYforward
2022-11-07 16:31:081273browse

This article brings you relevant knowledge about JavaScript, which mainly introduces related issues about closures, including what closures are, why they are designed in this way and how they can be used. Let’s take a look at the relevant content below. I hope it will be helpful to everyone.

JavaScript summary sharing closure

[Related recommendations: JavaScript video tutorial, web front-end]

What is a closure?

For a knowledge point, I have always believed that no matter where you start from, you need to thoroughly understand three questions before you can truly understand this knowledge point, and then practice it in practice in order to be able to claim it. To be mastered. These three questions are: What is

  • ?
  • Why design?
  • Where can it be used?

First answer the question of what closure is. Most people should have read many related articles, and many people have also given their own explanations, so I will first give an explanation that I understand, that is: There are two prerequisite concepts:

  • Closure has been determined during lexical analysis, so it will be related to lexical scope.

  • The prerequisite for the existence of closures is that a programming language needs to support functions as first-class citizens, so it will be related to functions.

So the final conclusion is:

  • The closure is first a structure, and the components of this structure are a function. The lexical scope at
  • is The closure is a structure generated by a function and the function can remember to declare its own lexical scope.
  • Understanding in memory is that when a function is called, The scope chain in the function execution context it generates saves its parent lexical scope, so the parent variable object is referenced due to its existence It will not be destroyed and will reside in memory for its use. This situation is called closure.

The above explanation should be clear to those who have already understood closures, but in fact, if it is for a person who does not know closures at all, it may be completely incomprehensible. What's more, many people actually just remember this definition, but don't really understand the connotation.

So I want to use an analogy that is not necessarily accurate to help understand what closures are. Imagine that you wrote an article and put it on your own server, and cited 3 of your own articles as references. . So at this time, the server environment of an article is similar to a closure.

After being published on the Internet, it will be reprinted on other platforms. If readers on other platforms click on your article and want to continue reading the articles you quoted, they will be accurately redirected. Go to the article on your server.

In this example, this article saves a reference to the server environment in which this article was written. Therefore, no matter where you read the article, the reference article reference remembered in the article will always point to the address in the server. This situation is called using the closure feature.

Maybe the example is still not easy to understand, after all, it is not very accurate. The concept of closure is a bit abstract, and I didn't think of any concrete examples in reality that can be used as a metaphor. If anyone has a better analogy to point out, I'll annotate and describe it.

Why should we design closure?

As for why this is designed, my superficial understanding is because JavaScript is an asynchronous single-threaded language. The biggest problem with asynchronous programming is that when you write a function, the time it is actually called may be at any time later.

This is a big problem for memory management. For normally synchronously executed code, the data required when the function is declared and called still remains in the memory. You can Accessible. Asynchronous code often declares that the context of the function may have been destroyed. By the time it is called, if some external data it requires has been cleared in the memory, this is a big problem.

So the solution for JavaScript is to allow the function to remember the range of data it can obtain before, and all of them are stored in the memory. As long as the function is not recycled by the memory, it itself and what it can remember None of the scopes will be destroyed.

The scope that can be remembered here refers to the lexical scope, which needs to be remembered because it is static.

This is again caused by the static design scope of JavaScript. If it is a dynamic scope, when the function is called, it only needs the environment when it is called, and there is no need to remember its own scope.

So to summarize:

  • Closures are created to solve the problems caused by lexical scope and poor memory management in data acquisition in asynchronous programming.

Classic question

Originally my idea was to explain the closure situation from the lowest level. Later, when I checked various articles, I found that one article was already well written. . That is the underlying operating mechanism of JavaScript closures. I think you can read this explanation first and then read what I write later.

Since there are a lot of articles starting from the following very classic interview question, but no one seems to have really explained it from the bottom level, so I plan to sort out the whole process to understand it. difference.

for (var i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}

Basically all people with basic skills can tell at a glance that the output is three 3's.

Then modify it to output in order. Usually you only need to modify var to let:

for (let i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}

In this way, the output is 0, 1, 2. And it is output at the same time, not every time Output once every one second.

Then the question is, why?

You can write your own explanation first without reading the following to see if it is the same as what I wrote.

1. Let’s first discuss the situation where variable i is var.

When the code starts executing, the situation in the execution context stack and memory is as follows: The variable i in the global object and the variable i in the variable environment in the global execution context are the same variable.

Then the loop starts. When i = 0, the first timer is thrown into the macro task queue. The content related to macro tasks belongs to the event loop category. For the time being, Just understand that setTimeout will be thrown into the queue and executed later. At this time, its callback function cb will be created in the heap memory, and [[scope]] will be created when the function is created. In the actual ECMA rules, [[scope]] will point to the parent scope of the function, which is the current The global object (scope is a conceptual thing, and the actual manifestation in memory is a structure that saves data, which may be an object or something else). However, in the implementation of the V8 engine, it does not actually point to the global object. Instead, it analyzes which variables in the parent scope are used by the function, stores these variables in the Closure, and then points to the scope. Each function has exactly one Closure object.


Here is the information about where the Closure object can be seen in Chrome: As you can see, when the bar function is created, it only references the name variable of the parent scope, so only the variable name is stored in the closure object, and the variable age does not exist.


Similarly, i = 1, and i = 2 are the same, and the final result will become:

Finally, i = 3 because of i, the loop ends, and the global code is executed. The result at this time is:

Then the execution process of the timer callback function begins. Start executing the callback function in the first timer, push it into the execution context stack, and execute the output i. However, the variable i cannot be found in the lexical environment and variable environment, so we go to its own [[scope]] and look up. i is found in the Closure object and is equal to 3, and the output result is 3.

Similarly, the process is the same for the next two timers, and in fact the time when the timer is started is executed immediately in the loop, resulting in the actual three The timing of each function is consistent for 1 second, and the final output result is that three 3's are output almost simultaneously. Instead of outputting 3 after every 1 second interval, of course this is timer-related knowledge.

2. Then discuss what actually changed after changing var to let

When it was first created, the situation shown is:

After entering the loop body, when i = 0:

Then enter the situation when i = 1:

Finally enters the situation of i = 2, which is basically similar to i = 1:

Finally i becomes i value 3 , the cycle ends. Start timer work:

当执行第一个定时器的回调函数时,创建了函数执行上下文,此时执行输出语句i时,会先从自己的词法环境里寻找变量i的值,也就是在 record环境记录里搜索,但是不存在。因而通过自己外部环境引用outer找到原先创建的块级作用域里 i = 0的情况, 输出了i值为0的结果。

对于之后的定时器也都是一样的情况,原先的块级作用域由于被回调函数所引用到了,因而就产生了闭包的情况,不会在内存中被销毁,而是一直留着。

等到它们都执行完毕后,最终内存回收会将之全部都销毁。

其实以上画的图并不是很严谨,与实际在内存中的表现肯定是有差异的,但是对于理解闭包在内存里的情况还是不影响的。

闭包能用在哪?

首先需要先明确一点,那就是在JavaScript中,只要创建了函数,其实就产生了闭包。这是广义上的闭包,因为在全局作用域下声明的函数,也会记着全局作用域。而不是只有在函数内部声明的函数才叫做闭包。

通常意义上所讨论的闭包,是使用了闭包的特性

1. 函数作为返回值

let a = 1function outer() {  let a = 2

  function inside() {
    a += 1
    console.log(a)
  }  return inside
}const foo = outer()foo()

此处outer函数调用完时,返回了一个inside函数,在执行上下文栈中表示的既是outer函数执行上下文被销毁,但有一个返回值是一个函数。 该函数在内存中创建了一个空间,其[[scope]]指向着outer函数的作用域。因而outer函数的环境不会被销毁。

当foo函数开始调用时,调用的就是inside函数,所以它在执行时,先询问自身作用域是否存在变量a, 不存在则向上询问自己的父作用域outer,存在变量a且值为2,最终输出3。

2. 函数作为参数

var name = &#39;xavier&#39;function foo() {  var name = &#39;parker&#39;
  function bar() {    console.log(name)
  } console.log(name)  return bar
}function baz(fn) {  var name = &#39;coin&#39;
  fn()
}baz(foo())baz(foo)

对于第一个baz函数调用,输出的结果为两个'parker'。 对于第二个baz函数的调用,输出为一个'parker'。

具体的理解其实跟上面一致,只要函数被其他函数调用,都会存在闭包。

3. 私有属性

闭包可以实现对于一些属性的隐藏,外部只能获取到属性,但是无法对属性进行操作。

function foo(name) {  let _name = name  return {    get: function() {      return _name
    }
  }
}let obj = foo(&#39;xavier&#39;)
obj.get()

4. 高阶函数,科里化,节流防抖等

对于一些需要存在状态的函数,都是使用到了闭包的特性。

// 节流function throttle(fn, timeout) {  let timer = null
  return function (...arg) {    if(timer) return
    timer = setTimeout(() => {
    fn.apply(this, arg)
    timer = null
    }, timeout)
  }
}// 防抖function debounce(fn, timeout){  let timer = null
  return function(...arg){    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arg)
    }, timeout)
  }
}

5. 模块化

在没有模块之前,对于不同地方声明的变量,可能会产生冲突。而闭包能够创造出一个封闭的私有空间,为模块化提供了可能性。 可以使用IIFE+闭包实现模块。

var moduleA = (function (global, doc) {  var methodA = function() {};  var dataA = {};  return {    methodA: methodA,    dataA: dataA
  };
})(this, document);

【相关推荐:JavaScript视频教程web前端

The above is the detailed content of JavaScript summary sharing closure. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete