You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript递归函数中return返回机制及调用栈执行解析

JavaScript递归中return的返回逻辑&示例函数拆解(仿《JavaScript: Understanding the Weird Parts》风格)

先搞懂递归里的return到底在干啥

咱们先把递归的底层逻辑用大白话讲清楚:每次你调用一个递归函数,JS引擎都会给这个调用创建一个执行上下文——你可以把它想象成一张任务卡片,上面写着当前函数的参数、局部变量,还有代码执行到哪一行了。这些卡片会被堆成一摞,这就是调用栈(Call Stack)。

那return在这里扮演啥角色?

  • 当函数遇到return时,它会把指定的值扔给调用它的那个任务卡片(也就是栈里的上一张卡片),然后自己从栈顶被拿走(弹出),把控制权交回给上一层。
  • 如果递归里没写return,那函数执行完就默认返回undefined,上一层拿不到有用的结果,递归相当于白跑一趟。
  • 递归里的return分两种:一种是终止条件的return(比如找到目标值直接返回结果),另一种是传递递归结果的return(把下一层递归的结果原封不动传上去)。

拆解findSolution(13)的完整执行流程

先把咱们要分析的函数贴出来:

function findSolution(target) { 
  function find(start, history) { 
    if (start == target) return history; 
    else if (start > target) return null; 
    else return find(start + 5, "(" + history + " + 5) ") || find(start * 3, "(" + history + " * 3) "); 
  } 
  return find(1, "1"); 
} 
console.log(findSolution(13));

接下来咱们一步步模拟调用栈的变化,就像Udemy课里那样,把每一步的执行上下文(任务卡片)都理清楚:

初始调用:启动findSolution(13)

  1. 调用findSolution(13),创建执行上下文A:里面存着target=13,然后它调用内部的find(1, "1")。
  2. 上下文A被压到栈底,新的执行上下文B(find(1, "1"))被放到栈顶,开始执行。

第一次分支探索:走+5的路

  1. 上下文B:start=1不等于13,也不大于13,所以执行return find(6, "(1 + 5) ") || find(3, "(1 * 3) ")。先调用第一个find(6, ...),上下文B暂停,创建上下文C(find(6, "(1 + 5) "))压到栈顶。
  2. 上下文C:start=6≠13,不大于13,执行return find(11, "((1 + 5) + 5) ") || find(18, "((1 + 5) * 3) ")。调用第一个find(11, ...),创建上下文D(find(11, "((1 + 5) + 5) "))压栈。
  3. 上下文D:start=11≠13,不大于13,执行return find(16, "(((1 + 5) + 5) + 5) ") || find(33, "(((1 + 5) + 5) * 3) ")。调用第一个find(16, ...),创建上下文E(find(16, "(((1 + 5) + 5) + 5) "))压栈。
  4. 上下文E:start=16>13,触发return null。这个null返回给上下文D,E弹出栈。
  5. 上下文D继续执行||后面的find(33, ...):创建上下文F(start=33>13),F返回null给D。D现在计算null || null,结果是null,所以D返回null给上下文C,D弹出栈。
  6. 上下文C继续执行||后面的find(18, ...):创建上下文G(start=18>13),G返回null给C。C计算null || null,返回null给上下文B,C弹出栈。

切换分支:走*3的路

  1. 上下文B拿到C返回的null,触发||的短路逻辑,开始调用第二个分支find(3, "(1 * 3) ")。创建上下文H(find(3, "(1 * 3) "))压到栈顶。
  2. 上下文H:start=3≠13,不大于13,执行return find(8, "((1 * 3) + 5) ") || find(9, "((1 * 3) * 3) ")。调用第一个find(8, ...),创建上下文I(find(8, "((1 * 3) + 5) "))压栈。
  3. 上下文I:start=8≠13,不大于13,执行return find(13, "(((1 * 3) + 5) + 5) ") || find(24, "(((1 * 3) + 5) * 3) ")。调用第一个find(13, ...),创建上下文J(find(13, "(((1 * 3) + 5) + 5) "))压栈。

找到目标:返回结果

  1. 上下文J:start=13正好等于target!所以return history,也就是字符串"(((1 * 3) + 5) + 5) "。这个值返回给上下文I,J弹出栈。
  2. 上下文I拿到这个字符串,因为它是truthy值,||直接短路,把这个字符串返回给上下文H,I弹出栈。
  3. 上下文H拿到字符串,返回给上下文B,H弹出栈。
  4. 上下文B拿到字符串,返回给上下文A,B弹出栈。
  5. 上下文A把这个字符串返回,最后console.log输出它——也就是(((1 * 3) + 5) + 5) 。

关键细节:||在这里的作用

这个递归里的||是核心:它会先尝试+5的路径,如果这条路径走不通(返回null),就立刻切换到*3的路径继续探索。只要某一条路径返回了非null的值(也就是找到目标),这个值就会顺着调用栈一层一层往上传递,直到回到最开始的调用,最终输出结果。

内容的提问来源于stack exchange,提问作者Aarizzz Hakim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:51:54