var funcs=[];
for(var i=0;i<3;i++){
funcs[i]=function(){
console.log(i);
};
}
for(var j=0;j<3;j++){
funcs[j]();
}
In this way ,I know it will alert 3 all.Because the functions were called after i was assigned to 3.
But in the below code, I can't understand why this happens.
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 2000);
}
In my oppinion, when i was assigned to 0, the first function setTimeout would be executed before i was assigned to 1.
Am I wrong in the order of this loop?
console.log(i) will be called after the for loop finished, the for loop which because of the way var is function scoped. Will be 10 by the time (2000ms) it reaches the console.log statement. One way to give the expected result is using let instead of var. However you are removing some browser support with using let.
Good reading on this topic would be getify's "You Don't Know Js" book.
for(let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 2000);
}
You need to put the i variable into a closure, otherwise, it will be rewritten in each loop iteration. The same occurs for the setTimeout function, you should put the i variable into a closure as well.
var funcs=[];
for(var i=0;i<3;i++){
(function(i){
funcs[i]=function(){
console.log(i);
};
})(i);
}
for(var j=0;j<3;j++){
funcs[j]();
}
Related
I want to take if condition out of for loop to resolve cyclomatic complexity. How can I take out the break statement out of for loop?
here is the sample code. abc2 was the original function
function abc2() {
for(var i=1; i<8; i++){
if (i == 5) break
console.log(i)
}
}
function abc() {
for(var i=1; i<8; i++) {
aa(i)
console.log(i)
}
}
function aa(i) {
if (i == 5) break // how to use break here
}
<html>
<body>
<button type = "button" onclick = "abc()">Click.</button>
</body>
</html>
You can refactor the aa(i) function to return a boolean. The break statement breaks out of a switch or a loop which is not the case you showed above, hence you're receiving the error.
function abc(){
for(var i=1; i<8; i++){
if (aa(i)){
console.log(i)
break;
}
}
}
function aa(i){
return i == 5;
}
abc();
From a morre abstract perspective, you intend to write a function where you affect the program flow in the calling context.
Other than returning a value from the function or setting some state accessible from the context (ie. producing side effects) this will not work - and rightly so as it violates basic best practice of SW engineering (namely encapsulation and locality) and is guarantueed to entail code hard to reason about and to maintain. Note that many seasoned developers already frown upon side effects.
What you can do is changing the loop bound;
the loop into two or modifying the increment operation:
function abc(){
for(var i=1; i<5; i++){
aa(i);
console.log(i)
}
// Now handle special case i == 5. Higher values of i will not be reached with the original loop definition
}
Maybe you intend to skip i==5in the original loop. You should split the loop into 2:
function abc(){
for(var i=1; i<5; i++){
aa(i);
console.log(i)
}
for(var i=6; i<8; i++){
aa(i);
console.log(i)
}
}
You might want to generalize that into some function if you apply thes pattern frequently.
Alternatively, modify the incerement:
function abc(){
for(var i=1; i<8; i = (i === 4) ? (i+2) : (i+1)){
aa(i);
console.log(i)
}
}
I am not sure if it is possible to take out if because it is a condition and you need to exclude that condition either with an if or some other conditional. Maybe like a do/while.
In case you want only to skip the value of 5, you can use this:
function abc2(){
for(var i=1; i<8;) {
if(i==5){
// don't run any code here, just increment i
i++;
} else {
someFunction() // run whatever code you want
i++; // then increment i
}
}
}
does anyone know why the output of this code is 5 times 5?
for (var i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
The for statement never gets to 5 :S
You need to make a closure in order to preserve the value of the i variable. Otherwise, the i variable will be the value of the last iteration when the setTimeout executes
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function() { console.log(i); }, i * 1000 );
})(i);
}
Take a look into reading about IIFEs
Just modify a little your code, and it works:
for (var i = 0; i < 5; i++) {
setTimeout(function(x) { console.log(x); }, i * 1000, i);
}
setTimeout
Classic example of closure:
Closures are functions that refer to independent (free) variables. In
other words, the function defined in the closure 'remembers' the
environment in which it was created.
Your inner function has access to the environment (in this example with the outer variable i). After the loop has ended the value of i is 5 and your function use this value. of course if you use console.log(i) after the loop will end.
Try this article - Closure - MDN
I've found some example in a tutorial (said it was the canonical example)
for (var i=1; i<=5 ; i++) {
setTimeout(function() {
console.log("i: " + i);
}, i*1000);
}
Now, I understand that, closure passes in the current scope in to the function, and I assume that it should output 1,2,3,4,5. But instead, it prints number 6 five times.
I ran it in the chrome debugger, and first it goes through the loop without going in to the function while doing the increment of the i value and only after that, it goes in to the inner function and execute it 5 times.
I'm not sure why its doing that, I know, the current scoped is passed in to the function because of closure, but why does it not execute each time the loop iterate?
By the time the timeout runs, the for loop has finished, and i is 6, that's why you're getting the output you see. You need to capture i during the loop:
for (var i=1; i<=5 ; i++) {
(function(innerI) {
setTimeout(function() {
console.log("i: " + innerI);
}, innerI*1000);
})(i);
}
This creates an inner function with it's own parameter (innerI), that gets invoked immediately and so captures the value of i for use within the timeout.
If you didn't want the complex-looking IIFE as explained in James' answer, you can also separate out the function using bind:
function count(i) {
console.log("i: " + i);
}
for (var i = 1; i <= 5; i++) {
setTimeout(count.bind(this, i), i * 1000);
}
Thank you for you help,
I found out another solution and it was a minor change.
On the top of the page I turned on the strict mode and also in the for loop, Instead of var, I used the "let" keyword.
I would like to name many setTimeouts differently, so that I can clear each one individually. I tried the following approach:
for(i=0; i<5; i++){
var wordTimeout = i + "_T_i_m_e_o_u_t";
clearTimeout( wordTimeout );
var wordTimeout = setTimeout( function() { console.log( i + ' timeout called' );}, 2000);
console.log(i);
}
http://jsfiddle.net/NU4E8/2/
But it looks like I renamed wordTimeout, and then the variable didn't matter. Ideally, in this example all of the setTimeouts would have been individually cleared after being called. So no timeouts would show up in the log. Thanks.
You can't name your own timeouts. setTimeout and setInterval return a numeric ID, which is what must be passed into clearTimetout. You can, however, use an array or an object to store your timeouts:
var timeoutIds = [];
for(var i=0; i<5; i++){
timeoutIds.push(setTimeout(function(){ /* ... */ }));
}
Or, if you need to name them, you can do it like this:
var timeoutIds = {};
for(var i=0; i<5; i++){
timeoutIds['timeout_' + i] = setTimeout(function(){ /* ... */ });
}
You should also be aware that in your loop, as you've written it, all the timeouts will print '4 timeout called'; that's because the functions are declared in a closure that contains i, and by the time they get around to executing, the loop will have finished, and have the final value 4 (unless it's re-used by other code in the same scope, of course). What you probably want to do is this:
var timeoutIds = [];
for(var i=0; i<5; i++){
timeoutIds.push(
(function(i){
// i now captured as a function-scope variable
return setTimeout(function(){ console.log(i + ' timeout called'); });
})(i)
);
}
This creates an immediately-invoked function expression (IIFE), which "captures" the value of i as a function-scope variable.
You can create an array and store the timer references there using the index as the key like
var timers = [];
for (i = 0; i < 5; i++) {
timers.push(setTimeout(function () {
console.log(i + ' timeout called');
}, 2000));
console.log(i);
}
then access the timer reference for i=0 using timers[0]
You have another common problem known as usage of a closure variable in a loop
Javascript closure inside loops - simple practical example
Creating closures in loops: A common mistake
Create an array where you will store the numbers returned by each of the setTimeout calls.
var timeouts=[];
for(i=0; i<5; i++){
clearTimeout(timeouts[i]);
timeouts[i]=setTimeout((function(){
console.log(this.i+' timeout called');
}).bind({i:i}),2000);
console.log(i);
}
Then, when you need to do the clearing, you will have the timeout numbers stored by i.
The .bind trick is because of i's scope.
Just can't seem to figure this out, how to prevent the outer loop continuing until the inner loop has executed using setTimeout:
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(function(){ $('b'+i).style.width = j+'%';},200);
}
}
This little bit of code is supposed to first make elements t0 visible, and then set the width of another element b0 in 1% steps with a time interval of 200ms, and then continue with t1,b1, t2,b2 etc etc.
But there's no 200ms delay, the whole code executes immediately.
--- EDIT ---
I have not explained very well, here's what I want to do:
1. show element Ax
2. increase element Bx width by 1% every 200ms until 50%
3. wait until element Bx reaches 50% before continuing
4. increment x
5. goto 1
Two problems:
The timeout functions see the wrong i and j values
They all run at the same time (200ms later)
The timeout functions see the wrong i and j values
The main problem is that the function you're passing into setTimeout has an enduring reference to the i and j variables, not copies of them as of when the function was created. That means all of the functions will see the value of i and j as of when they run, which will be 6 and 50 respectively. This is called "closing over" those variables (and the function is called a "closure").
The usual way to fix this is to create the functions in a way that they close over something that doesn't change. There are a couple of ways to do that; my favorite is to use a factory function:
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(makeHandler(i, j), 200);
}
}
function makeHandler(ivalue, jvalue) {
return function(){ $('b'+ivalue).style.width = jvalue+'%';};
}
Now we call makeHandler, and it returnns us a function that closes over ivalue and jvalue, which won't change. Or a refinement of the above which lets us dispose of the maker function when we're done with it:
function $(s) { return document.getElementById(s); }
var i = 0;
var makeHandler = function(ivalue, jvalue) {
return function(){ $('b'+ivalue).style.width = jvalue+'%';};
};
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(makeHandler(i, j), 200);
}
}
makeHandler = undefined;
If you can rely on ES5 features (because of the environment you're targeting, or because you've included an ES5 shim), you can get much the same effect using the new Function#bind. Function#bind creates a new function just like makeHandler does, but there's always the possibility the engine can optimize a bit:
function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
$('t'+i).className = 'show';
cl('t'+i);
for (var j=0; j<50; j++){
cl('b'+i+'='+j+'%');
setTimeout(handler.bind(undefined, i, j), 200);
}
}
function handler(){
$('b'+ivalue).style.width = jvalue+'%';
}
The first argument to bind is what this should be in the function; in our case we don't care, so I've specified undefined (which means the function will get this referencing the global object — window, on browsers — unless this is strict mode code, in which case this will actually be undefined).
They all run at the same time (200ms later)
All of your functions are being scheduled to run 200ms after the above code. And so they will. :-) If you want to space them out, you'll want to increase that 200ms for each call to setTimeout. We can just multiply by i and j:
setTimeout(makeHandler(i, j), (200 * i * j) + 200);
Now the first one will run after 200ms, the second one 200ms later, etc. The whole thing will take about a minute to complete. This assumes you want the first element to grow, then the next, then the next, as opposed to all six of them growing in parallel with each other.
Alternately, you might want to have each function call its successor. That's probably what I'd do. So rather than scheduling 300 function calls, just schedule one and when it happens, schedule the next:
function $(s) { return document.getElementById(s); }
// Our counters are here
var i = 0, j = 0;
// This handles the outer portion of the loop (mostly)
function outer() {
$('t'+i).className = 'show';
cl('t'+i);
j = 0;
// Schedule the first inner portion 200ms from now
setTimeout(inner, 200);
}
// This handles the inner portion of the loop (mostly)
function inner() {
// Do this bit
$('b'+i).style.width = j+'%';
++j;
if (j < 50) {
// Continue the inner loop in 200ms
setTimeout(inner, 200);
}
else {
// Set up next outer loop
j = 0;
++i;
if (i < 6) {
// Not done yet, keep going
setTimeout(outer, 200);
}
}
}
// Kick it off
setTimeout(outer, 200);
In the above, I've also moved these lines:
$('t'+i).className = 'show';
cl('t'+i);
...into the delayed code, which I suspected was appropriate.
More to explore:
Closures are not complicated
It's because you're not closing over the i and j:
setTimeout(function(i, j) {
return function() {
$('b'+i).style.width = j+'%';
}
}(i, j), 200);
setTimeout(function () { alert('Refreshing…'); }, 2000);
It means after two seconds (2000 ms), show alert