Google Analytics Asynchronous Tracking: How it works
The Google Analytics asynchronous tracking code came out of beta two weeks ago, and given my prior post on how scripts block page loads , I figured I’d briefly point out some interesting aspects of how the new tracking code works. The two things that I find interesting are (1) the asynchronous script loading and (2) the new syntax related to queuing events.
Which browsers support the async script attribute?
Here is the code that Google asks you to include, notice the “ga.async = true;
” part:
var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXX-X']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })();
It appears that setting the “async” attribute to true makes the magic happen, but if you look into which browsers currently support the async attribute , you’ll discover that it’s only Firefox 3.6+. So what gives?
It turns out that if you insert a script—that you created using document.createElement—dynamically into a page, then it will be asynchronous in most browsers. I ran some tests when this code was first released, and my results are included below.
Note: this table describes for each browser whether the the domContentLoaded event waits for an “async script” to finish before triggering, or if it goes ahead and triggers while the script is still loading. It does the same for window.load and also shows if a script that appears lower on the page will wait for an “async script” to load before executing or if it just goes ahead.
domContentLoaded event (jQuery) | window.load event | Execution of lower script on page | |
---|---|---|---|
Mac: | |||
Safari 4.0.4 | goes | waits | goes |
FF 3.5.7 | waits | waits | waits |
FF 3.6 | goes | waits | goes |
Opera 10.10 | waits | waits | waits |
Chrome | goes | waits | goes |
Windows: | |||
IE6 | goes | goes | goes |
IE7 | goes | goes | goes |
IE8 | goes | goes | goes |
FF2 | goes | waits | waits |
FF3.6 | goes | waits | goes |
Chrome | goes | waits | goes |
As you can see, the most important actions—the domContentLoaded event firing and a lower script executing—can both happen before the “async script” finishes loading for all of these browsers except for Opera and Firefox less than 3.6. It’s mostly asynchronous… mostly.
The asynchronous syntax: _gaq.push ()
There’s a new syntax for tracking events, and also for setting the initial variables (like your account id) which looks like this:
var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXX-X']); _gaq.push(['_trackPageview']);
<a href="#" onClick="_gaq.push(['_trackEvent', 'Videos', 'Play', 'How is babby formed?']);">Play</a>
The magic happens when the script actually loads and then replaces the _gaq array with a new object that immediately executes all previously queued events and has a “push” method that will also immediately execute any event whenever it is called. The code for the new _gaq object looks something like this (a little simplified):
var GoogleAnalyticsQueue = function () { this.push = function () { for (var i = 0; i < arguments.length; i++) try { if (typeof arguments[i] === "function") arguments[i](); else { // get tracker function from arguments[i][0] // get tracker function arguments from arguments[i].slice(1) // call it! trackers[arguments[i][0]].apply(trackers, arguments[i].slice(1)); } } catch (e) {} } // more code here… }; // get the existing _gaq array var _old_gaq = window._gaq; // create a new _gaq object window._gaq = new GoogleAnalyticsQueue(); // execute all of the queued up events - apply() turns the array entries into individual arguments window._gaq.push.apply(window._gaq, _old_gaq);
It’s a pretty cool idea that should make Computer Science professors who love interfaces really happy. If you want to read some more about the asynchronous tracking code, Mathias Bynens wrote a great post describing how to optimize the asynchronous Google Analytics snippet —it has some real passion for minimizing bits.
Extra: the “apply” method
If you’re not familiar with the “apply” method, it can be called on any function and takes two arguments: (1) an object that should be treated as the this
object inside the function and (2) an array that will be the arguments
of the function. It’s being used in the above examples to convert arrays into the arguments of a function (almost like unpacking in Python, but not as elegant).
Also, if you want to use the “apply” method purely to set the this
object inside a function, you should consider using “call” instead, which is slightly faster, but requires you to pass the arguments in directly (not in an array).
my_func.apply(this, ['foo', 'bar']); my_func.call(this, 'foo', 'bar');