JavaScript for-loop alternative: repeat(n, function(i) { ... }); - javascript

This is the regular for-loop:
for (var i = 0; i < n; i++) { ... }
It is used to iterate over arrays, but also to just repeat some process n times.
I use the above mentioned form, but it repulses me. The header var i = 0; i < n; i++ is plain ugly and has to be rewritten literally every time it is used.
I am writing this question because I came up with an alternative:
repeat(n, function(i) { ... });
Here we use the repeat function which takes two arguments:
1. the number of iterations,
2. a function which body represents the process that is being repeated.
The "code-behind" would be like so:
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i);
}
}
(I am aware of the performance implications of having two additional "levels" in the scope chain of the process)
BTW, for those of you who use the jQuery library, the above mentioned functionality can be achieved out-of-the-box via the $.each method like so:
$.each(Array(n), function(i) { ... });
So what do you think? Is this repeat function a valid alternative to the native for loop? What are the down-sides of this alternative (other than performance - I know about that)?
Native:
for (var i = 0; i < 10; i++) {
// do stuff
}
Alternative:
repeat(10, function(i) {
// do stuff
});

You say you want a revolution... Well, you know: ruby did it just before (?)
Number.prototype.times = function(func) {
for(var i = 0; i < Number(this); i++) {
func(i);
}
}
means
(50).times(function(i) {
console.log(i)
})
Anyway, don't fight against C, you'll always lose :-P

it's an interesting thought, but if you dislike the syntax for the loop, you could always do a different type of loop:
var i = arr.length;
while (i--) {
// do stuff
}
the reverse while loop is generally faster than a for loop as well.

To address the issue of not having the break statement as others have mentioned, I would solve it this way:
function repeat(n, f) {
for (var i = 0; i < n; i++) {
if (f(i) === false) return;
}
}
Then returning false from within a loop handler will be equivalent to break.
Another disadvantage is that the context changes. You may want to add the option of proxying a context into the loop handlers:
function repeat(context, n, f) {
if (!f) f = n, f = context, context = window;
for (var i = 0; i < n; i++) {
if (f.call(context, i) === false) return;
}
}
Now, an advantage is that the index is preserved by the function scope, to avoid a common bug:
for (var i = 0; i < 10; i++) {
setTimeout(function () {
alert(i); // Will alert "10" every time
}, 1000);
}
repeat(10, function (i) {
setTimeout(function() {
alert(i); // Will alert "0", "1", "2", ...
}, 1000);
});

It seems pretty valid. I honestly don't think that performance would decrease too much. But there is however one big downside, that is easily fixable: the break statement.
function repeat(n, f) {
for (var i = 0; i < n; i++) {
var tcall=i;
tcall.die=function(){i=n}
f.call(tcall);
}
}
This way you would be able to call this.die() instead of break; which I think would throw an error.

Besides what you have already stated the main downside I see is that a "return" statement will work differently. (Which is often why I end up using "for" over "$.each" many times in my own ventures.)

Related

If condition vs loop one item

I was just curious, is it worth to have if condition before looping some array, that in 90% will be array of 1 item?
Code example:
const a = [3];
const aLength = a.length;
if(aLength > 1) {
for(let i = 0; i < aLength; i++) {
func(i);
}
} else {
func();
}
function func(position = 0) {
console.log('hi' + position);
}
I agree with Federico's comment, a single for loop is the most readable in this case.
Also, even though you reuse it, there is not much point to extracting a.length into aLength
const a = [3];
for(let i = 0; i < a.length; i++) {
func(i);
}
function func(position) {
console.log('hi' + position);
}
Warning: very personal perspective down there, you could achieve the same level of clarity with comments too.
Well, unless the single element case has a very specific meaning in your domain. In which case, I would separate them with two functions with very specific names as follows:
const a = [3];
if(a.length > 1) {
handleMultiple(a);
} else {
handleSingleAndWhyItIsASpecialCase(a)
}
handleMultiple(array) {
for(let i = 0; i < array.length; i++) {
func(i);
}
}
handleSingleAndWhyItIsASpecialCase(array) {
func();
}
function func(position = 0) {
console.log('hi' + position);
}
As Hamid said below, you can easily turn it into a oneliner:
[45,63,77].forEach((element, index) => console.log(index));
Consider using forEach instead of map to make your intent clear though.
Write clean code and make everyone happy.
you can eliminate if and loop:
const a=[5,6,3]
a.forEach((value,index)=>console.log('hi'+index));

Why won't my function work when I use splice?

I am trying to write a function which should calculate all prime numbers up to an input parameter and return it. I am doing this for practice.
I wrote this function in a few ways but I was trying to find new ways to do this for more practice and better performance. The last thing I tried was the code below:
function primes(num){
let s = []; // sieve
for(let i = 2; i <= num; i++){
s.push(i);
}
for(let i = 0; i < s.length; i++) {
for(let j = s[i]*s[i]; j <= num;) {
//console.log(j);
if(s.indexOf(j)!= -1){
s.splice(s.indexOf(j), 1, 0);
}
j+=s[i];
}
}
s = s.filter(a => a != 0);
return s;
}
console.log(primes(10));
The problem is that when I run this in a browser it keeps calculating and won't stop and I don't know why.
Note: when I comment out the splice and uncomment console.log(j); everything works as expected and logs are the things they should be but with splice, the browser keep calculating and won't stop.
I am using the latest version of Chrome but I don't think that can have anything to do with the problem.
Your problem lies in this line:
s.splice(s.indexOf(j), 1, 0);
Splice function third argument contains elements to be added in place of the removed elements. Which means that instead of removing elements, you are swapping their values with 0's, which then freezes your j-loop.
To fix it, simply omit third parameter.
function primes(num){
let s = []; // sieve
for(let i = 2; i <= num; i++){
s.push(i);
}
for(let i = 0; i < s.length; i++) {
for(let j = s[i]*s[i]; j <= num;) {
//console.log(j);
if(s.indexOf(j)!= -1){
s.splice(s.indexOf(j), 1);
}
j+=s[i];
}
}
return s;
}
console.log(primes(10));
Your problem is in this loop:
for(let j = s[i]*s[i]; j <= num;)
This for loop is looping forever because j is always less than or equal to num in whatever case you're testing. It is very difficult to determine exactly when this code will start looping infinitely because you are modifying the list as you loop.
In effect though, the splice command will be called setting some portion of the indexes in s to 0 which means that j+=s[i] will no longer get you out of the loop.

Why not declaring a variable as an argument of a function?

I don't see that in any shorthand technique or stackOverflow questions..then I wonder if the following can be a shorthand technique.
Imagine I have a function which I know there is exactly 1 argument that will be passed to it :
function myFunc(arr) {
var i;
for(i = 0; i < arr.length; i++ ) {
if(arr[i] === 3) {return true;}
}
}
Is it a good practice in that case to write :
function myFunc(arr, i) {
for(i = 0; i < arr.length; i++ ) {
if(arr[i] === 3) {return true;}
}
}
I know that in most case we only save 4 bytes and this represent a very small improvement but sometimes for a short function it can be more readable without wasting 1/2 lines just to declare variables.
Edit: also I want to declare i in the scope of the function and not in the for loop sinc I want to be able to reuse it.
You would only do this if you were going to actually use i in the for loop.
e.g:
function myFunc(arr, i) {
for(i; i < arr.length; i++ ) {
arr[i];
}
}
// Why you would do this though, is another matter (and bizarre)
myFunc(anArray, 9);
Instead, it would be better to do:
function myFunc(arr) {
for(var i = 0; i < arr.length; i++ ) {
arr[i];
}
}
and not worry about 4 bytes...

How should I declare for cursor variable in javascript?

As we know, javascript has no BLOCK SCOPE, so when I wrote a for loop like below:
list = [1, 2, 3, 4];
// notice about the `var` keyword
for(var i = 0; i < list.length; ++i) {
// Do something.
}
console.log(i); // shows 4
The matter is: should I use var to declare the variable i?
IF SO:
When I have two or more consecutive for loop, I want to use the same cursor variable i, it will be declare more than once! That simply have problem!
for(var i = 0; i < list1.length; ++i) {
// do something.
}
for(var i = 0; i < list2.length; ++i) {
// do something.
}
// `i` was declared more than once!
In this form, the cursor variable i maybe declared more than once, and the code itself implies that the i variable is likely to have a scope inside the for block, but indeed NOT.
IF NOT SO:
Should I explicitly declare all for cursors earlier in the function?
var i, j, k; // and maybe a long list that I didn't expected?
// Maybe some other code.
for(i = 0; i < count1; ++i) {
// do something
}
for(j = 0; j < count2; ++j) {
// do something
}
for(k = 0; k < count3; ++k) {
// do something
}
If I code this way, I think the code is terrible: it has a long distance between the declaration and use, and obscure about what they are when declared.
If I omit the declarations for them all, and just use, these cursor variables falls into globals! That's much more terrible!
So I'm asking for a good practice, how to declare the cursor variable in this case?
Need your help.
Typically it's simplest to declare the variable, then use it in multiple non-nested loops.
var i;
for(i = 0; i < list1.length; i++) {
// do something.
}
for(i = 0; i < list2.length; i++) {
// do something.
}
There is no problem with reusing i in multiple loops. As soon as the second loop starts, the value is set to the initial value and everything is fine.
Declaring a variable hoists it to the top of the function and successive declarations are syntactically legal but ignored. So:
var i, j, k; // and maybe a long list that I didn't expected?
// Maybe some other code.
for(i = 0; i < count1; ++i) {
// do something
}
...
is what the interpreter does under the hood if you declared it multiple times.
Declaring it in this way (variables on the top) is therefore usually what people suggest (and what JSLint suggests).

JavaScript variable binding and loop

Consider such loop:
for(var it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
The output is:
=> 2
=> 2
I would like it to be: 0, 1. I see two ways to fix it:
Solution # 1.
This one based on the fact that we can pass data to setTimeout.
for(var it = 0; it < 2; it++)
{
setTimeout(function(data) {
alert(data);
}, 1, it);
}
Solution # 2.
function foo(data)
{
setTimeout(function() {
alert(data);
}, 1);
}
for(var it = 0; it < 2; it++)
{
foo(it);
}
Are there any other alternatives?
Not really anything more than the two ways that you have proposed, but here's another
for(var it = 0; it < 2; it++)
{
(function() {
var m = it;
setTimeout(function() {
alert(m);
}, 1);
})();
}
Essentially, you need to capture the variable value in a closure. This method uses an immediately invoked anonymous function to capture the outer variable value it in a local variable m.
Here's a Working Demo to play with. add /edit to the URL to see the code
With the let keyword you can get around this completely:
for(let it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
Similar to above solution but self invoking inside of setTimeout function
for(var it = 0; it < 2; it++)
{
setTimeout(function(cur) {
return function(){
alert(cur);
};
}(it), 1);
}
Similar to the other solutions, but in my opinion cleaner:
for (var it = 0; it < 2; it++) {
// Capture the value of "it" for closure use
(function(it) {
setTimeout(function() {
alert(it);
}, 1);
// End variable captured code
})(it)
}
This keeps the same variable name for the capture, and does it for the entire loop, separating that from the logic of the timeout setup. If you want to add more logic inside the block, you can trivially do that.
The only thing I don't like about the solution is the repeat of "it" at the end.

Categories