js递归
说递归之前感觉有必要说一下函数的执行栈。
- 任何代码的执行都必须有一个执行环境,执行环境为代码的执行提供支持。
- 执行环境是放到执行栈中的。
- 每个函数的调用,都需要创建一个函数的执行环境,函数调用结束,执行环境销毁。
用一段代码解释吧,这个代码最终会输出什么,答案是 1.global begein 2.A begin 3.B begin 4.C begin 5.C over 6.B over 7.A over 8.global over。 如果你的答案是对的,可能后面的你就不需要看了。
下面解释一下为啥会是这样的
- 执行代码创建全局环境,遇到 A B C 三个函数先不管,因为没有执行,接下来遇到 console.log("global begin")这是一个函数调用,放入执行栈中,执行完毕输出global begin后console.log出栈
- 遇到A(),函数A环境入栈,执行函数A,遇到console.log("A begin");入栈执行,输出A begin,此时A函数执行环境还在执行栈内,因为没有执行完。
- 遇到 B(),函数B环境入栈,执行函数B,遇到console.log("B begin");入栈执行,输出B begin;此时B函数执行环境还在执行栈内。
- 遇到C(),函数C环境入栈
- 执行函数C,遇到console.log("C begin");入栈执行,输出C begin,遇到console.log("C over");入栈执行,输出C over;函数C执行完毕,出栈。
- 接下来继续执行函数B,输出B over,函数B出栈
- 继续执行函数A,输出A over,函数A出栈
- 继续执行全局环境,输出global over。执行栈清空,代码执行完毕。
接下来说递归,函数递归的概念很简单,就是 函数直接或间接调用自身。 举个经典的例子,求斐波拉契数列第n位的值
代码其实很简单,重点在于理解
- 先创建全局环境,遇到console.log(f(5));入栈执行,
- 遇到f(5),入栈执行,得出f(5)=f(4)+f(3);
- 遇到f(4),入栈执行,得出f(4)=f(3)+f(2);
- 遇到f(3),入栈执行,得出f(3)=f(2)+f(1);
- 遇到f(2),入栈执行,得出f(2)=1;继续执行f(3)函数,遇到f(1)=1,f(3)执行完毕,f(3)=2;
- 继续执行f(4)函数,遇到f(2),得出f(2)=1,f(4)=f(3)+f(2)=2+1=3;f(4)执行完毕;f(4)=3;
- 继续执行f(5)函数,遇到f(3),f(3)=f(2)+f(1)=1+1=2;f(3)执行完毕;至此f(5)执行完毕;f(5)=f(4)+f(3)=3+2=5
- 其实就是函数入栈出栈,一步步算出结果。入栈出栈图形没画,太麻烦了。
递归函数必须要有出口,如果没有出口会无线递归,会导致栈溢出,报Maximum call stack size exceeded,超过最大调用栈大小。 无线递归和死循环不一样,死循环不会报错,也不会导致栈溢出,但是浏览器会一直转圈,没反应