views:

44

answers:

3

I just tried the for...in statement in Javascript.

This gives no error:

var images = document.getElementsByTagName('img');

for(x in images){
    document.write(images[x]) + " ");
}

However, this does what it should but gives an error in the FF error console.

for(x in images){
    images[x].style.visibility="visible";
}

This made me VERY curious as to what's going on.

Doing this:

for(x in images){
    document.write(x);
}

...gave me this:

01234567891011121314151617lengthitemnamedItem

What's there at the end? I assume this makes the document.images / document.getElementsByTagName('img') array not suitable to use with the for...in statement since the values for x at the end won't correspond to an image? Maybe a for loop is better?

+4  A: 

Don't iterate through arrays with for ... in loops. Use an index:

for (var i = 0; i < arr.length; ++i) {
  // images[i] ...
}

The for ... in construct isn't wrong, it's just not what you want to do; it's for when you want to iterate through all properties of an object. Arrays are objects, and there are other properties besides the semantically-interesting indexed elements.

(Actually what comes back from getElementsByTagName isn't really an Array; it's a node list. You can however treat it like an array and it'll generally work OK. The same basic caveat applies for for ... in in any case.)

Pointy
Mostly agree about the counting loop, but sometimes when dealing with sparse arrays, it's useful to use `for..in` -- you just have to know that you're not looping through array indexes but property names and act accordingly. :-)
T.J. Crowder
Well sure - rules are meant to be broken, but probably somebody who's had to come here to ask the question should probably be pretty careful until getting some more experience ...
Pointy
I see.. out of interest, how are the other properties in the array stored? Because I assume length should return the number of properties in the array, and even though there were more properties than index numbers (0-17), the array.length still returned 18 and not more (as would have been expected).
fast-reflexes
@fast-reflexes: `length` is defined as being the value of the highest numeric index plus one; it is not a count of properties on the array. The special handling of `length` is the only really special thing about arrays in JavaScript, in fact. `length` changes if you set a new numerically-named property that's higher than any previous ones, and setting `length` to a lower number automatically deletes the properties with numeric names that fall outside the range 0..length-1 inclusive.
T.J. Crowder
Thanks a lot! Deeper knowledge acquired :)
fast-reflexes
+1  A: 

for..in does not loop through the indexes of an array, it loops through the enumerable property names of an object. It happens that the only enumerable properties array instances have, by default, are array indexes, and so it mostly works to think it does array indexes in limited situations. But that's not what for..in does, and misunderstanding this will bite you. :-) It breaks as soon as you add any further properties to the array (a perfectly valid thing to do) or any library you're using decides to extend the array prototype (also a valid thing to do).

In any case, what you get back from document.getElementsByTagName isn't an array. It's a NodeList. Your best bet for iterating through NodeLists is to use an explicit index a'la Pointy's answer -- e.g., a straight counting loop:

var i;
for (i = 0; i < list.length; ++i) {
    // ... do your thing ...
}

Somewhat off-topic because it doesn't relate to your NodeList, but: When you are actually working with a real array, because arrays in JavaScript are sparse, there is applicability for for..in, you just have to be clear about what you're doing (looping through property names, not indexes). You might want to loop only as many times as the array has actual entries, rather than looping through all the indexes in the gaps in the sparse array. Here's how you do that:

var a, name;
a = [];
a[0] = "zero";
a[10000] = "ten thousand";
for (name in a) {
    // Only process this property name if it's a property of the
    // instance itself (not its prototype), and if the name survives
    // transition to and from a string unchanged -- e.g., it's numeric
    if (a.hasOwnProperty(name) && parseInt(name) == name) {
        alert(a[name]);
    }
}

The above only alerts twice, "zero" and "ten thousand"; whereas a straight counting loop without the checks would alert 10,001 times (mostly saying "undefined" because it's looping through the gap).

T.J. Crowder
Very interesting indeed... would you mind briefly explaining var a,name, a=[] (a is array?)?
fast-reflexes
@fast-reflexes: Yes. `a = [];` is exactly the same as `a = new Array();`. Similarly, `o = {};` is exactly the same as `o = new Object();` It's just a briefer form using literal notation rather than an explicit call to the constructor function.
T.J. Crowder
thanks and "var a, name". variable a of type name?
fast-reflexes
@fast-reflexes: It just declares two variables, `a` and `name`, exactly like `var a; var name;` but in fewer keystrokes. :-) It's probably worth getting a decent intro-to-JavaScript book and working through some exercises, etc. Enjoy!
T.J. Crowder
Thanks mate! Yeah, it's such a creative world full of shortcuts :)
fast-reflexes
Cool, now after a few hours I understand this one... gave me some good insight about how this stuff works.. thanks again :)
fast-reflexes
A: 

The problem with the for ... in construct is that everything from the prototype(s) gets included in the enumeration.

What your output is showing is basically every attribute of images, which is an array object. So you get all the elements in your array (the numbers that appear in your output). The problem is that the functions available on your array are also included, which is why you see length in your output for example. And this is where your code barfs, since a functions does not have a style attribute

So, yes, as Pointy shows, you need to use a for loop to iterate over the elements of an array

Rodrigue
Thanks, I now know a whole lot more about this than expected :)
fast-reflexes