开发者问题收集

另一个 Call 与 Apply 查询

2015-05-30
54

当我尝试运行以下代码片段时:

function flatten(a) {
  return [].slice.call(arguments).reduce(function(acc, val) {
    return acc.concat(Array.isArray(val) ? flatten.call(null, val) : val);
  }, []);
}

我收到以下错误:

Uncaught RangeError: Maximum call stack size exceeded

但是,如果我使用 flatten.apply 而不是 flatten.call ,它就可以完美运行。(深度展平输入数组)。

除了这个 link1 link2 之外,我还阅读了其他一些博客和文章,以了解它为什么会如此表现。我要么找不到答案,要么一定是我忽略了它。(如果是后一种情况,请原谅我的眼睛)

当然,这两者之间的根本区别是:

apply - requires the optional parameter be an array

call - requires the optional parameters be listed explicitly

通常,函数可以采用任何类型的参数 - 原始和非原始。所以,我的问题是:

  1. call 方法的可选参数之一可以是 Array 类型或其他非原始类型吗?

  2. 当使用 call 时,为什么上述代码超出了调用堆栈?

编辑:由于使用了 2 个 call 方法,我的描述含糊不清。我做了一些更改以澄清。

2个回答

您的错误在这里:

[].slice.call(arguments).reduce
              ^^^^^^^^^

因此,当您传递 [x] 时,您会在 [[x]] 上调用 reduce ,并且第一个 val 变成 [x] 。然后,您再次使用 [x] 调用 flatten ,故事又重复了一遍。

另一方面, apply(..., val) 只会将 x 传递给 flatten ,从而将嵌套级别减少一级。

如果您对如何使用 apply 展平深层嵌套数组感兴趣,则无需递归即可实现:

while(ary.some(Array.isArray))
  ary = [].concat.apply([], ary);

以下是 callapply 的一个小例子:

function fun() {
  var len = arguments.length;
  document.write("I've got " 
                 + len 
                 + " " 
                 + (len > 1 ? " arguments" : "argument")
                 + ": " 
                 + JSON.stringify(arguments)
                 + "<br>");   
}

fun.call(null, 123);
// fun.apply(null, 123); <-- error, won't work

fun.call(null, [1,2,3]);
fun.apply(null, [1,2,3]);
                    
                    
georg
2015-05-30

当您调用 flatten.call(null, val) 时,由于 val 是一个数组, flatten 函数会接收 val 作为参数,而您将其作为可选参数传递给 call (未检查类型),因此 arguments[val]

您会看到 val 数组位于数组内部。当您调用 reduce 时,回调中的 val 仍然 是一个数组,您没有展平任何内容,这就是它永不停止的原因。

当您调用 flatten.apply(null, val) 时,由于 val 是一个数组, flatten 函数会接收 元素 val 作为参数,因此 argumentsval 相同(而不是 [val] ):您已经有效地解开了数组。这就是它起作用的原因。

Denys Séguret
2015-05-30