什么时候在Javascript中设置调用堆栈?

什么时候在Javascript中设置调用堆栈?,javascript,arrays,recursion,backtracking,callstack,Javascript,Arrays,Recursion,Backtracking,Callstack,在试图解决以下问题时: Generate all combinations of an array of string. Ex: Input: ['A', 'T', 'C', 'K'] Output: [ 'ATCK', 'ATC', 'ATK', 'AT', 'ACK', 'AC', 'AK', 'A', 'TCK', 'TC', 'TK', 'T', 'CK', 'C', 'K', '' ] 我有以下代码: function getSp

在试图解决以下问题时:

Generate all combinations of an array of string.
Ex: Input: ['A', 'T', 'C', 'K']
Output: [
  'ATCK', 'ATC', 'ATK',
  'AT',   'ACK', 'AC',
  'AK',   'A',   'TCK',
  'TC',   'TK',  'T',
  'CK',   'C',   'K',
  ''
]


我有以下代码:

function getSpellableWords(arr) {
    const res = [];
    // helper(arr, res, 0, '');
    helper(arr, res, 0, []);
    return res;
}

function helper(arr, res, i, slate) {
  const char = arr[i];
  console.log(i, 'i')
  if(i === arr.length) {
    res.push(slate.join(''));
    return;
  }

  // slate = slate + char;
  slate.push(char)
  console.log(slate, 'slate')
  helper(arr, res, i+1, slate);
  slate.pop()

  helper(arr, res, i+1, slate);
}

getSpellableWords(['A', 'T', 'C', 'K']);
我的问题是:

如果删除代码中的以下行:

helper(arr, res, i+1, slate);
一旦
i
等于5(即array.length),代码将在将
slate
推入
res
后停止。但是,如果我将该行保留,将设置一个调用堆栈,因此
I
将从1->5上升,然后逐渐弹出到
4
,然后是
3
,然后返回到
4
,依此类推。为什么会这样


澄清:因此我理解,对于每个递归调用,都会生成另一个
I
变量。然而,我希望第二个递归调用也能从1->4再次生成
I
,但这次不是线性递增,而是进行回溯。为什么第一次调用中没有回溯,为什么第二次调用只生成第一个结果,而第二次调用生成其余的结果?

每个
helper
的递归调用确实会在调用堆栈上添加一个级别,这样当递归调用返回到调用方时,调用代码可以继续使用自己的本地执行上下文

helper
的每次执行都有自己的执行上下文,其中包括一个仅对该执行上下文可见的局部变量
i
。它仅在调用堆栈中的该级别起作用

请注意,
helper
代码从不更改其
i
变量的值。当它被调用时,用作为第三个参数传递的任何值初始化它,这是它将拥有的唯一值

您注意到的对
i
的更改实际上没有任何更改。对于
i
所看到的每一个不同的值实际上都与恰好具有相同名称的不同变量有关

下面是一个关于
i
变量寿命的小模式,用于
res
变量的长度为2时(只是为了不让它太长!):


因此,我们看到,在这个特定的算法中,调用堆栈的大小(即递归树的深度)正好对应于当前执行上下文中变量
i
的值。当函数返回时,调用堆栈的大小减小(即递归深度减小),因此我们到达一个状态(从堆栈弹出)其中还有另一个
i
实例,该实例的值也与调用堆栈的当前大小相匹配。

每个
helper
的递归调用确实会在调用堆栈上添加一个级别,以便当递归调用返回其调用方时,调用代码可以继续使用其自己的本地执行上下文

helper
的每次执行都有自己的执行上下文,其中包括一个仅对该执行上下文可见的局部变量
i
。它仅在调用堆栈中的该级别起作用

请注意,
helper
代码从不更改其
i
变量的值。当它被调用时,用作为第三个参数传递的任何值初始化它,这是它将拥有的唯一值

您注意到的对
i
的更改实际上没有任何更改。对于
i
所看到的每一个不同的值实际上都与恰好具有相同名称的不同变量有关

下面是一个关于
i
变量寿命的小模式,用于
res
变量的长度为2时(只是为了不让它太长!):


因此,我们看到,在这个特定的算法中,调用堆栈的大小(即递归树的深度)正好对应于当前执行上下文中变量
i
的值。当函数返回时,调用堆栈的大小减小(即递归深度减小),因此我们到达一个状态(从堆栈弹出)其中还有另一个
i
实例,该实例的值也与当前调用堆栈的大小相匹配。

Trincot对该函数如何在内部工作给出了有用的详细响应。我只想指出一个重要的简化,你可以写:

const getSpellebleWords=([x,…xs])=>
x==未定义
? ['']
:((ps=getSpellableWords(xs))=>[…ps.map(p=>x+p),…ps])()
控制台日志(
GetSpelliablewords(['A','T','C','K']))
)

.as控制台包装{max height:100%!important;top:0}
Trincot对该函数如何在内部工作给出了有用的详细响应。我只想指出一个重要的简化,你可以写:

const getSpellebleWords=([x,…xs])=>
x==未定义
? ['']
:((ps=getSpellableWords(xs))=>[…ps.map(p=>x+p),…ps])()
控制台日志(
GetSpelliablewords(['A','T','C','K']))
)

作为控制台包装{max height:100%!重要;top:0}
,因为
i
是一个局部变量,每个新执行的
helper
都有自己的
i
版本,可以有不同的值。当递归调用返回并且调用代码继续时,它自己的
i
是相关的(再次)。因此我理解,对于每个递归调用,会生成另一个
i
变量。然而,我希望第二个递归调用也能从1->4再次生成
I
,但这次不是线性递增,而是进行回溯。为什么第一次调用中没有回溯,为什么第二次调用只生成第一个结果,而第二次调用生成其余结果?为什么您希望递归调用更改
i
?从来没有。如果您查看代码,您会发现在
helper
中没有对
i
的赋值。唯一可能发生的事
helper(arr, res, 0, []); // The initial call
    +--------top level helper execution context----+
    | i = 0                                        |
    | ....                                         |
    | slate.push(char)                             |
    | helper(arr, res, i+1, slate);                |
    |      +---nested helper execution context---+ |
    |      | i = 1                               | |
    |      | ....                                | |
    |      | slate.push(char)                    | |
    |      | helper(arr, res, i+1, slate);       | |
    |      |      +--deepest exec. context-----+ | |
    |      |      | i = 2                      | | |
    |      |      |  ...                       | | |
    |      |      | res.push(slate.join(''));  | | |
    |      |      | return;                    | | |
    |      |      +----------------------------+ | |
    |      | // i is still 1                     | |
    |      | slate.pop()                         | |
    |      | helper(arr, res, i+1, slate);       | |
    |      |      +----------------------------+ | |
    |      |      | i = 2                      | | |
    |      |      |  ...                       | | |
    |      |      | res.push(slate.join(''));  | | |
    |      |      | return;                    | | |
    |      |      +----------------------------+ | |
    |      | // i is still 1                     | |
    |      +-------------------------------------+ |
    | // i is still 0                              |
    | slate.pop()                                  |
    | helper(arr, res, i+1, slate);                |
    |      +-------------------------------------+ |
    |      | i = 1                               | |
    |      | ....                                | |
    |      | slate.push(char)                    | |
    |      | helper(arr, res, i+1, slate);       | |
    |      |      +----------------------------+ | |
    |      |      | i = 2                      | | |
    |      |      |  ...                       | | |
    |      |      | res.push(slate.join(''));  | | |
    |      |      | return;                    | | |
    |      |      +----------------------------+ | |
    |      | // i is still 1                     | |
    |      | slate.pop()                         | |
    |      | helper(arr, res, i+1, slate);       | |
    |      |      +----------------------------+ | |
    |      |      | i = 2                      | | |
    |      |      |  ...                       | | |
    |      |      | res.push(slate.join(''));  | | |
    |      |      | return;                    | | |
    |      |      +----------------------------+ | |
    |      | // i is still 1                     | |
    |      +-------------------------------------+ |
    | // i is still 0                              |
    +----------------------------------------------+