My Fundamental Misunderstanding Of The jQuery Event Object

Posted December 17, 2009 at 9:45 PM by Ben Nadel

Tags: Javascript / DHTML

Typically, when I deal with event binding in jQuery, I use it in a very one-off kind of way. Meaning, I rarely have multiple event handlers bound to the same element and I rarely use event bubbling in which an event is utilized at more than one level of the DOM tree. As such, I never needed more than a superficial understanding of the jQuery event object. This morning, however, as I was further exploring custom event types in jQuery, it quickly became apparent that my understanding of the event object was, in reality, a critical misunderstanding.

 
 
 
 
 
 
 
 
 
 

To demonstrate where I was going wrong, I have put together this small example. In the following code, I am binding two event handlers to the same action ("click") on the same element ("p"). With each of these bindings, I am also defining additional event data which I plan to leverage in the event handler; the caveat being that I don't intend to make immediate use of the resultant event object:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>jQuery Event Object</title>
  • <script type="text/javascript" src="jquery-1.4a1.js"></script>
  • <script type="text/javascript">
  •  
  • jQuery(function( $ ){
  •  
  • // Bind click with extra event data.
  • $( "p" ).bind(
  • "click",
  • {
  • which: "first"
  • },
  • function( event ){
  • delayEvent( event );
  • }
  • );
  •  
  •  
  • // Bind click with extra event data.
  • $( "p" ).bind(
  • "click",
  • {
  • which: "second"
  • },
  • function( event ){
  • delayEvent( event );
  • }
  • );
  •  
  •  
  • // This is the same handler used by both event
  • // bindings above. Notice that it doesn't make use
  • // of the EVENT object right away, but rather delays
  • // its usage.
  • var delayEvent = function( event ){
  •  
  • // Output current event data.
  • console.log( "A:", event.data.which );
  •  
  • // In half a second, check the EVENT object to see
  • // what bound data is available.
  • setTimeout(
  • function(){
  • console.log( "B:", event.data.which );
  • },
  • 500
  • );
  •  
  • };
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <p>
  • Holy cow! Did you see Tricia today?!? HOT!!
  • </p>
  •  
  • </body>
  • </html>

As you can see above, both events define a unique value for the property, "which," which will be available in the event handler callback. Each event callback turns around and calls the same delayEvent() method, which itself, turns around and creates a timer. The passed-in event object is then only finally utilized within this timer callback, which outputs the event-data-scoped "which" value.

When we run the code above, we get the following console output:

A: first
A: second

B: second
B: second

As you can see, both calls to the delayEvent() method output the intended "which" value. However, once the subsequent timer executes (once per binding), both timer-delayed callbacks report the last-bound "which" value.

When I started to see this happen, I was working in a context that had closures inside of closures inside of closures; as such, I figured it was some sort of crazy variable-binding issue that I had never seen before. Once I moved the code into this white-page example, the problem became clear: I had a fundamental misunderstanding of how jQuery uses the resultant event objects across the various event binding callbacks.

My mis-assumption was that jQuery passed a fresh Event object to each event handler. What I realize now is that jQuery actually passes the same Event object around to every event handler that would be triggered by the given action. Because of this, the event object passed to the first event handler is subsequently populated with the second event handler's data by the time the first delayed-method gets executed. In hindsight, this makes perfect sense; after all, event methods like Event.isDefaultPrevented() and Event.isPropagationStopped() and Event.isImmediatePropagationStopped() almost only make sense if the same Event object is passed around to multiple event handlers.

With this clearer understanding in hand, I re-worked the above demo. This time, rather than depending on the delayed integrity of the given Event object, I am simply copying the required data out of the Event, to be lexically-bound to the setTimeout() callback:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>jQuery Event Object</title>
  • <script type="text/javascript" src="jquery-1.4a1.js"></script>
  • <script type="text/javascript">
  •  
  • jQuery(function( $ ){
  •  
  • // Bind click with extra event data.
  • $( "p" ).bind(
  • "click",
  • {
  • which: "first"
  • },
  • function( event ){
  • delayEvent( event );
  • }
  • );
  •  
  •  
  • // Bind click with extra event data.
  • $( "p" ).bind(
  • "click",
  • {
  • which: "second"
  • },
  • function( event ){
  • delayEvent( event );
  • }
  • );
  •  
  •  
  • // This is the same handler used by both event
  • // bindings above. Notice that it doesn't make use
  • // of the EVENT object right away, but rather delays
  • // its usage.
  • var delayEvent = function( event ){
  •  
  • // Because of the way jQuery uses the EVENT object
  • // in immediate and long-term propagation, we have
  • // to copy the event data out of the event object
  • // if we want to reference it later.
  • var data = event.data;
  •  
  • // Output current event data.
  • console.log( "A:", data.which );
  •  
  • // In half a second, check the copied DATA object
  • // to see what bound data is available.
  • setTimeout(
  • function(){
  • console.log( "B:", data.which );
  • },
  • 500
  • );
  •  
  • };
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <p>
  • Holy cow! Did you see Tricia today?!? HOT!!
  • </p>
  •  
  • </body>
  • </html>

Now that my primary event handler copies the event data out of the Event object and into the local function scope:

  • var data = event.data;

... my delayed callback references data that cannot be overridden asynchronously. And so, when we run the page this time, we get the following console output:

A: first
A: second

B: first
B: second

This time, it works like a charm. Now that I have a better understanding of how jQuery uses the generated event objects across multiple event handlers, I can get back to playing around with custom jQuery event types.




Reader Comments

Dec 18, 2009 at 12:03 AM // reply »
9 Comments

Makes sense. Thanks for bringing that to my attention!


Dec 18, 2009 at 1:43 AM // reply »
155 Comments

So is what's actually happening that the browser instantiates the event object internally and jQuery gets handed that, which IT then feeds to all of the subscribing handlers by reference?

I wonder if jQuery has a setting or option to let you get the event object by value instead which seems like it would let you author more-easily-shareable code with less worrying about conflict with other jQuery code and inadvertent sabotage. Like adding your custom drag and drop animation effect to someone else's existing app, both of which might severely manipulate the mouse down event object. Coo post.


Dec 18, 2009 at 1:48 AM // reply »
155 Comments

PS I like that you're totally obsessed with jQuery, I love jQuery too.

I'd be interested in hearing your thoughts on the Flash Platform and Actionscript, and if you've thought about it, why you're as excited as you are about jQuery and AJAX apps without being even MORE excited about Actionscript and Flash/AIR-based web apps, which get me even hotter and bothereder than jQuery, especially given the silly great ColdFusion integration and performance.


Dec 18, 2009 at 1:54 AM // reply »
155 Comments

Scratch that. jQuery creates its own scratch object and actually copies over properties from the browser's original event object. That knowledge will come in handy some day if there's ever a Fallout 3 apocalypse and I find myself sitting around playing some kind of legacy technology trivia game in a vault.


Dec 18, 2009 at 2:33 AM // reply »
11 Comments

I am really liking these videos you are doing. It helps a ton to hear your voice talk as you mouse around the code.

I know the videos take a lot of extra time to produce, but they are making my job of consuming your content that much faster.


Dec 18, 2009 at 3:24 AM // reply »
7 Comments

Thanks Ben for figuring this out. As usual, your drive to go WAY beyond 'a superficial understanding' of the technologies you work with, taught me something new. You rock dude, keep exploring!


Dec 18, 2009 at 4:56 AM // reply »
5 Comments

Ben, you cleared up this common misunderstanding, that some might not even stumbled upon. Nice!


Dec 18, 2009 at 7:42 AM // reply »
11,314 Comments

@David,

I believe that jQuery attaches its own event listeners to whatever the target objects are (in a cross-browser compatible way, of course), and then, as you are saying, creates its own event object (new jQuery.Event()) which it then manually propagates.

As far as Flash and ActionScript, I actually do want to learn more about FLEX-type programming as I think it, like jQuery, is very much event-driven and would shed a lot of light on perhaps some "Best Practices" in event programming that I am not privy to.

Just not enough hours in the day ;)

@Dave,

I'm glad you like the videos - I'll keep making them. I think it helps to really paint the picture.

@Martijn,

Thank you.

@Ricardo,

Exactly! I've been using jQuery for a long time and only just ran into this issue for the first time. Granted, it took me over two hours to debug; but now that I see what's going on, I can act accordingly.


Dec 18, 2009 at 11:08 AM // reply »
33 Comments

Would this work as well?

// Bind click with extra event data.
$( "p" ).bind(
"click",
{which: "second"},
function( event ){
delayEvent( event, event.data);
}
);

var delayEvent = function( event, data){
// Output current event data.
console.log( "A:", data.which );
setTimeout(
function(){
console.log( "B:", data.which );
},
500
);
};

I just like the idea of splitting off the data as soon as possible on a potentially shared event object, never been to sure how stuff will actually execute, had weird circumstances where the sequence stuff executed was inconsistent, although that was with events at different levels instead of the same element, but it has made me paranoid about execution order or taking anything for granted with eventing.


Dec 18, 2009 at 11:11 AM // reply »
11,314 Comments

@David,

That should work and is definitely a good idea! Nice thinking.


Dec 18, 2009 at 1:09 PM // reply »
2 Comments

Nice post. This made me curious so I modified the original example to pass slightly different data in the 2 events.

jQuery(function( $ ){

// Bind click with extra event data.
$( "p" ).bind(
"click",
{
foo: "bar",
which: "first"
},
function( event ){
delayEvent( event );
}
);


// Bind click with extra event data.
$( "p" ).bind(
"click",
{
fizz:"buzz",
which: "second"
},
function( event ){
delayEvent( event );
}
);


// This is the same handler used by both event
// bindings above. Notice that it doesn't make use
// of the EVENT object right away, but rather delays
// its usage.
var delayEvent = function( event ){

// Output current event data.
console.log( "A:", event.data );

// In half a second, check the EVENT object to see
// what bound data is available.
setTimeout(
function(){
console.log( "B:", event.data );
},
500
);

};

});

Notice I'm logging the whole data object. The output is as follows.

A: Object foo=bar which=first
A: Object fizz=buzz which=second
B: Object fizz=buzz which=second
B: Object fizz=buzz which=second

So essentially the data property is entirely replaced with each event handler. This supports your findings. But does that make sense? It might be handier to do a merge on each subsequent event so you end up with something like this.

A: Object foo=bar which=first
A: Object foo=bar fizz=buzz which=second
B: Object foo=bar fizz=buzz which=second
B: Object foo=bar fizz=buzz which=second

That way you can treat the data object as state that is maintained between events. There are other solutions for that though so this is just academic.


Dec 18, 2009 at 4:20 PM // reply »
11,314 Comments

@Marco,

Good thinking. As I was looking at your code, I knew immediately where you were going with it. I think you are right - the entire data value is replaced. As to whether or not it makes sense to merge vs. replace, I am not sure.

The reason that merging might not make sense is that you can get overriding values. And, at that point - where we might lose data integrity for a given key - I think merging loses its usefulness.


Dec 18, 2009 at 4:43 PM // reply »
2 Comments

It could be a problem but it could also be useful. It depends on why you're stacking events and how you're using them.

Consider a scenario where you have a page in your app with the usual events. But you add some functionality that also needs to run if a user with certain roles is authenticated.

You could put checks in your original code to check user state. Or your new code could just add additional events that run. That code could override user state values to indicate some settings have changed.

This is definitely a contrived example and I'm not sure if I buy it myself :) But this bothers me less than actually losing data with the current implementation.


Jan 5, 2010 at 9:03 AM // reply »
11,314 Comments

@Marco,

I think it could work - you just have to be careful about how your events are wired together. After all, one of the nice things about event-driven architecture is that you don't have to worry about how many listeners are "listening" for events - they act independently.

Just something to be aware of in a pros / cons.



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 18, 2013 at 3:39 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
Hi Ben, THANKS! While not bleeding edge, it is new to me & I like learning new things every day! ... read »
Jun 18, 2013 at 12:30 PM
Disabling Auto-Correct And Auto-Capitalize Features On iPhone Inputs
Also spellcheck="false" should be mentioned as part of html5 specs ... read »
Jun 18, 2013 at 8:40 AM
Using Named Functions Within Self-Executing Function Blocks In Javascript
Hi Ben, you forgot to mention the most important thing for named self-executing functions - they can be referenced by name ONLY inside their execution context (which is parens in this case), it mean ... read »
dee
Jun 18, 2013 at 7:01 AM
My Safari Browser SQLite Database Hello World Example
hai ben, this program is really good i could understand the concept but i dint know how to save it and how to open it as you have done in the video can u give that details pls ... read »
Jun 18, 2013 at 6:04 AM
Clearing Inline CSS Properties With jQuery
Thanks a lot for for post! It helped me a lot... after being stuck since 24 hrs.. found solution from your post. Thanks again! ... read »
Jun 18, 2013 at 2:31 AM
SOTR 2013 - The Best Conference I Never Went To
I keep watching it, should keep me happily distracted until SotR14 ;) ... read »
Jun 17, 2013 at 9:45 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, As I was reading what you wrote, it occurred to me that maybe I do something similar to that in some of my client-side code. In an application I'm working on, there are a bunch of unrelated ... read »
Jun 17, 2013 at 9:36 PM
Object Thinking By David West
@Jonah, Please, don't feel bad at all. I appreciate all that you have contributed to the conversation. And, the more points of view I get, the more confident I am that I will some day, some how und ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools