Maison >interface Web >js tutoriel >Un chauffeur expérimenté vous aidera à bien comprendre les différents pièges des fermetures JS
L'ancien pilote vous aidera à bien comprendre les différents pièges des fermetures JS
Les fermetures sont js Techniques de développement courantes, que sont les fermetures ?
Une fermeture fait référence à une fonction qui peut accéder à des variables dans la portée d'une autre fonction. Pour le dire clairement : une fermeture est une fonction qui peut accéder à des variables dans le cadre d’autres fonctions. Par exemple :
function outer() { var a = '变量1' var inner = function () { console.info(a) } return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域 }
Beaucoup de gens ne comprennent pas la relation entre les fonctions anonymes et les fermetures. En fait, les fermetures sont définies du point de vue de la portée, car l'intérieur accède aux variables dans la portée externe, donc l'intérieur est une fonction de fermeture. Bien que la définition soit très simple, il existe de nombreux pièges, comme ce pointeur et la portée des variables. Un peu de négligence peut provoquer des fuites de mémoire. Laissons le problème de côté et réfléchissons à une question : Pourquoi les fonctions de fermeture peuvent-elles accéder à la portée d'autres fonctions ?
Regarder les fonctions js du point de vue de la pile
Variables de base La valeur de est généralement stockée dans la mémoire de pile, tandis que la valeur de la variable de type objet est stockée dans la mémoire tas et la mémoire de pile stocke l'adresse d'espace correspondante. Types de données de base : Nombre, Booléen, Non défini, Chaîne, Null.
var a = 1 //a是一个基本类型 var b = {m: 20 } //b是一个对象
correspond au stockage mémoire :
Quand on exécute b={m:30}, il y a un nouvel objet {m:30} dans le tas de mémoire, b dans la mémoire de pile pointe vers la nouvelle adresse d'espace (pointant vers {m : 30}), et le {m : 20} d'origine dans la mémoire de tas sera récupéré par le moteur du programme, économisant ainsi de l'espace mémoire. Nous savons que les fonctions js sont également des objets, et elles sont également stockées dans la mémoire tas et pile. Jetons un coup d'œil à la conversion :
var a = 1; function fn(){ var b = 2; function fn1(){ console.log(b); } fn1(); } fn();
**
<.>La pile est une structure de données premier entré, dernier sorti : 1 Avant d'exécuter fn, nous sommes dans l'environnement d'exécution global (le navigateur est la portée de la fenêtre), et il y a une variable a dans la portée globale ; 2 Entrez fn. À ce moment, la mémoire de la pile poussera un environnement d'exécution de fn. Cet environnement contient la variable b et l'objet fonction fn1. Ici, vous pouvez accéder aux variables définies par sa propre exécution. environnement et l'environnement d'exécution global 3 Entrez fn1 À ce moment, la mémoire de la pile poussera un environnement d'exécution de fn1. Aucune autre variable n'y est définie, mais nous pouvons accéder aux variables dans fn et le. environnement d'exécution global, car lorsque le programme accède aux variables, il se déplace vers la pile inférieure. Si vous constatez qu'il n'y a pas de variable correspondante dans l'environnement d'exécution global, le programme générera une erreur sous-définie. 4 Lorsque fn1() est exécuté, l'environnement d'exécution de fn1 est détruit par cup, puis fn() est exécuté, l'environnement d'exécution de fn sera également détruit, ne laissant que l'environnement d'exécution global, et maintenant, il n'y a plus de variables b et d'objets de fonction fn1, seulement a et fn (la portée de la déclaration de fonction est sous la fenêtre)**L'accès à une variable dans une fonction est jugé en fonction de la chaîne de portée de fonction Si la variable existe et si la chaîne de portée de fonction est initialisée par le programme en fonction de la pile d'environnement d'exécution où se trouve la fonction, donc dans l'exemple ci-dessus, nous imprimons la variable b dans fn1 et trouvons l'environnement d'exécution fn correspondant selon la chaîne de portée de la variable fn1 b. Ainsi, lorsque le programme appelle une fonction, il effectue le travail suivant : préparer l'environnement d'exécution, la chaîne de portée de la fonction initiale et l'objet paramètre d'argumentsNous revenons maintenant à l'exemple original externe et internefunction outer() { var a = '变量1' var inner = function () { console.info(a) } return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域 } var inner = outer() // 获得inner闭包函数 inner() //"变量1"Lorsque le programme termine l'exécution de var inner = external(), en fait, l'environnement d'exécution de external n'est pas détruit, car la variable a qu'il contient est toujours référencée par la chaîne de portée de fonction de inner lorsque le programme termine d'exécuter inner(). , ce n'est que lorsque l'environnement d'exécution interne et externe sera détruit et ajusté ; le livre "JavaScript Advanced Programming" recommande : Parce que les fermetures porteront la portée de la fonction qui les contient, car elles occuperont plus de contenu que les autres fonctions, excessives l'utilisation de fermetures entraînera une utilisation excessive de la mémoire. Maintenant, nous comprenons la fermeture, la portée et la chaîne de portée correspondantes, revenons au sujet :
Picture 1 : Les variables référencées peuvent changer
function outer() { var result = []; for (var i = 0; i<10; i++){ result.[i] = function () { console.info(i) } } return result }Il semble que chaque fonction de fermeture dans le résultat imprime le nombre correspondant, 1, 2, 3, 4,...,10. Ce n'est pas réellement le cas car chaque fonction de fermeture accède à la variable i dans l'environnement d'exécution externe. Variable i, à la fin de la boucle, i est devenu 10, donc chaque fonction de fermeture est exécutée et le résultat affiche 10, 10, ..., 10Comment résoudre ce problème ?
function outer() { var result = []; for (var i = 0; i<10; i++){ result.[i] = function (num) { return function() { console.info(num); // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样 } }(i) } return result }
Pit point 2 : cela pointe vers le problème
var object = { name: ''object", getName: function() { return function() { console.info(this.name) } } } object.getName()() // underfined // 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向window
Pit point 3 : Problème de fuite de mémoire
function showId() { var el = document.getElementById("app") el.onclick = function(){ aler(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放 } } // 改成下面 function showId() { var el = document.getElementById("app") var id = el.id el.onclick = function(){ aler(id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放 } el = null // 主动释放el }
Astuce 1 : utilisez des fermetures pour résoudre les problèmes d'appels récursifs
function factorial(num) { if(num<= 1) { return 1; } else { return num * factorial(num-1) } } var anotherFactorial = factorial factorial = null anotherFactorial(4) // 报错 ,因为最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现 // 使用闭包实现递归 function newFactorial = (function f(num){ if(num<1) {return 1} else { return num* f(num-1) } }) //这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial
** Astuce 2 : utilisez des fermetures pour imiter la portée au niveau du bloc**
es6 n'est pas encore sorti. Auparavant, il y avait un problème de promotion des variables lors de l'utilisation de var pour définir des variables, par exemple :for(var i=0; i<10; i++){ console.info(i) } alert(i) // 变量提升,弹出10 //为了避免i的提升可以这样做 (function () { for(var i=0; i<10; i++){ console.info(i) } })() alert(i) // underfined 因为i随着闭包函数的退出,执行环境销毁,变量回收Bien sûr, la plupart d'entre elles sont désormais définies à l'aide de let et const d'es6.
Tutoriel vidéo JavaScript du site Web PHP chinois !
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!