Sunday 30 January 2011

Skipping the protocol

I just found out about this incredibly useful trick for loading absolute resources with either http: or https: depending on the protocol with which your page was loaded (to avoid those "mixed content" complaints from browsers).

Amazingly, you can just leave the scheme (protocol) part off the URL entirely. So for instance, if you're loading jQuery from the Google CDN with this path:

<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js'></script>
you can load it like this instead:
<script src='//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js'></script>
If the page that's in is served via http, that path will become
http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js
If it's https, that will become
https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js
E.g., it's a path relative to the protocol (scheme) but in all other ways absolute.

(Naturally, if you load a file via the file: protocol — e.g., locally — it will try to resolve that locally and blow up. But you don't do that, do you?)

Don't trust it? Neither did I, but I haven't found a browser in which it doesn't work, nor apparently have these guys.

And you thought you knew all there was to know about URIs...

Sunday 23 January 2011

A myth of arrays

When is an array not an array?

When it's a JavaScript array.

(Update: Here I'm talking about JavaScript's standard Array type. Many environments now also support new typed arrays, which are different.)

JavaScript objects are basically just key=value maps, and JavaScript arrays are nothing more than objects that have:

  • special handling for keys that are numeric strings
  • a special length property
  • and some functions they inherit from Array.prototype
That's it. Although we conventionally write array "indexes" as numbers, like all property names they are strings — a[0] is converted to a["0"] by the interpreter (although implementations are free to optimize this as long as the behavior remains as per the spec).

All of this is covered by Section 15.4 of the specification, which starts with this paragraph:

Array objects give special treatment to a certain class of property names. A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 2^32−1. A property whose property name is an array index is also called an element. Every Array object has a length property whose value is always a nonnegative integer less than 2^32. The value of the length property is numerically greater than the name of every property whose name is an array index; whenever a property of an Array object is created or changed, other properties are adjusted as necessary to maintain this invariant. Specifically, whenever a property is added whose name is an array index, the length property is changed, if necessary, to be one more than the numeric value of that array index; and whenever the length property is changed, every property whose name is an array index whose value is not smaller than the new length is automatically deleted. This constraint applies only to own properties of an Array object and is unaffected by length or array index properties that may be inherited from its prototypes.

There are some consequences to this:

  • Barring implementation optimizations, accessing array elements by index is not a constant-time operation as it is in (say) C; like all maps, it will vary depending on the layout of the key storage, the size of the map, and the specific key you're looking up.
  • Barring implementation optimizations, using an array is no more efficient than using a plain object.
  • JavaScript arrays are inherently "sparse" (since the whole concept of a contiguous block of memory just doesn't exist for them anyway).
  • Arrays are inherently one-dimensional. A "2D" JavaScript array is merely an array in which each element is, in turn, an array, and it's important to remember that those sub-arrays may have different lengths (and you may even choose to intermix arrays and non-arrays as values of the outer array's elements).
  • Arrays can have non-index properties, which can be handy. For instance, this is perfectly valid:
    var a = ["zero", "one", "two", "three"];
    a.foo = "bar";
    alert(a.length); // alerts "4"
    alert(a.foo); // alerts "bar"
    This is one reason why it's important to understand for..in loops before using them on arrays (see Myths and realities of for..in).

None of which means that we shouldn't use arrays for ordered lists of things. We should. Doing so is useful semantically, and of course it's entirely possible that an implementation will optimize some of those things above. But it's important to remember what you're dealing with, and taking advantage of an array's non-array nature can be very useful sometimes.

Happy coding,