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)
- 调用
findSolution(13),创建执行上下文A:里面存着target=13,然后它调用内部的find(1, "1")。 - 上下文A被压到栈底,新的执行上下文B(
find(1, "1"))被放到栈顶,开始执行。
第一次分支探索:走+5的路
- 上下文B:
start=1不等于13,也不大于13,所以执行return find(6, "(1 + 5) ") || find(3, "(1 * 3) ")。先调用第一个find(6, ...),上下文B暂停,创建上下文C(find(6, "(1 + 5) "))压到栈顶。 - 上下文C:
start=6≠13,不大于13,执行return find(11, "((1 + 5) + 5) ") || find(18, "((1 + 5) * 3) ")。调用第一个find(11, ...),创建上下文D(find(11, "((1 + 5) + 5) "))压栈。 - 上下文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) "))压栈。 - 上下文E:
start=16>13,触发return null。这个null返回给上下文D,E弹出栈。 - 上下文D继续执行
||后面的find(33, ...):创建上下文F(start=33>13),F返回null给D。D现在计算null || null,结果是null,所以D返回null给上下文C,D弹出栈。 - 上下文C继续执行
||后面的find(18, ...):创建上下文G(start=18>13),G返回null给C。C计算null || null,返回null给上下文B,C弹出栈。
切换分支:走*3的路
- 上下文B拿到C返回的
null,触发||的短路逻辑,开始调用第二个分支find(3, "(1 * 3) ")。创建上下文H(find(3, "(1 * 3) "))压到栈顶。 - 上下文H:
start=3≠13,不大于13,执行return find(8, "((1 * 3) + 5) ") || find(9, "((1 * 3) * 3) ")。调用第一个find(8, ...),创建上下文I(find(8, "((1 * 3) + 5) "))压栈。 - 上下文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) "))压栈。
找到目标:返回结果
- 上下文J:
start=13正好等于target!所以return history,也就是字符串"(((1 * 3) + 5) + 5) "。这个值返回给上下文I,J弹出栈。 - 上下文I拿到这个字符串,因为它是truthy值,
||直接短路,把这个字符串返回给上下文H,I弹出栈。 - 上下文H拿到字符串,返回给上下文B,H弹出栈。
- 上下文B拿到字符串,返回给上下文A,B弹出栈。
- 上下文A把这个字符串返回,最后
console.log输出它——也就是(((1 * 3) + 5) + 5)。
关键细节:||在这里的作用
这个递归里的||是核心:它会先尝试+5的路径,如果这条路径走不通(返回null),就立刻切换到*3的路径继续探索。只要某一条路径返回了非null的值(也就是找到目标),这个值就会顺着调用栈一层一层往上传递,直到回到最开始的调用,最终输出结果。
内容的提问来源于stack exchange,提问作者Aarizzz Hakim
相关产品推荐
相关产品推荐

