Maison  >  Article  >  interface Web  >  Fermeture du partage du résumé JavaScript

Fermeture du partage du résumé JavaScript

WBOY
WBOYavant
2022-11-07 16:31:081193parcourir

Cet article vous apporte des connaissances pertinentes sur JavaScript, qui présente principalement des problèmes liés aux fermetures, notamment ce que sont les fermetures, pourquoi elles sont conçues de cette manière et comment elles peuvent être utilisées. Ce qui suit est Jetons un coup d'œil, espérons-le. aide tout le monde.

Fermeture du partage du résumé JavaScript

[Recommandations associées : Tutoriel vidéo JavaScript, front-end web]

Qu'est-ce qu'une fermeture ?

Pour un point de connaissance, j'ai toujours pensé que peu importe d'où l'on part, il faut bien comprendre trois questions pour vraiment comprendre ce point de connaissance, puis le mettre en pratique pour pouvoir le maîtriser. Ces trois questions sont : Qu'est-ce que

  • ?
  • Pourquoi concevoir ?
  • Où peut-il être utilisé ?

Répondez d’abord à la question de savoir ce qu’est la fermeture. La plupart des gens auraient dû lire de nombreux articles connexes, et de nombreuses personnes ont également donné leurs propres explications, je vais donc d'abord donner une explication que je comprends, c'est-à-dire : Il y a deux notions préalables :

  • La clôture a été déterminée lors de l'analyse lexicale, elle sera donc liée à la portée lexicale.

  • La condition préalable à l'existence des fermetures est qu'un langage de programmation doit prendre en charge les fonctions en tant que citoyens de premier ordre, il sera donc lié aux fonctions.

La conclusion finale est donc :

  • La fermeture est d'abord une structure. Les composants de cette structure sont une fonction + la portée lexicale de la fonction闭包首先是一个结构体,这个结构体的组成部分为  一个函数 + 该函数所处的词法作用域
  • 也就是闭包是由一个函数并且该函数能够记住声明自己的词法作用域所产生的结构体
  • 在内存中理解就是, 当一个函数被调用时,它所产生的函数执行上下文里的作用域链保存有其父词法作用域,所以父变量对象由于存在被引用而不会销毁,驻留在内存中供其使用。这样的情况就称为闭包。

上述的解释对于已经了解过闭包的人应该是一目了然的,但其实如果对于一个完全不知晓闭包的人来说,很可能是完全看不懂的。更甚至很多人其实仅仅只是记住了这种定义,而不是真的理解了这内涵。

所以我想用一个不一定精准的类比去帮助理解什么是闭包这东西,想象你写了一篇文章放在自己的服务器上,并且引用了自己的3篇文章作为参考。那么此时 一篇文章 + 服务器的环境 就类似于闭包。

在发表到网络上后被转载到其他的平台上,而其他平台上的读者点开你的文章阅读后想继续看你所引用的那些文章,就被准确无误的跳转到了你服务器里的文章中去。

在这个例子中,这篇文章保存了写这篇文章的服务器环境里的引用。  因而不论是在哪里读到文章,文章里所记得的参考文章引用指向永远是服务器里的地址。 这种情况叫做使用了闭包的特性。

可能例子还是不太好理解,毕竟它也没有很准确,闭包这概念就是有点抽象,没有想到现实中有什么具体的例子可以用来比喻。 如果有人想出更好的类比可以指出,我加以注释和描述。

为什么要设计出闭包?

对于为什么设计这点,仅以我自己粗浅的理解就是由于JavaScript是异步单线程的语言。对于异步编程来说,最大的问题就是当你编写了函数,而等到它真正调用的时机可能是之后任意的时间节点。

这对于内存管理来说是一个很大的问题,正常同步执行的代码,函数声明时和被调用时所需要的数据都还存留在内存中,可以无障碍的获取。而异步的代码,往往声明该函数的上下文可能已经销毁,等到在调用它时,如果内存中已经把它所需要的一些外部数据给清理了,这就是个很大的问题。

所以JavaScript解决的方案就是让函数能够记得自己之前所能获取数据的范围,统统都保存在内存里,只要该函数没有被内存回收,它自身以及所能记住的范围都不会被销毁

C'est A. La fermeture est une structure générée par une fonction qui mémorise la portée lexicale de sa déclaration.

Comprendre en mémoire que lorsqu'une fonction est appelée, la chaîne de portée dans le contexte d'exécution de la fonction qu'elle génère enregistre sa portée lexicale parent, de sorte que l'objet variable parent ne sera pas détruit car il existe et est référencé, réside. en mémoire pour son utilisation. Cette situation s’appelle la fermeture.

L'explication ci-dessus devrait être claire pour les personnes qui ont déjà compris les fermetures, mais en fait, s'ils ignorent complètement les fermetures, elles peuvent être complètement incompréhensibles. De plus, beaucoup de gens se souviennent simplement de cette définition, mais n’en comprennent pas vraiment la connotation.

🎜Je souhaite donc utiliser une analogie qui n'est pas nécessairement exacte pour aider à comprendre ce que sont les fermetures. Imaginez que vous ayez écrit un article et que vous l'ayez mis sur votre propre serveur, et que vous ayez cité 3 de vos propres articles comme références. Alors à ce moment-là, un environnement article + serveur s’apparente à une fermeture. 🎜🎜Après avoir été publié sur Internet, il a été réimprimé sur d'autres plateformes, et les lecteurs d'autres plateformes ont cliqué sur votre article pour lire et ont voulu continuer à lire les articles que vous avez cités, et ils ont été redirigés avec précision vers votre serveur. Accédez à l'article. . 🎜🎜Dans cet exemple, cet article enregistre une référence à l'environnement serveur dans lequel cet article a été rédigé. Par conséquent, peu importe où vous lisez l'article, la référence de l'article de référence mémorisée dans l'article pointera toujours vers l'adresse du serveur. Cette situation est appelée à l'aide de la fonction de fermeture. 🎜🎜Peut-être que l'exemple n'est toujours pas facile à comprendre, après tout, il n'est pas très précis. Le concept de fermeture est un peu abstrait, et je n'ai pensé à aucun exemple concret dans la réalité qui puisse être utilisé comme métaphore. Si quelqu'un peut penser à une meilleure analogie à souligner, je vais l'annoter et la décrire. 🎜

Pourquoi devrions-nous concevoir des fermetures ? 🎜🎜Quant à la raison pour laquelle ceci est conçu, je comprends simplement que JavaScript est un langage asynchrone à thread unique. Le plus gros problème avec la programmation asynchrone est que lorsque vous écrivez une fonction, le moment où elle est réellement appelée peut être ultérieur à tout moment. 🎜🎜C'est un gros problème pour la gestion de la mémoire Pour le code normalement exécuté de manière synchrone, les données requises lorsque la fonction est déclarée et appelée restent toujours en mémoire et peuvent être exécutées sans aucune entrave. Le code asynchrone déclare souvent que le contexte de la fonction peut avoir été détruit au moment de son appel, si certaines données externes dont elle a besoin ont été effacées de la mémoire, c'est un gros problème. 🎜🎜La solution de JavaScript consiste donc à permettre à la fonction de mémoriser la plage de données qu'elle peut obtenir auparavant et de les enregistrer toutes en mémoire. Tant que la fonction n'est pas recyclée par la mémoire, elle et la plage dont elle peut se souvenir le seront. ne sera pas détruit. 🎜🎜La portée mémorisable fait ici référence à la portée lexicale, dont il faut se souvenir car elle est statique. 🎜🎜Cela est encore une fois dû à la portée de conception statique de JavaScript. S'il s'agit d'une portée dynamique, lorsque la fonction est appelée, elle n'a besoin que de l'environnement lorsqu'elle est appelée et il n'est pas nécessaire de se souvenir de sa propre portée. 🎜🎜Donc pour résumer :🎜
  • La fermeture est pour 解决词法作用域引发的问题内存不好管理异步编程里数据获取所产生的。

经典题

原本我的想法是从最底层来解释闭包的情况,后来在查阅各种文章时发现, 有一篇文章已经写的很好了。 那就是JavaScript闭包的底层运行机制, 我觉得可以先看看这篇的讲解然后在看我之后所写的内容。

由于有非常多的文章都从下面这个非常经典的面试题入手,但似乎都没有人真正从最底层讲解过,所以我就打算将整个过程梳理一遍,来明白这其中的差异性。

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

基本所有有基础的人一眼就能看出输出的是三个3。

然后让修改成按顺序输出,通常只需要修改var成let:

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

这样就成了输出为0,1,2.并且是同时间输出,而不是每间隔一秒输出一次。

那么问题来了,为什么?

这里可以先不看下面,先写写自己的解释,看看是否跟我写的一样。

1. 先来探讨变量i是var的情况。

当代码开始执行时,此时执行上下文栈和内存里的情况是这样: 其中全局对象里的变量i和全局执行上下文里变量环境里的变量i être la même variable.

Ensuite, la boucle démarre. Lorsque i = 0, le premier timer est jeté dans la file d'attente des tâches macro. Pour le moment, il vous suffit de comprendre cela. setTimeout sera jeté dans la file d'attente, puis exécuté. À ce stade, sa fonction de rappel cb sera créée dans la mémoire du tas et [[scope]] sera créé lors de la création de la fonction. Dans les règles ECMA actuelles, [[scope]] pointera vers la portée parent du. fonction, qui est l'objet global actuel (la portée est une chose conceptuelle et la manifestation réelle en mémoire est une structure qui enregistre les données, qui peuvent être un objet ou autre chose). Cependant, dans l'implémentation du moteur V8, il ne pointe pas réellement vers l'objet global, mais analyse quelles variables de la portée parent sont utilisées par la fonction, stocke ces variables dans la fermeture, puis pointe vers la portée. Chaque fonction a exactement un objet Closure.


Voici les informations sur l'endroit où l'objet Closure peut être vu dans Chrome : Comme vous pouvez le voir, lorsque la fonction bar est créée, elle fait uniquement référence à la variable name de la portée parent, donc seul le nom de la variable est stocké dans l'objet de fermeture, mais la variable age n'existe pas.


De même, i = 1 et i = 2 sont identiques, et le résultat final deviendra :

Au final, i = 3 à cause de i++, la boucle se termine et le code global est exécuté. Le résultat à ce moment est :

Ensuite, le processus d'exécution de la fonction de rappel du minuteur commence, Commencez à exécuter la fonction de rappel dans le premier minuteur, poussez-la dans la pile de contexte d'exécution et exécutez la sortie i. Cependant, la variable i est introuvable dans l'environnement lexical et l'environnement variable, nous allons donc dans sa propre [[portée]. ] et recherchez Trouvé i égal à 3 dans l'objet Closure, et le résultat de sortie est 3.

De même, le processus est le même pour les deux minuteries suivantes, et en fait l'heure à laquelle la minuterie démarre est exécutée immédiatement dans la boucle, ce qui fait que le timing d'une seconde des trois fonctions est cohérent , le résultat final est de produire trois 3 presque en même temps. Au lieu d'en afficher 3 toutes les 1 secondes d'intervalle, il s'agit bien sûr de connaissances liées à la minuterie.

2. Discutez ensuite de ce qui a réellement changé après avoir modifié var pour laisser

Lors de sa création, la situation affichée est :

Entrez ensuite le corps de la boucle, lorsque i = 0 :

puis entre dans la situation où i = 1 :

Entre enfin dans la situation où i = 2, qui est fondamentalement similaire à i = 1 :

Enfin, i++ devient i valeur 3, boucle Finish. Démarrer le travail du minuteur :

当执行第一个定时器的回调函数时,创建了函数执行上下文,此时执行输出语句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前端

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer