var element = document.getElementById('test-button');
//event handler
element.onclick = (function outer() {
var counter = 0;
return function inner() {
counter++;
alert('Number of clicks: ' + counter);
};
})();
<button id="test-button">Test</button>
I don't understand why the variable counter won't reset to 0
every time the button is clicked, or maybe when it's clicked, only the function inner()
get executed, then why the variable counter get set to 0
when first click occurs.
The code is from this link, after carefully read their explanation, I'm still confused.
why the variable
counter
get set to 0 when first click occurs.
That is not the moment it gets set to 0. This happens when the assignment to .onclick
happens. At that moment outer
is defined and executed. So that is the moment counter
is set to 0. No click is handled yet.
only the function inner() get executed
Indeed. outer
is only executed once, when the main script is executed on page load, and then never again. inner
is the function that gets assigned to onclick
and thus has the role of event handler. inner
has access to the counter
that is defined in outer
, but outer
is never executed again.
outer
is a so-called immediately executing function.
As a comparison: you could achieve something similar with this code:
var counter = 0;
element.onclick = function inner() {
counter++;
alert('Number of clicks: ' + counter);
};
...but the downside now is that counter
is a global variable, that could be altered by some other code. To make sure counter
is only available to the click handler, a closure is created for it, which you could also do like this:
function outer() {
var counter = 0;
element.onclick = function inner() {
counter++;
alert('Number of clicks: ' + counter);
};
}
outer();
The downside here is that outer
has a so-called side-effect: it assigns to element.onclick
. This is the reason to choose to return inner
and leave it to the caller of outer
to do something with it, e.g. assign it to .onclick
.
Then we get something like this:
function outer() {
var counter = 0;
return function inner() {
counter++;
alert('Number of clicks: ' + counter);
};
}
element.onclick = outer();
This is essentially the same as your code: the function is first defined, and then immediately executed. The function that gets assigned to onclick
is the one that is returned by outer
, i.e. inner
.
Yet another way to achieve this encapsulation of counter
:
function inner() {
this.counter++;
alert('Number of clicks: ' + this.counter);
}
element.onclick = inner.bind({ counter: 0 });
Here the we provide an unnamed object that is always passed as this
-argument to inner
whenever it is called. As we don't retain a reference to that object outside of this construct, we achieve the same principle: the counter's state is maintained, but is not accessible outside the handler.