Maison > interface Web > js tutoriel > Vous amène à mieux comprendre les fermetures js (détails)

Vous amène à mieux comprendre les fermetures js (détails)

不言
Libérer: 2018-10-18 13:39:18
original
2428 Les gens l'ont consulté

Le contenu de cet article est de vous aider à mieux comprendre les fermetures js (en détail). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Traducteur : On a tellement parlé des fermetures que même si je ne comprends pas les fermetures, je suis gêné de dire que je connais JS. Mais quand j'ai lu cet article, mes yeux se sont illuminés et. cela m'a aussi fait réfléchir aux fermetures. J'ai une nouvelle compréhension des packages, et cela implique une certaine connaissance des classes et des chaînes de prototypes. Il s'agit d'un article de 2012. Il est un peu tôt et le contenu est légèrement basique, mais il est très. clair. J'espère que cela pourra apporter une nouvelle compréhension aux lecteurs.

La fermeture est une fonctionnalité quelque peu complexe et mal comprise du langage JavaScript. En termes simples, une fermeture est un objet qui contient une méthode (fonction) et une référence à l'environnement lors de la création de la méthode. Afin de bien comprendre les fermetures, nous devons également comprendre deux fonctionnalités de js, l'une est la fonction de première classe et l'autre est la fonction interne.

Fonctions de première classe

En js, une méthode est un citoyen de première classe car elle peut être facilement convertie en d'autres types de données. Par exemple, une méthode de premier niveau peut être construite à la volée et affectée à une variable. Il peut également être transmis à d’autres méthodes ou renvoyé via d’autres méthodes. En plus de répondre à ces critères, les méthodes ont également leurs propres propriétés et méthodes.
A travers l'exemple suivant, jetons un œil aux capacités de la méthode de premier niveau.

var foo = function() {
  alert("Hello World!");
};

var bar = function(arg) {
  return arg;
};

bar(foo)();
Copier après la connexion
Note du traducteur : en omettant l'explication textuelle du code dans le texte original, ce qui se reflète ici est que la méthode de premier niveau peut renvoyer des paramètres, les paramètres peuvent être une autre fonction de premier niveau et le résultat renvoyé peut également être appelé.

Méthodes internes/Fonctions internes

Les méthodes internes ou méthodes imbriquées font référence à des méthodes définies dans d'autres méthodes. Chaque fois que la méthode externe est invoquée, l'instance de la méthode interne est créée. L'exemple suivant reflète l'utilisation de méthodes internes. La méthode add est une méthode externe et doAdd est une méthode interne.

function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3
Copier après la connexion

Dans cet exemple, une caractéristique importante est que la méthode interne obtient la portée de la méthode externe, ce qui signifie que la méthode interne peut utiliser les variables, paramètres, etc. Dans l'exemple, les paramètres value1 et value2 de add() sont passés aux paramètres operand1 et operand2 de doAdd(). Cependant, cela n'est pas nécessaire car doAdd peut obtenir directement value1 et value2. On peut donc aussi écrire l'exemple ci-dessus comme ceci :

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3
Copier après la connexion

Création de fermetures/Création de fermetures

La méthode interne obtient la portée de la méthode externe, formant un fermeture. Un scénario typique est que la fonction externe renvoie sa méthode interne, qui conserve une référence à l'environnement externe et enregistre toutes les variables dans la portée.
L'exemple suivant montre comment créer et utiliser des fermetures.

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3
Copier après la connexion

Explication :

  • add renvoie la méthode interne doAdd, doAdd appelle les paramètres de add et la fermeture est créée.

  • value1 est une variable locale de la méthode add, qui est une variable non locale pour doAdd (une variable non locale signifie que la variable n'est ni dans le corps de la fonction lui-même ni dans le monde global), et value2 est une variable locale de doAdd .

  • Lorsque add(1) est appelé, une fermeture est créée et stockée par incrément. Dans l'environnement de référence de la fermeture, la valeur1 est liée à 1 et la limite 1 est équivalente à. "blocage" dans cette fonction, qui est aussi à l'origine du nom "fermeture".

  • Lorsque l'incrément(2) est appelé, la fonction de fermeture est entrée, ce qui signifie que la valeur comptable doAdd1 est 1 est appelée, donc la fermeture peut essentiellement être considérée comme la fonction suivante :

function increment(value2) {
  return 1 + value2;
}
Copier après la connexion

Quand utiliser les fermetures ?

Les fermetures peuvent remplir de nombreuses fonctions. Par exemple, liez la fonction de rappel aux paramètres spécifiés. Parlons de deux scénarios qui facilitent votre vie et votre développement.

  1. Travailler avec des minuteries

Les fermetures sont très utiles en combinaison avec setTimeout et setInterval. Les fermetures vous permettent de transmettre des paramètres spécifiés à la fonction de rappel, comme ce qui suit Par exemple, insérez une chaîne dans le dom spécifié chaque seconde.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      window.setInterval(showMessage, 1000, "some message<br />");
    });

    function showMessage(message) {
      document.getElementById("message").innerHTML += message;
    }
  </script>
</head>
<body>
  <span id="message"></span>
</body>
</html>
Copier après la connexion

Malheureusement, IE ne prend pas en charge la transmission de paramètres au rappel setInterval. La page dans IE n'affichera pas "un message" mais "indéfini" (aucune valeur n'est transmise à showMessage() pour résoudre). ce problème Question, nous pouvons lier la valeur attendue à la fonction de rappel via la fermeture. Nous pouvons réécrire le code ci-dessus :

window.addEventListener("load", function() {
  var showMessage = getClosure("some message<br />");

  window.setInterval(showMessage, 1000);
});

function getClosure(message) {
  function showMessage() {
    document.getElementById("message").innerHTML += message;
  }

  return showMessage;
}
Copier après la connexion

2. Simuler les propriétés privées
La plupart des langages de programmation orientés objet sont pris en charge. la confidentialité des propriétés des objets, cependant, js n'est pas un pur langage orienté objet, il n'y a donc pas de concept de propriétés privées. Cependant, nous pouvons simuler des propriétés privées via des fermetures. Rappelons qu'une fermeture contient une référence à l'environnement dans lequel elle a été créée. Cette référence n'est plus dans la portée actuelle, cette référence n'est donc accessible qu'au sein de la fermeture. Il s'agit essentiellement d'une propriété privée.
Regardez l'exemple suivant (Traducteur : omettre la description textuelle du code) :

function Person(name) {
  this._name = name;

  this.getName = function() {
    return this._name;
  };
}
Copier après la connexion

Il y a un problème sérieux ici, car js ne prend pas en charge les attributs privés, nous ne pouvons donc pas empêcher les autres de modifier Dans le champ Nom de l'instance, par exemple, nous créons une instance de Personne appelée Colin, puis changeons son nom en Tom.

var person = new Person("Colin");

person._name = "Tom";
// person.getName() now returns "Tom"
Copier après la connexion

没有人愿意不经同意就被别人改名字,为了阻止这种情况的发生,通过闭包让_name字段变成私有。看如下代码,注意这里的_name是Person构造器的本地变量,而不是对象的属性,闭包形成了,因为外层方法Person对外暴露了一个内部方法getName。

function Person(name) {
  var _name = name;// 注:区别在这里

  this.getName = function() {
    return _name;
  };
}
Copier après la connexion

现在,当getName被调用,能够保证返回的是最初传入类构造器的值。我们依然可以为对象添加新的_name属性,但这并不影响闭包getName最初绑定的值,下面的代码证明,_name字段,事实私有。

var person = new Person("Colin");

person._name = "Tom";
// person._name is "Tom" but person.getName() returns "Colin"
Copier après la connexion

什么时候不要用闭包?

正确理解闭包如何工作何时使用非常重要,而理解什么时候不应该用它也同样重要。过度使用闭包会导致脚本执行变慢并消耗额外内存。由于闭包太容易创建了,所以很容易发生你都不知道怎么回事,就已经创建了闭包的情况。本节我们说几种场景要注意避免闭包的产生。
1.循环中
循环中创建出闭包会导致结果异常。下例中,页面上有三个按钮,分别点击弹出不同的话术。然而实际运行,所有的按钮都弹出button4的话术,这是因为,当按钮被点击时,循环已经执行完毕,而循环中的变量i也已经变成了最终值4.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      for (var i = 1; i < 4; i++) {
        var button = document.getElementById("button" + i);

        button.addEventListener("click", function() {
          alert("Clicked button " + i);
        });
      }
    });
  </script>
</head>
<body>
  <input type="button" id="button1" value="One" />
  <input type="button" id="button2" value="Two" />
  <input type="button" id="button3" value="Three" />
</body>
</html>
Copier après la connexion

去解决这个问题,必须在循环中去掉闭包(译者:这里的闭包指的是click事件回调函数绑定了外层引用i),我们可以通过调用一个引用新环境的函数来解决。下面的代码中,循环中的变量传递给getHandler函数,getHandler返回一个闭包(译者:这个闭包指的是getHandler返回的内部方法绑定传入的i参数),独立于原来的for循环。

function getHandler(i) {
  return function handler() {
    alert("Clicked button " + i);
  };
}

window.addEventListener("load", function() {
  for (var i = 1; i < 4; i++) {
    var button = document.getElementById("button" + i);

    button.addEventListener("click", getHandler(i));
  }
});
Copier après la connexion

2.构造函数里的非必要使用
类的构造函数里,也是经常会产生闭包的错误使用。我们已经知道如何通过闭包设置类的私有属性,而如果当一个方法不需要调用私有属性,则造成的闭包是浪费的。下面的例子中,Person类增加了sayHello方法,但是它没有使用私有属性。

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };

  this.sayHello = function() {
    alert("Hello!");
  };
}
Copier après la connexion

每当Person被实例化,创建sayHello都要消耗时间,想象一下有大量的Person被实例化。更好的实践是将sayHello放入Person的原型链里(prototype),原型链里的方法,会被所有的实例化对象共享,因此节省了为每个实例化对象去创建一个闭包(译者:指sayHello),所以我们有必要做如下修改:

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Person.prototype.sayHello = function() {
  alert("Hello!");
};
Copier après la connexion

需要记得一些事情

闭包包含了一个方法,以及创建它的代码环境引用

闭包会在外部函数包含内部函数的情况下形成

闭包可以轻松的帮助回调函数传入参数

类的私有属性可以通过闭包模拟

类的构造器中使用闭包不是一个好主意,将它们放到原型链中

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