Problems with JavaScript "for in" loop - javascript

I have an array of objects which will be the basis for a certain menu in my website. It will be build using JavaScript:
[
{"menuName":"Contact Info","sectionName":"contacts"},
{"menuName":"Facilities","sectionName":"facilities"},
{"menuName":"Locations","sectionName":"locations"},
{"menuName":"Packages","sectionName":"packages"},
{"menuName":"Policies","sectionName":"policies"},
{"menuName":"Reviews","sectionName":"reviews"},
{"menuName":"Rooms","sectionName":"rooms"}
]
So I decided to use the "for in loop" so that I won't have to deal with indexes and lengths. I expect seven items to appear in the menu when it gets built (I'll be using <ul> and <li>).
When I was debugging and accidentally added a background color to the <li>, is when all hell broke loose. I found at least 30 empty <li> after the visible 7th menu <li>.
Why is this happening?
EDIT:
Here's the loop. The loop creates another object for another function to parse later on. (It creates an <li> with an <a> inside with properties provided by the previous array.) I know that the other function works fine because when I change this "for-in" loop to an ordinary for loop, or while loop, it works fine.
this.sectionList = function(menu, id) {
var list = new Array();
for(var i in menu) {
var listItem = {
"element" : "li",
"contains" : [{
"element" : "a",
"attr" : {
"href" : menu[i].sectionName + ':' + id
},
"contains" : menu[i].menuName
}]
}
list.push(listItem);
}
}

for in iterates over object properties. Javascript arrays are just a specific kind of object with some handy properties to help you treat them as just arrays, but they still have internal object properties .. and you don't mean to iterate over these).
So, you shouldn't use for in to iterate over arrays. This only became evident to you when you added your background colour, but it'll always be the case.
Instead, loop with a counter and array .length.

Your object gets methods and properties passed by JavaScript itself. Those are methods that every object gets when its created.
You have to use .hasOwnProperty to find only the properties and methods you assigned to the object!
for(var i in myObject){
if(myObject.hasOwnProperty(i)){
console.log(myObject.i);
}
}
Hope that helps!
Here are two articles that helped me two understand it better:
http://bonsaiden.github.com/JavaScript-Garden/#object.hasownproperty
http://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/

I see no difference between the two ways of iterating your data structure in this jsFiddle: http://jsfiddle.net/jfriend00/HqLdk/.
There are lots of good reasons why you should not use for (x in array) on arrays. The main reason is that that type of iteration iterates over the properties of the object, not just the array elements. Those properties can include other properties of the array if any have been added where as the for (var i = 0; i < array.length; i++) method will never have issues with added properties because by definition, it only iterates over the array elements.
It is somewhat luck that when no additional properties have been added to the array object, that iterating over the properties happens to include just the array elements. The language does not want you to iterate array elements that way. You should iterate arrays with
for (var i = 0; i < array.length; i++).
I understand the seduction of the simpler syntax, but it is not the right way to do it.

Related

How do you iterate over a JavaScript object's properties like an array?

I don't mean using a for loop using the keys.
For example if you had an array like:
var fruitArray = ["Apples", "Bananas", "Coconuts"],
curFruit = 0;
You could "iterate" over that using buttons, say you've got a prev/next button and all those buttons do is decrement/increment the curFruit index.
So if I'm on Bananas, I could get Apples using the prev button using:
fruitArray[0] // Apples
Where the index was at 1 for Bananas but the prev button drops the index by one.
So with an unknown-length object, and not using a numbered-index, how could you do that?
Say it's like this:
var fruitObject = {"Apples":"Red", "Bananas":"Yellow", "Coconuts":"Brown"};
I realize you could have set 0, 1, 2 and the fruit name, but just to show that you don't know what the object key/index might be.
So if I'm on Bananas using
fruitObject.Bananas // yellow
How could I get to Apples using the same decrement idea as the Array?
My question is not about the for in method
I don't know why that's not clear, it was literally the first line in my question.
I think I found a solution anyway though it might not be direct/the most effective way to do it where I'll create a temporary local "look up".
To explain:
Here's an object
var colorObject = {"red":"apple", "blue":"blueberries", "yellow":"banana"};
I don't want to use the for in method to go from left to right complete.
I want to be able to start/keep a current position like I'm at "blue":"berries" and I can jump to "red":"apple" without knowing what "red":"apple" is
My current solution in mind is, yes, though ironically I would use the for in method but to create a look up array.
Something like
var tempArray = [];
for (var key in colorObject) {
tempArray.push(key);
}
Then I can navigate the object starting from anywhere using the increment/decrement method.
tempArray[1] would return "blue" and I could go to "red" by doing tempArray[0] which is interpreted as
colorObject.blue // blueberries
Edit thanks to the editor for making my question more clear
I did poorly word it so that's on me.
This is not possible. JavaScript object properties do not have a defined order, so there is no concept of Y key comes after X key.
Excerpt from EnumerateObjectProperties section of ECMAScript 2017 Draft (ECMA-262) (emphasis added):
The mechanics and order of enumerating the properties is not specified but must conform to the rules specified below.
If you were to iterate over the properties of your fruitObject object, "Bananas" may or may not come before "Coconuts", this behavior simply is not defined by the specification. There does not even appear to be a guarantee the properties will come back in the same order each time you iterate over the properties.
The only way to have a consistent order would be to have an array. Optionally, you could do this by creating an array of the properties via Object.keys.
var fruitObject = {"Apples":"Red", "Bananas":"Yellow", "Coconuts":"Brown"};
// Get the property keys in an ordered list:
var fruitObjectKeys = Object.keys(fruitObject);
// An array of properties with a consistent order:
fruitObjectKeys.forEach(function(prop) {
console.log(prop, '=', fruitObject[prop]);
});
Note however that the JavaScript engine running the code will decide what the order of the resulting array of properties is if you do it this way.
Objects don't have order, so you can't say that the first element of fruitObject is "Apples", the second is "Bananas" and the third is "Cocounts".
You could get the keys as an array with Object.keys() and then do something like
var keys=Object.keys(fruitObject);
fruitObject[keys[0]];
Remember that in JS it's the obj.key is the same than obj["key"].

getElementsByClassName returns duplicate elements

Looking more for an explanation rather than solving a problem. I return 4 items from:
var inputFields = document.getElementsByClassName("calc-input-url");
but when I log the array as output I get this:
[
input#tippedEmployees.form-control.calc-input-url,
input#hourlyWage.form-control.calc-input-url,
input#weeklyTips.form-control.calc-input-url,
input#hoursTippedEmp.form-control.calc-input-url,
tippedEmployees: input#tippedEmployees.form-control.calc-input-url,
hourlyWage: input#hourlyWage.form-control.calc-input-url,
weeklyTips: input#weeklyTips.form-control.calc-input-url,
hoursTippedEmp: input#hoursTippedEmp.form-control.calc-input-url,
item: function…
]
I then logged the length of the array to double-check and only got a return value of 4.
Where do these extra elements in the array come from, and why are they there/how can I make use of them (if at all)?
What you see there is not a regular array, but an HTMLCollection. The fact that the array "has keys" (tippedEmployees:) should be a giveaway that this is no ordinary array. The HTMLCollection exposes the elements in an array-like manner, but also directly via their ids. This is a convenience for you.
In practice there should be no problems, since you're either going to iterate the "array part" of it using for (var i = 0; i < list.length; i++) list[i]..., or you're going to access the elements directly by id.

for..in loop loops over non-numeric indexes “clean” and “remove”

This is something very basic I might be missing here but I haven't seen such result till now.
I have a for loop where options.headers.length is 3. And in for loop I am dynamically creating a table header. Ideally this loop should run three times for 0 1 and 2 but when I have printed index it's printing 0,1,2,clean and remove. I haven't seen clean and remove as indexes. I know this information is not sufficient enough but if you have any clue please suggest. something might be overriding this is all I am concluded too after my debugging.
for (index in options.headers)
if you don't want to iterate clean and remove then change the loop to:
for (var i=0; i< options.headers.length;i++){
//use i for getting the array data
}
if you use for (index in options.headers) it will iterate for non-numeric keys also.
don use just index (as that is = window.index = global = bad) use var index
(read more here https://www.google.pl/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=globals+javascript+bad)
you have to check does the array has it as own property or maybe its some function (more after answer)
for (var index in options.headers) {
if (options.headers.hasOwnProperty(index) {
// code here
}
}
more about #2:
let's say we have
var array = [0,1,2,3];
and besides that, extending array with function (arrays can have functions in javascript and strings too)
Array.prototype.sayHello = function() {
alert('Hello');
};
then your loop would print sayHello as part of the array, but that's not it's own property, only the arrays
I assume that options.headers is an Array?
This happens when you (or some framework you load) adds methods to the Array prototype. The "for in" loop will enumerate also these added methods. Hence you should do the loop for an array with:
for (var i = 0; i < options.headers.length; i++)
That way you will only get the real values instead of added methods.

Prototype object when looping through array?

I'm getting confusing results when looping through an array.
Filling the array looks like
var tables = [];
// ...
// t is table as jQuery object
tables[t.attr('id')] = t;
Later, when looping through the tables array, I'm getting one more element than I've actually added. The program breakes when the other object reaches t.removeClass()
for (t in tables) {
var t = tables[t];
t.removeClass(...);
}
Visual Studio Debugger describes the other object as "clone", which is the first method of the prototype object/property(?).
tables
[prototype]
[Methods]
clone
...
[prototype]
MyTable0
MyTable1
I've read that every javascript object comes with a prototype property but why is prototype here treated as an object?
Note : if your id aren't numbers, then you don't need an array, look at the other answer.
To loop over an array, don't use the for..in construct.
Use
for (var i=0; i<tables.length; i++) {
var t = tables[i];
t.removeClass(...);
}
or if you don't care for IE8 :
tables.forEach(function(t) {
t.removeClass(...);
});
or with jQuery :
$.each(tables, function(_,t){
t.removeClass(...);
});
Side note : it looks like somebody poorly enriched the prototype of the native Array class. It's generally seen as bad practice. Doing so and making the property enumerable is even worse. You should consider dropping or fixing the library you use.
Your declaration for tables should be:
var tables = {};

Javascript collection of DOM objects - why can't I reverse with Array.reverse()?

What could be the problem with reversing the array of DOM objects as in the following code:
var imagesArr = new Array();
imagesArr = document.getElementById("myDivHolderId").getElementsByTagName("img");
imagesArr.reverse();
In Firefox 3, when I call the reverse() method the script stops executing and shows the following error in the console of the Web Developer Toolbar:
imagesArr.reverse is not a function
The imagesArr variable can be iterated through with a for loop and elements like imagesArr[i] can be accessed, so why is it not seen as an array when calling the reverse() method?
Because getElementsByTag name actually returns a NodeList structure. It has similar array like indexing properties for syntactic convenience, but it is not an array. For example, the set of entries is actually constantly being dynamically updated - if you add a new img tag under myDivHolderId, it will automatically appear in imagesArr.
See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-536297177 for more.
getElementsByTag() returns a NodeList instead of an Array. You can convert a NodeList to an Array but note that the array will be another object, so reversing it will not affect the DOM nodes position.
var listNodes = document.getElementById("myDivHolderId").getElementsByTagName("img");
var arrayNodes = Array.slice.call(listNodes, 0);
arrayNodes.reverse();
In order to change the position, you will have to remove the DOM nodes and add them all again at the right position.
Array.prototype.slice.call(arrayLike, 0) is a great way to convert an array-like to an array, but if you are using a JavaScript library, it may actually provide a even better/faster way to do it. For example, jQuery has $.makeArray(arrayLike).
You can also use the Array methods directly on the NodeList:
Array.prototype.reverse.call(listNodes);
this problem can Actually be solved easily with array spread operator.
let elements = document.querySelectorAll('button');
elements = [...elements];
console.log(elements) // Before reverse
elements = elements.reverse(); // Now the reverse function will work
console.log(elements) // After reverse
<html>
<body>
<button>button1</button>
<button>button2</button>
<button>button3</button>
<button>button4</button>
<button>button5</button>
</body>
</html>
getElementsByTag() returns a NodeList instead of an Array. You need to convert the NodeList to an array then reverse it.
var imagesArr = [].slice.call(document.getElementById("myDivHolderId").getElementsByTagName("img"), 0).reverse();
I know this question is old but I think it needs a bit of clarification as some of the answers here are outdated as W3C changed the definition, and consequently the return value of these methods getElementsByTagName() and getElementsByClassName()
These methods as of the time of writing this answer return an object - empty or not - of type HTMLCollection and not NodeList.
It's like the difference between the properties children which returns an object of type HTMLCollection since it's only composed of elements and excluding text or comment nodes, and childNodes which returns an object of type NodeList since it could contain other node types like text and comments as well.
Note: I'd go on tangent here and express my lack of insight on why querySelectorAll() method currently returns a NodeList and not an HTMLCollection since it exclusively works on element nodes in the document and nothing else.
Probably it has something to do with potential coverage of other node types in the future and they went for a more future proof solution, who knows really? :)
EDIT: I think I got the rationale behind this decision to opt for a NodeList and not an HTMLCollection for the querySelectorAll().
Since they constructed HTMLCollection to be exclusively and entirely live and since this method doesn't need this live functionality, they decided for a NodeList implementation instead to best serve its purpose economically and efficiently.
Your first line is irrelevant, since it doesn't coerce the assignment to the variable, javascript works the other way. imagesArr, is not of Type Array(), its of whatever the return type of getElementsByTagName("img") is. In this case, its an HtmlCollection in Firefox 3.
The only methods on this object, are the indexers, and length. In order to work in reverse, just iterate backwards.
This worked for me, I did a reverse for loop and allocated the nodes to an array
var Slides = document.getElementById("slideshow").querySelectorAll('li');
var TempArr = [];
for (var x = Slides.length; x--;) {
TempArr.push(Slides[x]);
}
Slides = TempArr;

Categories

Resources