Debugging jQuery with Chrome's Developer Tools

Debugging jQuery with Chrome's Developer Tools

When I first started experimenting with JavaScript back in the late 90's, most people used it to dynamically write web pages — Javascript code often looked something like this:

<script>
var c = document.cookie.split('; ');
for (var i in c)	{
	if (c[i].startsWith("accessCount"))	{
		document.write("You have access this page " + (c[i].split('=')[1]) + " times");
	}
}
</script>

Example 1: dynamic page rewriting with Javascript

(And yes, late 90's Javascript was actually that lame). Over time, server-side frameworks got to be more powerful (and more secure) for dynamic page creation, and Javascript was pushed out to the "fringes" of user interactivity. More and more Javascript was designed in event handlers : code executed in response to a user action like a button click or a form submission. By the early 21st century, more of the Javascript I came across was along the lines of:

<script>
function validateForm(f)	{
	if (f.name.value == "")	{
		alert("You must supply a name");
		return false;
	}
	return true;
}
</script>
<form id="inputForm" method="post" onsubmit="return validateForm(this)">

Example 2: event handler Javascript

(Yep, early 21st century Javascript was pretty lame, too).

One unifying characteristic that these early Javascript experiments had was that the Javascript was tightly intertwined with the HTML. Buttons had onclick, onmouseover, onmouseout, etc. handlers whose code was either in the <head> section of the HTML page if you were lucky, or included just before the code that used it if you were less lucky. What ended up happening as a result was that code ended up being cut and pasted and slightly modified into different contexts. As Javascript got to be more and more complex, the desire for reusable components was greater, and Javascript implementations started supporting more of what was called "unobtrusive" Javascript. An unobtrusive form handler would have been implemented as:

<script>
window.onload = function()	{
	var f = document.getElementById("inputForm");
	f.onsubmit = function(f)	{
		if (f.name.value == "")	{
			alert("You must supply a name");
			return false;
		}
		return true;
	}
}
</script>

Example 3: Unobtrusive Javascript form validation

You could make this even more dynamic (and introduce support for multiple event handlers per element) with addEventListener — although going down that path introduced some browser incompatibilities, since browsers could never quite seem to agree on what to call that function. While this format made the HTML cleaner and could, in theory, be more easily reused (example 3 couldn't be, but you can probably see how this could easily be extended into a more robust attribute-driven validation framework), it made debugging just a little bit harder. Back in the early days, if a form handler was misbehaving, you could do a "View source", find the form declaration, look for its onsubmit attribute, and then search for that function name in your code. With unobtrusive javascript, the onsubmit attribute is always separated from the element declaration, so you would often have to go on a bit of a hunt to find the function declaration. However, the handler function, once bound, was still bound to the element itself; if you knew the name of the element, you could always open up the console or, if you were stuck with a browser without one, always resort to something like: javascript:alert(document.getElementById('inputForm').onsubmit) on the URL bar. With Chrome, you can even open up the debugging tools and go straight to the Event Listener tab of any given element and see a list of every event handler registered with it.

The benefits of separating presentation from behavior were clear enough that the extra debugging effort was worth it. With the advent of jQuery, though, this got to be even more obfuscated and more difficult. Examples 4, 5 and 6 demonstrate the "old school", unobtrusive old school, and jQuery way to register the same button onclick handler:

<script>
function report(b)	{
	alert("You clicked the button");
}
</script>
<button id="b" class="report" onclick="report(this)">click</button>

Example 4: Old school click handler, hard to reuse, easy to track down

<head>
<script>
window.onload = function()	{
	document.getElementById("b").onclick = report;
}
</script>

Example 5: Unobtrusive click handler; easier to reuse, harder to track down

<head>
<script>
$(function()	{
	$('#b').click(report);
}
</script>

Example 6: jQuery click handler registration; reusable, but where does it go?

While jQuery went a long way toward making really responsive Javascript UIs a possibility, giving us accordions, drop-down menus, fade in/out, animations, custom themes and more without having to resort to Flash — all in a truly cross-platform way — it also took over event registration and hid it deep within the internals of the jQuery library itself. This is never as evident as when you find yourself debugging somebody else's misbehaving Javascript — if you click a button and it doesn't do what it's supposed to do (or just does nothing at all, as buggy Javascript is prone to do), how do you go about tracking down the handler that's associated with the element? As figure 1 shows, even Chrome's debugging tools don't offer much assistance in this regard; the click handler isn't the custom code that you associated with the element, but a jQuery function.

Figure 1: Inspecting the event handlers associated with a jQuery-registered element

A bit of debugging through the associated jQuery code reveals that jQuery associates a new property called events with each object that has a jQuery callback registered with it. As shown in figure 2, you can take advantage of this knowledge and click through the defined function to inspect it, break on it, whatever you need to do. It's worth noting that events is an object which can contain multiple event handlers — you're not limited to just one event handler in jQuery.

Figure 2: jQuery's events property

Not so fast, though. If you take a closer look at figure 1, you may notice that this click handler is associated with jQuery 1.0.4... which is a pretty old jQuery version (9 years as of this writing). jQuery has gone through quite a few revisions and events was an internal, "behavior is undefined" implementation detail. As it turned out, other framework implementers had similar ideas, and this property ended up conflicting with those frameworks. As of jQuery 1.1, the property was renamed to $events.

jQuery 1.2 changed the internals of event handling yet again, and integrated event handling with its general data tracking mechanism. With jQuery 1.2, if you wanted to track down the handlers associated with an element whose ID you knew, you could query: $.data($('#b')[0], "events"). This works whether the event was registered by ID or not — in other words, if the registration code were $(".report").click(report), $.data($('#b')[0], "events") would still show you the handler list for the button.

jQuery developers thought that the $.data($('#b')[0], "events") construct was a bit awkward, though, so jQuery's developers added a simplified construct: $('#b').data('events'). This was just a shortcut to the longer form that jQuery 1.2 supported, but was obviously preferred.

This continued to work until jQuery 1.8, when they changed the (undocumented, mind you) internals of jQuery data handling yet again and renamed data to _data, and (inexplicably) removed the shortcut for that was introduced in jQuery 1.3. As of the time of this writing, the highest released version of jQuery is jQuery 2.1.4, which continues to store event handlers in $._data.

TL;DR
jQuery versionfind event handlers for element id
1.0.0-1.0.4$('#id').events
1.1.0-1.1.4$('#id').$events
1.2.0-1.2.6$.data($('#id')[0], "events")
1.3.0-1.7.2$('#id').data("events")
1.8.0+$._data($('#id')[0], "events")
It bears repeating that all of this is documented as being strictly undocumented (in other words, subject to change) — so I would recommend against relying on this anywhere except in an interactive debugging session.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts