Updated based on feedback from @BenAston & @trincot
Roughly, this is what's happening in both cases:
For loop
- Set the index variable to its initial value
- Check whether or not to exit the loop
- Run the body of your loop
- Increment the index variable
- Back to step 2
The only overhead that happens on every iteration is the check & the increment, which are very low-load operations.
forEach loop
- Instantiate the callback function
- Check if there's a next element to process
- Call the callback for the next element to process, with a new execution context (this comprises the "scope" of the function; so its context, arguments, inner variables, and references to any outer variables -- if used)
- Run the contents of your callback
- Teardown of callback function call
- Return to step 2
The overhead of the function setup & teardown in steps 3 & 5 here are much greater than that of incrementing & checking an integer for the vanilla for-loop.
That said, many modern browsers recognize & optimize forEach calls, and in some cases, the forEach might even be faster!