递归与数组:JavaScript中return的作用及递归执行疑问
问题背景
我正在学习freeCodeCamp的JavaScript课程,其中一道题目要求编写仅接受参数n的递归函数,生成从n到1的倒序数组。我写出了如下解题代码:
// Only change code below this line function countdown(n) { if (n < 1) { return []; } else { const countArray = countdown(n - 1); countArray.unshift(n); return countArray; } } // Only change code above this line // my test console.log(countdown(1))
我通过Python Tutor可视化工具分析执行过程,现存在三个疑问:
- 步骤3中block1的
countArray显示undefined,是否是因为数组定义延迟到基准case触发后才完成? - 步骤6中
n=0触发基准case返回空数组,代码如何将其与countArray关联并填充? - 重点疑问:为何基准case(
n=0)触发return后,函数仍继续执行?按我的理解return会立即终止函数,不应得到正确数组。
疑问解答
1. 关于block1的countArray显示undefined的原因
完全正确,就是递归的执行顺序导致的。当调用countdown(1)时,进入else分支,执行const countArray = countdown(n - 1)——也就是调用countdown(0)。此时当前的countdown(1)函数会暂停执行,等待countdown(0)的返回结果。在countdown(0)执行完成并返回之前,countArray还未被赋值,所以此时它的状态就是undefined,直到基准case触发并返回后,才会拿到具体的数组值。
2. 基准case返回的空数组如何关联并填充
当n=0触发基准case返回[]时,这个数组会直接作为返回值传递给调用它的上一层函数——也就是countdown(1)里的countArray变量。此时countArray被赋值为这个空数组,接着执行countArray.unshift(1),把1添加到数组开头,最后返回这个变成[1]的数组。如果是n=2的场景,流程会是:countdown(2)调用countdown(1),countdown(1)调用countdown(0)得到[],countdown(1)把1插入数组后返回[1],countdown(2)拿到这个数组后插入2,最终返回[2,1],以此类推。
3. 基准case return后函数仍“继续执行”的原因
这里你混淆了单个函数调用的终止和整个递归调用栈的执行流程。当countdown(0)触发return时,只是终止了countdown(0)这一个单独的函数调用,它会把返回值传递给调用它的上一层(countdown(1)),然后countdown(1)会从之前暂停的位置——也就是const countArray = countdown(n-1)这行代码之后——继续执行,完成unshift和后续的return操作。整个递归是先一层一层向下调用,触达基准case后再一层一层向上收尾,每一层的函数调用在return后都会终止,但上层未完成的调用会继续执行,所以看起来像是“函数还在继续执行”,其实是不同层级的函数调用在依次完成收尾工作。
内容的提问来源于stack exchange,提问作者alex the listener

