前言
第一次认识递归这个概念在两年前, 一个递归函数(不是深拷贝)看了好久才看懂, 在函数内部再次调用自己, 返回一个新的值, 再此期间还涉及多次对自己的调用, 这对于当时的我还是太过超前了.
不过直到最近, 我对’递归’的认知也几乎只是这些, 我还是希望能有更深刻的理解.
一、何为’递归’
递归
并不像我理解的那样只是’自己调自己’这种, 正相反, '对自身的调用’这一行为包含在递归的范畴内.
在第三版《学习JavaScript数据结构与算法》里对递归的定义是:
递归是一种解决问题的办法, 它从解决问题的各个小部分开始, 直到解决最初的大问题. 递归通常涉及函数调用自身.
但是对于递归函数, 就是特指能够直接或者间接调用自身的函数了.
比如:
function recursiveFunction1 (someParam) {
recursiveFunction2(someParam);
}
但是很明显不能在开发中使用上面这个函数——它会一直执行下去, 无休无止.
因此一个完整的递归函数应当具备基线条件
, 即一个不再递归调用的条件, 就像while
那样.
如果换成这样的话:
function understandRecursion (doIunderstandRecursion) {
const recursionAnswer = confirm('understand');
if (recursionAnswer === true) return; // 基线条件
understandRecursion();
}
是一个合格的JavaScript
递归函数了.
二、调用栈
每当一个函数被一个算法调用, 该函数会进入调用栈的顶部, 而递归函数对自身的调用也将导致更多的自己被压入调用栈, 因为每一次调用都可能依赖上一次调用的结果.
1.观察调用栈运作
调用栈的情况可以通过浏览器探查, 用一个递归阶乘函数作为例子:
function factorial (n) {
if (n === 1 || n === 0) {
// 基线条件
return 1;
}
return n * factorial(n - 1);
}
console.log(factorial(5));
在基线打断点, 暂停执行, 然后刷新页面, 让调用栈回到初始状态:
factorial(3)
调用.
factorial(3)
: 等待factorial(2)