Как получить почти бесконечное зацикливание без использования циклов и без переполнения стека вызовов:
// Установите 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) - это займёт тысячи лет, и стек никогда не переполнится.
