Maison > interface Web > js tutoriel > Un chauffeur expérimenté vous aidera à bien comprendre les différents pièges des fermetures JS

Un chauffeur expérimenté vous aidera à bien comprendre les différents pièges des fermetures JS

angryTom
Libérer: 2019-11-25 17:00:43
avant
3283 Les gens l'ont consulté

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函数的作用域
}
Copier après la connexion

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是一个对象
Copier après la connexion

correspond au stockage mémoire :

Un chauffeur expérimenté vous aidera à bien comprendre les différents pièges des fermetures JS

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();
Copier après la connexion

Un chauffeur expérimenté vous aidera à bien comprendre les différents pièges des fermetures JS

**

<. 🎜>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'arguments

Nous revenons maintenant à l'exemple original externe et interne

function outer() {
     var  a = &#39;变量1&#39;
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var  inner = outer()   // 获得inner闭包函数
inner()   //"变量1"
Copier après la connexion

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
}
Copier après la connexion

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, ..., 10

Comment 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
}
Copier après la connexion

Pit point 2 : cela pointe vers le problème

var object = {
     name: &#39;&#39;object",
     getName: function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向window
Copier après la connexion

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
}
Copier après la connexion

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
Copier après la connexion

** 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随着闭包函数的退出,执行环境销毁,变量回收
Copier après la connexion
Bien sûr, la plupart d'entre elles sont désormais définies à l'aide de let et const d'es6.


Cet article est terminé ici. Pour un contenu plus passionnant, vous pouvez faire attention à la colonne

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!

Étiquettes associées:
source:jianshu.com
Déclaration de ce site Web
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
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal