7

Google Analytics initially uses a _gaq [object Array]. Passing an array to a function is in JavaScript passing an object, thus by reference.

(Edit: As pointed out in answers, the reference is passed by value. See https://stackoverflow.com/a/5314911/120521 for more details on reference/value passing in JavaScript.)

The code below uses jQuery to wait for the DOM to load, then attach a change event which will send a virtual pageview to Google Analytics once the user changes <input/> field.

var _gaq = _gaq || [];
_gaq.push(['_setAccount', _gAAccount]);
_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);
})();

var Tracking = {
  trackInputs: function ($, gaObject) {
    var inputs = $('#signUp').find('input');
    inputs.bind('change', function () {
      gaObject.push(['_trackPageview', '/virtual/input']);
      console.log(gaObject); // Outputs:
                         // [Array[2], Array[1], Array[2]]
                         // i.e. _setAccount, _trackPageview,
                         // and _trackPageview calls.
    });
  }
};

jQuery(document).ready(function($) {
  Tracking.trackInputs($, _gaq);
});

// ... DOM begins below

However, as can be seen in the comment above, the trackInputs() method prints the 'original' array. Ordinarily what the Google Analytics script does is it changes the _gaq array into a _gaq object and alters the push prototype for the object to have it make requests to the Google Analytics servers once a new call is pushed to the object.

Why isn't this alteration also passed by reference into trackInputs()?

I realise the loaded Google Analytics script will (or will it?) occur after the Tracking.trackInputs() definition, so the browser might not understand it is now an [object object] but will persist in thinking it was the original [object Array]. But, then it is not a reference anymore, is it?

(Using the _gaq object globally (not passing it to the method at all) would solve the problem, but I want to understand why this doesn't work.)

Community
  • 1
  • 1
David Andersson
  • 1,246
  • 2
  • 11
  • 17

4 Answers4

5
jQuery(document).ready(function($) {
  Tracking.trackInputs($, _gaq);
});

Since the DOM is ready before GA is loaded, the reference of _gaq still points to an ordinary array, which is then perpetuated by the local symbol gaObject inside the trackInputs() function.

trackInputs: function ($, gaObject) {

After GA has loaded, the global symbol _gaq is replaced by the tracker, but gaObject still points to the old symbol.

You could use the "ready" feature of GA to call your trackInputs() function instead of using $(document).ready(...):

_gaq.push(function() {
    // when this runs, the tracker would have initialized;
    Tracking.trackInputs($, _gaq);
});
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • And I guess that what you're trying to say is that the reference is lost when ga.js _replaces_ the complete content of the object, right? Because, if you're only pushing new values (or modifying existing keys) to an object in it will modify the original object. – joakimdahlstrom Mar 11 '13 at 13:05
  • @joakimdahlstrom well, the reference is kept in `gaObject` inside the `trackInputs` function :) – Ja͢ck Mar 11 '13 at 13:08
  • Well, yes. But I ment that trackInputs will reference the wrong object when replacing the full object content. Right? – joakimdahlstrom Mar 11 '13 at 13:27
  • @joak if you mean that the "old version" of _gaq is kept inside trackInputs(), then yes. – Ja͢ck Mar 11 '13 at 14:11
3

Google Analytics initially uses a _gaq [object Array]. Passing an array to a function is in JavaScript passing an object, thus by reference.

You initial presumption is incorrect. Javascript always use pass by value. Pass by value. PASS BY VALUE.

you are passing a reference to an object by value. The reference is passed BY VALUE. At the end of the day, you have many references to the same underlying array, since each time a reference is passed by value you have a new copy of the reference, but the object never changes.

Ordinarily what the Google Analytics script does is it changes the _gaq array into a _gaq object and alters the push prototype for the object to have it make requests to the Google Analytics servers once a new call is pushed to the object.

Where does it do that?

However, as can be seen in the comment above, the trackInputs() method prints the 'original' array.

Of course it does.

var _gaq = _gaq || [];
_gaq.push(['_setAccount', _gAAccount]);
_gaq.push(['_trackPageview']);

define and array and put things in it.

jQuery(document).ready(function($) {
  Tracking.trackInputs($, _gaq);
});

pass that array by value into a method

var Tracking = {
  trackInputs: function ($, gaObject) {
    ...
    inputs.bind('change', function () {
      gaObject.push(['_trackPageview', '/virtual/input']);
      ...
    });
  }
};

create a closure around gaObject, which is a reference to the original array, and use it in the change handler. It's not the same reference as _gaq, its a copy of that reference, but it points to the same array.

hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
  • 1
    -1 you are getting at a semantic issue but not attempting to solve the question – anthony sottile Mar 11 '13 at 12:34
  • If I say a "car engine works by nuclear fusion, but I don't understand how that works when I put gas in the car", and you answer "cars don't run on nuclear fusion", there really is no reason to answer the other part of the question is there? – hvgotcodes Mar 11 '13 at 12:41
  • "Where does it do that?" - in ga.js – joakimdahlstrom Mar 11 '13 at 12:54
  • where -- edit the original question to show the prototype manipulation. And even if it does, it doesn't matter...the entire question seems to center around confusion as to how the same array is being used everywhere – hvgotcodes Mar 11 '13 at 13:02
  • To me the question focuses on that there is a difference between modifying existing keys /adding new keys, and replacing the full content as discussed here -> http://stackoverflow.com/a/3638034. Analytics replaces the _gaq content, and this will not affect the already created reference. Am I right? – joakimdahlstrom Mar 11 '13 at 13:31
  • 1
    Hmm it will point to the original array, but when GA changes it to an object, the function with a reference will not automatically get that new object by using the same reference, _it is still the old array_. – joakimdahlstrom Mar 11 '13 at 13:55
  • where is it changed to an object? I have used http://jsbeautifier.org to look at the ga.js code, and can't find anything like that. Anyway, you can't change the type of something dynamically. All you can do is replace a reference to one object with a reference to another object. – hvgotcodes Mar 11 '13 at 14:08
  • @hvgotcodes Thanks for taking your time helping me out. Obviously it is an array from the start. But if you inspect _gaq once the GA script has loaded and treated it, a console.log(_gaq) returns "Y {I: Ja, p: Array[0], Na: function, push: function, Va: function…}" which is an object. – David Andersson Mar 11 '13 at 14:32
  • @hvgotcodes "it points to the same array" which is intuitive, but this array has now turned into an object. Are you saying that the GA script alters the `_gaq` reference, pointing it not to the original array, but to this new object? And that what's in the function closure is a copy of the original reference, which is going to an array, that won't be altered outside the function scope? – David Andersson Mar 11 '13 at 14:37
  • 1
    as @jack says in his answer, its because _gaq is initially an array, while it is an array its closed-in to a function, so pass by value means you will have 2 references to the 1 array, and then after that closure is set up, the original reference is changed to an object. So now you have 2 references pointing to 2 objects, one of which is the array. – hvgotcodes Mar 11 '13 at 16:37
0

At the risk of saying something foolish as I've been staring at your script for about 20 minutes and nothing obvious is jumping out at me... Is it at all possible that it is working as intended however your test-cases are discounted on Analytics as your own IP address is listed under profile filters?

Emissary
  • 9,954
  • 8
  • 54
  • 65
  • Haha, I love the "did you check you do have power?" sanity check. Yes I have dismissed this. It is rather easy to check for net activity and look for requests for __utm.gif. – David Andersson Mar 11 '13 at 12:29
  • @DavidAndersson Fair enough - I'm a little puzzled by it - I've never had to push tracker code beyond the default configuration. What's confusing me is if in the dev. console on my own site I do `console.log(_gaq)` - I get an array of arrays such as that listed in your comments. But if I do the same on stackoverflow for example I get an object and I can't spot the difference in how stackoverflow is loading ga.js? – Emissary Mar 11 '13 at 12:36
  • I'm not even sure if that is relevant or helpful - just an observation. – Emissary Mar 11 '13 at 12:42
0
jQuery(document).ready(function() {
  _gaq.push(function() {
    Tracking.trackInputs($, _gaq);
  });
});
CrayonViolent
  • 32,111
  • 5
  • 56
  • 79