Как получить почти бесконечное зацикливание без использования циклов и без переполнения стека вызовов:
// Установите N = 64, и эта функция никогда не завершится
// Количество вызовов (calls) = 2^(N+1)
// Максимальная глубина вложенности = N
let calls = 0
const N = 18
function func(state, visited) {
calls++
if (calls > 10_000_000) {
throw new Error('calls: ' + calls)
}
if (visited.includes(state)) return
const newVisited = [...visited, state]
func((state + 1) % N, newVisited)
func((state + 1) % N, newVisited)
}
func(0, [])
console.log('calls:', calls)
Почему это работает без переполнения стека?
func(0, [])
├── func(1, [0])
│ ├── func(2, [0,1])
│ │ └── ... глубина растёт до N
│ │ и перебираются все возможные комбинации значений в newVisited
│ └── func(2, [0,1]) - возвращается, глубина УМЕНЬШАЕТСЯ
└── func(1, [0]) - второй вызов, стек уже освободился
А Garbage Collector (GC) при этом бесконечно удаляет созданные ранее массивы newVisited
Стек "дышит" - достигает максимума N, потом сворачивается, потом снова растёт. Это обход огромного дерева, имеющего небольшую глубину, но очень большую ширину. Это не бесконечная рекурсия. Но при N = 64 количество вызовов будет 2^65 (примерно 10^19) - это займёт тысячи лет, и стек никогда не переполнится.
