dean.edwards.name/weblog/2006/11/hooray/

How To Subclass The JavaScript Array Object

It seems easy doesn’t it? Just do this:

// create the constructor
var Array2 = function() {
	// initialise the array
};

// inherit from Array
Array2.prototype = new Array;

// add some sugar
Array2.prototype.each = function(iterator) {
	// iterate
};

No problemo.

Well, there is one problemo. Quite a big problemo in fact. You see, every developer’s favourite browser (Internet Explorer), refuses to maintain the length property of a subclass created like this. I mean point blank refuses:

var list = new Array2;

list.push(123);
alert(list.length); // => 0

list[99] = 789;
alert(list.length); // => 0

Bloody typical.

Ultimately we are left with only two choices:

  1. Extend Array.prototype instead of subclassing
  2. Give up and live with the Array object as it is

Most library authors would love to extend the Array object (especially for those tasty iteration methods) but shy away from doing so for fear of breaking other scripts. So nearly all (with the noteable exception of Prototype) leave Array and other built-in objects alone.

But I’ve been playing with iframes.:-P

Every JavaScript environment (i.e. a browser window or iframe) has its own copy of the core JavaScript objects and classes. What if we “borrow” one of those objects?

// create an <iframe>
var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);

// write a script into the <iframe> and steal its Array object
frames[frames.length - 1].document.write(
	"<script>parent.Array2 = Array;<\/script>"
);

We now have a clean copy of the core Array object (called Array2) but it is not the same object as the one defined in our environment!

alert(Array2 == Array); // => false

We can now extend Array2 without affecting our core Array object:

Array2.prototype.each = function(iterator) {
	// iterate
};

If we want to create iterable arrays we use the Array2 constructor instead of the Array constructor:

var list1 = new Array(1, 2, 3);
list1.each(print); // => ERROR!

var list2 = new Array2(1, 2, 3);
list2.each(print); // => 1, 2, 3

// hooray!

It seems to me that running library code in an iframe is the way forward. You can create global properties and functions and extend the built-in objects, all without affecting code in the main browser window. Libraries and user scripts clashing with each other might be a thing of the past.

Comments (74)

Leave a comment

It’s going to sod up people who think that they know how many frames there are on their page because they designed the page, though. It might be worth removing the created frame from the frames array. Looks to me like you can assign to the frames array’s length after you’ve created it, so you can steal its objects and then remove it from the array. In fact, you can even assign null to frames[frames.length-1] and it still works. So the iframe maybe only needs to exist for long enough for you to steal its objects, and then it can be destroyed, which means that it doesn’t hang around to pollute the page…

  • Comment by: sil
  • Posted:

@sil – I tried removing the iframe but the code choked. I think that the host environment still has to exist for the constructors to work. Maybe just removing it from the array as you suggest is the solution? Not that I think this is a very big problem compared to namespace and prototype pollution.

  • Comment by: -dean
  • Posted:

Very nifty idea.

Wow, that’s ingenious. I wonder if there are any performance / memory leak implications. Really smart and useful trick.

I wonder if there are any performance / memory leak implications?

Maybe. I’ve only just started experimenting with running code in iframes. I was trying to solve a real-world problem with sandboxing JavaScript. After messing with iframes for a bit, a dozen light bulbs went off in my head.

  • Comment by: -dean
  • Posted:

Oh, this is totally brilliant!:-DHave you tried it across a range of browsers and OSes yet to probe its portability yet? You know, this ought to work both ways — similarly, code run inside that frame should be similaryly protected from Prototype poisoning. A big hug to you, Dean! ^_^

Here I used to think all the OO magic of javascript came wrapped in one perverted form of the function object or another, and then you go showing off there’s one hidden in the cooling shadow of a frame. Amusingly beautiful!

Good stuff. There’s one leak I know of with iframes, read about it (warning: shameless plug!) at novemberborn.net/javascript/edgvl.

I’ve heard this specific leak was fixed in IE7, but I haven’t verified this myself.

P.S. If you leave an http colon slash slash inside a real HTML link, the preview will convert it to invalid XHTML.

Brilliant.

  • Comment by: Neil Mix
  • Posted:

Love it. Looks like you can also remove the iframe after you’ve made the Array2 copy and still be able to use Array2 afterwards.

Associative arrays and for..in — here I come.

  • Comment by: adriand
  • Posted:

Excellent, an associative array not considered harmful:

window.onload = function()
{
	Object.prototype.ignore = "Dictionary should ignore this";
	Function.prototype.ignore = "Dictionary should ignore this";

	var iframe = document.createElement("iframe");
	//iframe.style.display = "none"; //safari doesn't seem to know about frames with a display:none
	iframe.height = 0;
	iframe.width = 0;
	iframe.border = 0;
	document.body.appendChild(iframe);

	window.frames[window.frames.length-1].document.write(
		"[script]parent.AArray = function(){return this};[\/script]"
	);

	document.body.removeChild(iframe);

	aa = new AArray();

	Object.prototype.ignore2 = "Dictionary should ignore this too";
	Function.prototype.ignore2 = "Dictionary should ignore this too";
	Array.prototype.each = function(iterator) {
		// iterate
	};

	aa.thing = "1";
	aa.nextThing = "2";
	for (i in aa) alert("aa property: " +i);

	obj = {};
	for (i in obj) alert("obj property: " +i);

	arr = [];
	for (i in arr) alert("arr property: " +i);
}

(You’ll need to adjust the square brackets around the script tags I had to use to post this comment)

  • Comment by: adriand
  • Posted:

How To Subclass The JavaScript Array Object Dean Edwards shows an incredibly clever hack for subclassing the Array object in Javascript.

very clever indeed!

  • Comment by: troelskn
  • Posted:

This is very interesting.

Until now I had been using nested closures (quite irresponsibly) for my library to keep it encapsulated:-

I.e. in very simple terms:-

function MyLibrary()
{
    function closure(source)
    {
         eval(source);  
    }
     this.import = closure;
}
MyLibrary.import("source code for a module")

Your iframe suggestion effectively does this through a separate window.

Have you tested it for performance?

  • Comment by: Julian Turner
  • Posted:

One other thing with this:-

parent.Array2 = Array;

if you are implementing your suggestions using a “.hta” file, then one needs to rember to do the following in IE:-

iframe.application = "yes";

to enable the iframe to access its parent.

  • Comment by: Julian Turner
  • Posted:

Now that’s cool.

  • Comment by: Sam Stephenson
  • Posted:

alert(list2.constructor==Array2); // => Safari false; Firefox true alert(list2.constructor==Array); // => Safari false; Firefox true

Tricky business…

  • Comment by: Doekman
  • Posted:

@Doekman – it’s true I never tested on Safari. I hope that we can hack around it because this is kind of important to library authors.

  • Comment by: -dean
  • Posted:

I’d be interested in a different approach: using an iframe as an “intrinsic” namespace (similar to what’s being implemented in JS2). It’d be useful to have a “clean slate” at your disposal whenever you need it.

@Dean: to generically determine the object-type, I used “object-sniffing” (from a real world used library, see URL):

function Function_getName(o) {
  var a=o.toString().match(/function\s*([a-zA-Z$_][a-zA-Z0-9$_]*)?\(/);
  if(a&&typeof a[1]=='string'&&a[1]!='') return a[1];
  //Method inspection for Safari (with inter-frame javascript the following problems occur:
  //  (Date.constructor!=parent.Date.constructor)
  //  (new Date() instanceOf parent.Date==false)
  //and Safari returns the string "(Internal Function)" for all internal functions:-(
  if(o.prototype.toGMTString) return 'Date'; 
  if(o.prototype.splice) return 'Array';
  if(o.prototype.compile) return 'RegExp';
  if(o.prototype.arguments) return 'Function';
  return null; //anonymous function
}

But maybe it’s better to test each constructor in all frames available. Ie:

for(subframe in all_frames) {
  if(o.constructor==subframe.Date) ...
  • Comment by: Doekman
  • Posted:

[…] How To Subclass The JavaScript Array Object […]

[…] Dean Edwards strikes again. This time he solved a problem with extending built-in objects that tends to break other scripts. It seems this might be utterly useful. […]

In order to use this, don’t you need to wait until onload to append the new iframe? That means your new Array2 won’t be available to any included libraries until the page is loaded, and any libraries wanting to use this approach all have to wait until page load to initialize. Isn’t this a problem?

  • Comment by: Matt Kruse
  • Posted:

A followup to your experiment from Hedger: http://www.hedgerwow.com/360/dhtml/js-array2.html (and you’ll be happy to know he credits you). Interesting addition to your technique in order to avoid waiting for the load event.

A followup to your experiment from Hedger:

http://www.hedgerwow.com/360/dhtml/js-array2.html

Now that’s some good hacking.

  • Comment by: -dean
  • Posted:

you really are java{‘demi’:[God]}script(devil)

  • Comment by: muljono
  • Posted:

In reference to Hedger Wang error hack (btw, very interesting), I think that the declaration of the Array2 variable should precede the call to createPopup()

[…] How To Subclass The JavaScript Array Object http://dean.edwards.name/weblog/2006/11/hooray/ […]

Instead of an iframe, how about experimenting with IE’s XML data islands or with the Document object that is returned with XMLHttpRequest.responseXML.ownerDocument.parentWindow, which may or may not (I don’t know) return a Window object that is separate from the current one.

  • Comment by: Oliver Clevont
  • Posted:

Andrea, the declaration of the Array2 variable is always executed before the code in a popup window. Maybe it’s by design. However, I think it’s more intuitive to put it before the call to createPopup() because it’s a bad practice to depend on any implementation specific behaviors.

  • Comment by: Arrix
  • Posted:

I am interested in finding a way to add methods to the XML Element objects returned by XMLHTTPRequest.

I’m working on the OpenLaszlo runtime (www.openlaszlo.org), our data model is a superset of the DOM API, we have some additional methods which our data nodes support. For IE we are forced to deep copy the entire DOM tree for a data set into a tree of our own data nodes, whereas in Firefox we can just add the new methods to the native XML node class prototype.

Clever idea! However, it appears to me a little bit of waste. To avoid traffic jam, create another universe?

  • Comment by: Linan Wang
  • Posted:

To avoid traffic jam, create another universe?

More like changing lane.

  • Comment by: -dean
  • Posted:

Interesting observation but to me this seems like a lot of potentially buggy hackery for some sugar you want but do not need. In order to get the sugar, reduce your for loops by a couple characters and also decrease performance, how much code will you need to hack around the browser bugs? When a new browser is released and the code breaks then what?

Weren’t we all happy when we could stop using iframes and could start using XMLHttpRequest objects?

you can also use new ActiveXObject(”htmlfile”); in IE instead of an iframe (I know the popup method has been discuss for IE too). A link discussing the ActiveXObject(“htmlfile”) can be found here: http://alex.dojotoolkit.org/?p=538

  • Comment by: JD
  • Posted:

Peter, there are many useful things that can be done with IFRAMES, they where not meant to transport data, but I see you already have come to know they are good also for this…

@Dean, you are on the right lane, this is a good use of IFRAMES, I would add that they were meant for it… They provide a level of abstraction while beeing child of the same window, they are new native ‘scope’ (that may be extended or not).

I repeat myself once more, Web Applications need IFRAMES…and this method adds another reason to use them, also a bit naif it’s better than loosing native Javascript funcionality forever.

Diego

[…] Dean Edwards: How To Subclass The JavaScript Array Object (tags: javascript prototype programming) […]

Dean,
I can actually SCOPE different API this way without one affecting the other and using both of them in the same page/document. I have seen on jQuery docs that some tricks and modifications are needed to load jQuery and Prototype at the same time. I have them both running, and I tested adding Mootools also…

I still haven’t setup any text for it, but you may view the sources in the JsCOPE example.

Unfortunately all these API where not designed to work this way…I should fiddle and adjust some ‘top’, ‘self’ and ‘parent’ references to window and document here and there…but most of it works…I tested it live with JShell bookmarklet.

I don’t think this will be practical for everyday use, but just in case some developer whish to have a peek at it I will post some more examples and documentation as soon as I find more time…

Tested in Mozilla/Firefox and IE, should work on Safari and Konqueror if the API are supported but only minor tests has been done on these browsers. Please report.

I may be missing the point, but why not just do something like this?

function XAArray()
{
	var a = [];
	a.constructor = XAArray;

	for (var i in XAArray.prototype)
	{
		if (!Object.prototype[i])
		{
            a[i] = XAArray.prototype[i];
		}
	}
	return a;
}

XAArray.prototype.newMethod = function()
{
    alert('!');
}

var myArray = new XAArray();
myArray.newMethod();

It seems to me a much more straightforward solution. Of course, you’d probably want to make sure that arguments passed to the XAArray constructor were passed on to the “a” array.

  • Comment by: matthew
  • Posted:

Actually length==0 is not the only problem in IE. The push method doesn’t work either so

x=new Array2();x.push(1) //x[0] returns undefined in IE

And in no browser I tested in does this work:

x=new Array2(1,2,3)

But (iframe approaches aside) I think this will work in all browsers:

// create the constructor
var Array2 = function() {
	// initialise the array
	var x=[], a=arguments;
	for(var i=0;i<a.length;i++){x.push(a[i])};
	for(var i in this){x[i]=this[i]};
	return x;
};

// inherit from Array
Array2.prototype = []

// add some sugar
Array2.prototype.each = function(iterator) {
	// iterate
};

@Thomas/Matthew – two tips:

  1. It is usually a good idea to read the original post before commenting.
  2. It is always a good idea to test sample code before posting.
  • Comment by: -dean
  • Posted:

@Dean: You’re in a happy mode this Saturday;-) My code is tested. You’re post is drifting from the original subject towards iframes, I know. However I chose to comment on the original subject.

@Thomas/Matthew – Apologies. Your posted code does indeed work. I should follow my own advice and read and test things properly! Although your posted code works it is not a true subclass and will certainly perform pretty badly. That said, I still should have tested it before bashing it. Sorry!:-)

  • Comment by: -dean
  • Posted:

@Dean: Apologies accepted!:-D

“certainly perform pretty badly” – please elaborate: have yo observed it perform badly, sir?;-) do you think it would be slower? eat more memory? slow down the upcoming iframe world domination?

@Thomas:

As I mentioned in an email to Dean, the drawback to the method that you and I proposed is that the methods have to be added to each Array instance at the time of instantialization. In normal “subclassing” (or prototype extension) the methods only have to be added once (to the prototype) when the script is loaded.

So, like Dean implied, it’s possible that this could add quite a bit of overhead, depending on the number of methods you’re adding. However, I don’t think it’s as bad as it sounds.

I did a quick test (using FF and IE5.0) and found that creating an instance of Dean’s Array2 is approximately twice as fast in IE5 and six times as fast in FF, when 10 methods are added. (The more methods one adds, the greater the benefit of using Dean’s technique.) Of course, these numbers should be kept in context: creating 1000 XAArrays (with ten extra methods) still only takes ~187ms.

In the end, it’s up to the user to weigh the drawbacks and benefits of each method. My first reaction was to agree with Linan Wang’s comment: the solution posed here seems bigger than the problem. However, I think using an iframe in place of a vanilla object for a namespace is a great idea that shows some real promise. And if you’ve already got the iframe sitting around, why not borrow its Array prototype?

  • Comment by: matthew
  • Posted:

I think using an iframe in place of a vanilla object for a namespace is a great idea that shows some real promise.

Me too.

  • Comment by: -dean
  • Posted:

@Dean and Matthew

I think using an iframe in place of a vanilla object for a namespace is a great idea that shows some real promise.

Oh to h— with false prudence, me too :-D, I proposed it in september 2006 in a comment to my article Object prototype is erlaubt… but I don’t think anyone noticed;-)

Thanks for taking the time to speed test a bit, Matthew!

@Thomas – If you create your own namespace with an iframe then Object.prototype is indeed erlaubt.:-)

  • Comment by: -dean
  • Posted:

[…] A week ago Joe Walker posted a note on the insecurity of JSON. I’m using JSON for almost anything so I was shocked a bit by his findings. Sleepless nights went by and then I thought of a counter hack so one could secure his JSON again. I remembered a post by Dean Edwards (yeah, the packer guy) about subclassing Array objects. In this article I’ll present a way to take over the hacked Array object, and secure it again. […]

[…] How To Subclass The JavaScript Array Object […]

i can’t seem to paste code here so whatever, wangs idea breaks after a small number of times less than 300, throws back [Object Error], shrugs

  • Comment by: Pedram
  • Posted:

Great idea of bending the rules a bit, maybe a bit complex for my taste…

  • Comment by: David
  • Posted:

Just got into it myself and looked for a more “easy” approach. How about the following:

var Array2 = function() {
	var temp = new Array();
	temp.each = function() {
		// iterate
	}
	return temp;
}
  • Comment by: Ofir Herzas
  • Posted:

[…] Another nifty usage of the iframe (others are fake AJAX Uploading, subclassing the Array object, … erm, is there any other?): print only contents of a specified element. I feel a Prototype rewrite coming up Spread the word! […]

I’m afraid every object created as

var obj = new Array2();

as suggests Ofir Herzas will occupy memory with the body of the method “each”. There will be as many “each” methods as are instances of the Array2. With prototype property of the constructor you have the advantage that the body of the method is in memory only once and is shared by all other objects created as instances through that constructor.

  • Comment by: Vlado
  • Posted:

@Vlado – Agreed. Prototype methods are fastest.

  • Comment by: -dean
  • Posted:

If you use an iframe to transport a native constructor this will uses its prototype as expected.

What is less expected is that if you use some native prototype method that return a new instance of the native constructor (i.e. slice) this will be an instanceof Array, and not an instanceof TransportedArray.

This means that you loose all your stuff and you have to convert each time every returned variables.

For this reason, and many others, why do not use directly an ArrayObject?

Ooops, wrong link, I am sorry. The page to visit has this title: Sorry Dean, I subclassed Array again:)

Kind Regards

great idea thanks.

I see that the original article is quite old, so I don’t know if Dean’s opinion on using iframes as a namespace for your own library would still hold.

While this would be a perfect solution for extending the native JavaScript objects, you would have to avoid the use of ‘window’ and ‘document’ in your library code. (Something that you would always encounter when writing a ui-library and working with iframes.)

Also type-checking and instanceof-checking will be more complex.

  • Comment by: Andy
  • Posted:

That is a great way to make your hacks unobtrusive. It has always bothered me the way some libraries like jQuery/Prototype don’t attempt to stay inside their own namespace. “$” anyone?

  • Comment by: Tim
  • Posted:

Doesn’t work on Safari. It probably has global constructors and not document specific ones.

  • Comment by: Orr
  • Posted:

[…] a new object of type Array rather than whatever you gave it. This causes a lot of headaches for a few people who are trying to make subclasses of Array, but it’s very handy in our […]

Hi Dean. Nice article. But i have couple of question.

What’s happen if i iterate over Array2 instance with: for .. in The attached property and method of the Array2 is visible in loop. Just like associative arrays and just like Array.prototype.someProperty.

What is performance of this hack? If i want clean copy of Array, i need to load new JavaScript machine with clean Array.prototype and everyone else object in JavaScript.

Why the article title is “How To Subclass The JavaScript Array Object”? This is not subclass of Array Object. This is clean copy. If i have code like this: Array2 instanceof Array //false This break object oriented concept.

For my more clean is static member of Array. If i want each, i will make: Array.each No confused for me, and my reader. Array.each is more simple. Only disadvantage of this technique is allocation memory for one pointer to array object in arguments of each.

With big respect!

  • Comment by: SmashThePain
  • Posted:

Nevermind my last comment… just ran into the fact that this.pop.self does not behave as expected in a multilevel hash array because it is referring to the prototype and not to the actual array.

  • Comment by: Jonathan Falkner
  • Posted:

[…] einen interessanten Artikel von Dean Edwards gefunden, der genau das macht. Allerdings erzeugt er noch zusätzlich einen unsichtbaren iframe. Da […]

[…] big change to arrays is the ability to properly inherit from the Array type. Dean Edwards has a whole post about trying to create a subtype of Array and the problems he encountered. The biggest problem is […]

At some point several weeks ago I started thinking about building a Collections framework for JS that would mimic the feature set that we have in Java Collections. I started out with a class that used Arrays as storage repositories. I then realized that I was still leaving the door open to unintended copies of this object, getting passed around even if the core data repo was reference based. I found this tutorial and started weeks of experimentation sub-classing Array. Heres what I found,

1. A subclassed Array (obj.prototype = new Array) is many many orders of magnitude slower than obj = Array; that stands to reason.

2. so now I need to get Array as an assignment for the collection. ok… sub frame to extract for all browsers. Not Safari actually.. ok.. window to extract for all browsers.. not for FF 3.6, ok … a combination, carving out the best method for the browser at hand. Let me tell ya, I’ve never had so much frustration as I did that week. When all was said and done:

3. An object that encapsulated the array, was just as fast as the obj=Array, and much faster than prototyping Array. That was a bit supprising at first, not so much once you think about what happens when you call “this” on an object’s prototype (V8 engine is the exception). if the user wanted array functionality for direct iteration over large data sets he/she/i could get it at obj.array.

It’s an interesting idea, in the end though, It’s more trouble than its worth. That is, unless you have to have an object that has the special properties of Array. If that’s the case. obj = Array; is your best bet;-)

[…] popular solution is by Dean Edwards. This one takes a completely different approach — instead of creating an object that inherits […]

[…] of screencasts about the core idea behind FuseJS. I clearly remember the Dean Edwards post that started it. Funny how the reaction to a technical musing could grow to such an impressive project. Interesting […]

Awesome. Hours of googling has made me realize you’re the one person who has ever written about this problem. I can’t believe more people haven’t run into it. Thanks for the writeup, even if it was from 4 years ago.

  • Comment by: David
  • Posted:

Hello Matthew,

I know this post is old, but was hoping you could help me. Is there a way to make your code work with an already existing method? So basically I want to override the push method but still call it before doing the additional functionality I need. I made an example: http://jsbin.com/ilehu5/7/edit

  • Comment by: Koh
  • Posted:

[…] prototype functions to get access to private variables. I have to admit that this idea came from this post from JavaScript extraordinaire, Dean Edwards. Have […]

[…] http://dean.edwards.name/weblog/2006/11/hooray/ […]

@Dean,
consider the following:

  1. frames === window
  2. frames[frames.length – 1] is also a window object, but of a newly created frame

thus your code can be simplified like this:

var iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
var Array2 = frames[frames.length - 1].Array;

which also eliminates the need for Array2 to be a global variable

Comments are closed.