Maison  >  Article  >  interface Web  >  Comprendre les fermetures en javascript

Comprendre les fermetures en javascript

高洛峰
高洛峰original
2017-01-20 11:41:471026parcourir

Lecture des contenus

Qu'est-ce qu'une fermeture ?

Caractéristiques des fermetures

Fonction des fermetures :

Exemples de code de fermetures

Notes

Résumé

Fermeture est un concept important en JavaScript, et c'est également une technologie fréquemment utilisée dans le travail quotidien. Faisons-en un petit résumé

Qu'est-ce qu'une fermeture ?

Déclaration officielle :

Une fermeture fait référence à une fonction qui a accès à une variable dans la portée d'une autre fonction. Une façon courante de créer une fermeture est de créer une autre fonction à l'intérieur d'une fonction et d'accéder aux variables locales de cette fonction via une autre fonction

Ce qui suit est une fermeture simple :

function A(){
 var text="hello world";
 function B(){
 console.log(text);
 }
 return B;
}
var c=A();
c(); // hello world 

Selon le sens littéral : la fonction B a le droit d'accéder à la variable (texte) dans le cadre de la fonction A, et d'accéder au texte de la variable locale de cette fonction via une autre fonction C. La fonction B forme donc une fermeture. On peut aussi dire que C est une fermeture, car C exécute réellement la fonction B.

Il convient de noter qu'il n'y aura aucune réaction si A(); Parce que return B n'est exécuté que s'il s'agit de return B();

Caractéristiques de la fermeture

La fermeture a trois caractéristiques :

1. Fonction fonction imbriquée

2. Les paramètres et variables externes peuvent être référencés à l'intérieur de la fonction

3. Les paramètres et variables ne seront pas recyclés par le mécanisme de collecte des ordures

Expliquez le point 3, pourquoi les paramètres de fermetures et Will les variables ne sont-elles pas recyclées par le mécanisme de récupération de place ?

Tout d'abord, comprenons le principe du garbage collection en javascript :

(1) En javascript, si un objet n'est plus référencé, alors l'objet sera GC (garbage collection). ) Recyclage ;

(2) Si deux objets font référence l'un à l'autre et ne sont plus référencés par un tiers, alors les deux objets qui font référence l'un à l'autre seront également recyclés.

Dans l'exemple de code ci-dessus, A est la fonction parent de B, et B est affecté à une variable globale C (le cycle de vie de la variable globale ne se terminera que lorsque le navigateur déchargera la page), ce qui provoque B doit toujours être en mémoire, et l'existence de B dépend de A, donc A est toujours en mémoire et ne sera pas recyclé par le mécanisme de récupération de place (garbage collection) une fois l'appel terminé.

Le rôle de la fermeture :

En fait, le rôle de la fermeture est également déterminé par les caractéristiques de la fermeture. Selon les caractéristiques de la fermeture ci-dessus, le rôle de la fermeture est le suivant :

1. Vous pouvez lire les variables à l'intérieur de la fonction au lieu de définir des variables globales pour éviter de polluer l'environnement

2. Gardez les valeurs de ces variables en mémoire.

Exemples de code de fermetures

Ce qui suit présente principalement plusieurs fermetures courantes et les analyse :

demo1 Accumulation de variables locales.

function countFn(){
 var count=1;
 return function(){  //函数嵌套函数
 count++;
 console.log(count);
 }
}
var y = countFn(); //外部函数赋给变量y;
y(); //2 //y函数调用一次,结果为2,相当于countFn()()
y(); //3 //y函数调用第二次,结果为3,因为上一次调用的count还保存在内存中,没有被销毁,所以实现了累加
y=null; //垃圾回收,释放内存
y(); // y is not a function

Puisque la première exécution est terminée, le nombre de variables est toujours stocké en mémoire, il ne sera donc pas recyclé, afin que la dernière valeur puisse être mise à jour lors de la seconde exécution. Additionnez simplement. Lorsque y=null est introduit, la référence est détruite et la mémoire est libérée

demo2 utilise des fermetures dans la boucle

Le code est le suivant (trois exemples de code ci-dessous) : Notre but est de utilisez-le dans chaque boucle Appelez le numéro de séquence de la boucle :

demo2-1

for (var i = 0; i < 10; i++) {
 var a = function(){
 console.log(i)
 }
 a() //依次为0--9
}

Le résultat de cet exemple est incontestable, nous avons imprimé 0-9

Chaque couche de fonctions anonymes et de variables forme une fermeture, mais il n'y a aucun problème dans la boucle, car la fonction est exécutée immédiatement dans le corps de la boucle

demo2-2

Mais c'est différent dans setTimeout

for(var i = 0; i < 10; i++) {
 setTimeout(function() {
 console.log(i); //10次10
 }, 1000);
}

Ce à quoi nous nous attendons, c'est d'imprimer 0 à 10 dans l'ordre, mais la situation réelle est d'imprimer 10 à 10 fois. Même si le temps setTimeout est modifié à 0, 10 10 s seront imprimés. Pourquoi est-ce ?

Cela est dû au mécanisme de setTimeout. setTimeout commence le chronométrage à la fin de la file d'attente des tâches. S'il y a un processus devant qui n'est pas terminé, il attend qu'il se termine avant de démarrer le chronométrage. Ici, la file d'attente des tâches est sa propre boucle.

setTimeout ne démarre pas le chronométrage jusqu'à la fin de la boucle, donc quoi qu'il en soit, le i dans setTimeout est le i de la dernière boucle. Dans ce code, le dernier i vaut 10, donc 10 10 sont imprimés

C'est pourquoi le rappel de setTimeout ne prend pas la valeur à chaque boucle, mais prend la dernière valeur

. demo2-3

Résoudre le problème selon lequel setTimeout ci-dessus ne peut pas imprimer la boucle en séquence

for(var i=0;i<10;i++){
 var a=function(e){
 return function(){
  console.log(e); //依次输入0--9
 }
 }
 setTimeout(a(i),0);
}

Parce que le premier paramètre de setTimeout nécessite une fonction, il renvoie one La fonction le donne, et lors du retour, i est transmis en tant que paramètre et i est mis en cache via le paramètre formel e. En d'autres termes, la variable e est équivalente à une copie de i et est introduite dans la fonction renvoyée. .

Lorsque setTimeout est exécuté, il a une référence à e, et cette valeur ne sera pas modifiée par la boucle.

peut également s'écrire de la manière suivante, similaire à celle ci-dessus :

for(var i = 0; i < 10; i++) {
 (function(e) {
 setTimeout(function() {
  console.log(e); //依次打印出0-9
 }, 0);
 })(i);
}

demo3 Ajouter des événements dans la boucle

Regardez un démo typique ci-dessous.

Nous espérons qu'à chaque fois que nous cliquons sur li, la valeur d'index de li sera alertée, nous utilisons donc le code suivant :

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes = document.getElementsByTagName("li");
for(i = 0,len=nodes.length;i<len;i++){
 nodes[i].onclick = function(){
 alert(i); //值全是4
 };
}

Cela se retourne contre vous, peu importe sur lequel vous cliquez, li, sont toutes des alertes (4), c'est-à-dire qu'elles sont toutes des valeurs d'index après la fin de la boucle d'alerte. Pourquoi est-ce ?

En effet, les événements sont liés à différents éléments de la boucle. Si une variable liée à la boucle est appelée dans la fonction de rappel d'événement, cette variable prendra la dernière valeur de la boucle.

由于绑定的回调函数是一个匿名函数,所以上面的代码中, 这个匿名函数是一个闭包,携带的作用域为外层作用域(也就是for里面的作用域),当事件触发的时候,作用域中的变量已经随着循环走到最后了。

还有一点就是,事件是需要触发的,而绝大多数情况下,触发的时候循环已经结束了,所以循环相关的变量就是最后一次的取值。

要实现点击li,alert出li的索引值,需要将上面的代码进行以下的修改:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes=document.getElementsByTagName("li");
for(var i=0;i<nodes.length;i++){
 (function(e){
 nodes[i].onclick=function(){
  alert(e);
 };
 })(i)
}

解决思路: 增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标)。

当立即执行函数执行的时候,e 值不会被销毁,因为它的里面有个匿名函数(也可以说是因为闭包的存在,所以变量不会被销毁)。执行后,e 值 与全局变量 i 的联系就切断了,

也就是说,执行的时候,传进的 i 是多少,立即执行函数的 e 就是多少,但是 e 值不会消失,因为匿名函数的存在。

也可以用下面的解法,原理是一样的:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes=document.getElementsByTagName(&#39;li&#39;);
for(var i = 0; i<nodes.length;i++){
 (function(){
 var temp = i;
 nodes[i].onclick = function () {
  alert(temp);
 }
 })();
}

注意事项

1、造成内存泄露

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以只有在绝对必要时再考虑使用闭包。

2、在闭包中使用this也可能会导致一些问题。

其实我们的目的是想alert出object里面的name

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  return function(){
  return this.name;
  }
 }
 }
 alert(object.getNameFunc()()); // The Window

因为在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。

每个函数在被调用时,都会自动取的两个特殊变量:this和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止。也就是说,里面的return function只会搜索

到全局的this就停止继续搜索了。因为它永远不可能直接访问外部函数中的这两个变量。

稍作修改,把外部作用域中的this对象保存在一个闭包能够访问的变量里。这样就可以让闭包访问该对象了。

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  var that=this;
  return function(){
  return that.name;
  }
 }
 }
 alert(object.getNameFunc()()); // My Object

我们把this对象赋值给了that变量。定义了闭包之后闭包也可以访问这个变量。因此,即使在函数返回之后,that也仍引用这object,所以调用object.getNameFunc()()就返回 “My Object”了。

总结

当在函数内部定义了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量。

闭包的作用域包含着它自己的作用域、包含函数的作用域和全局作用域。

当函数返回一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。

使用闭包必须维护额外的作用域,所有过度使用它们可能会占用大量的内存

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持PHP中文网!

更多理解javascript中的闭包相关文章请关注PHP中文网!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn