52

Can someone explain this code 'step by step','line by line'? I would like to learn more about Asynch code and how Google loads their script, how to 'hide' javascrippt from users (I know that I can't hide it but at least make it something like Google does, not to show all code in one file)

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
  ga('send', 'pageview');
</script>
fernandopasik
  • 9,565
  • 7
  • 48
  • 55
Milos Miskone Sretin
  • 1,748
  • 2
  • 25
  • 46
  • *letter by letter?* anonymous function with short variables to load another script (asynchronously) before any other scripts (in document tree) and execute 2 functions. You cannot read step by step because obfuscation. Start with `i s o g r` - that are `(window,document,'script','//www.google-analytics.com/analytics.js','ga')` – Mihai Iorga Mar 28 '14 at 15:12
  • (i[r].q=i[r].q||[]).push(arguments) ??? i[r]=i[r]||function()...??? What this means? – Milos Miskone Sretin Mar 28 '14 at 15:16
  • `window['ga']`, push defined arguments - `'create', 'UA-xxxxxxxx-x', 'xxxxxx.com'` – Mihai Iorga Mar 28 '14 at 15:17
  • `i` from isogr is first variable from anonymous function - `window`, `r` is second - `ga` .. – Mihai Iorga Mar 28 '14 at 15:18

7 Answers7

121

First of all, I would pass this through a beautifier, e.g. http://jsbeautifier.org/

 (function (i, s, o, g, r, a, m) {
     i['GoogleAnalyticsObject'] = r;
     i[r] = i[r] || function () {
         (i[r].q = i[r].q || []).push(arguments)
     }, i[r].l = 1 * new Date();
     a = s.createElement(o),
     m = s.getElementsByTagName(o)[0];
     a.async = 1;
     a.src = g;
     m.parentNode.insertBefore(a, m)
 })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

 ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
 ga('send', 'pageview');

After that lets evaluate the closure

(function (i, s, o, g, r, a, m) {
...
 })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

by replacing each of the named parameters: i, s, o, g, r with their corresponding values window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'

Note that a and m parameters do not have input values and are more like result variables.

This would be roughly (not bothering about variable scope, etc.) equivalent to

(function (i, s, o, g, r, a, m) {
     window['GoogleAnalyticsObject'] = 'ga';
     window['ga'] = window['ga'] || function () {
         (window['ga'].q = window['ga'].q || []).push(arguments)
     }, window['ga'].l = 1 * new Date();
     a = document.createElement('script'),
     m = document.getElementsByTagName('script')[0];
     a.async = 1;
     a.src = '//www.google-analytics.com/analytics.js';
     m.parentNode.insertBefore(a, m)
 })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

 ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
 ga('send', 'pageview');

In short what this code does in essence, is that it creates a new script tag with the line:

a = document.createElement('script'),

Then finds the first script tag

m = document.getElementsByTagName('script')[0];

Then it sets the newly created script tag to asynchronous execution (More insight on async execution could be obtained at Understanding Asynchronous Code in Layman's terms should you need it)

a.async = 1;

1 in the line above is equivalent to true, it is used 1 just because it is shorter.

After that the src of this script tag is set

 a.src = '//www.google-analytics.com/analytics.js';

Note that above no protocol (http or https) is specified in the URL. This would allow for the script to be loaded in the current browser protocol.

And finally it is inserted before the first script tag, so the browser could start loading it.

 m.parentNode.insertBefore(a, m)

So to summarize:

  1. We create a script tag
  2. We set it to load asynchronously async=true
  3. We insert this script tag, before the first script tag in the document

Specifics related to google analytics.

 window['ga'] = window['ga'] || function () {
     (window['ga'].q = window['ga'].q || []).push(arguments)
 }, window['ga'].l = 1 * new Date();

defines global function named ga that pushes its arguments in a queue Array (named q)

Then with the lines

 ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
 ga('send', 'pageview');

it pushes these "events" in the queue Array.

When the script is loaded, it checks the value of GoogleAnalyticsObject, which earlier was set to point to the name of ga with the line

 window['GoogleAnalyticsObject'] = 'ga';
starball
  • 20,030
  • 7
  • 43
  • 238
Zlatin Zlatev
  • 3,034
  • 1
  • 24
  • 32
  • 7
    Great answer @Zlatin. Wonderingn if you might know why this script makes a *new* script tag dynamically? Why not simple use – cavalcade Mar 01 '15 at 19:47
  • 3
    Check http://www.nczonline.net/blog/2009/07/28/the-best-way-to-load-external-javascript/ and http://www.html5rocks.com/en/tutorials/speed/script-loading/ - they would give you insight on why one may prefer using script based loading of other scripts over pure script tag loading of them. Also check http://stackoverflow.com/questions/8996852/load-and-execute-order-of-scripts – Zlatin Zlatev Mar 05 '15 at 16:45
  • Thanks for the links Zlatin. Having read them, still struggling to see why Google does it this way specifically – cavalcade Mar 07 '15 at 03:42
  • Hi, why this line: "a.src = '//www.google-analytics.com/analytics.js';" instead of just "a.src = g" ? – Typo Nov 08 '17 at 00:21
  • I am explaining what parameter values are resulting in... original code is a.src = g; – Zlatin Zlatev Nov 08 '17 at 22:11
  • "Note that a and m parameters do not have input values and are more like result variables." m = the script element after it has been created in the DOM I thought? – brandito Feb 21 '18 at 23:09
  • 1
    @Brandito yes, that is correct. But it does not receive an initial value from the call of the closure (there is no parameter that passes value to m in the self-executing anonymous function) – Zlatin Zlatev Feb 22 '18 at 12:57
  • 1
    @ZlatinZlatev Right, I get you now, thanks for the layman explanation for me :p – brandito Feb 23 '18 at 01:41
  • 3
    i really wish there were more of these explanation questions on stackoverflow – PirateApp Aug 18 '19 at 04:27
  • 1
    @PirateApp do you have a particular library or piece of code in mind? If yes - do not hesitate to ask a question. – Zlatin Zlatev Aug 27 '19 at 09:49
36

Google has published the un-minified version of this code:

<!-- Google Analytics -->
<script>
/**
 * Creates a temporary global ga object and loads analytics.js.
 * Parameters o, a, and m are all used internally. They could have been
 * declared using 'var', instead they are declared as parameters to save
 * 4 bytes ('var ').
 *
 * @param {Window}        i The global context object.
 * @param {HTMLDocument}  s The DOM document object.
 * @param {string}        o Must be 'script'.
 * @param {string}        g Protocol relative URL of the analytics.js script.
 * @param {string}        r Global name of analytics object. Defaults to 'ga'.
 * @param {HTMLElement}   a Async script tag.
 * @param {HTMLElement}   m First script tag in document.
 */
(function(i, s, o, g, r, a, m){
  i['GoogleAnalyticsObject'] = r; // Acts as a pointer to support renaming.

  // Creates an initial ga() function.
  // The queued commands will be executed once analytics.js loads.
  i[r] = i[r] || function() {
    (i[r].q = i[r].q || []).push(arguments)
  },

  // Sets the time (as an integer) this tag was executed.
  // Used for timing hits.
  i[r].l = 1 * new Date();

  // Insert the script tag asynchronously.
  // Inserts above current tag to prevent blocking in addition to using the
  // async attribute.
  a = s.createElement(o),
  m = s.getElementsByTagName(o)[0];
  a.async = 1;
  a.src = g;
  m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

// Creates a default tracker with automatic cookie domain configuration.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sends a pageview hit from the tracker just created.
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->

https://developers.google.com/analytics/devguides/collection/analyticsjs/tracking-snippet-reference

Zlatin's line by line explanation is still valid.

broc.seib
  • 21,643
  • 8
  • 63
  • 62
4

i['GoogleAnalyticsObject']=r; This is assigning 'ga' to the 'GoogleAnalyticsObject' property of 'window'

window['ga'] = window['ga'] || function(){
        (window['ga'].q = window['ga'].q || []).push(arguments)
    }, window['ga'].l = 1 * new Date();

This part is assigning the 'ga' property of window as a function (or itself if it already exists). The function then assigns the q property as an empty array and adds all of the functions arguments to it. It then assigns the l property the current timestamp (it's multiplied by 1 to force it as an integer).

The next lines just make a script tag and assign it some attributes such as source and async = true and then it adds this script tag to the document.

Cjmarkham
  • 9,484
  • 5
  • 48
  • 81
2

The code is minified. Using http://jsbeautifier.org/ you can revert that (sort off) and make it a bit more readable. Basically It's a small function that adds another javascript (www.google-analytics.com/analytics.js) to the dom using the same protocol, http or https.

(function (i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function () {
        (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o),
    m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
Phobos
  • 91
  • 4
1

The code has been run through a minifier, and looks like this when pretty printed:

(function (i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function () {
        (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o),
    m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
ga('send', 'pageview');

To know what it exactly does, you'd probably have to take a look at their analytics.js file, but as that's very likely to be minified as well, you're not going to get much out of it.

If you want to do the same thing, you could use a code minifier like JSMin. It replaces any unnessecary whitespace and newline characters, among other things, to help reduce bandwidth.

Adowrath
  • 701
  • 11
  • 24
fnostro
  • 4,531
  • 1
  • 15
  • 23
  • It is not that hard to unminify the code, should you bother to do so - check my comment below. – Zlatin Zlatev Mar 28 '14 at 15:41
  • 1
    I didn't say it was hard, I said the OP would have to go through Google's code and learn. Reverse engineering what Google is doing is probably a good idea to gain understanding, but far more time than I can spend on an answer. You did an excellent job on breaking things down though, very nice, kudos to you :) – fnostro Mar 28 '14 at 15:59
0

Better-optimized code:

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create','UA-xxxxxxxx-x','xxxxxx.com');ga('send','pageview');

The Google Analytics tracking entry point has been beautified:

(function(i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function() {
        (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o),
    m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
ga('send', 'pageview');

Cixon added // comments to this formatted code:

(function(i, s, o, g, r, a, m) {
    // Pointer - analytics.js uses window['GoogleAnalyticsObject'] to access the string 'ga', sort of a longness of their minified code, that is a problem. But it is to support renaming.
    i['GoogleAnalyticsObject'] = r;
    // Create a GA function if one does not exist, and assign it to window with the string 'ga'. What it does is pushes arguments to the end of an array that is either already defined or is defined in the function
    i[r] = i[r] || function() {
        (/*set to support a case where it is not defined*/ i[r].q = /*if it is already defined, get it*/ i[r].q /*define it here*/ || []).push(arguments)
    };
    // Sets the time (as an integer) this tag was executed.
    // Used for timing hits.
    i[r].l = 1 * new Date();
    // Create the script tag for Google Analytics
    a = s.createElement(o);
    // Get the first script tag
    m = s.getElementsByTagName(o)[0];
    // Set the async property to true (1) and set the src property to the path for Analytics
    a.async = 1;
    a.src = g;
    // Makes this GA tracking code script element (a) go before this 'first script element' (m)
    m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
// Initialize the actual GA runtime
ga('create', 'UA-xxxxxxxx-x', 'xxxxxx.com');
// Send a PageView request to Google Analytics
ga('send', 'pageview');

Can you understand that commented code?

To really understand this, you have to take a look at their Analytics.js file, but that is minified too, so you are not going to get much out of that until I finish this.

0

Google explains it here: https://developers.google.com/analytics/devguides/collection/analyticsjs/tracking-snippet-reference#the-google-analytics-tag under 'Unminified version'

<!-- Google Analytics -->
<script>
/**
 * Creates a temporary global ga object and loads analytics.js.
 * Parameters o, a, and m are all used internally. They could have been
 * declared using 'var', instead they are declared as parameters to save
 * 4 bytes ('var ').
 *
 * @param {Window}        i The global context object.
 * @param {HTMLDocument}  s The DOM document object.
 * @param {string}        o Must be 'script'.
 * @param {string}        g Protocol relative URL of the analytics.js script.
 * @param {string}        r Global name of analytics object. Defaults to 'ga'.
 * @param {HTMLElement}   a Async script tag.
 * @param {HTMLElement}   m First script tag in document.
 */
(function(i, s, o, g, r, a, m){
  i['GoogleAnalyticsObject'] = r; // Acts as a pointer to support renaming.

  // Creates an initial ga() function.
  // The queued commands will be executed once analytics.js loads.
  i[r] = i[r] || function() {
    (i[r].q = i[r].q || []).push(arguments)
  },

  // Sets the time (as an integer) this tag was executed.
  // Used for timing hits.
  i[r].l = 1 * new Date();

  // Insert the script tag asynchronously.
  // Inserts above current tag to prevent blocking in addition to using the
  // async attribute.
  a = s.createElement(o),
  m = s.getElementsByTagName(o)[0];
  a.async = 1;
  a.src = g;
  m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

// Creates a default tracker with automatic cookie domain configuration.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sends a pageview hit from the tracker just created.
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
3rik82
  • 45
  • 7