// 1
array.forEach(() => { ... });
// 2
array.map(() => { ... });
// 3
array.reduce(() => { ... });
// 4
for (let i = 0; i < array.length; i++) { ... }
// 5
for (let i in array) { if (!array.hasOwnProperty(i)) continue; ... }
// 6
for (let i of array) { ... }
// 7 - do not try this at home!
let recursiveArrayHandling;
recursiveArrayHandling = (array, i) => {
  i = i || 0;
  const item = array[i];
  ...
  if (i + 1 < array.length) recursiveArrayHandling(array, i + 1);
};
recursiveArrayHandling(array);

I guess you could add a few more...

 

So long as you add return:

  if (i + 1 < array.length) return recursiveArrayHandling(array, i + 1);

You will be fine in Apple browsers :D

 

I knew I forgot something :-)

In any case, you won't be fine if the lenght of the array exceeds the one of the call stack.

Nuh-uh, bronathan! In fact, I assume that the logic / base case of the handling is void-returning.
The return here is purely to opt in to tail call optimization, which means you won't run out of stack :)
Once again, only implemented on Apple browsers at the moment, and probably staying that way.

Your misunderstanding might be resolved if you take the following as an example:

const array = new Array();
array.length = 1/0;
...

An extreme example, I concede, but as an exercise, you can try to figure out the call stack sizes for different JS engines (and AFAIK they're all smaller than the maximum number the 52bit mantissa of a Number can store).

Optimized tail calls don't grow the stack.

They're implemented with a goto, it's basically a loop.


{
  let i = 0
  const rec = () => (i++, rec())
  try {
    rec()
  } catch {}
  console.log(i)
}

On most browsers this will give you a number.
On Safari, Mobile Safari, and some embeddable runtimes like latest Duktape It will be an infinite loop.

 
 

I didn't explain much, just wrote down the first few I could think of. I didn't even say why you should avoid recursion (you might overflow the call stack).

I'd be more wary of someone that does #5 :)
Or 2 or 3 for side effects.

 

Here's one more that doesn't overflow stack :)

const recursiveArrayHandling = (array, i = array.length) => {
  const item = array[--i]
  if (i % 4000) return recursiveArrayHandling(array, i)
  console.log(i, item)
  if (i) return setTimeout(recursiveArrayHandling, 0, array, i)
}
recursiveArrayHandling(new Array(1024 ** 3))

It's full of dumb hacks though, in real life I'd do this:

const recursiveArrayHandling = (array, fn) => {
  const { length } = array
  const innerFunc = (arr, i = 0) => {
    if (i === length) return
    arr[i] = fn(arr[i])
    if (i % 0x80) return innerFunc(arr, i + 1)
    if (i % 0x8000) console.log(i, arr[i]) // TODO: remove
    return setTimeout(innerFunc, 0, arr, i + 1)
  }
  innerFunc(array)
}

recursiveArrayHandling(new Array(1024 ** 3), x => x)

Why only 0x80? To have a lot of headroom, in case the fn is also something recursive.

The real problem here is that a 1024**3 array will quickly use up your memory, even if you start filling it with 0, undefined or even pointers to the same object. It can only exist as sparse.

Classic DEV Post from Dec 31 '18

My 2019 Resolutions as a Developer

The goals I will accomplish in 2019, and some others I probably won't.

Fullstack developer in ReactJs, Symfony4, NodeJs, Javascript, html5, css3, php and AWS.
Join dev.to