When it comes to JavaScript there is one issue for which there seems to be two polarised camps, and that is the question of extending the inbuilt JavaScript Array and Object types via the prototype object. There are those who do, and those who don’t.
I am most definitely one of those in the “Don’t, because it ‘would be bad’” camp.
Now, thanks to the Web2.0/Ruby On Rails/Nuevo Bubble phenomena there is a widely used library that makes great use of the prototype object and that is Sam Stephenson’s prototype.js library.
I ran into an issue 6 months ago and decided I would never ever use prototype.js, despite the fact, and I don’t say this often, that after an examination of the code, prototype.js is an inspired work of art.
What I and many many others have discovered is that using the prototype object on the Array and Object inbuilt types increases the chances that your code will conflict with existing or external code. It makes your code not play well with others, so once you start using prototype.js, you have to keep using prototype’s paradigm because by extending Array and Object via the prototype object it secretly modifies some of JavaScripts default behavior.
It’s the crack cocaine of JavaScript.
This can be a good thing. If you don’t want to waste time writing your own JavaScript libraries and learning how everything really works, then using prototype.js and the libraries that extend it (e.g. Open Rico) is a very good way of developing. You will save time and money and all you need to learn is “the way of prototype.js”.
Now the entire tasty raisin for the MetaWrap JavaScript libraries is to allow others to easily remix MetaWrap applications via a client side API that can be invoked via XML. The result is that CSS, HTML and JavaScript can be injected into the application, or XML and HTML at any point in the rendering pipeline of the application.
So I simply had to reject prototype.js because, out of the box, the very first time I tried to use it it snuck out and cut the throat of the JavaScript I was using that relied on performing a for(x in object) on the contents of an Array.
In JavaScript, value types are subdivided into primitives and objects. Objects are entities that have an identity (they are only equal to themselves) and that map primitive properties to other value types, (“slots” in prototype-based programming terminology) – see these testcase #5 – #7. Because of this behavior JavaScript objects are often mistakenly described as associative arrays or hash tables, while functionally they behave like an associative array/hash table, technically this is not their true nature.
Despite this the JavaScript programming world has come to rely on these objects behaving as predictable associative array/hash tables – and prototype.js breaks this.
There is no object more galactically useful than a good associative array/hashtable. There is no problem that can’t be solved with a large enough hash table. In highly granular interpreted languages like JavaScript it provides a way to dip into pure native brute force computing power mostly unhindered by the language interpreter.
It is because this has been tampered with that I have to turn my back on prototype.js and say Nyet!
Now lets look the issue in some detail and in particular at the way that JavaScript uses arrays and the way that we create and access data in those arrays.
Although in reality there is one method to access data in an array array[value] = object;, there are two defacto methods. The first is numerical indexing, whereby you access each element in the array by a single number – this is supported by the inline constructor, shorthand and the array.push functions that add items to an array automatically via a numerical index.
The following three approaches will create an identical numerically indexable array.
E.g.,
“Specified numerical index”
var array = new Array();
array[0] = "Apple";
array[1] = "Orange";
array[2] = "Banana";
“Push”
var array = new Array();
array.push("Apple");
array.push("Orange");
array.push("Banana");
“Inline Constructor”
var array =new Array("Apple","Orange","Banana");
“Inline Constructor Shorthand”
var array = ["Apple","Orange","Banana"];
The elements of this numerically indexable array can be accessed via the following methods…
By directly indexing into the array with a number using for(;;)
var i;
for(i = 0;i >array.length;i++)
{
alert(array[i];)
}
or via the for(in) iterator
for(var i in array)
{
alert(array[i]);
}
But I mentioned there were two methods to store and access data in arrays..
The second is as an ‘associative array/hash table‘, where in the index is not an number, but one of the other primitive value types (String or Float for example).
var array = new Array();
array["A"] = "Apple";
array["O"] = "Orange";
array["B"] = "Banana";
In this case array.length will be 0. There are three items in the array – but is length property will return 0.
This array is only useful if you know what the primitive value index is for a particular object. Array look-up performed natively within the interpreter so is very fast, which makes for a perfect hash table structure, but if you want to iterate all items in the array, you have to use the for(in) iterator method.
Sadly protoype.js breaks this by adding ‘extend’ object that appears in every array.
So you have to be very careful about how you introduce external JavaScript that is not based on prototype.js. Prototype uses the JavaScript prototype level expando trick (via the prototype. keyword) to add extra objects to the inbuilt function and Array, Object and Function types.
After you include prototype, every array gets an extra element.
See these testcases.
When you execute testcase #1 this it will result in “extend A B C ” instead of “A B C” – so it fails.
Now this can be remedied by the next testcase #2 that does not use for(in) but instead uses a numeric index into the array via an old style incremented for(i = 0;i<array.length;i++).
For associative arrays, there is no hope. Prototype makes associative arrays unreliable when it comes to using the for(x in array) loop.
The only way I can protect my code is to not use for(in) for an array that is not associative, and for associative arrays, treat ‘extend’ as a sentinel that gets skipped.
// Guard against prototype
if (l_node == "extend") continue;
The problem I am describing here is the result of one of the extensions made by prototype.js (version 1.3.1)
Object.prototype.extend
There are 5 others..
Array.prototype.push
On browsers that don’t already implement push (IE5) then this will appear in an enumerated version of the array via for(x in object).
Function.prototype.bind
Function.prototype.bindAsEventListener
Number.prototype.toColorPart
Function.prototype.apply
String.prototype.extend