My goal was to create a function for a click event that would count up on each click, and use that counter as an array index in order to increment through an array. Can someone help me understand why the first code block produced the desired result, but the second does not. I have a feeling it has to do with variable scope, but this is my first JavaScript project. Thank you.
var i = 0;
function click(){
if(i < Questions.length-1){
i++;
div.innerHTML = Questions[i].question;
}
}
Does not produce them saves results as this code:
function click(){
for(var i = 0; i < Questions.length-1; i++){
div.innerHTML = Questions[i].question;
}
}
In the first one you have a global i.
This means:
If anything else uses i it will clobber the value used in click, and
After you call click the first time you're using the value of i as set by the last call of click, or whatever modified it outside of click.
In the second one you have an i local only to the click function. It's initialized, used, and discarded when the method completes.
You set the innerHTML to all the questions, and the last one set will be the one you see.
Even though the first one works (at the moment) having a global variable named i is pretty risky, and brittle. You'd be better off encapsulating the question index.
It doesn't have anything to do with the scope.
The first method is correct because, on each click i is incremented once, and the ith question is displayed.
In the second method, on every click you're iterating through all the questions in one go. Hence, in the end only the last question would be printed in the div.
Javascript arrays begin at index 0, so you need to access the array before incrementing your counter. For instance, your first code should look like :
var i = 0;
function click(){
if (i <= Questions.length-1) {
div.innerHTML = Questions[i].question;
i++;
}
}
or even better (more concise) :
var i = 0;
function click(){
if (i <= Questions.length-1) {
div.innerHTML = Questions[i++].question;
}
}
Note the <= instead of = because if your have a 2-sized array, index 1 is good (and the last entry, actually).
Your second code iterates over the whole array at each click (an then displays only the before-last question in array) whereas the first code only increment when the element is clicked on.
Here is an interpretation. Not sure if that is what you're looking for.
The code block has nothing special to do with javascript.
The first block simply increment i once and the second loops through it until array ends.
Here are some excerpts from my Chrome console:
var i=0;
function callMe(){
if (i < arr.length-1){
i++;
console.log(arr[i]);
}
}
arr = ["one", "two", "three", "four"]
function callMe2(){
for (var j=0; j < arr.length; j++){
console.log(arr[j]);
}
}
And then..
callMe()
two
callMe()
three
callMe()
four
callMe()
<blank>
callMe2()
one
two
three
four
Hope this helps!
Related
I have a for loop which will iterate through all choices and and set a value for them.
For some reason, when I run the code it does the first iteration, then skips to the last and does that one 3 times, or so according to the console.
code:
for (i = 0; i < 4; i += 1) {
console.log(i)
var generated = word
while (generated == word) {
generated = wordsJson.characters[Math.floor(Math.random() * wordsJson.characters.length)]
}
choices[i].innerHTML = translate(generated)
}
What I get in console:
0
(3) 3
This is my first time asking something on stackoverflow. If you need more information, please ask.
It appears that the variable i is getting modified outside of the for loop.
Typically you would want to declare your iterator variable (in this case i) so that it's scoped to the loop, which would look like:
for (let i = 0; i < 4; i += 1) { ... }
Note, specifically, the addition of let. Since you haven't done that, it means that either i is already explicitly declared somewhere or, if not, that you've created a new global variable i.
Since you've got code you haven't shown that also seems to relate to i (choices[i]) and methods that we don't know the exact function of (translate()) it's hard to say for certain, but that would be the first place to look.
If not, posting some additional code so we can see the other functionality would be helpful.
In a book that I'm reading (JavaScript & JQuery - Interactive Front End Development by Jon Duckett) there's an interesting error or (at least I think so) which doesn't stop the code from working:
for (var i = [0]; i < options.length; i++) {
addEvent(options[i], 'click', radioChanged);
}
This is a part of script that loops through all radio buttons in a form and attaches an event listener (it doesn't really matter what it does).
But...
Why is i initialised as an array at all?
Why does the incrementation work?
Why does the whole loop work?
Of course if you replace var i = [0] with var i = 0 the code still works.
When you add some alerts to check the value of i in each iteration of the loop and the type of i, at the second iteration type of i changes from object (after all in the first iteration it is an array) to number.
That's a kind of implicit type conversion I have never come across so far (and google don't help much). Can anyone explain what's going on under the hood?
for (var i = [0]; i < options.length; i++) {
addEvent(options[i], 'click', radioChanged);
alert(i); // --> 1 2 3 ...
alert(type of i); // --> object number number ...
}
The spec says (ยง 11.3.1) that the ++ operator converts its operand to a number before incrementing:
Let oldValue be ToNumber(GetValue(lhs)).
When called on an object, the GetValue internal operation will call toString(), which, for an array, will join its elements, returning '0'.
I have never seen a JavaScript loop such as this for( ; i-- ; ), used in the code:
uid: function (len) {
var str = '';
var src = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var src_len = src.length;
var i = len;
for (; i--;) {
str += src.charAt(this.ran_no(0, src_len - 1));
}
return str;
}
I understand the behavior, but I would like it if somebody could share some insights about this type of for loop.
This is a syntax of the for-loop construction:
for ([initialization]; [condition]; [final-expression])
statement
In your case for (; i--;) {:
no variables are initialized, because var i = len; inintialized earlier, so it's not needed.
condition part will be truthy until i becomes 0 then loop will terminate. i-- is executed on before each iteration, and due to -- operator it will eventually become 0, so it's falsy, and loop will stop.
since i is decremented in condition part of the loop, final-expression is not needed too. Another way to put it: since i is not used inside the loop, it does not matter whether we decrement it before each loop iteration or after each loop iteration.
That being said, it's better to avoid writing loops like above, as it's pretty confusing and hard to read. Prefer traditional for-loops notation.
From MDN - for - Optional for expressions:
All three expressions in the head of the for loop are optional.
You don't have to specify all three expressions in for loops. For example, for (;;) is a common wa of writing infinite loop.
In your case, while(i--) would have done the same, there is no good reason to write for (; i--;).
I'd also note that for(var i=len;i>=0;i--) is more robust - it protects you from the case len is negative.
This could be rewritten to
uid: function (len) {
var str = '';
var src = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var src_len = src.length;
var i = len;
while (i >= 0) {
str += src.charAt(this.ran_no(0, src_len - 1));
i = i - 1;
}
return str;
}
The for statement creates a loop that consists of three optional
expressions.
Javascript consider 0 == false that's why in the case you presented the loop will run until the i variable became zero. It will loop as many times as the src string size.
Note: i-- uses the variable value then decrements it. Take a look at the following situation:
for(;i--;) { // i = length in the condition
// i === length - 1 here. It will not overflow the array
}
for(;i--;) { // i = 1
// i === 0 here. It will be the last loop
}
for(;i--;) { // i == 0 == false
// Not executed
}
There is nothing wrong.
for(`INIT`;`TEST`;`ACTION`)
{
`WORK`;
}
The INIT (initialization) can be done outside the loop.
var i=0;
for(;i<=100;i++)
//do something
The TEST part yield a result that is either true or false. Now in this case value of i is tested. Until it becomes zero this works.
The ACTION part is generally used to change the loop variable. But you can leave it also or probably add it to the TEST section like it is done here.
Look this examples are going to clear your idea
var i=0;
for( i++; i++; i+=j); //No body
var i=0;
for(;;); //an infinite loop
var i;
for(i=-4;i;i++);//
Even sometimes WORK is placed in ACTION.
Example:
factorial of x
for(var i=1,ans=1;i<=x;ans=ans*(i++));
Which can be written this way also-
var ans=1;
for(var i=1;i<=x;i++)
ans=ans*i;
NOTE: You can write whichever way you want. It doesn't matter as long as you have written the code properly. Get used to this kind of form you will see them a lot.
Though, it is better to write sometimes in compact form , but remember you should keep the readability.
That's just for loop. Before the first semicolon the variable used in the loop is usually declared. But in this case the variable, the variable used in the loop is declared earlier:
var i = len;
^^^
for (; i--;) {...
So there is no need to redeclare.
After the first semicolon, there is a condition for the loop to run (i.e. i<6, i>3, etc). If condition returns false, or 0, the loop is exited. In this case, the loop will be broken out when i is 0. It happens eventually, because -- after i decrements it, and therefore there is no need for the expression, which is the place after the second semicolon is reserved for.
The first parameter of the for loop has already been defined, so the initial comma is there to delimit it's place in the parameter list.
I was just writing two JavaScript functions, one of which took in a long string, looped over it until it hit a space, then called the other function to print the input before the space into the DOM. The first function would then continue on with input after the space, hit a space, call the print function, etc.
In the process, I kept hitting infinite loops, but only if the string contained a space. I couldn't figure out why, since all the looping seemed to be set up properly. I ultimately figured out that my iterator variable was jumping scope out of my second function printMe and back into the first, readAndFeed, and because of the way the functions were set up, it would always come back as a lower number than the terminating value if there was a space involved.
The first function's loop looked like this:
function readAndFeed(content){
var output = "";
var len = content.length;
for(i = 0; i < len; i++)
{
console.log(i+" r and f increment")
if(content[i] == (" "))
{
printMe(output);
output = "";
}
else if(i==len-1){
output += content[i];
printMe(output)
}
else
{
output += content[i]
}
}
}
The second function is printMe(), and it looped over the string, broke it into three bits, looped over each of them separately (not in a nested fashion), and printed them to the DOM. I used similar loops in it, and I also used i as an iterator.
This would loop over strings with no spaces just fine, but if I threw a space in there, the browser would crash. I tried a bunch of different stuff, but ultimately (by logging the iterator values) realized something was up with i. What worked was changing the i in the printMe function to a j.
I'm confused; this doesn't seem like how I understand variable scope. The functions are defined separately, so it seems like the iterators should be local to those functions and not able to jump out of one into the other.
Here's a jsfiddle
Uncomment the "is an example" part at the bottom to crash your browser. Again, changing the i variables to j in the printMe function completely solved this, but whaaa?
When you don't declare a variable, it is implicitly global. Since you've not declared your loop iteration index i, it is global. If you do that in multiple functions, those globals will collide and one function will accidentally modify the other's variable.
The solution is to make SURE your local variables are declared with var as in:
for (var i = 0; i < len; i++) {
In your case, you need to fix both readAndFeed() and printMe() as they both have the same issue and thus they both try to use the global i. When you call one from the other, it trounces the original's use of i. Here's a fixed version of readAndFeed():
function readAndFeed(content) {
var output = "";
var len = content.length;
// add var here before i
for (var i = 0; i < len; i++) {
console.log(i + " r and f increment")
if (content[i] == (" ")) {
printMe(output);
output = "";
} else if (i == len - 1) {
output += content[i];
printMe(output)
} else {
output += content[i]
}
}
}
If you run your Javascript code in strict mode, then trying to use an undeclared variable actually causes an error (rather than implicitly make it a global) so you can't accidentally shoot yourself in the foot like this.
In your example, i is in fact a global variable. Any assignment without var statement to an undeclared variable declares an implicit global.
To make i local, just include var:
for (var i = 0; i < len; i++)
I can't seem to find the answer to this anywhere...
I have a function that needs to set the bg color of two tables. Only the first table in the function is being affected. Is only one for loop allowed in a function? ...or is the 1st for loop maybe never exiting?
I can pretty much work around this by creating multiple functions but I really want to understand why this behaves the way it does!
Thanks!
Here is my simplified code:
function setColor()
{
//This works
var t1rows=document.getElementById("table1").getElementsByTagName("tr");
var x;
for (x in t1rows)
{
t1rows[x].style.backgroundColor='yellow';
}
//this one does not work
var t2rows=document.getElementById("table2").getElementsByTagName("tr");
var y;
for (y in t2rows)
{
t2rows[y].style.backgroundColor='yellow';
}
}
getElementsByTagName() returns a NodeList object, and for-in will iterate its properties which you don't want. You want to use a straight for loop:
for(var i=0; i < t2rows.length; i++) {
t2rows[i].style.backgroundColor='yellow';
}
You're not getting to the second loop because the first is failing when it tries to access a non-existent style property on one of the NodeList's members.