Tuesday, May 20, 2008

Habemus Array ... unlocked length in IE8, subclassed Array for every browser

History


I do not know how many time, during these years, JavaScript Ninjas tried to subclass the native Array to create libraries over its powerful methods without losing performances. I have finally discovered the way to remove locked length from Internet Explorer 8, and to solve problems with every other browser.

We tried to inherit Array instead of Object


This is where my last trip started, simply looking at arguments behaviour. It was there, since 2000 when I started to code in JavaScript, and it was so simple that probably few developers thought about them!

var o = {
length:0,
push:Array.prototype.push,
toString:Array.prototype.join
};

o.push(1,2,3);
alert(o); // 1,2,3

arguments, in JavaScript, is an instanceof Object, and not an Array, as is in ActionScript since version 1.0
What we have done all this time, is to use Array.prototype methods injecting a basic object, with a simple length parameter, inside.
If an object with a length value can be used as an Array, why on heart above code should not work?

We all love prototypal inheritance, we all want an instanceof Array


If you try to inherit directly an array as prototype, Internet Explorer will fix every instance length property, destroying possibility to use simple for loop over generated values.

function MyArray(){};
MyArray.prototype = [];

var a = new MyArray;
a.push(1,2,3);
alert(a.length); // 0 with every Internet Explorer

Problems are much more than a fixed length, as I wrote many months ago when I presented my ArrayObject.
On the other hand, this kind problem has been fixed for Internet Explorer 8, and 7 emulation.
Yes, finally I did it!

/**
* Choose a name for subclassed Array
*/
Stack = (function(){ // (C) Andrea Giammarchi - Mit Style License

/**
* Your personal Array constructor
*/
function Stack(length){
if(arguments.length === 1 && typeof length === "number")
this.length = -1 < length && length === length << 1 >> 1 ? length : this.push(length);
else if(arguments.length)
this.push.apply(this, arguments);
};

// Solution 1:
// Declaration of generic function
// with an array as prototype
function Array(){};
Array.prototype = [];

// Solution 2:
// use the prototype chain to inherit
// Array constructor and its native prototype
Stack.prototype = new Array;

// Solution 3:
// overwrite inherited length with zero value
Stack.prototype.length = 0;

// Solution 4:
// redeclare toString method in this way
// to let JScript core feel better
Stack.prototype.toString = function(){
return this.slice(0).toString();
};

/**
* Return and assign subclassed Array
*/
Stack.prototype.constructor = Stack;
return Stack;

})();

Above code is the basis to create an alternative Array constructor that will be able to work as expected with every browser plus IE8, without length problems.
If you try to remove a single comma from some Solution, it will never work.
If you directly assign an array to the prototype, length will be fixed.
If you remove toString prototype, FireFox and others will not work as expected.

The definitive workaround for every browser


Since first part of this post could be used in every browser, starting from IE 5.5, these old browser can simply use a constructor with a prototype full of native methods, but without instanceof Array behavior.
At the same time, every other cool browser (Safari, Firefox, Opera) could use above code to have the same behavior of IE8.

This is the full cross browser Stack constructor

While this is an improvement over basic JS 1.5 Array, to have JS 1.7 methods too, natives with updated browsers, emulated in a fast standard way with every other.
Stack Extended JS 1.7 Subclassed Array

The last problem to solve, the concat method


concat, is as simple as truly bastard prototype!
There is no way to use native concat method, even with prototypal chain inherited native Array instances.
This is why I have normalized that method, in a Stack self compatible way.
On the other hand, you cannot send a Stack instance as concat parameter, but you can always use native slice method, fast as native one is.

Best performances ever


Yes, using native, incore, prototypes, makes your code execution faster.
This compatibility + benchmark page, can tell you more about this Stack implementation than me, specially with IE8, Safari, and Opera, where performances are neary the same of a generic Array.

FireFox is probably the one that has more problems to manage native code with dynamic constructors, but hey, I am talking about Firefox beta 3, while probably RC1 or nex release will be fast as Safari, or Opera, are.

What to do with Stack?


Libraries, libraries, and libraries, finally with core performaces, the possibility to truly extend the Array, removing every fake iframe, popup, whathever you have used during these days.

Have fun with Stack, and see you soon for some other cool example with them :geek:

No comments:

Post a Comment