Maison  >  Article  >  interface Web  >  Une explication détaillée des fermetures en JavaScript

Une explication détaillée des fermetures en JavaScript

青灯夜游
青灯夜游original
2023-04-24 17:57:493517parcourir

La fermeture JavaScript est un concept important largement utilisé dans la programmation JavaScript. Bien que cela puisse prêter à confusion pour les débutants, il s’agit de l’un des concepts clés pour comprendre le cœur du langage JavaScript. Cet article examine en profondeur les fermetures JavaScript afin que vous compreniez comment elles fonctionnent et comment les utiliser dans des applications réelles.

Une explication détaillée des fermetures en JavaScript

Que sont les fermetures JavaScript ?

En JavaScript, une fermeture signifie qu'une fonction peut accéder à des variables définies en dehors d'elle. Ces variables sont souvent appelées « variables libres » car elles ne sont pas locales à la fonction et ne sont pas non plus des arguments de la fonction. Les fermetures peuvent être créées à l’intérieur d’une fonction ou à l’extérieur d’une fonction.

Chaque fonction en JavaScript est une fermeture car elles ont toutes accès à des variables libres. Lorsqu'une fonction est appelée, elle crée un nouvel environnement d'exécution qui contient les variables et paramètres locaux de la fonction. Le contexte d'exécution comprend également une référence à la portée dans laquelle la fonction est définie. Cette référence est appelée « chaîne de portée » de la fonction, qui est une liste chaînée de tous les objets de portée contenant la définition de la fonction. [Apprentissage recommandé : Tutoriel vidéo javascript]

Lorsqu'une variable libre doit être accédée à l'intérieur d'une fonction, elle vérifiera d'abord si la variable existe dans sa propre variable locale. Si elle n'existe pas, il continue dans la chaîne de portée jusqu'à ce qu'il trouve la variable. C’est le mécanisme de base des fermetures.

En termes simples, une fermeture est une fonction qui contient des références à des variables externes. Ces variables sont définies en dehors de la fonction mais peuvent toujours être accessibles et manipulées à l'intérieur de la fonction. L'essence de la fermeture est d'encapsuler une fonction et les variables externes auxquelles elle se réfère, formant un environnement exempt d'interférences externes, afin que la fonction puisse accéder et modifier les variables externes, et ces modifications seront également reflétées dans les variables extérieures à la fonction.

Comprendre le fonctionnement des fermetures est crucial pour écrire du code JavaScript de haute qualité, car cela nous permet de mieux gérer la portée des variables et des fonctions, et d'implémenter des fonctionnalités plus complexes.

Utilisations des fermetures

Encapsulation de variables et de fonctions

Les fermetures peuvent être utilisées pour encapsuler des variables afin qu'elles ne soient pas soumises à des interférences externes. En effet, une fermeture peut définir une variable à l'intérieur d'une fonction et créer une fonction en dehors de la fonction qui accède à cette variable. Cette fonction d'accès peut accéder à la variable, mais la variable n'est pas directement accessible de l'extérieur, assurant ainsi la sécurité de la variable.

Par exemple, nous pouvons utiliser des fermetures pour implémenter un compteur :

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Dans cet exemple, nous utilisons des fermetures pour encapsuler le nombre de variables du compteur afin qu'il ne soit pas soumis à des interférences externes. Chaque fois que la fonction compteur est appelée, elle renvoie la valeur suivante du compteur.

Mise en cache des données

L'utilisation de fermetures peut mettre en cache les résultats de calcul des fonctions pour éviter de calculer plusieurs fois la même valeur, améliorant ainsi les performances du code. Cette méthode convient aux fonctions qui nécessitent une grande quantité de calculs mais dont les résultats ne changent pas fréquemment, comme la séquence de Fibonacci.

Regardez un exemple de code ci-dessous :

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    } else {
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  }
}

function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // 输出 55
console.log(memoizedFib(10)); // 输出 55,直接从缓存中读取

Dans cet exemple, nous définissons une fonction memoize qui accepte une fonction comme paramètre et renvoie une fonction de fermeture. La fonction de fermeture maintient en interne un cache d'objets de cache, qui est utilisé pour enregistrer les résultats de calcul de la fonction. Chaque fois que la fonction de fermeture est appelée, elle génère une valeur de clé unique basée sur les paramètres transmis et tente de lire le résultat du calcul à partir du cache. Si la valeur de la clé existe déjà dans le cache, le résultat du cache est renvoyé directement, sinon la fonction transmise est appelée pour calculer le résultat et le résultat est enregistré dans le cache. Cette approche améliore les performances de votre code en évitant de calculer plusieurs fois la même valeur.

Réaliser la modularité

L'utilisation de fermetures peut permettre d'obtenir une méthode de programmation modulaire. Cette méthode peut diviser le code en plusieurs modules, de sorte que chaque module se concentre uniquement sur ses propres fonctions, améliorant ainsi la maintenabilité et la sécurité du code. Dans le même temps, les fermetures peuvent également englober des variables publiques et privées, évitant ainsi la pollution des variables globales.

Par exemple, nous pouvons utiliser des fermetures pour implémenter un module simple :

const module = (function() {
  const privateVar = &#39;I am private&#39;;
  const publicVar = &#39;I am public&#39;;
  function privateFn() {
    console.log(&#39;I am a private function&#39;);
  }
  function publicFn() {
    console.log(&#39;I am a public function&#39;);
  }
  return {
    publicVar,
    publicFn
  };
})();

console.log(module.publicVar); // 输出 &#39;I am public&#39;
module.publicFn(); // 输出 &#39;I am a public function&#39;
console.log(module.privateVar); // 输出 undefined
module.privateFn(); // 报错,无法访问私有函数

Dans cet exemple, nous définissons une fonction d'exécution immédiate qui renvoie un objet en interne. Les objets contiennent des variables et des fonctions publiques, ainsi que des variables et des fonctions privées. De cette façon, nous pouvons diviser le code en plusieurs modules, chaque module se concentrant uniquement sur ses propres fonctions, améliorant ainsi la maintenabilité et la lisibilité du code. Dans le même temps, les variables et fonctions privées ne sont visibles qu'à l'intérieur de la fonction, et elles ne peuvent pas être consultées ni modifiées de l'extérieur, évitant ainsi la pollution des variables globales.

Gestion des événements

Ce qui suit est un exemple d'utilisation de fermetures pour la gestion des événements :

function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log(`Clicked ${count} times`);
  }

  function decrement() {
    count--;
    console.log(`Clicked ${count} times`);
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    decrement,
    getCount
  };
}

const counter = createCounter();

document.querySelector(&#39;#increment&#39;).addEventListener(&#39;click&#39;, counter.increment);
document.querySelector(&#39;#decrement&#39;).addEventListener(&#39;click&#39;, counter.decrement);

在这个示例中,我们定义了一个名为createCounter的函数,该函数返回一个对象,该对象包含三个方法:increment,decrement和getCount。increment方法将计数器加1,decrement方法将计数器减1,getCount方法返回当前计数器的值。

我们使用createCounter函数创建了一个计数器对象counter,并将increment方法和decrement方法分别注册为加1和减1按钮的点击事件处理函数。由于increment和decrement方法内部引用了createCounter函数内部的局部变量count,因此它们形成了闭包,可以访问和修改count变量。

这个示例中,我们将计数器对象的逻辑封装在一个函数内部,并返回一个包含方法的对象,这样可以避免全局变量的使用,提高代码的可维护性和可重用性。

函数柯里化

以下是一个使用闭包实现的函数柯里化例子:

function add(x) {
  return function(y) {
    return x + y;
  }
}

const add5 = add(5); // x = 5
console.log(add5(3)); // 输出 8
console.log(add5(7)); // 输出 12

在这个例子中,我们定义了一个名为add的函数,该函数接受一个参数x并返回一个内部函数,内部函数接受一个参数y,并返回x + y的结果。

我们使用add函数创建了一个新的函数add5,该函数的x值为5。我们可以多次调用add5函数,每次传入不同的y值进行求和运算。由于add函数返回了一个内部函数,并且内部函数引用了add函数内部的参数x,因此内部函数形成了一个闭包,可以访问和保留x值的状态。

这个例子中,我们实现了一个简单的函数柯里化,将接收多个参数的函数转化为接收一个参数的函数。函数柯里化可以帮助我们更方便地进行函数复合和函数重用。

异步编程

以下是一个使用闭包实现的异步编程的例子:

function fetchData(url) {
  return function(callback) {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        callback(null, data);
      })
      .catch(error => {
        callback(error, null);
      });
  }
}

const getData = fetchData(&#39;https://jsonplaceholder.typicode.com/todos/1&#39;);
getData(function(error, data) {
  if (error) {
    console.error(error);
  } else {
    console.log(data);
  }
});

在这个例子中,我们定义了一个名为fetchData的函数,该函数接受一个URL参数,并返回一个内部函数。内部函数执行异步操作,请求URL并将响应解析为JSON格式的数据,然后调用传入的回调函数并将解析后的数据或错误作为参数传递。

我们使用fetchData函数创建了一个getData函数,该函数请求JSONPlaceholder API的一个TODO项,并将响应解析为JSON格式的数据,然后将数据或错误传递给回调函数。由于fetchData函数返回了一个内部函数,并且内部函数引用了fetchData函数内部的URL参数和回调函数,因此内部函数形成了闭包,可以访问和保留URL参数和回调函数的状态。

这个例子中,我们使用了异步编程模型,通过将回调函数作为参数传递,实现了在异步请求完成后执行相关的操作。使用闭包可以方便地管理异步请求和相关的状态,提高代码的可读性和可维护性。

闭包的缺陷

JS 闭包具有许多优点,但也有一些缺点,包括:

内存泄漏问题

由于闭包会将外部函数的局部变量引用保存在内存中,因此如果闭包一直存在,外部函数的局部变量也会一直存在,从而导致内存泄漏。

在 JavaScript 中,闭包是指一个函数能够访问并操作其父级作用域中的变量,即便该函数已经执行完毕,这些变量仍然存在。由于闭包会引用父级作用域中的变量,因此,这些变量不会在函数执行完毕时被垃圾回收机制回收,从而占用了内存资源,这就是闭包引起内存泄漏的原因。

以下是一个闭包引起内存泄漏的示例:

function myFunc() {
  var count = 0;
  setInterval(function() {
    console.log(++count);
  }, 1000);
}

myFunc();

在这个示例中,myFunc 函数中定义了一个变量 count,然后创建了一个计时器,在每秒钟打印 count 的值。由于计时器函数是一个闭包,它会保留对 myFunc 中的 count 变量的引用,这意味着即使 myFunc 函数执行完毕,计时器函数仍然可以访问 count 变量,从而阻止 count 变量被垃圾回收机制回收。如果我们不停地调用 myFunc 函数,将会创建多个计时器函数,每个函数都会占用一定的内存资源,最终会导致内存泄漏。

性能问题

由于闭包会在每次函数调用时创建新的作用域链,因此会增加函数的内存消耗和运行时间。在循环中创建闭包时,尤其需要注意性能问题。

在JavaScript中,每当创建一个函数时,都会为该函数创建一个新的作用域链。函数作用域链是一个指向其父级作用域的指针列表,其中包含了该函数能够访问的变量和函数。

Une fermeture fait référence à une fonction définie à l'intérieur d'une fonction qui a accès aux variables et paramètres de la fonction externe et peut continuer à utiliser ces variables et paramètres après l'appel de la fonction externe. Lorsqu'une fermeture est créée, elle enregistre une référence à la chaîne de portée de la fonction externe afin qu'elle soit accessible en cas de besoin.

Étant donné que la fermeture contient une référence à la chaîne de portées de la fonction externe, une nouvelle chaîne de portées est créée à chaque appel de fonction. En effet, chaque appel à une fonction crée une nouvelle chaîne de portée de fonction, même si la fonction est créée par la même fermeture. Cela signifie que chaque fermeture a sa propre chaîne de portées et que chaque appel à la fermeture crée une nouvelle chaîne de portées.

C'est l'une des raisons pour lesquelles vous devez être prudent lorsque vous utilisez des fermetures. Étant donné que chaque appel à une fermeture crée une nouvelle chaîne de portées, cela peut entraîner des problèmes de consommation de mémoire et de performances. Dans certains cas, il peut être nécessaire de libérer manuellement les ressources de la fermeture pour éviter les problèmes de fuite de mémoire.

Problèmes de sécurité

Étant donné que les fermetures peuvent accéder aux variables locales des fonctions externes, si des données privées sont accidentellement stockées dans des variables locales, elles peuvent être consultées et modifiées par la fermeture, provoquant des problèmes de sécurité.

Problèmes de lisibilité

Étant donné que les fermetures prolongent la durée de vie des variables et transmettent implicitement des données, elles peuvent rendre le code difficile à comprendre et à déboguer, en particulier lors de l'imbrication de plusieurs niveaux de fonctions.

Résumé

Par conséquent, bien que les fermetures soient une technique de programmation puissante, vous devez faire attention aux défauts ci-dessus lors de leur utilisation et choisir des scénarios d'application et des styles de programmation appropriés pour garantir la maintenabilité et les performances du code.

Pour plus de connaissances liées à la programmation, veuillez visiter : Enseignement de la programmation ! !

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:
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