Olá leitor.
Às vezes, para resolver o problema, é necessário usar a
recursão , que tem seus prós e contras. Corri para o problema de estouro de pilha.
A profundidade máxima da recursão é limitada pelo mecanismo JavaScript. Você pode contar com precisão com 10.000 chamadas aninhadas, alguns intérpretes permitem mais, mas para a maioria delas, 100.000 chamadas estão além do escopo dos recursos. Existem otimizações automáticas que ajudam a evitar o transbordamento da pilha de chamadas ("otimização da recursão de cauda"), mas elas ainda não são suportadas em todos os lugares e funcionam apenas em casos simples.
Um exemplo de uma função recursiva:
function sum(n) { return n === 0 ? 0 : n + sum(n - 1) } sum(5)
A recursão da cauda permite otimizar as chamadas do compilador e já está no padrão
ES6 , mas o suporte ao navegador
deixa muito a desejar .
Um exemplo de uma função recursiva da cauda:
function sum(number, s = 0){ return number === 0 ? s : sum(number - 1, s + number) }
Mas, sem o suporte do navegador, enfrentaremos o mesmo problema - estouro de pilha. Podemos tentar usar junto com o
trampolim .
Escreveremos um decorador que aceitará a função recursiva modificada (próxima etapa) e a executaremos em um loop, sem aumentar a profundidade da chamada.
function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } }
Agora nossa função recursiva deve retornar uma função que será imediatamente executada pelo decorador. Dessa maneira, criaremos condicionalmente uma matriz de funções e as executaremos sucessivamente. Mas como retornamos uma nova função anônima a cada vez, esse código funcionará um pouco mais devagar.
function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) }
Nós usamos:
const trampolineSum = trampoline(sum) trampolineSum(100000)
Este é o meu primeiro artigo. Tentei não recontar os conceitos e forneci um link para as fontes. O tópico não é exclusivo e existe há muito tempo em sites em inglês, infelizmente não o encontrei em russo.
Obrigado pela atenção. Tenha um bom dia :)
UpdateDesempenho:
# 1 Recursão
Código # 1 function sum(n) { return n === 0 ? 0 : n + sum(n - 1) } console.time("run") sum(1000) console.timeEnd("run")
Resultado nº 1 do Safari 12.1.2Execução [depuração]: 0.353ms
Execução [depuração]: 0.281ms
Execução [depuração]: 0.305ms
Execução [depuração]: 0.315ms
Execução [depuração]: 0.319ms
Execução [depuração]: 0.231ms
Execução [depuração]: 0.255ms
Execução [depuração]: 0.334ms
Execução [depuração]: 0.370ms
Execução [depuração]: 0,274ms
Execução [depuração]: 0.260ms
Resultado # 1 Google Chrome 78.0.3892.0execução: 0.118896484375ms
execução: 0.101806640625ms
execução: 0.099853515625ms
execução: 0.10205078125ms
execução: 0.10302734375ms
execução: 0.099853515625ms
execução: 0.106201171875ms
execução: 0.103759765625ms
execução: 0.105224609375ms
execução: 0.262939453125ms
execução: 0.136962890625ms
execução: 0.10107421875ms
execução: 0.10302734375ms
Iteração nº 2
Código # 2 function sum(n) { let sum = 0 for (let i = 1; i <= n; i++) { sum += i } return sum } console.time("run") sum(1000) console.timeEnd("run")
Resultado nº 2 do Safari 12.1.2Execução [depuração]: 0.562ms
Execução [depuração]: 0.552ms
Execução [depuração]: 0.502ms
Execução [Debug]: 0.527ms
Execução [depuração]: 0.434ms
Execução [depuração]: 0.462ms
Execução [depuração]: 0.511ms
Execução [depuração]: 0.528ms
Execução [depuração]: 0.549ms
Execução [depuração]: 0.475ms
Execução [depuração]: 0.530ms
Execução [depuração]: 0.494ms
Resultado nº 2 do Google Chrome 78.0.3892.0execução: 0.932861328125ms
execução: 0.751953125ms
execução: 0.4580078125ms
execução: 0.678955078125ms
execução: 0.424072265625ms
execução: 0.505859375ms
execução: 0.563720703125ms
execução: 0.404052734375ms
execução: 0.411865234375ms
execução: 0.634033203125ms
execução: 0.4169921875ms
execução: 0.390869140625ms
execução: 0.464111328125ms
# 3 Recursão da cauda e trampolim
Código # 3 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } const trampolineSum = trampoline(sum) console.time("run") trampolineSum(1000) console.timeEnd("run")
Resultado nº 3 do Safari 12.1.2Execução [depuração]: 0.936ms
Execução [depuração]: 0.792ms
Execução [depuração]: 0.882ms
Execução [depuração]: 0.826ms
Execução [depuração]: 0.968ms
Execução [depuração]: 0.818ms
Execução [depuração]: 1.681ms
Execução [depuração]: 0.777ms
Execução [depuração]: 1.109ms
Execução [depuração]: 0.832ms
Execução [depuração]: 0.826ms
Resultado nº 3 do Google Chrome 78.0.3892.0execução: 0.60888671875ms
execução: 0.989990234375ms
execução: 0.567138671875ms
execução: 0.56005859375ms
execução: 1.0087890625ms
execução: 0.5400390625ms
execução: 0.578125ms
execução: 0.541015625ms
execução: 0.557861328125ms
execução: 1.97607421875ms
execução: 0.570068359375ms
execução: 0.593017578125ms
execução: 0.530029296875ms
execução: 0.89794921875ms
execução: 0.590087890625ms
# 4 Recursão da cauda e trampolim (um grande número)
Código # 4 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } const trampolineSum = trampoline(sum) console.time("run") trampolineSum(100000) console.timeEnd("run")
Resultado nº 4 do Safari 12.1.2Execução [depuração]: 33.693ms
Execução [depuração]: 24.564ms
Execução [depuração]: 25.313ms
Execução [depuração]: 23.262ms
Execução [depuração]: 24.848ms
Execução [depuração]: 23.909ms
Execução [depuração]: 24.248ms
Execução [depuração]: 32.416ms
Execução [depuração]: 24.090ms
Execução [depuração]: 23.986ms
Resultado nº 4 do Google Chrome 78.0.3892.0execução: 40.73681640625ms
execução: 33.955078125ms
execução: 40.907958984375ms
execução: 37.693115234375ms
execução: 28.929931640625ms
execução: 30.7548828125ms
execução: 29.720947265625ms
execução: 40.8310546875ms
execução: 31.5830078125ms
execução: 30.712890625ms
execução: 30.162841796875ms
execução: 31.56103515625ms
De acordo com os resultados, vemos que o nosso decorador, embora nos permita evitar o erro de estouro de pilha, mas funciona mais lentamente que a versão recursiva e iterativa. Portanto, esse método deve ser usado apenas se você não puder substituir a recursão pela iteração ou tiver medo de estouros de pilha ao executar sua função recursiva.