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).