I'm fairly new to closures in javascript (certainly when applying them to event handlers!), and trying to grasp the underlying principles leading to the behavior of this code:
function showClick() {
var clicks = 0
return function(e) {
clicks++
console.log("click "+clicks+" at "+[e.clientX,e.clientY])
return clicks
}
}
document.getElementById("b1").onclick=showClick()
document.getElementById("b2").onclick=showClick()
<button id="b1">Record my Clicks</button> <!--only stores click history of b1, disregarding b2-->
<button id="b2">Record MY Clicks</button> <!--only stores click history of b2, disregarding b1-->
I can see that the value for "clicks" is being updated and stored (presumably in the event handler function return?) each time I trigger the event, but is only doing so for the respective button being clicked. How are these values being stored, and why aren't they being shared between the two event handlers? Would I need to assign a variable outside the scope of both if I want the value of "clicks" to be shared between both buttons?
You have to think in 'scopes'. When you call showClick()
there is a scope. Inside this scope it creates a variable, called 'clicks'. This variable is valid within this function and all subfunctions / closures.
The function returns a new function, a so called closure. This closure keeps access to the scope of the encapsulated function. Everytime when you call showClick()
a new scope is generated and within this scope the variable clicks
. Furthermore there is the closure created and returned. The scopes, the variables within this scopes and the closures are not the same. There different everytime you call showClicks.
That's the reason why they count seperately for both links.
If you want to count both clicks together... there are multiple solutions.
EDIT: Like asked in the comments here two approaches to save the closure and reuse it:
1) The "Singleton-Approach I" (save the closure itself)
function showClick() {
if(showClick.closure) return showClick.closure;
var clicks = 0;
return showClick.closure = function(evt) {
clicks++;
console.log(clicks+' at '+evt.clientX+' / '+evt.clientY);
}
}
document.getElementsByTagName('a')[0].onclick = showClick();
document.getElementsByTagName('a')[1].onclick = showClick();
2) The "Singleton-Approach II" (save the clickcount)
function showClick() {
showClick.clicks = showClick.clicks || 0;
return function(evt) {
showClick.clicks++;
console.log(showClick.clicks+' at '+evt.clientX+' / '+evt.clientY);
}
}
document.getElementsByTagName('a')[0].onclick = showClick();
document.getElementsByTagName('a')[1].onclick = showClick();
3) The "Context-Approach" (save the closure as the context of the factory function)
var showClick = (function showClick() {
return this;
}).bind((function() {
var clicks = 0;
return function(evt) {
clicks++;
console.log(clicks+' at '+evt.clientX+' / '+evt.clientY);
};
})());
document.getElementsByTagName('a')[0].onclick = showClick();
document.getElementsByTagName('a')[1].onclick = showClick();
You are creating multiple instances of the function. Each one of those instances has their own clicks
variable, which is why they're increasing on their own. You have several options, amongst them:
Keep a reference to the function:
function showClick() {
var clicks = 0;
return function(e) {
clicks++
console.log("click "+clicks+" at "+[e.clientX,e.clientY])
return clicks
}
}
var clickEvent = showClick(); // we instance the function only once
document.getElementById("b1").onclick=clickEvent; // and use it here
document.getElementById("b2").onclick=clickEvent; // and here
Keep the variable in the global scope
function showClick() {
this.clicks = 0; // this === window here
return function(e) {
clicks++
console.log("click "+clicks+" at "+[e.clientX,e.clientY])
return clicks
}
}
document.getElementById("b1").onclick=showClick();
document.getElementById("b2").onclick=showClick();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With