views:

1152

answers:

6

I used jslint on a javacript file of mine. It threw an error:

for( ind in evtListeners ) {

             Problem at line 41 character 9: The body of a for in should be
             wrapped in an if statement to filter unwanted 
             properties from the prototype.

what does this mean?

A: 

This means that you should filter the properties of evtListeners with the hasOwnProperty method.

Fabien Ménager
+12  A: 

First of all, never use a for in loop to enumerate over an array. Never. Use good old for(var i = 0; i<arr.length; i++).

The reason behind this is the following: each object in JavaScript has a special field called prototype. Everything you add to that field is going to be accessible on every object of that type. Suppose you want all arrays to have a cool new function called filter_0 that will filter zeroes out.

Array.prototype.filter_0 = function() {
    var res = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i] != 0) {
            res.push(this[i]);
        }
    }
    return res;
};

console.log([0, 5, 0, 3, 0, 1, 0].filter_0());
//prints [5,3,1]

This is a standard way to extend objects and add new methods. Lots of libraries do this. However, let's look at how for in works now:

var listeners = ["a", "b", "c"];
for (o in listeners) {
    console.log(o);
}
//prints:
//  0
//  1
//  2
//  filter_0

Do you see? It suddenly thinks filter_0 is another array index. Of course, it is not really a numeric index, but for in enumerates through object fields, not just numeric indexes. So we're now enumerating through every numeric index and filter_0 now. But filter_0 is not a field of any particular array object, every array object has this property now.

Luckily, all objects have a hasOwnProperty method, which checks if this field really belongs to the object itself or if it is simply inherited from the prototype chain and thus belongs to all the objects of that type.

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}
 //prints:
 //  0
 //  1
 //  2

Note, that although this code works as expected for arrays, you should never, never, use for in and for each in for arrays. Remember that for in enumerates the fields of an object, not array indexes or values.

var listeners = ["a", "b", "c"];
listeners.happy = "Happy debugging";

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}

 //prints:
 //  0
 //  1
 //  2
 //  happy
vava
You should not use `for in` to iterate over arrays because the language does not garauntee the order in which `for in` will enumerate over an array. It might not be in numeric order. In addition, if you use the `for(i=0;i<array.length;i++) style construct, you can be sure that you're *only* iterating numeric indexes in order, and no alphanumeric properties.
Breton
thanks for the detailed answer :)
Here Be Wolves
+1  A: 

Douglas Crockford, the author of jslint has written (and spoken) about this issue many times. There's a section on this page of his website which covers this:

for Statement

A for class of statements should have the following form:

for (initialization; condition; update) {
    statements
}

for (variable in object) {
    if (filter) {
        statements
    } 
}

The first form should be used with arrays and with loops of a predeterminable number of iterations.

The second form should be used with objects. Be aware that members that are added to the prototype of the object will be included in the enumeration. It is wise to program defensively by using the hasOwnProperty method to distinguish the true members of the object:

for (variable in object) {
    if (object.hasOwnProperty(variable)) {
        statements
    } 
}

Crockford also has a video series on YUI theater where he talks about this. Crockford's series of videos/talks about javascript are a must see if you're even slightly serious about javascript.

Breton
+1  A: 

Vava's answer is on the mark. If you use jQuery, then the $.each() function takes care of this, hence it is safer to use.

$.each(evtListeners, function(index, elem) {
    // your code
});
HRJ
A: 

Surely it's a little extreme to say

...never use a for in loop to enumerate over an array. Never. Use good old for(var i = 0; i<arr.length; i++)

?

It is worth highlighting the section in the Douglas Crockford extract

...The second form should be used with objects...

If you require an associative array ( aka hashtable / dictionary ) where keys are named instead of numerically indexed, you will have to implement this as an object, e.g. var myAssocArray = {key1: "value1", key2: "value2"...};.

In this case myAssocArray.length will come up null (because this object doesn't have a 'length' property), and your i < myAssocArray.length won't get you very far. In addition to providing greater convenience, I would expect associative arrays to offer performance advantages in many situations, as the array keys can be useful properties (i.e. an array member's ID property or name), meaning you don't have to iterate through a lengthy array repeatedly evaluating if statements to find the array entry you're after.

Anyway, thanks also for the explanation of the JSLint error messages, I will use the 'isOwnProperty' check now when interating through my myriad associative arrays!

tomfumb
You are deeply confused. There is no such thing as "associative arrays" in javascript. That is strictly a php concept.
Breton
A: 

What if you want all the properties? That's invalid JS? How are 'those' properties not the properties you want? Forget about Arrays. If I add properties to the prototype and then run down the properties of an object, I want all the properties (doesn't that makes sense?). Why is that 'invalid' according to JSLint?

Rudie