Je suis essaye mon plus difficile à comprendre les fermetures de JavaScript.
Je comprends qu’en renvoyant une fonction interne, il aura accès à n’importe quelle variable définie dans son parent immédiat.
Où cela me serait-il utile? Peut-être que je ne l'ai pas encore bien compris. La plupart des exemples que j'ai vus en ligne ne fournissent aucun code réel, mais des exemples vagues.
Est-ce que quelqu'un peut me montrer l'utilisation réelle d'une fermeture?
Est-ce celui-ci, par exemple?
var warnUser = function (msg) {
var calledCount = 0;
return function() {
calledCount++;
alert(msg + '\nYou have been warned ' + calledCount + ' times.');
};
};
var warnForTamper = warnUser('You can not tamper with our HTML.');
warnForTamper();
warnForTamper();
J'ai utilisé des fermetures pour faire des choses comme:
a = (function () {
var privatefunction = function () {
alert('hello');
}
return {
publicfunction : function () {
privatefunction();
}
}
})();
Comme vous pouvez le voir, a
est maintenant un objet, avec une méthode publicfunction
(a.publicfunction()
) qui appelle privatefunction
, qui n'existe que dans la fermeture. Vous pouvezPASappeler privatefunction
directement (c'est-à-dire a.privatefunction()
), juste publicfunction()
.
C'est un exemple minimal mais peut-être que vous pouvez voir des utilisations? Nous avons utilisé cela pour appliquer des méthodes publiques/privées.
Supposons que vous souhaitiez compter le nombre de fois où l'utilisateur a cliqué sur un bouton} sur une page Web.
Pour cela, vous déclenchez une fonction sur l’événement onclick
du bouton pour mettre à jour le nombre de variables.
<button onclick="updateClickCount()">click me</button>
1) Vous pouvez utiliser une variable globale, et une fonction pour augmenter le compteur:
var counter = 0;
function updateClickCount() {
++counter;
// do something with counter
}
Mais le piège est que tout script de la page peut changer le compteur, sans appeler updateClickCount()
.
2) Maintenant, vous pourriez penser à déclarer la variable à l'intérieur de la fonction:
function updateClickCount() {
var counter = 0;
++counter;
// do something with counter
}
Mais salut! Chaque fois que la fonction updateClickCount()
est appelée, le le compteur est remis à 1.
3) Vous pensez à fonctions imbriquées?
Les fonctions imbriquées ont accès à la portée "ci-dessus".
Dans cet exemple, la fonction interne updateClickCount()
a accès à la variable de compteur dans la fonction parent countWrapper()
function countWrapper() {
var counter = 0;
function updateClickCount() {
++counter;
// do something with counter
}
updateClickCount();
return counter;
}
Cela aurait pu résoudre le dilemme du contre, si vous pouviez accéder à la fonction updateClickCount()
de l'extérieur et que vous deviez également trouver un moyen d'exécuter counter = 0
une seule fois, pas à chaque fois.
4) Closure to the rescue! (Fonction auto-invoquante)} _:
var updateClickCount=(function(){
var counter=0;
return function(){
++counter;
// do something with counter
}
})();
La fonction d'invocation automatique ne s'exécute qu'une fois. Il définit counter
sur zéro (0) et renvoie une expression de fonction.
De cette façon, updateClickCount
devient une fonction. La partie "merveilleuse" est qu'il peut accéder au compteur dans la portée parente.
Ceci s'appelle un fermeture de JavaScript. Cela permet à une fonction d'avoir des variables "private".
La counter
est protégée par la portée de la fonction anonyme et ne peut être modifiée qu'en utilisant la fonction add!
Exemple plus vivant à la fermeture:
<script>
var updateClickCount=(function(){
var counter=0;
return function(){
++counter;
document.getElementById("spnCount").innerHTML=counter;
}
})();
</script>
<html>
<button onclick="updateClickCount()">click me</button>
<div> you've clicked
<span id="spnCount"> 0 </span> times!
</div>
</html>
L'exemple que vous donnez est excellent. Les fermetures sont un mécanisme d'abstraction qui vous permet de séparer les problèmes de manière très nette. Votre exemple est un cas de séparation de l'instrumentation (comptage des appels) et de la sémantique (une API de rapport d'erreur). Les autres utilisations comprennent:
Passage du comportement paramétré dans un algorithme (programmation classique d'ordre supérieur):
function proximity_sort(arr, midpoint) {
arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
}
Simulation de la programmation orientée objet:
function counter() {
var a = 0;
return {
inc: function() { ++a; },
dec: function() { --a; },
get: function() { return a; },
reset: function() { a = 0; }
}
}
Implémentation d'un contrôle de flux exotique, tel que la gestion des événements et les API AJAX de jQuery.
Oui, c’est un bon exemple de fermeture utile. L'appel à warnUser crée la variable calledCount
dans sa portée et renvoie une fonction anonyme qui est stockée dans la variable warnForTamper
. Comme il existe toujours une fermeture utilisant la variable appeléeCount, elle n'est pas supprimée à la sortie de la fonction. Chaque appel à la warnForTamper()
augmentera la variable scoped et alertera la valeur.
Le problème le plus courant que je vois sur StackOverflow concerne les cas où quelqu'un souhaite "retarder" l'utilisation d'une variable augmentée à chaque boucle. Toutefois, comme la variable est étendue, chaque référence à la variable intervient après la fin de la boucle. état final de la variable:
for (var i = 0; i < someVar.length; i++)
window.setTimeout(function () {
alert("Value of i was "+i+" when this timer was set" )
}, 10000);
Ainsi, chaque alerte afficherait la même valeur de i
, valeur à laquelle elle avait été augmentée à la fin de la boucle. La solution consiste à créer une nouvelle fermeture, une portée distincte pour la variable. Cela peut être fait en utilisant une fonction anonyme exécutée instantanément, qui reçoit la variable et stocke son état en tant qu'argument:
for (var i = 0; i < someVar.length; i++)
(function (i) {
window.setTimeout(function () {
alert("Value of i was "+i+" when this timer was set" )
}, 10000);
})(i);
Dans le langage JavaScript (ou n’importe quel langage ECMAScript), en particulier, les fermetures sont utiles pour masquer la mise en oeuvre des fonctionnalités tout en révélant l’interface.
Par exemple, imaginons que vous écriviez une classe de méthodes utilitaires de date et que vous souhaitez autoriser les utilisateurs à rechercher les noms des jours de la semaine par index, sans pour autant pouvoir modifier le tableau de noms que vous utilisez sous le capot.
var dateUtil = {
weekdayShort: (function() {
var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return function(x) {
if ((x != parseInt(x)) || (x < 1) || (x > 7)) {
throw new Error("invalid weekday number");
}
return days[x - 1];
};
}())
};
Notez que le tableau days
pourrait simplement être stocké en tant que propriété de l'objet dateUtil
mais qu'il serait alors visible par les utilisateurs du script et qu'ils pourraient même le modifier s'ils le souhaitaient, sans même avoir besoin de votre code source. Toutefois, comme elle est délimitée par la fonction anonyme qui renvoie la fonction de recherche de date, elle n’est accessible que par la fonction de recherche; elle est donc inviolable.
Je sais que je suis très en retard pour répondre à cette question, mais cela pourrait aider tous ceux qui cherchent encore la réponse en 2018.
Les fermetures javascript peuvent être utilisées pour implémenter les fonctionnalités throttle et debounce dans votre application.
étranglement :
La limitation limite le nombre maximal d'appels d'une fonction sur une période donnée. Comme dans "Exécuter cette fonction au plus une fois toutes les 100 millisecondes".
Code:
const throttle = (func, limit) => {
let isThrottling
return function() {
const args = arguments
const context = this
if (!isThrottling) {
func.apply(context, args)
isThrottling = true
setTimeout(() => isThrottling = false, limit)
}
}
}
Debouncing :
Les débogages ont limité la possibilité pour une fonction de ne pas être appelée jusqu'à ce qu'un certain temps se soit écoulé sans qu'elle soit appelée. Comme dans "Exécuter cette fonction uniquement si 100 millisecondes se sont écoulées sans qu'elle soit appelée".
Code:
const debounce = (func, delay) => {
let debouncing
return function() {
const context = this
const args = arguments
clearTimeout(debouncing)
debouncing = setTimeout(() => func.apply(context, args), delay)
}
}
Comme vous pouvez le constater, les fermetures ont aidé à mettre en œuvre deux fonctionnalités intéressantes que chaque application Web devrait avoir pour fournir une fonctionnalité d’expérience utilisateur fluide.
J'espère que ça va aider quelqu'un.
Il existe une section sur Fermetures pratiques sur le Réseau de développeurs Mozilla .
Une autre utilisation courante des fermetures consiste à lier this
dans une méthode à un objet spécifique, ce qui permet de l'appeler ailleurs (par exemple, en tant que gestionnaire d'événements).
function bind(obj, method) {
if (typeof method == 'string') {
method = obj[method];
}
return function () {
method.apply(obj, arguments);
}
}
...
document.body.addEventListener('mousemove', bind(watcher, 'follow'), true);
Chaque fois qu'un événement mousemove est déclenché, watcher.follow(evt)
est appelé.
Les fermetures sont également un élément essentiel des fonctions d’ordre supérieur, car elles permettent au schéma très courant de réécrire plusieurs fonctions similaires en une seule fonction d’ordre supérieur en paramétrant les parties différentes. À titre d'exemple abstrait,
foo_a = function (...) {A a B}
foo_b = function (...) {A b B}
foo_c = function (...) {A c B}
devient
fooer = function (x) {
return function (...) {A x B}
}
où A et B ne sont pas des unités syntaxiques mais des chaînes de code source (et non des littéraux de chaîne).
Voir " Rationaliser mon javascript avec une fonction " pour un exemple concret.
Ici, j'ai une salutation que je veux dire à plusieurs reprises. Si je crée une fermeture, je peux simplement appeler cette fonction pour enregistrer le message d'accueil. Si je ne crée pas la fermeture, je dois donner mon nom à chaque fois.
Sans fermeture ( https://jsfiddle.net/lukeschlangen/pw61qrow/3/ ):
function greeting(firstName, lastName) {
var message = "Hello " + firstName + " " + lastName + "!";
console.log(message);
}
greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");
Avec une fermeture ( https://jsfiddle.net/lukeschlangen/Lb5cfve9/3/ ):
function greeting(firstName, lastName) {
var message = "Hello " + firstName + " " + lastName + "!";
return function() {
console.log(message);
}
}
var greetingBilly = greeting("Billy", "Bob");
var greetingLuke = greeting("Luke", "Schlangen");
greetingBilly();
greetingBilly();
greetingBilly();
greetingLuke();
greetingLuke();
greetingLuke();
Si vous êtes à l'aise avec le concept d'instanciation d'une classe au sens orienté objet (c'est-à-dire pour créer un objet de cette classe), vous êtes sur le point de comprendre les fermetures.
Pensez-y de cette façon: lorsque vous instanciez deux objets Personne, vous savez que la variable de membre de classe "Nom" n'est pas partagée entre les instances; chaque objet a sa propre "copie". De même, lorsque vous créez une fermeture, la variable free ("appeléCount" dans votre exemple ci-dessus) est liée à "l'instance" de la fonction.
Je pense que votre saut conceptuel est quelque peu gêné par le fait que chaque fonction/fermeture renvoyée par la fonction warnUser (à part: c’est une fonction d’ordre supérieur ) se lie à «appeléCount» avec la même valeur initiale (0), lors de la création de fermetures, il est souvent plus utile de passer différents initialiseurs à la fonction d'ordre supérieur, un peu comme si on transmettait des valeurs différentes au constructeur d'une classe.
Donc, supposons que, lorsque 'appeléCount' atteigne une certaine valeur, vous souhaitez terminer la session de l'utilisateur; vous voudrez peut-être des valeurs différentes pour cela, selon que la demande provient du réseau local ou du grand réseau internet (oui, c'est un exemple artificiel). Pour ce faire, vous pouvez transmettre différentes valeurs initiales pour CalledCount à warnUser (c'est-à-dire -3 ou 0?).
Une partie du problème de la littérature réside dans la nomenclature utilisée pour les décrire ("portée lexicale", "variables libres"). Ne vous y trompez pas, les fermetures sont plus simples qu'il n'y parait ... à première vue ;-)
Ici, j’ai un exemple simple de concept de fermeture que nous pouvons utiliser dans notre site de commerce électronique ou dans beaucoup d’autres également .. Je rajoute mon lien jsfiddle avec l’exemple .. ... articles et un compteur de panier.
//Counter clouser implemented function;
var CartCouter = function(){
var counter = 0;
function changeCounter(val){
counter += val
}
return {
increment: function(){
changeCounter(1);
},
decrement: function(){
changeCounter(-1);
},
value: function(){
return counter;
}
}
}
var cartCount = CartCouter();
function updateCart(){
document.getElementById('cartcount').innerHTML = cartCount.value();
}
var productlist = document.getElementsByClassName('item');
for(var i = 0; i< productlist.length; i++){
productlist[i].addEventListener('click',function(){
if(this.className.indexOf('selected')<0){
this.className += " selected";
cartCount.increment();
updateCart();
} else{
this.className = this.className.replace("selected", "");
cartCount.decrement();
updateCart();
}
})
}
.productslist{
padding:10px;
}
ul li{
display: inline-block;
padding: 5px;
border: 1px solid #ddd;
text-align: center;
width: 25%;
cursor: pointer;
}
.selected{
background-color: #7CFEF0;
color: #333;
}
.cartdiv{
position: relative;
float:right;
padding: 5px;
box-sizing: border-box;
border: 1px solid #f1f1f1;
}
<div>
<h3>
Practical Use of JavaScript Closure consept/private variable.
</h3>
<div class="cartdiv">
<span id="cartcount">0</span>
</div>
<div class="productslist">
<ul >
<li class="item">Product 1</li>
<li class="item">Product 2</li>
<li class="item">Product 3</li>
</ul>
</div>
</div>
Le modèle de module JavaScript utilise des fermetures. Son joli motif vous permet d’avoir quelque chose de similaire "public" et "privé".
var myNamespace = (function () {
var myPrivateVar, myPrivateMethod;
// A private counter variable
myPrivateVar = 0;
// A private function which logs any arguments
myPrivateMethod = function( foo ) {
console.log( foo );
};
return {
// A public variable
myPublicVar: "foo",
// A public function utilizing privates
myPublicFunction: function( bar ) {
// Increment our private counter
myPrivateVar++;
// Call our private method using bar
myPrivateMethod( bar );
}
};
})();
Il y a quelque temps, j'ai écrit un article sur la manière dont les fermetures peuvent être utilisées pour simplifier le code de gestion des événements. Il compare la gestion des événements ASP.NET à jQuery côté client.
http://www.hackification.com/2009/02/20/closures-simplify-event-handling-code/
J'aime la fabrique de fonctions de Mozilla exemple .
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var addFive = makeAdder(5);
console.assert(addFive(2) === 7);
console.assert(addFive(-5) === 0);
Dans l'échantillon donné, la valeur de la variable incluse 'compteur' est protégée et ne peut être modifiée qu'à l'aide des fonctions données (incrémenter, décrémenter). parce que c'est dans une fermeture,
var MyCounter= function (){
var counter=0;
return {
increment:function () {return counter += 1;},
decrement:function () {return counter -= 1;},
get:function () {return counter;}
};
};
var x = MyCounter();
//or
var y = MyCounter();
alert(x.get());//0
alert(x.increment());//1
alert(x.increment());//2
alert(y.increment());//1
alert(x.get());// x is still 2
Une grande partie du code que nous écrivons dans le langage JavaScript frontal est basé sur des événements. Nous définissons un comportement, puis nous l'attachons à un événement déclenché par l'utilisateur (comme un clic ou une frappe). Notre code est généralement associé en tant que rappel: une seule fonction qui est exécutée en réponse à l'événement . size12, size14 et size16 sont maintenant des fonctions qui redimensionneront le corps du texte à 12, 14 et 16 pixels, respectivement. Nous pouvons les attacher à des boutons (dans ce cas des liens) comme suit:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
Ce fil de discussion m'a énormément aidé à mieux comprendre le fonctionnement des fermetures. Depuis, j’ai fait mes propres expériences et proposé ce code assez simple qui pourrait aider d’autres personnes à comprendre comment les fermetures peuvent être utilisées de manière pratique et comment les utiliser à différents niveaux pour conserver des variables similaires/ou des variables globales sans risque de les écraser ou de les confondre avec les variables globales. Cela permet de garder une trace des clics sur les boutons, au niveau local pour chaque bouton individuel et au niveau global, en comptant chaque clic sur le bouton, contribuant ainsi à obtenir un chiffre unique. Remarque: je n'ai utilisé aucune variable globale pour le faire, ce qui est un peu le but de l'exercice: disposer d'un gestionnaire pouvant être appliqué à n'importe quel bouton contribuant également à quelque chose de global.
S'il vous plaît, faites-moi savoir si j'ai commis de mauvaises pratiques ici! J'apprends toujours ce truc moi-même.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Closures on button presses</title>
<script type="text/javascript">
window.addEventListener("load" , function () {
/*
grab the function from the first closure,
and assign to a temporary variable
this will set the totalButtonCount variable
that is used to count the total of all button clicks
*/
var buttonHandler = buttonsCount();
/*
using the result from the first closure (a function is returned)
assign and run the sub closure that carries the
individual variable for button count and assign to the click handlers
*/
document.getElementById("button1").addEventListener("click" , buttonHandler() );
document.getElementById("button2").addEventListener("click" , buttonHandler() );
document.getElementById("button3").addEventListener("click" , buttonHandler() );
// Now that buttonHandler has served its purpose it can be deleted if needs be
buttonHandler = null;
});
function buttonsCount() {
/*
First closure level
- totalButtonCount acts as a sort of global counter to count any button presses
*/
var totalButtonCount = 0;
return function () {
//second closure level
var myButtonCount = 0;
return function (event) {
//actual function that is called on the button click
event.preventDefault();
/*
increment the button counts.
myButtonCount only exists in the scope that is
applied to each event handler, therefore acts
to count each button individually whereas because
of the first closure totalButtonCount exists at
the scope just outside, so maintains a sort
of static or global variable state
*/
totalButtonCount++;
myButtonCount++;
/*
do something with the values ... fairly pointless
but it shows that each button contributes to both
it's own variable and the outer variable in the
first closure
*/
console.log("Total button clicks: "+totalButtonCount);
console.log("This button count: "+myButtonCount);
}
}
}
</script>
</head>
<body>
<a href="#" id="button1">Button 1</a>
<a href="#" id="button2">Button 2</a>
<a href="#" id="button3">Button 3</a>
</body>
</html>
Référence: Utilisation pratique des fermetures
En pratique, les fermetures peuvent créer des conceptions élégantes, permettant la personnalisation de divers calculs, appels différés, rappels, création d'une étendue encapsulée, etc.
Un exemple de la méthode de tri des tableaux qui accepte comme argument la fonction de condition de tri:
[1, 2, 3].sort(function (a, b) {
... // sort conditions
});
Mapper les fonctionnels en tant que méthode de mappage de tableaux qui mappe un nouveau tableau par la condition de l'argument fonctionnel:
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
Il est souvent pratique d'implémenter des fonctions de recherche en utilisant des arguments fonctionnels définissant des conditions presque illimitées pour la recherche:
someCollection.find(function (element) {
return element.someProperty == 'searchCondition';
});
Nous pouvons également noter l’application de fonctions comme, par exemple, une méthode forEach qui applique une fonction à un tableau d’éléments:
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3
Une fonction est appliquée aux arguments (à une liste d'arguments - à appliquer, et à des arguments positionnés - à l'appel):
(function () {
alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);
Appels différés:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
Fonctions de rappel:
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
// callback, which will be called deferral ,
// when data will be ready;
// variable "x" here is available,
// regardless that context in which,
// it was created already finished
alert(x); // 10
};
Création d'une portée encapsulée dans le but de masquer des objets auxiliaires:
var foo = {};
(function (object) {
var x = 10;
object.getX = function _getX() {
return x;
};
})(foo);
alert(foo.getX());// get closured "x" – 10
Les fermetures sont un moyen utile de créer generators , une séquence incrémentée à la demande:
var foobar = function(i){var count = count || i; return function(){return ++count;}}
baz = foobar(1);
console.log("first call: " + baz()); //2
console.log("second call: " + baz()); //3
Les différences se résument comme suit:
Fonctions anonymes Fonctions définies Ne peut pas être utilisé comme méthode Peut être utilisé comme méthode d'objet Existe uniquement dans l'étendue dans laquelle il est défini Existe dans l'objet. défini dans Peut uniquement être appelé dans l'étendue dans laquelle il est défini Peut être appelé à n'importe quel moment du code Peut être réaffecté à une nouvelle valeur ou supprimé Ne peut pas être supprimé ou modifié
Références
Utilisation des fermetures:
Les fermetures sont l’une des fonctionnalités les plus puissantes de JavaScript. JavaScript permet d'imbriquer des fonctions et accorde à la fonction interne un accès complet à toutes les variables et fonctions définies dans la fonction externe (ainsi qu'à toutes les autres variables et fonctions auxquelles la fonction externe a accès). Cependant, la fonction externe n'a pas accès aux variables et fonctions définies dans la fonction interne. Cela fournit une sorte de sécurité pour les variables de la fonction interne. De plus, puisque la fonction interne a accès à la portée de la fonction externe, les variables et fonctions définies dans la fonction externe vivront plus longtemps que la fonction externe elle-même, si la fonction interne parvient à survivre au-delà de la vie de la fonction externe. Une fermeture est créée lorsque la fonction interne est en quelque sorte mise à la disposition de toute étendue en dehors de la fonction externe.
Exemple :
<script>
var createPet = function(name) {
var sex;
return {
setName: function(newName) {
name = newName;
},
getName: function() {
return name;
},
getSex: function() {
return sex;
},
setSex: function(newSex) {
if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
sex = newSex;
}
}
}
}
var pet = createPet("Vivie");
console.log(pet.getName()); // Vivie
console.log(pet.setName("Oliver"));
console.log(pet.setSex("male"));
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver
</script>
Dans le code ci-dessus, la variable de nom de la fonction externe est accessible aux fonctions internes et il n'y a aucun autre moyen d'accéder aux variables internes, à l'exception des fonctions internes. Les variables internes de la fonction interne agissent comme des magasins sûrs pour les fonctions internes. Ils contiennent des données «persistantes», mais sécurisées, avec lesquelles les fonctions internes peuvent travailler. Les fonctions ne doivent même pas être affectées à une variable, ni avoir un nom . Read here for detail