In your first example your giving the V8 compiler lots of hints about what the type is, so there is no boxing / unboxing for it to worry about.
A slightly modified version of the for loop here..
On my machine the for loop is now about 5 times faster than the reduce.
var len = 8e6
function *rands(){
for(let i =0; i < len; i++)
yield Math.random()
}
var add = (a,b) => a + b
var arr = new Float64Array([...rands()])
console.time('reduce')
var sum = arr.reduce(add)
console.log(sum)
console.timeEnd('reduce')
console.time('loop')
var sum = new Float64Array([0]);
for(var i = 0; i < len; i++)
sum[0] += arr[i];
console.log(sum[0])
console.timeEnd('loop')
As you can see sum[0] += arr[i];
, it's now easy for the V8 compiler to know that this calculation is using a Float64 for adding, because both left & right sides have to be a Float64.
When you had -> sum += arr[i]
, the right side V8 knows it's a Float64, but the left side could be anything, it could be an integer, string, or even a Float64, so the V8 has to check if it needs boxing into a Float64.
With reduce, again it's implicit that the left & right sides of the add function are going to be Float64, because the v8 engine is traversing an array of Float64, a & b it knows are going to be Float64.