Recently I ran into some interesting behaviour related to event-bubbling. I have created a code-pen to illustrate this: https://codepen.io/anon/pen/XGmxXr
All I am doing there is bind two event-listeners (or I guess three including the one that removes the button). One on the document and one on the document.body.
When clicking on the button, only the console.log from the document would show up.
Why? Wouldn't the event bubble to document.body first and then to document?
Or asked differently: how can an event bubble up to document but not stop by document.body?
$(document).on("click", "button", () => console.log("document knows"));
$(document.body).on("click", "button", () =>
console.log("document body knows")
);
$("button").on("click", e => $("button").remove());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>Click me</button>
This has got be a jQuery event related problem. With the normal DOM API both document
and document.body
get notified correctly:
document.addEventListener("click", (e) => {
if (e.target.tagName === 'BUTTON') {
console.log("document knows");
};
});
document.body.addEventListener("click", (e) => {
if (e.target.tagName === 'BUTTON') {
console.log("document.body knows");
};
});
document.querySelector("button").addEventListener("click", function(e) {
this.parentElement.removeChild(this);
})
<button type="button">Click me</button>
TL/DR: The event bubbles to both body
and document
. The button is not in DOM at that moment. But for body
, before handler execution jQuery tries to ensure that button
exists inside it. For document
, id does't do such a check.
Explanation
I'm not sure why it's designed like this. It's just explanation why it happens.
First of all, the handlers are bound to document
and body
respectively, not to the button. When event bubbles to the document
, jQuery tries to find descendants, specified by selector (button
in our case) and executes the handler against each of them. Same for body
. The difference is in the way how it checks the descendants.
When the event is bubbled to document
and body
, the button is already removed from DOM, but the button element still exists in memory and is accessible via event.target
.
for both document
and body
jQuery searches the elements to execute the handler against:
event.target
(our removed button)body
or document
) (founds none)The difference in 3rd step.
2 pieces of jQuery code are responsible for this:
https://github.com/jquery/jquery/blob/e743cbd28553267f955f71ea7248377915613fd9/external/sizzle/dist/sizzle.js#L1985
outermostContext = context === document || context || outermost;
https://github.com/jquery/jquery/blob/e743cbd28553267f955f71ea7248377915613fd9/external/sizzle/dist/sizzle.js#L1925
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
(checkContext = context).nodeType ?
matchContext( elem, context, xml ) :
matchAnyContext( elem, context, xml ) );
First piece sets outermostContext
variable.
For document
, the outermostContext
is true
, for body
it's body
.
Second piece sets variable ret
which decides will the handler be called or not.
For body
, context !== outermostContext
is false
so matcher proceeds to 'matchContext' and eventually returns false
.
For document
, context !== outermostContext
is true
so the matcher returns true without context check.
The meaning of this is that for body
it tries to ensure that button
exists inside it. For document - it does not.
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