为了账号安全,请及时绑定邮箱和手机立即绑定

函数递归调用

函数递归调用

在程序中,所谓的递归调用就是函数直接调用自己或者间接调用自己
需注意的是,递归一定要有个结束自己调用自己的出口

我们先看个例子:求1 到100的和

// 常规的写法
var sum = 0,
  n = 100
for (var i = 1; i <= n; i++) {
  sum += i
}
console.log(sum)

我们可以用递归实现来实现:

// 用递归实现。函数调用函数自己。
function sumNum(num) {
  // 递归一定要有个结束自己调用自己的出口。
  if (num <= 1) {
    return num // 函数只要执行到return当前函数立即结束执行。
  }
  // 实现函数调用函数自己。
  return sumNum(num - 1) + num
  // arguments.callee在严格模式下回报错,所以不要这么用
  // return arguments.callee(num -1) + num;
}
console.log(sumNum(100))

我们再看看个例子:求f(n)的斐波那契数列的值。

//  f(n) = f(n-1) + f(n-2);  n>=3
// f(0) = 0, f(1) = 1  f(2) = 1, f(3) = 2....f(4)= 3 f(5) = 5 f(6)= 8
function fibonacci(n) {
  if (n == 1) {
    return 1
  }
  if (n == 0) {
    return 0
  }
  return fibonacci(n - 1) + fibonacci(n - 2)
}
console.log(fibonacci(6))

递归的缺点

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

尾递归优化

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();
// 等同于
function f() {
  return g(3);
}
f();
// 等同于
g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录。

这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。

参考文章

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
1.1万
获赞与收藏
786

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消