Strange behavior in Javascript enhanced for...in loop - javascript

I am making a Javascript game with the canvas tag, and I am using an enhanced for loop to update the player positions.
In brief:
var actors = new Array();
var player = new Actor(0, 0, img);
actors[0] = player;
function update_positions() {
//position 1
for(var a in actors) {
//position2
a.xpos += a.xvel;
a.ypos += a.yvel;
}
}
Just outside of the for loop at position 1, I can access the correct value of actors[0].xvel. Inside the for loop at position 2, a.xvel is undefined. Can someone explain to me what is happening?

The for...in statement is meant to be used to iterate over object properties, by looking your code seems that actors is an Array (you are setting the initial element with index 0).
This statement will also crawl up the prototype chain, if you have extended the Array.prototype those properties will be iterated, and also the order of iteration is not warranted.
I would recommend you to avoid problems and iterate using a normal for loop:
for (var i = 0; i < actors.length; i++) {
actors[i].xpos += actor.xvel;
actors[i].ypos += actor.yvel;
}
If I'm wrong, and actors is not an Array, I would recommend you to use the hasOwnProperty method, to ensure that the property exists in the object itself, not up somewhere in the prototype chain:
for (var name in object) {
if (object.hasOwnProperty(name)) {
//
}
}

it looks like you're trying to access object properties on the name, not the value here. the index, in this case '0', gets assigned to 'a' in the for/in loop.
what you want to do is access the value of the array member, ie: actors[a].
try this:
for(var a in actors) { // a=0 the first time around the loop,
actor = actors[a]; // same thing as actors[0];
actor.xpos += actor.xvel;
actor.ypos += actor.yvel;
}

The for (x in y) construct iterates through the indexes of an array, not its members.

Try using actors[a].xpos instead of just a.xpos.
See here for more info on JavaScript for-in loops.

Another option is to use the underscore library:
_.each( actors, function(a) {
a.xpos += a.xvel;
a.ypos += a.yvel;
});
or if you don't want to use underscore but are using JQuery anyway, then you can do:
$.each( actors, function(i, a) {
a.xpos += a.xvel;
a.ypos += a.yvel;
});
One nice feature of this functional pattern of iteration is that you can use var to declare variables in the loop that are scoped to the body of the loop, which helps avoid getting bitten by the odd variable scoping rules of JavaScript.

Related

JS For Loop Stopping Early after Calling Function

I have a Javascript Array that holds the contents of a page. In order to draw all the objects in the right places when the page loads I loop over the array and pull out the elements. This worked very well until I allowed the objects to have children within them.
The current array structure is
0-> {elements=[] frame={I keep the frame attributes here with key value pairs}}, 1-> {elements=[] frame={}}
However, I just started adding sub-elements to the elements array in each object. So now I have to loop through/draw each element, check to see if there are any children and if so then I have to draw them too.
The problem I'm having is that after I loop through the first 0 object and it's children the for loop stops running. Is it because I'm calling the same function multiple times? I've done that before so I don't think that's what is happening.
this.run_loop = function (spawn, dom) {
console.log(spawn)
alert(spawn.length)
for (i = 0; i < spawn.length; i++) {
console.log(spawn[i])
//alert("i one")
var newdom = dom + "_" + i;
this.synthesize_elements(spawn[i], dom, newdom)
if (spawn[i].hasOwnProperty('elements')) {
//alert("FOUND")
var newarray = spawn[i]['elements'];
if (newarray.length > 0) {
this.run_loop(newarray, newdom)
}
}
}
}
This is a little old but I encountered a similar issue and found my way here, but I figured it out and thought I'd post the solution if anyone else came across this. The issue in my case (and it looks like in your case as well though I can't be sure) was in the declaration of the for loop:
for (i = 0; i < spawn.length; i++)
You're not declaring that i is a new var. so if anything inside the
this.run_loop(newarray, newdom)
function also manipulates a counter variable called i that isn't declared a new var, it will also change the one in your outer scope, and kick you out of the loop if it goes over the length of spawn
just always declare:
for (var i; i< spawn.length; i++)
in your loops or make sure your counters are unique.

Convert for loop to a while loop in JavaScript

I've been trying to convert the following JS code that uses a for loop to a while loop and/or do-while loop.
var unique = function(array)
{
var newArray = []
array.sort()
for(var x in array) if(array[x] != array[x-1]) newArray.push(array[x])
return newArray
}
The code is suppose to return only distinct names from an array of names that repeat. I've been trying to convert the for loop but so far I've been running into problems using this:
do
{
newArray.push(array[x])
}
while(array[x] != array[x-1])
return newArray;
Can anyone help me? Thanks!
Is it guaranteed that your names are only going to be duplicated in sequence i.e. is it true that if a name does have a duplicate it will be directly after it? If not, then checking the elements that are directly next to each will not find all the duplicates. You'll have to do a nested for loop or some other n^2 algorithm.
var duplicated = false;
for (int x = 0; x < array.length; x++)
{
for (int y = 0; y < array.length; y++)
{
if (array[x] == array[y])
{
duplicated = true;
}
}
if (!duplicated)
{
array.push(array[x]);
}
duplicated = false;
}
return newArray;
please note, this implementation is very poor but it gets the point across.
You're very close. The following preserves the original sequence:
function getUnique(array) {
var newArray = array.slice(); // copy original
var i = newArray.length - 1;
do {
if (newArray[i] == newArray[--i]) {
newArray.splice(i, 1);
}
} while(i)
return newArray;
}
Note that the above assumes a sorted, contiguous array (no missing members). If you can't be sure of that, sort newArray before the do..while loop and maybe compact it to make it contiguous.
A while loop doesn't make sense for your use case. A while loop is good when you want to loop as long as some condition is met and then stop the first time it fails. But a for loop is good when you want to loop through a certain number of items (for instance all of them).
So stick with a for loop. Also a few notes on while loops for whenever you do use them:
A for loop automatically updates the loop index which in your case is x. A while loop doesn't. So, to replicate your for loop, you would need to manually increment x.
Also, you test at the top of the for loop. To mirror that behavior you would want a while instead of a do- while (while tests before executing each loop, do-while tests after).
But if you used a while loop, you'd exit the loop the first time array[x] != array[x-1] failed. And it sounds like you don't want that. You want to push all values that meet that test.

Javascript .pop() and .sort() not working right in a for loop

I'm trying to remove the last element in the array but it ends up putting the removed element back to the top of the list and some of the other elements are listed differently.
Same thing using the .sort() method. The output shows this sentence five times:
Tolstoy wrote War and Peace,Twain wrote Huckleberry Finn
var author_title = new Array(5)
author_title[0]="Tolstoy wrote War and Peace";
author_title[1]="Twain wrote Huckleberry Finn";
author_title[2]="Hardy wrote The Return of the Native";
author_title[3]="Dickens wrote A Christmas Carol";
author_title[4]="Uris wrote Exodus";
for(var count=0;count<author_title.length;count++) {
document.write(author_title.pop() + "<br>");
}
for(var count=0;count<author_title.length;count++) {
document.write(author_title.sort()+"<br>");
}
If you wanted to alter your array then use pop, writing a variable that is equivalent to array.length outside of the for loop, then setting your variable to that declared variable. You have to remember that you are changing your array every time you .pop(), thus changing the for-loop in each iteration. Also, the <br /> is for HTML so you may have just forgotten your self-closing tag on that
var length = array.length;
for( var i = 0; i > length; i++ ) {
document.write( array.pop() + "\n")
}
No part of this should work. for..in loops iterate over the keys for each properly, not the values. It's not meant for iterating over arrays. On my machine, it produces 0,1,2,etc.
You're also attempting to pop elements while iterating over the array. pop modifies the array, so your iterator will be invalidated on each iteration.
What you want is a simple for loop:
for (var i = 0; i < author_title.length; ++i) {
console.log(author_title[i]);
}

Changing values of each object in Javascript

What is the best way to add the additional path information to each javascript object? Like "assets/img/upload/" before each jpg name? That I have url="assets/img/upload/02.jpg" etc.? That would be great help!
My data right now:
[Object { url="02.jpg"},
Object { url="03.jpg"},
Object { url="09.jpg"},
Object { url="04.jpg"},
Object { url="5.jpg"}
...]
A simple for loop:
for(var i = 0; i < array.length; i++)
{
array[i].url = "assets/img/upload/" + array[i].url;
}
Suppose your array of objects is called MyArray:
for (var i = 0, LoopTimes = MyArray.length; i < LoopTimes; i++) {
MyArray[i].url = "assets/img/upload/" + MyArray[i].url;
}
Note that:
a) the opening curly brace goes on the same line as the for statement. See Crokfod on Javascript The Good Parts on youtube for the explanation. In javascript, putting the opening brace on the next line can create some weird bugs that are hard to detect.
b) I cache the length of MyArray in LoopTimes so that I don't have to evaluate the length of the array at every iteration.
If you are only going to be running this code in a modern browser, you could always consider using the map method of the Array object.
Assuming your array of objects is called "array"
array.map(function(item) {return item.url = "assets/img/upload/" + item.url;});
This runs the anonymous function, that takes an "item" in the array, and returns the modified url field, over each element of the array.
If you need your code to run on older browsers, stick with the for loops suggested by the other contributors.

What's the best way to loop through a set of elements in JavaScript?

In the past and with most my current projects I tend to use a for loop like this:
var elements = document.getElementsByTagName('div');
for (var i=0; i<elements.length; i++) {
doSomething(elements[i]);
}
I've heard that using a "reverse while" loop is quicker but I have no real way to confirm this:
var elements = document.getElementsByTagName('div'),
length = elements.length;
while(length--) {
doSomething(elements[length]);
}
What is considered as best practice when it comes to looping though elements in JavaScript, or any array for that matter?
Here's a nice form of a loop I often use. You create the iterated variable from the for statement and you don't need to check the length property, which can be expensive specially when iterating through a NodeList. However, you must be careful, you can't use it if any of the values in array could be "falsy". In practice, I only use it when iterating over an array of objects that does not contain nulls (like a NodeList). But I love its syntactic sugar.
var list = [{a:1,b:2}, {a:3,b:5}, {a:8,b:2}, {a:4,b:1}, {a:0,b:8}];
for (var i=0, item; item = list[i]; i++) {
// Look no need to do list[i] in the body of the loop
console.log("Looping: index ", i, "item" + item);
}
Note that this can also be used to loop backwards.
var list = [{a:1,b:2}, {a:3,b:5}, {a:8,b:2}, {a:4,b:1}, {a:0,b:8}];
for (var i = list.length - 1, item; item = list[i]; i--) {
console.log("Looping: index ", i, "item", item);
}
ES6 Update
for...of gives you the name but not the index, available since ES6
for (const item of list) {
console.log("Looping: index ", "Sorry!!!", "item" + item);
}
Note that in some cases, you need to loop in reverse order (but then you can use i-- too).
For example somebody wanted to use the new getElementsByClassName function to loop on elements of a given class and change this class. He found that only one out of two elements was changed (in FF3).
That's because the function returns a live NodeList, which thus reflects the changes in the Dom tree. Walking the list in reverse order avoided this issue.
var menus = document.getElementsByClassName("style2");
for (var i = menus.length - 1; i >= 0; i--)
{
menus[i].className = "style1";
}
In increasing index progression, when we ask the index 1, FF inspects the Dom and skips the first item with style2, which is the 2nd of the original Dom, thus it returns the 3rd initial item!
I like doing:
var menu = document.getElementsByTagName('div');
for (var i = 0; menu[i]; i++) {
...
}
There is no call to the length of the array on every iteration.
I had a very similar problem earlier with document.getElementsByClassName(). I didn't know what a nodelist was at the time.
var elements = document.getElementsByTagName('div');
for (var i=0; i<elements.length; i++) {
doSomething(elements[i]);
}
My issue was that I expected that elements would be an array, but it isn't. The nodelist Document.getElementsByTagName() returns is iterable, but you can't call array.prototype methods on it.
You can however populate an array with nodelist elements like this:
var myElements = [];
for (var i=0; i<myNodeList.length; i++) {
var element = myNodeList[i];
myElements.push(element);
};
After that you can feel free to call .innerHTML or .style or something on the elements of your array.
At the risk of getting yelled at, i would get a javascript helper library like jquery or prototype they encapsulate the logic in nice methods - both have an .each method/iterator to do it - and they both strive to make it cross-browser compatible
EDIT: This answer was posted in 2008. Today much better constructs exist. This particular case could be solved with a .forEach.
I think using the first form is probably the way to go, since it's probably by far the most common loop structure in the known universe, and since I don't believe the reverse loop saves you any time in reality (still doing an increment/decrement and a comparison on each iteration).
Code that is recognizable and readable to others is definitely a good thing.
I too advise to use the simple way (KISS !-)
-- but some optimization could be found, namely not to test the length of an array more than once:
var elements = document.getElementsByTagName('div');
for (var i=0, im=elements.length; im>i; i++) {
doSomething(elements[i]);
}
Also see my comment on Andrew Hedges' test ...
I just tried to run a test to compare a simple iteration, the optimization I introduced and the reverse do/while, where the elements in an array was tested in every loop.
And alas, no surprise, the three browsers I tested had very different results, though the optimized simple iteration was fastest in all !-)
Test:
An array with 500,000 elements build outside the real test, for every iteration the value of the specific array-element is revealed.
Test run 10 times.
IE6:
Results:
Simple: 984,922,937,984,891,907,906,891,906,906
Average: 923.40 ms.
Optimized: 766,766,844,797,750,750,765,765,766,766
Average: 773.50 ms.
Reverse do/while: 3375,1328,1516,1344,1375,1406,1688,1344,1297,1265
Average: 1593.80 ms. (Note one especially awkward result)
Opera 9.52:
Results:
Simple: 344,343,344,359,343,359,344,359,359,359
Average: 351.30 ms.
Optimized: 281,297,297,297,297,281,281,297,281,281
Average: 289.00 ms
Reverse do/while: 391,407,391,391,500,407,407,406,406,406
Average: 411.20 ms.
FireFox 3.0.1:
Results:
Simple: 278,251,259,245,243,242,259,246,247,256
Average: 252.60 ms.
Optimized: 267,222,223,226,223,230,221,231,224,230
Average: 229.70 ms.
Reverse do/while: 414,381,389,383,388,389,381,387,400,379
Average: 389.10 ms.
Form of loop provided by Juan Mendez is very useful and practical,
I changed it a little bit, so that now it works with - false, null, zero and empty strings too.
var items = [
true,
false,
null,
0,
""
];
for(var i = 0, item; (item = items[i]) !== undefined; i++)
{
console.log("Index: " + i + "; Value: " + item);
}
I know that you don't want to hear that, but: I consider the best practice is the most readable in this case. As long as the loop is not counting from here to the moon, the performance-gain will not be uhge enough.
I know this question is old -- but here's another, extremely simple solution ...
var elements = Array.from(document.querySelectorAll("div"));
Then it can be used like any, standard array.
I prefer the for loop as it's more readable. Looping from length to 0 would be more efficient than looping from 0 to length. And using a reversed while loop is more efficient than a foor loop as you said. I don't have the link to the page with comparison results anymore but I remember that the difference varied on different browsers. For some browser the reversed while loop was twice as fast. However it makes no difference if you're looping "small" arrays. In your example case the length of elements will be "small"
I think you have two alternatives. For dom elements such as jQuery and like frameworks give you a good method of iteration. The second approach is the for loop.
I like to use a TreeWalker if the set of elements are children of a root node.

Categories

Resources