3

I'm using a JS array to Map IDs to actual elements, i.e. a key-value store. I would like to iterate over all elements. I tried several methods, but all have its caveats:

for (var item in map) {...}

Does iterates over all properties of the array, therefore it will include also functions and extensions to Array.prototype. For example someone dropping in the Prototype library in the future will brake existing code.

var length = map.lenth;
for (var i = 0; i < length; i++) {
  var item = map[i];
  ...
}

does work but just like

$.each(map, function(index, item) {...});

They iterate over the whole range of indexes 0..max(id) which has horrible drawbacks:

var x = [];
x[1]=1;
x[10]=10;
$.each(x, function(i,v) {console.log(i+": "+v);});

0: undefined
1: 1
2: undefined
3: undefined
4: undefined
5: undefined
6: undefined
7: undefined
8: undefined
9: undefined
10: 10

Of course my IDs wont resemble a continuous sequence either. Moreover there can be huge gaps between them so skipping undefined in the latter case is unacceptable for performance reasons. How is it possible to safely iterate over only the defined elements of an array (in a way that works in all browsers and IE)?

3
  • Is the proper numeric order a concern in your code? Commented Jan 12, 2011 at 17:31
  • No, it's an unordered map, I do not care about ordering. My only concern is to iterate over defined elements and only defined elements. Commented Jan 12, 2011 at 17:43
  • 2
    relevant discussion stackoverflow.com/questions/368280/… Commented Jan 12, 2011 at 18:01

7 Answers 7

4

Use hasOwnProperty within for ... in to make sure that prototype additions aren't included:

for (var item in map)
  if (map.hasOwnProperty(item)) {
    // do something
  }
Sign up to request clarification or add additional context in comments.

2 Comments

Awesome. Just would like to add that hasOwnProperty doesn't work in IE7, but I just stopped to care about that thing called IE...
You can save a level using this oneliner: if(!map.hasOwnProperty(item)) continue
4

There are three issues:

  1. You should not use for...in to iterate arrays.
  2. You are using the wrong data type for your requirements.
  3. You are not using for...in correctly.

If you want to have something like a hash table then use a plain object:

var map = {};
map[123] = 'something';
map.foo = 'bar';
// same as map['foo'] = 'bar';
//...

It looks like an array, but it is not. It is an object with property 123. You can use either dot notation obj.key (only if the key is a valid identifier - 123 would not be valid so you have to use the following notation) or array notation obj['key'] to access object properties.

It seems that an object would be a more appropriate data structure.

But even then you should make a call to hasOwnProperty (every time you use for...in):

for(var key in obj) {
    if(obj.hasOwnProperty(key)) {
        //do something
    }
}

This checks whether a property is inherited from the prototype (it will return false then) or is truly an own property.

2 Comments

Actually it's for each...in that's deprecated, not for...in.
@Ian: Where did I say that for...in is deprecated?
3

Use the EcmaScript 5 builtin Object.keys, and on non ES5 browsers, define it thus:

Object.keys = function (o) {
  var keys = [];
  var hasOwnProp = Object.prototype.hasOwnProperty;
  if (Object.prototype.toString.call(o) === '[object Array]') {
    for (var k in o) {
      if (+k === (k & 0x7fffffff) && hasOwnProp.call(o, k)) {
        keys[keys.length] = k;
      }
    }
    keys.sort(keys, function (a, b) { return a - b; });
  } else {
    for (var k in o) {
      if (hasOwnProp.call(o, k)) {
        keys[keys.length] = k;
      }
    }
  }
  return keys;
};

Comments

2

1) use an object like already suggested, it is by far the best solution.

2) if you for some reason need to use an array - don't be scared looping over it with

for(var i, len = arr.length;len < i;i++)

it's very very fast.

3) don't use $.each or similar methods if you want performance - they create a new callstack for every iteration, which is a huge overhead.

6 Comments

What do you mean by using an object?
Use an object as a map to do lookups against: var map = {}; map[id] = true; if(map[id]) { ...}
-1 if(map[id]} shouldn't be used, because it will look to the prototype.
An "object" and an "array" is the same thing is javascript.
@sibidiba: No it is not. An array is an object but an object is not an array. In JS, an array is a numerical array with continuous indices while an object would be more like an associative array or a map. Arrays are defined via [] whereas objects are defined via {}. They are not the same. Run your the last example with var x = {}; instead of var x = []; and you will see the difference.
|
0

Don't use an array. Use an object hash instead

var map = {};
map[key] = value;
...
for (var key in map) {
   do something to map[key]
}

6 Comments

This is exactly what I'm doing. There is no difference. for (var key in map) will return object's/array's member functions too.
@sibidiba: There is a difference. An object literal as used in this answer from @Yads should not have its prototype affected at all, and as such, a for/in would work. If you run into member functions, then it would be because you're using some code that has extended Object.prototype, which is a really bad idea. Since numeric order is not a concern, use an object literal instead of an array literal.
@patrick dw: But that is what sibidiba is referring to. Someone might add the Prototype library, which extends the Object.prototype if I'm not wrong.
@Felix Kling: No, prototypejs doesn't extend Object.prototype. They only use the Object namespace to hold utilities. The object being operated on needs to be passed as the first parameter. From the docs: "Because it is dangerous and invasive to augment Object.prototype (i.e., add instance methods to objects), all these methods are static methods that take an Object as their first parameter." Here's a prototype example using for/in.
@patrick dw: Ah ok I didn't know that (could it be that they used to extend it?). Anyway if your code is used in an environment that you don't control, you have to use hasOwnProperty imo.
|
0

You can't do a lot without actually doing a check to see if the value is undefined and then doing operation a or operation b. It would be better to use a predicate to determine if the value is undefined:

x = $.grep(x, function(v, i) { return (typeof(v) != "undefined"); });

Comments

-1

There isn't. The only way would be to omit the items from the collection completely, any solution you come up with would still have to do a test on each element for the value.

You could come up with different methods of adding the items key/value to object literals or what have you, but you would still need to omit undefined entries if you do not wish to enumerate over them.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.