O que é Recursão e Como Ela Funciona?

Recursão é um recurso complicado que permite chamar uma função repetidamente até que um caso base seja alcançado. Ao contrário de um loop tradicional, a recursão permite que você lide com algo de profundidade desconhecida, como objetos/arrays profundamente aninhados ou uma árvore de arquivos. Mas você também pode usá-lo para tarefas mais básicas, como contar regressivamente a partir de um número dado. Vamos construir uma função para fazer exatamente isso. Chamaremos nossa função recursiveCountdown e ela precisa aceitar um número. Vamos fazer com que ele imprima este número no console:
const recursiveCountdown = (number) => {
  console.log(number);
};

recursiveCountdown(5);
Agora, se chamarmos isso e passarmos o número 5, veremos o número ser impresso no nosso terminal. Mas nada mais acontece – e o número 5 certamente não é uma contagem regressiva. Antes de começarmos a construir a parte recursiva da nossa função, precisamos estabelecer nosso caso base primeiro. Se você não tiver um caso base estabelecido, seu código continuará executando até exceder a alocação de memória e travar.
const recursiveCountdown = (number) => {
  if (number < 1) {
    return;
  }
  console.log(number);
};

recursiveCountdown(5);
Para o nosso caso base, queremos que a contagem regressiva pare se o número for menor que 1. Quando atingimos esse caso base, podemos retornar para sair da execução da função. Agora que preparamos com segurança um caso base, podemos configurar a recursão. O ponto chave que torna uma função recursiva é que ela chama a si mesma em sua execução. Neste caso, queremos chamar a função depois de imprimir o número. Mas para contar regressivamente, nosso novo número precisa ser um a menos:
const recursiveCountdown = (number) => {
  if (number < 1) {
    return;
  }
  console.log(number);
  recursiveCountdown(number - 1);
};

recursiveCountdown(5);
Isso registraria os números 5, 4, 3, 2 e 1 no console. Nós realmente obtemos nossos cinco números! Mas e se quisermos contar para cima? Ao invés de escrever uma função totalmente nova, podemos trocar a ordem do nosso log e da nossa chamada recursiva:
const recursiveCountdown = (number) => {
    if (number < 1) {
        return;
    }
    recursiveCountdown(number - 1);
    console.log(number);
  };

recursiveCountdown(5);
Isso registraria os números 1, 2, 3, 4 e 5 no console. Mas por que isso funciona? Bem, para entender isso você precisa entender a call stack. A pilha de chamadas é como o JavaScript rastreia e resolve chamadas de função. A pilha funciona como uma fila do tipo last-in-first-out. Para entender isso melhor, vamos adicionar alguns logs à nossa função:
const recursiveCountdown = (number) => {
  console.log(Function execution started for number: ${number});
  if (number < 1) {
    console.log(Base case reached, begin resolving stack);
    return;
  }
  console.log(Calling recursiveCountdown with number: ${number - 1});
  recursiveCountdown(number - 1);
  console.log(Function execution completed for number: ${number});
};

recursiveCountdown(5);
Adicionamos quatro declarações principais aqui. O primeiro log é executado quando uma chamada de função começa a ser executada. O terceiro log é executado pouco antes da função recursiva ser chamada. E o quarto log é executado quando a execução da função terminou. O resultado é:
Function execution started for number: 5
Calling recursiveCountdown with number: 4
Function execution started for number: 4
Calling recursiveCountdown with number: 3
Function execution started for number: 3
Calling recursiveCountdown with number: 2
Function execution started for number: 2
Calling recursiveCountdown with number: 1
Function execution started for number: 1
Calling recursiveCountdown with number: 0
Function execution started for number: 0
Base case reached, begin resolving stack
Function execution completed for number: 1
Function execution completed for number: 2
Function execution completed for number: 3
Function execution completed for number: 4
Function execution completed for number: 5
Mas como isso acontece? Bem, é aqui que a pilha de chamadas entra em ação. Quando chamamos recursiveCountdown(5), essa chamada de função é adicionada à pilha de chamadas. Quando essa chamada de função chega ao ponto em que precisa chamar recursiveCountdown(4), ela precisa parar e esperar por esse resultado. Enquanto isso, nosso recursiveCountdown(4) é adicionado à pilha de chamadas, acima do recursiveCountdown(5). Quando essa chamada de função chega ao ponto em que precisa chamar recursiveCountdown(3), ela precisa parar e esperar por esse resultado. Enquanto isso, nosso recursiveCountdown(3) é adicionado à pilha de chamadas, acima do recursiveCountdown(4). Quando essa chamada de função chega ao ponto em que precisa chamar recursiveCountdown(2), ela precisa parar e esperar por esse resultado. Enquanto isso, nosso recursiveCountdown(2) é adicionado à pilha de chamadas, acima do recursiveCountdown(3). Quando essa chamada de função chega ao ponto em que precisa chamar recursiveCountdown(1), ela precisa parar e esperar por esse resultado. Enquanto isso, nosso recursiveCountdown(1) é adicionado à pilha de chamadas, acima do recursiveCountdown(2). E finalmente, quando essa chamada de função alcança o ponto em que precisa chamar recursiveCountdown(0), ela precisa parar e esperar por esse resultado. Enquanto isso, nosso recursiveCountdown(0) é adicionado à pilha de chamadas, no topo do recursiveCountdown(1). Mas recursiveCountdown(0) não chama outra função – ele atinge nosso caso base, onde retorna cedo. Porque a execução dessa função terminou, a chamada da função pode ser considerada “resolvida”. Quando a chamada é resolvida, ela é removida da pilha. Agora nosso recursiveCountdown(1) não está mais esperando por essa chamada – ele está no topo da pilha e pode continuar executando. recursiveCountdown(1) é resolvido, removido da pilha e permite que recursiveCountdown(2) retome a execução. recursiveCountdown(2) é resolvido, removido da pilha e permite que recursiveCountdown(3) retome a execução. recursiveCountdown(3) é resolvido, removido da pilha e permite que recursiveCountdown(4) retome a execução. recursiveCountdown(4) é resolvido, removido da pilha e permite que recursiveCountdown(5) retome a execução. E recursiveCountdown(5) é resolvido e removido da pilha. Nossa pilha de chamadas está agora vazia, então a recursão está completa! Esta é uma visão geral básica de como a recursão funciona em JavaScript. É um conceito complicado e você deve experimentar com algum código e declarações de log até se sentir confortável com o comportamento da call stack. Para uma curiosidade, falamos sobre como a falta de um caso base pode fazer seu código travar quando ele fica sem memória. Isso acontece porque a recursão continua empilhando cada vez mais chamadas de função na pilha de chamadas até que a pilha transborde. Assim como o nome da popular comunidade de programação.
Este módulo não possui perguntas. Marque como concluído.