0

I'm getting two extra/unexpected array entries that are undefined. These show up when I iterate over an array of element nodes with 3 entries and push the values into another array:

I have a select element with 4 options in it. I want to put the last 3 options.innerText into an array

<select>
  <option>skip me</option>
  <option>text1</option>
  <option>text2</option>
  <option>text3</option>
<select>

I made a variable and grabbed the nodes:

var options = document.querySelectorAll('select option:not(:first-child)')

this gave me an array with 3 option elements in it as confirmed in the console

>>options
<<[<option>text1​</option>​, 
   <option>text2​</option>​, 
   <option>text3​</option>​]

options.length is 3.

iterating over the array proceeds as expected, but also includes a function in the log?

>>for (i in options){console.log(options[i])}
<< <option>text1</option>
   <option>text2</option>
   <option>text3</option>
<< 3
<< function item() {[native code]}

When I iterate over the array and push the innerText into a new array, I get not 3, but 5 entries in the array

>>var texts = [];
>>for (i in options){texts.push(options[i].innerText)}

This gives me an array texts with 5 values: ['text1','text2','text3',undefined,undefined]

texts.length is 5

Why am I getting those two extra array entries?

I'm sure this is something elementary that I just haven't come across yet, can anyone explain?

dan chow
  • 59
  • 2
  • 8

6 Answers6

1

Try iterating through a general for loop.

for (var i = 0; i < options.length; i++) {
    texts.push(options[i].innerText)
}

http://jsfiddle.net/d02urj1n/

Richard Hamilton
  • 25,478
  • 10
  • 60
  • 87
0

Because for (i in options) iterates through the properties of the options object. for/in was made to iterate over enumerable properties.

Array indexes are just enumerable properties with integer names and are otherwise identical to general Object properties. There is no guarantee that for...in will return the indexes in any particular order and it will return all enumerable properties, including those with non–integer names and those that are inherited.

So, while you may find some iterations of forEach or you can roll out your own, it is safer to loop over an array using a normal for loop.

Claudiordgz
  • 3,023
  • 1
  • 21
  • 48
0

If you are using jquery use $.map() like so...

var opts = $("select > option:not(:first-child)");
var texts = [];
$.map(opts, function(val, key){
    texts.push(val.innerText);
});
Aakash
  • 1,751
  • 1
  • 14
  • 21
0

I suggest to use basic JavaScript For Loop, just like following :

for (var i=0 ; i < options.length ; i++){
    console.log(options[i]);
}

//That will give you the expected result
<option>text1</option>
<option>text2</option>
<option>text3</option>

NOTE : Please take a look at Why is using “for…in” with array iteration such a bad idea?.

Hope this helps.

Community
  • 1
  • 1
Zakaria Acharki
  • 66,747
  • 15
  • 75
  • 101
0

Looping through the properties in the object gives you the following items:

"0":      <option>text1</option>
"1":      <option>text2</option>
"2":      <option>text3</option>
"length": 3
"item":   function () {[native code]}

The reason that you get a length and item property also, is that the object is not an array, it's a NodeList object. It works as an array for methods that expect an array because it has a length property and numbered items, so it's what's called an array-like object. The for( in ) loop doesn't expect an array.

Loop through the items as if it was an array, using the length property:

var texts = [];
for (var i = 0; i < options.length; i++){
  texts.push(options[i].innerText);
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
0

Thanks everyone, after some more inspection I did find some injected code objects! The standard for loop worked as expected. I didn't realize that for... in... would bring in inherited properties. Learn something new everyday.

dan chow
  • 59
  • 2
  • 8