9

I need to iterate over an array for which the keys are non-consecutive:

var messages = new Array();
messages[0] = "This is the first message";
messages[3] = "This is another message";

Obviously using the index of a for loop will not work as it depends on the keys being sequential:

for (var i=0 ; i<messages.length ; i++) {
    alert(messages[i]); // Will only alert the first message, as i is never equal to 3
}

What is the canonical way of dealing with this, seeing as the for-each syntax is not intended for iterating over values in an array in javascript? Thanks.

Community
  • 1
  • 1
dotancohen
  • 30,064
  • 36
  • 138
  • 197
  • 1
    How did you end up with such an array in the first place? Instead of looking for some hacks to iterate over it why don't you tackle the problem at its root => which is the way you obtain this array. – Darin Dimitrov Jan 30 '12 at 15:44
  • 1
    Have you considered using an Object instead? `{ "0": "this is the first message", "3": "this is another message"}` – Matt Jan 30 '12 at 15:45
  • 5
    *Will only alert the first message, as i is never equal to 3* this is not correct. `messages.length` will be `4`. – Yoshi Jan 30 '12 at 15:48
  • The array represents object, not simple string literals as in the example. The array keys are the database primary keys. Having the primary keys as another property of the object leads to all kinds of complications as I usually know which object to access by ID, so having the ID as the array key is very convenient. That is, other than this one issue. – dotancohen Jan 30 '12 at 16:36
  • @Yoshi: You are right, the loop must be throwing an exception and quitting when it hits the invalid array key. Either way, the approach won't work! – dotancohen Jan 30 '12 at 16:39

7 Answers7

10

The idiomatic way would be to use an object, not an array. Just be sure to check hasOwnProperty to make sure you don't pick up stray things which may have been added to the prototype.

var messages = { };
messages[0] = "This is the first message";
messages[3] = "This is another message";

for (var i in messages) {
    if (messages.hasOwnProperty(i))
        alert(messages[i]); 
}

Or, the more modern way would be to use Object.keys

Object.keys(messages).forEach(prop => {
    alert(messages[prop]);
});

Be sure to transpile that code with Babel if you plan on running it in older browsers like IE.

Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • 1
    ... squeezing in a `hasOwnProperty()` check would increase the awesomeness of this answer. – Matt Jan 30 '12 at 15:48
  • 1
    Why not call `hasOwnProperty` on the messages directly? `if( messages.hasOwnProperty( i ) ) { alert( messages( i ) ); }` – meouw Feb 17 '12 at 11:24
  • @meouw - it's just an extra-safe, though probably unnecessary precaution to protect you from the slim possibility that messages has defined a property or function named `hasOwnProperty` - `{}.hasOwnProperty.call` ensures that you'll always get the one on object.prototype – Adam Rackis Feb 17 '12 at 15:21
  • 1
    OK - I think it clouds the issue a bit though. You're answer is not as clear as it could be because you are addressing not only the OP's question but another issue as well. – meouw Feb 18 '12 at 23:21
  • Thank you for answering this question, it still confuses me how some of the stack community decide to start asking irrelevant questions or telling your your wrong but fail to give you good reasoning. – Kenziiee Flavius Oct 13 '16 at 11:11
5
for(var i in messages)
{
    console.log(messages[i]);
}
dotancohen
  • 30,064
  • 36
  • 138
  • 197
locrizak
  • 12,192
  • 12
  • 60
  • 80
3

You could ignore the undefined properties...

for (var i=0 ; i<messages.length ; i++) {
    if(messages[i] !== undefined)
        alert(messages[i]);
}

Or use forEach, which will ignore undefined undeclared properties...

messages.forEach(function(v,i) {
    alert(v);
});
  • ohhh - +1 for forEach - didn't know that ignored undefined properties. – Adam Rackis Jan 30 '12 at 15:58
  • 1
    `forEach` is not implemented in IE < 9 though. – Tim Down Jan 30 '12 at 16:00
  • 1
    @Tim - true...I guess I just (usually) have the luxury of ignoring that fact :) – Adam Rackis Jan 30 '12 at 16:51
  • To be clear, `forEach` does not ignore elements with a value of `undefined`. If you say `messages[n] = undefined` and then call `messages.forEach()`, the element at `n` will be processed. Instead, `forEach` skips indexes for which no element exists in the Array. The same logic can be used by testing `if (i in messages)`. – gilly3 Feb 18 '12 at 15:17
  • @gilly3: You're right. Undeclared would have been a better word. –  Feb 18 '12 at 15:40
2

Simple! if the array has regular gaps between the indices do this:

for (var i = 0 ; i < messages.length; i += gap) {
    alert(messages[i]); // Will only alert the messages at the regular interval/gap 
}
  • Though this would work for a gap that would be constant, the OP states that the keys are non-consecutive, which is not the same as having a constant gap. – dotancohen Feb 04 '16 at 12:54
  • 1
    Not the same but includes this case, then it is the code for it, which I clearly stated at the very beginning of my answer. – Mohsen Kadoura Feb 04 '16 at 13:14
1

You can use each() jQuery method to do this.

$.each(messages, function(index, val){
    alert(val); 
});

From jQuery docs

each()

A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, from 0 to length-1. Other objects are iterated via their named properties.

ShankarSangoli
  • 69,612
  • 13
  • 93
  • 124
  • 3
    Is "jQuery" *really* the best solution for simple enumeration? – Matt Jan 30 '12 at 15:47
  • 3
    @ShankarSangoli, and you find that `$.each(messages, function(index, val){` is simpler than `for (var i in messages) {`? I guess we have different definitions of *simplicity*. – Darin Dimitrov Jan 30 '12 at 15:51
  • 1
    I think using the fundamental language construct "`for`" is both **cleaner** and **simpler** than using `jQuery.each`. – Matt Jan 30 '12 at 15:52
  • 1
    Because `jQuery` gives the `index` and `value` seperated as callback arguments, you don't have to maintain variables. If performance is the concern I would definitely go with classic JavaScript way using `for` or `forEach`. – ShankarSangoli Jan 30 '12 at 15:55
1

When you create an array and give it values at 0 and 3, undefined values are created at 1 and 2. try this:

$.each(messages, function(i,val) { 
  if (val) {
    alert(val);
  } 
});
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • 1
    Is "jQuery" *really* the best solution for simple enumeration? – Matt Jan 30 '12 at 15:47
  • That depends on what you consider "best". For someone that is learning javascript and has access to jquery, using the jquery each can avoid other common issues found when using a for loop without knowing how they work. – Kevin B Jan 30 '12 at 15:49
  • If all you look at is "fastest", it would be best to never use a javascript library, right? – Kevin B Jan 30 '12 at 15:49
  • 1
    @Matt - the real answer here is that when you create an array with values at `0` and `3`, the indexes at `1` and `2` are populated and iterated through. His original code will alert all messages. How you iterate over them doesn't matter. – Kevin B Jan 30 '12 at 16:06
  • When you create an array with values at 0 and 3, there are no elements at 1 or 2. It's not that the elements at 1 and 2 contain a value of `undefined`. There simply are no elements at indexes 1 and 2. Try this in a JavaScript console: `var a = [1]; a[3] = 1; console.log(1 in a);` – gilly3 Feb 18 '12 at 15:28
0

For a use case such with the assumptions:

array.length >== 1 (i.e: an array with meaningful data in it already)

Where you are interested in the data from array[1], array[15], array[45] etc

You can do something similar to:

var array = ["you","will","become","strong","with","the","codes","padawan"];
var values = [1,5,7];


for (var i = 0; i < values.length; i++){
    var cypher = values[i];
    console.log(array[cypher]);
}
//will, the, padawan

Or perhaps something more meaningful such as:

for (var i = 0; i < values.length; i++){
    var cypher = values[i];
    aService.aFn.(array[cypher],cb);
}
//calls aService.aFn separately for each value array[1] , array[5] , array[7] passed as args
WlkrShrpe
  • 58
  • 5