平时在前端开发中,好像也没啥用到递归的地方。不过这并不表明递归不重要,若是你看过一些框架的源码,就会常常见到它的影子:好比渲染虚拟DOM的render函数,webpack中require依赖分析,Koa2洋葱式的中间件模型,其实都运用到了递归算法。javascript
博客原文前端
那么递归究竟是啥?先上两张图:java
图1:webpack
图2:web
递归,就是在运行的过程当中调用本身
咱们来看个最简单的阶乘函数:算法
5! = 5 * 4 * 3 * 2 * 1
function factorial(num) { if (num === 1) { // 基线条件 return 1; } // 递归条件 return num * factorial(num-1); } factorial(5);
一个常规的递归函数都有两部分:数据结构
if (num === 1)
):保证函数再也不调用本身,避免无限循环num * factorial(num-1)
):保证函数可以调用本身栈是一种先进后出的数据结构,它只有两种操做,出栈和入栈框架
const nekopara = ['chocolat', 'Coconut']; nekopara.push('vanilla'); // 入栈 nekopara.pop(); // 出栈
代码在运行过程当中,会有一个叫作调用栈(call stack)的概念。函数
function greet(name) { console.log(`hello, ${name}!`) greet2(name); console.log(`getting ready to say bye`); bye(); } function greet2(name) { console.log(`how are you, ${name}?`); } function bye() { console.log(`bye`); } greet('deepred');
调用greet('deepred')
时,计算机会首先给该函数分配一块内存,并将变量名name
设置为deepred
性能
每当调用函数时,都会分配一个内存块并将涉及到的变量值存储到内存中。
打印hello, deepred
后,调用了greet2('deepred')
。一样,计算机再次分配了一块内存,而且该内存块位于第一个内存块上面。
调用栈的最上面表示当前运行的函数,如图所示,如今正在运行的是greet2函数,打印输出how are you, deepred?
后,函数greet2执行完毕,栈顶的内存块被弹出。
如今栈顶的内存块又变回greet,这意味着咱们从greet2的函数中跳出,再次返回到了greet。
咱们在greet中调用了greet2时,greet只执行了一部分。
特别注意:调用另一个函数时,当前函数暂停而且处于未完成状态,暂停函数的全部变量的值仍然在内存中。
执行完greet2后,咱们回到了greet,并从离开的地方开始接着往下执行:首先打印getting ready to say bye
,而后调用bye函数。
在栈顶添加了bye函数的内存块后,开始执行bye函数,打印bye
,而后函数返回,内存块被弹出。
咱们又再次回到了greet中,此次没有其余要运行的代码了,因而从greet函数中返回,内存块被弹出,调用栈最后为空。
完整的一次调用流程:
递归一样使用调用栈
咱们来分析下阶乘fact(3)
的调用栈
function fact(num) { if (num === 1) { return 1; } return num * fact(num-1); } fact(3);
直接看图:
递归会致使程序的性能变低
若是递归嵌套很深,那么调用栈会很长,这将占用大量内存,可能会致使栈溢出