Opa, só para entender melhor. Para contornar este problema do estouro de pilha eu poderia otimizar a função recursiva, algo como:
function tailRecursiveFat(n, resultado = 1) {
if (n == 0) {
return resultado;
}
return tailRecursiveFat(n - 1, n * resultado);
}
Mas, coloquei ela no site de testes, e para este exemplo que você passou o loop for ainda é mais rápido.
Isso só elimina o estouro de pilha em linguagens que otimizam a recursão em cauda - o que não é o caso do JavaScript, pois ainda sim estoura a pilha, veja: https://ideone.com/n6mFHd
Ah, vale lembrar que o número que faz a pilha estourar pode variar, pois depende de detalhes da implementação. Mas o JavaScript (pelo menos na versão atual) não otimiza recursão em cauda, então para evitar o estouro em todos os casos, só usando loop mesmo.
Obs: parece que o Safari otimiza recursão em cauda, mas os demais pelo jeito não (testei no Node, e também estourou a pilha).