理解《Eloquent Javascript》中的Reduce函数

5
在《Eloquent Javascript》中,作者要求读者编写一个名为countZeroes的函数,该函数以数字数组作为其参数,并返回其中出现的零的数量作为reduce函数使用的另一个示例。
我知道:
- reduce函数的概念是将数组转换为一个值。 - 三元运算符是函数的核心部分。
我不知道:
- counter函数的参数是从哪里来的。
书中说:
function countZeroes(array) {
  function counter(total, element) { // Where are the parameter values coming from?
    return total + (element === 0 ? 1 : 0);
  }
  return reduce(counter, 0, array);
}

文本中的早期示例:

function reduce(combine, base, array) {
 forEach(array, function (element) {
    base = combine(base, element);
  });
  return base;
}

我在Codeacademy上写了一门关于这个主题的课程。它教授高级函数的基础知识,例如map、reduce、sort等。 https://www.codecademy.com/courses/javascript-advanced-en-eQcHT/0/1 - Christian Landgren
3个回答

8
函数`counter`作为第一个参数传递给`reduce`函数,当在您的第一块代码中调用时。在`reduce`函数中,第一个参数被称为`combine`。然后使用名为`base`和`element`的参数调用它们,这些是您正在寻找的神秘参数!
所以棘手的部分是,该函数不是在定义和命名的地方执行,而是作为参数传递给`reduce`函数,然后由其执行。
编辑:细化
因此...函数在定义和命名(在点1处)之后,将定义无名称传递给另一个函数(在点2处),并携带变量(我称之为i和ii),在那里它捕获第一个参数的名称(在点3处),然后被调用(在点4处)与其他参数一起调用。
编辑:进一步说明
我更新了图片,以更好地解释来自数组的元素。
因此,`i`更容易遵循,在调用`reduce`函数时创建为0,并分配名称`base`作为参数,然后重新分配为`counter`/`combine`函数的结果,该函数返回可能的增量与`base`。
`ii`的生命周期始于传递给`countZeroes`的数组,然后沿着链传递,直到被`forEach`循环迭代,该循环提取单个`element`并在其上执行`combine`函数(以及`base`)。

谢谢Billy,现在稍微清楚了一些,但我仍然很难看出它是如何从一行到另一行工作的。匿名函数的“element”参数来自哪里?您能否提供更详细的逐行分解? - KMcA
Billy,再次感谢。这绝对有所帮助。如何从数组中取出单独的元素以作为匿名函数参数呢? - KMcA
@AMK 为此,您需要查阅特定 forEach() 函数的实现,这可能是该函数首次引入的地方。 - phant0m
1
phant0m,是的,forEach函数的参数是一个数组和一个“操作”,在这种情况下是匿名函数。所以,我仍然不确定“element”参数来自哪里。 - KMcA

7

从代码来看,只有一个可能的答案:由于当counter函数被传递给reduce()时只引用了一次,因此reduce必须提供参数给该函数。

reduce的工作原理

这是reduce的工作原理可视化图。您需要从上到下阅读该图表,/\表示下面的参数传递给上方的counter()函数。

                   return value of reduce()
                   /
                 etc ...
                /
            counter
           /       \
       counter      xs[2]
      /       \
  counter      xs[1]
 /       \
0         xs[0]

counter()最初的计数值为0(因为当你还没有处理任何元素时,看到的零的总数就是零),同时也有第一个元素。

如果数组的第一个元素是零,则将该0增加一。在查看第一个元素后,counter(0, xs[0])将返回新的总数给reduce()函数。只要还有剩余元素,它就会将这个值作为新的待处理总数传递给counter()函数,以及数组中的下一个元素:xs[1]

只要数组中还有元素,就会重复这个过程。

将此概念映射到代码

function reduce(combine, base, array) {
 forEach(array, function (element) {
    base = combine(base, element);
  });
  return base;
}

正如图示所示,最初将0与表示第一个"迭代"中xs[0]element一起传递到函数中。然后将结果写回base
正如您在可视化中看到的,0是函数counter()的左参数,其结果随后作为左参数传递给counter()。由于base/totalforEach结构中充当左参数,因此将该值写回base/total是有意义的,这样以前的结果会在下一次迭代时再次传递给counter()/combine()。斜杠/的路径是base/total的"流"。 element来自哪里
《JavaScript编程精解》第6章中,这里是forEach()的实现(在注释中,我已经使用了最终的值替换了对action的调用)。
function forEach(array, action) {
  for (var i = 0; i < array.length; i++)
    action(array[i]); // <-- READ AS: base = counter(base, array[i]);
}

action表示一个函数,在迭代遍历 array 时通过简单的 for 循环对每个元素进行调用。

在你的情况下,这将作为 action 传递给 forEach()

function (element) {
    base = combine(base, element);
}

它是在每次调用reduce()时定义的匿名函数。因此,在for循环的每次迭代中,element实际上是array[i]


我非常欣赏您的图表。现在我唯一缺少的部分是如何从数组中提取单个元素。整体思路非常清晰,但我想了解“元素”从数组中被提取的确切位置。 - KMcA
@AMK,我已将其添加到我的帖子底部。 - phant0m
好的,现在我明白了,谢谢你们两个。我看到forEach的迭代提供了单个数组元素。 - KMcA
@AMK 不用谢。顺便提一下:我编辑了你的问题,这样访问者就不会被“大段文本”所迎接了。我已经将书名(相关)包含在标题中,这也使得问题立即清晰明了,而且不需要在问题中包含“谢谢”,这样我们就能更快地得到答案 ;) - phant0m

1

reduce方法将参数传递进去。在reduce函数的源代码中,可能会有类似以下的内容:

function reduce(iterator, memo, collection) { // in your example, iterator here is your counter function
    
    // while looping through the collection you passed in
    memo = iterator(memo, collection[i], i); // it calls your counter function and
                                             // passes the total and the current array
                                             // element as well as the current index,
                                             // then stores the result to be passed in
                                             // with the next array element
    
    return memo;
}

附录

也许这个链接(jsfiddle)能更好地说明问题:

// very simple reduce implementation
function reduce(iterator, memo, collection) {
    for (var i = 0; i < collection.length; i++) {
        memo = iterator(memo, collection[i], i);
    }
    return memo;
}

zeroes = [1,0,0,1,1,0,1];
function counter(total, element) { return total + (element === 0 ? 1 : 0); }

alert(reduce(counter, 0, zeroes));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接