After working on a few "classes" tonight (the notion that Javascript is object-oriented still makes me chuckle) I ran into an interesting problem with some of my global-level "constants" defined in the same file that I was working in, that my "class" just so happened to make use of. As I tend to do when I fall into situations like this to where I can't tell if I'm hallucinating or if something with Javascript has gone awry, I called over Sergio (in-house CSS master and Javascript Lvl. 60 Mage).
Some background to how Javascript works
Javascript engines essentially have two "modes" that it runs over your code that you can spot errors in. The first mode, "parsing", is where you'll find syntax errors spewing into the Javascript console. If you've used any interpreted language before (Python, Java, C#, Ruby), this is really just "compilation". Using Python as an example, when you import a module (i.e.
import some_module
) the Python interpreter actually compiles your code into Python byte-code to be executed at a later date. The second mode, "execution", is where you'll run into your run-time errors, using an accessing an undefined object property, overrunning an array index, etc. In Python/Java terms, this is where your compiled byte-code is actually being run in the Python/Java virtual machine.
The gripe
The crux of the problem comes down to two different ways to declare an associative array in Javascript, the following two notations are both correct and both "work":
Notation #1
var mapped_values = {};
mapped_values['key'] = 'value';
Notation #2
var mapped_values = {'key' : 'value'};
Everything looks correct yes? (hint: say yes)
Incorrect, because of the point at which the two are evaluated. The first example will be notated at run-time, whereas the second example will be evaluated at parse/compile-time. Who cares right? The distinction becomes much more apparent when you start to use references to other code for your keys. Keep in mind, with both notations it is actually valid to have a key "undefined" when you declare your associative array. For example:
Notation #1
/* the variable "foo" is not defined */
var mapped_values = {};
mapped_values[foo] = 'value';
Notation #2
/* the variable "foo" is not defined */
var mapped_values = { foo : 'value' };
This still works perfectly fine, both at parse/compile-time and at run-time. Since I mentioned I'm working on OpenSocial, chances are I'm going to need to reference some of the OpenSocial code. So for the next example let's say I need to create an associative array with one of the keys defined by the OpenSocial container, using the two different notations I would write something like:
Notation #1
var mapped_values = {};
mapped_values[opensocial.DataRequest.PeopleRequestFields.FILTER] = opensocial.DataRequest.FilterType.ALL;
Notation #2
var mapped_values = {opensocial.DataRequest.PeopleRequestFields.FILTER : opensocial.DataRequest.FilterType.ALL};
Because of the different points in time at which the two notations above will be evaluated, #1 will properly "compile" and then execute correctly when called (regardless of the scope-level at which it is defined). The second one however, will fail to "compile" when the browser's rendering engine is loading the Javascript (also regardless of the scope-level at which it is defined), and will result in the following error at load-time:
(verified in both IE6 and Firefox)
The issue here is that the variable "opensocial" is not defined at "compile-time" as far as the Javascript engine is concerned (which is fine) but the code attempts to access a property of that object, which causes the error at "compile-time". This will error at at *any* level (as far as I've tried) so it's not a scoping issue, just an unfortunate fact of life with how Javascript is loaded and eventually executed in the browser.
Sergio and I tried a few more examples (the scope of which was in side a function, not at the global level):
// Works in IE/FF
var test = { magic : 'thing' };
var test2 = {};
test2[rick.roll] = 'thing';
test2[alert] = 'somethingelse';
var test3 = {};
test3[function() { alert('sux'); }] = 'test';
// Fail in IE/FF
var test4 ={ opensocial['DataRequest']['PeopleRequestFields']['FILTER'] : opensocial['DataRequest']['FilterType']['ALL']} ;
var test5 = { rick['roll'] : 'thing'};
var test6 = { rick.roll : 'thing'};
var test7 = { function() { alert('sux'); } : 'test'};
Object accessor calls work in "test2" for example because that's going to be evaluated at run-time instead of at compile-time, as is happening in "test5" and "test6". I would love to be proven wrong on our analysis of the issue here (our tests were less than scientific, and there may have been Corona involved) but switching from inline-declarations for associative arrays (
var t = {'k' : 'v'};
) to the more sequential alternative (var t = {}; t['k'] = 'v';
) solved the issue of the Javascript engine's parser spewing errors on the loading of the Javascript.
Food for thought I suppose.