Your answer is in the first line of your question:
something()
is being called when clicking on an icon existing inside
my page.
So, you must already have set up something
as the click
handler for your icon in order for that behavior to work. This means that every time you click the icon, you are triggering a click
event that is handled by something
, but you are also registering a completely different click
event handler for the document
.
Since events bubble, any clicks on the icon (which are in the document
), will trigger that element's click
event handler (something
) and then the event will bubble up to the document
, which will trigger its click
event handler (clickHandler
).
So nothing is out of sequence here. This is a simple case of one event handler setting up another and because the first handler is invoked before the second one would receive the event, the second binding happens in time for the document
to handle the bubbled event.
Here's your code (slightly modified in order to run) with commentary:
document.querySelector("div").addEventListener("click", something);
function something(){
console.log("function something has been invoked because the div was clicked");
// Every time you click the div, you initiate a click event WITHIN the document
// that will propogate upward to the document itself and the next line sets up
// a handler at that level:
document.addEventListener('click', clickHandler);
}
function clickHandler(e) {
console.log(e.type + " triggered by: " + e.target + " handled at the document level");
}
div { background-color:#800080; color:orange;}
<p>First, click anywhere outside of the purple div and nothing will happen because the only thing
that initially has a click event set up for it is the div. Then click on the div and you'll
not only get the div's event handler to run, but because that handler sets up a click event
handler for the document and the event hasn't bubbled up there yet, you'll get that same
click event handled again, but by the document.
</p>
<p>
Then, click outside of the div again. Because the document now has an event handler set up
for it, the click will be handled there.
</p>
<div>Click me.</div>
If this behavior is not what you want, then you have a couple of options:
You can stop an event from bubbling any further (and therefore prevent it from getting to the document) using event.stopPropagation()
:
document.querySelector("div").addEventListener("click", something);
function something(e){
// Handle the event here but don't allow it to go anywhere else
e.stopPropagation();
console.log("function something has been invoked because the div was clicked");
// Every time you click the div, you initiate a click event WITHIN the document
// that will propogate upward to the document itself and the next line sets up
// a handler at that level:
document.addEventListener('click', clickHandler);
}
function clickHandler(e) {
console.log(e.type + " triggered by: " + e.target + " handled at the document level");
}
div { background-color:#800080; color:orange;}
<p>First, click anywhere outside of the purple div and nothing will happen because the only thing
that initially has a click event set up for it is the div. Then click on the div and you'll
not only get the div's event handler to run, but because that handler sets up a click event
handler for the document and the event hasn't bubbled up there yet, you'll get that same
click event handled again, but by the document.
</p>
<p>
Then, click outside of the div again. Because the document now has an event handler set up
for it, the click will be handled there.
</p>
<div>Click me.</div>
Handle all the clicks at the document
level by just letting everything bubble up there and then respond to the events as you like by determining which element was responsible for triggering the event in the first place. This is called "event delegation". event.target
lets you access that element:
// All clicks will eventually bubble up to the document
document.addEventListener('click', clickHandler);
function clickHandler(e) {
console.log(e.type + " triggered by: " + e.target + " handled at the document level");
// You can handle the event differently based on which element triggered it.
if(e.target.classList.contains("notSpecial")){
e.target.classList.add("special");
}
}
.special { background-color:red; }
.as-console-wrapper { max-height: 1.5em !important; }
<h1>click me</h1>
<p>No, click me!</p>
<div>What about me?!</div>
<div class="notSpecial">Click me. I'm special</div>