Javascript WTFery

Aug 06, 2008 23:07

I've been doing a bit of Javascript stuff recently.

First, a disclaimer.

I initially had much disdain for JS, what it was, and what it could, and what I thought it could not, do.

Then a few years ago I read this which helped me "get it" and realise how powerful it actually was, while at the same time realising that not all implementations needed to be as mind-numbingly slow as the first ones I used. Moore's Law (well, the corollary to it that says that performance keeps increasing) help with this as well. So I decided that I liked it, even though I still never got around to using it very much.

Recently I have been using it a bit and trying to get it to work in a fairly cross-browser manner. Now I hate it again. Well, I partially hate Javascript, and I partially hate differing partial and often broken implementations of the W3C DOM. I particularly hate IE. As Jan Wolter puts it the rest at least mean well.

Yesterday, I had a bug that took me all day to track down.

It only happened on IE.

I was adding things to an array, looping through the array, and not getting the same stuff back that I had put into it. But only on IE. On other browsers, I got out exactly what I put in.

I searched the net looking for similar problems, and found none.

I searched the discussion archives of a third-party component I was using, and which was the source of the items I was putting into the array which were seemingly coming back wrong. Nothing.

I wrote up a bug report, created a minimal single page test case that only loaded this one third-party component as an external script to attach to the report, got everything ready, tested the test case ... and it worked. I expanded the test case, taking more code from my original design, assuming something from that was throwing things off. Ran the test case ... it still worked. Brought more code in from the original ... still worked. Running out of ideas, I had the test page load a second third-party JS library that was being loaded by my original page, with the fault.

Broken! Yes! Now I could track down the bug some more by myself...

...and to cut a long story short, this other third-party JS library was making sure that Arrays has the lastIndexOf() method, by adding it to Array.prototype when it did not already exist.

Of course, on all modern browsers, Array.lastIndexOf() is built-in. Only IE's JS implementation is so old and crap as to not include it, so it was only on IE that this version was being added to Array.prototype.

And, of course, I was iterating through my array with for...in. Yes, yes, I know (now) that that page has a big warning telling you not to do that. And that the "reintroduction" article that I linked to earlier and had read some years ago also warns against it. I've now learned this lesson about JS properly. It's just one of those things about a new(ish) language that I can't always learn from reading the docs, but need to get spectacularly wrong myself to figure out.

Lesson learned - do not use for...in.

...

But...

What is the other way to iterate through an array? Well, it looks like you should do:

for (var i = 0; i < arr.length; ++i) {
// Do stuff with arr[i]
}
Hmmm...but array's aren't guaranteed to be contiguous. arr[i] for each i might not be defined. So you actually need:

for (var i = 0; i < arr.length; ++i) {
if (arr[i] === undefined)
continue;
// Do stuff with arr[i]
}
Which seems a bit odd in itself. The usage seems very un-array-like to someone coming from a C or C++ background. It's actually a map that has a few helper functions that make it kind of like a proper array, but which break down and introduce horrible metaphor shear under the right (or wrong, depending on how you look at it) conditions.

It seems terribly wasteful to be iterating through a whole bunch of indices that don't exist and doing that comparison for all of them. If you take the array example in the "reintroduction" article where only indices 0, 1, 2 and 100 are assigned to, that's 98 useless iterations for 4 cases you're interested in.

Besides which, the correct way to iterate over a map is to have it tell you what all the key/value pairs are, not guess at the keys. So, really, you should iterate over a JS array with for...in.

Hey, but actually, you can. That's what hasOwnProperty() is for!

Only it's not available in all JS implementations, so you might need an external implementation. (I'm sure I found a better one than that at work, but no idea where it is now...) Still, once you've got it you can do:

for (var i in arr) {
if (!array.hasOwnProperty(i))
continue;
// Do stuff with arr[i]
}

But ... we're still iterating over things we don't care about and ignoring them.

With my C++ background, I look at members of Array.prototype and think of them as members of the Array class, not as members of individual Array objects. I realise that this actually isn't the case. In Javascript the prototype is exactly that, a prototype. When you create a new object, the prototype is copied into the new object before new elements are added. Each object has its own copy of all the "class" members, which can be reassigned to something else. But that's not how I instinctively think of it.

This does bring me, finally, to the WTF.

Who, in the history of using Javascript, has ever found the behaviour of for...in to iterate over unmodified prototype members useful? Really? What possible purpose could it have? Couldn't that purpose be served by iterating over the prototype instead, e.g. for (var i in arr.prototype) ...?

Has anyone found this useful, ever? Have you? Anyone you know? Anyone you've heard of? Or is it just something that gets in the way?

And how do you iterate over JS Arrays? With for..in, ignoring non-hasOwnProperty() members? Or with for (i = 0; i < arr.length; ++i), ignoring indices that are undefined?

programming, geek, rant, javascript, bug

Previous post Next post
Up