Vad är ett praktiskt användningsområde för en closure i JavaScript?
Jag försöker mitt allra bästa att förstå JavaScript-closures.
Jag förstår att genom att returnera en inre funktion kommer den att ha tillgång till alla variabler som definieras i dess omedelbara förälder.
Var skulle detta vara användbart för mig? Kanske har jag inte riktigt förstått det än. De flesta exempel jag har sett online ger inte någon verklig kod, bara vaga exempel.
Kan någon visa mig ett verkligt exempel på användning av en closure?
Är det här en, till exempel?
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();
Svar #1
Anta att du vill räkna antalet gånger en användare klickade på en knapp på en webbsida.
För detta utlöser du en funktion på onclick-händelsen för knappen för att uppdatera värdet på variabeln
<button onclick="updateClickCount()">click me</button>
Nu kan det finnas många tillvägagångssätt som:
Du kan använda en global variabel och en funktion för att öka räknaren:
var counter = 0; function updateClickCount() { ++counter; // Do something with counter }Men fallgropen är att vilken som helst skript på sidan kan ändra räknaren, utan att anropa
updateClickCount().
Nu kanske du tänker på att deklarera variabeln inuti funktionen:
function updateClickCount() { var counter = 0; ++counter; // Do something with counter }Men, hej! Varje gång
updateClickCount()-funktionen kallas, ställs räknaren tillbaka till 1 igen.
Tänker på nästlade funktioner?
Inbäddade funktioner har tillgång till området "ovanför" dem.
I det här exemplet har den interna funktionen
updateClickCount()tillgång till räknarvariabeln i föräldrafunktionencountWrapper():function countWrapper() { var counter = 0; function updateClickCount() { ++counter; // Do something with counter } updateClickCount(); return counter; }Detta skulle kunna ha löst räkneproblemen, om du kunde nå
updateClickCount()-funktionen från utsidan och du behöver också hitta ett sätt att köracounter=0endast en gång, inte varje gång.
Closure till undsättning! (själv-anropande funktion):
var updateClickCount = (function(){ var counter = 0; return function(){ ++counter; // Do something with counter } })();Den självexekverande funktionen körs bara en gång. Den sätter
countertill noll (0) och returnerar ett funktionsuttryck.På detta sätt blir
updateClickCounten funktion. Den "fantastiska" delen är att den kan komma åt räknaren i den överordnade scopet.Detta kallas en JavaScript-closure. Det gör det möjligt för en funktion att ha "privata" variabler.
counterskyddas av den anonyma funktionens scope och kan endast ändras medupdateClickCount()-funktionen!
Ett mer livfullt exempel på closures
<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>Referens: JavaScript-stängningar
Svar #2
Jag har använt closures för att göra saker som:
a = (function () {
var privatefunction = function () {
alert('hello');
}
return {
publicfunction : function () {
privatefunction();
}
}
})();
Som du kan se där, är a nu ett objekt, med en metod publicfunction ( a.publicfunction() ) som anropar privatefunction, som bara finns inuti closure. Du kan inte anropa privatefunction direkt (d.v.s. a.privatefunction() ), bara publicfunction().
Det är ett minimalt exempel, men kanske kan du se användningar för det? Vi använde detta för att tvinga fram publika/privata metoder.
Svar #3
Exemplet du ger är ett utmärkt sådant. Closure är en abstraktionsmekanism som låter dig separera olika intressen på ett mycket tydligt sätt. Ditt exempel är ett fall av att separera instrumentering (räkna anrop) från semantik (ett felrapporterings-API). Andra användningar inkluderar:
Att skicka parametriserat beteende till en algoritm (klassisk högre-ordningens programmering):
function proximity_sort(arr, midpoint) { arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; }); }Simulering av objektorienterad programmering:
function counter() { var a = 0; return { inc: function() { ++a; }, dec: function() { --a; }, get: function() { return a; }, reset: function() { a = 0; } } }Implementera exotisk flödeskontroll, såsom jQuery:s händelsehantering och AJAX-API:er.
Svar #4
JavaScript-closures kan användas för att implementera throttle och debounce-funktionalitet i din applikation.
Strypning
Throttle begränsar det maximala antalet gånger en funktion kan anropas över tid. Som i "utför denna funktion högst en gång var 100:e millisekund."
Kod :
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
Debouncing sätter en begränsning på att en funktion inte ska kallas igen förrän en viss tid har passerat utan att den har kallats. Som i "utför denna funktion endast om 100 millisekunder har passerat utan att den har kallats."
Kod :
const debounce = (func, delay) => {
let debouncing
return function() {
const context = this
const args = arguments
clearTimeout(debouncing)
debouncing = setTimeout(() => func.apply(context, args), delay)
}
}
Som du kan se hjälpte closures till att implementera två vackra funktioner som varje webbapplikation bör ha för att ge en smidig användargränssnittsupplevelse.
Svar #5
Ja, det är ett bra exempel på en användbar closure. Anropet till warnUser skapar variabeln calledCount i dess scope och returnerar en anonym funktion som sparas i variabeln warnForTamper. Eftersom det fortfarande finns en closure som använder variabeln calledCount, tas den inte bort när funktionen avslutas, så varje anrop till warnForTamper() kommer att öka den scoped variabeln och varna värdet.
Det vanligaste problemet jag ser på Stack Overflow är när någon vill "fördröja" användningen av en variabel som ökas vid varje loop, men eftersom variabeln har ett avgränsat omfång så skulle varje referens till variabeln vara efter att loopen har avslutats, vilket resulterar i variabelns slutliga tillstånd:
for (var i = 0; i < someVar.length; i++)
window.setTimeout(function () {
alert("Value of i was "+i+" when this timer was set" )
}, 10000);
Detta skulle resultera i att varje alert visar samma värde av i, det värde det ökades till när loopen slutade. Lösningen är att skapa en ny closure, ett separat scope för variabeln. Detta kan göras med en omedelbart exekverad anonym funktion, som tar emot variabeln och lagrar dess tillstånd som ett 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);
Svar #6
I JavaScript (eller vilket ECMAScript-språk som helst) är closures särskilt användbara för att dölja implementeringen av funktionalitet samtidigt som gränssnittet fortfarande avslöjas.
Till exempel, föreställ dig att du skriver en klass med datumhjälpmetoder och du vill tillåta användare att slå upp veckodagsnamn efter index, men du vill inte att de ska kunna ändra arrayen med namn som du använder i bakgrunden.
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];
};
}())
};
Observera att days-arrayen enkelt skulle kunna lagras som en egenskap hos dateUtil-objektet, men då skulle den vara synlig för användare av skriptet och de skulle till och med kunna ändra den om de ville, utan att ens behöva din källkod. Eftersom den emellertid är innesluten av den anonyma funktionen som returnerar datumuppslagsfunktionen, är den endast åtkomlig för uppslagsfunktionen, så nu är den manipulationssäker.
