原文来自[https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/JS-Reduce-Made-Easy]
这太拗口了。它之所以被称为reduce(),是为了能更方便解决数组问题。所以更容易读取和理解。在JS中,for循环就不是这样的。他们不是精炼的,他们得让你绕一会儿。笑话归笑话,在计算机科学中,有两大问题是难解决的:缓存无效,命名。还有一种危险:写异步代码里面包含里不使用闭包的for循环。这篇文章将从一个声明开始:你可以避免使用一个for循环或while循环来解决任何数组相关的问题。相反,你可以用reduce()来解决他们所有的问题。如果你读之前,务必了解递归函数,和一些很酷的功能工具如map()或filter()。实践出真知。让我们展如何能习惯用reduce()。你该知道,如果你没有做完freecodecamp算法部分题目,你可能要停止这一部分阅读。那里有一些题目可以很好地体现这些问题。若没有做过,这可能是一篇文章陈词滥调的剧透。确保你对这些问题能自己独立动手,而不是采取偷看之前解决方案。此外,如果你已经足够好了解它,欢迎浏览,并提供反馈。
我可以减少任何数组相关的问题吗?
让我们采取一个例子。来创建一个很常见的网站的英文标题:标准的连字符间隔的字符串,如新闻标题,博客文章标题,甚至问一个论坛的问题。现在,我们来写一个实用的函数,创建这个标题。你可能会写成这样:
function createSlug(str){ return str.split(" ").reduce(function(prev, next){ return prev.concat([next.toLowerCase()]); }, []) .join("-"); }
来测试一下你的控制台输入像“Leo Finally Wins a Freaking Oscar!”看看它的回应。是的,它不是一个强大的实现。将空格替换为“-”,首尾不需要替换。结果如下:
leo-finally-wins-a-freaking-oscar
注意,最重要的下面这一条语句:
return prev.concat([next.toLowerCase()]);
这是我们想要的功能的核心:我们的函数体开始返回语句!
但我一点也不明白Reduce()!
每一个JavaScript函数有三件事你需要知道,了解函数的运作流程:
- 输入
- 输出
- 执行上下文
打开官方文档,你可能对于回调函数里的:prev 和 next 感到困惑。Array.prototype.reduce()将回调函数(callback)和初始值(initialValue)作为输入参数,其中,初始值(initialValue)是非常重要的,许多开发人员都忘记提供正确的初始值,而导致代码错误。
正如你所看的文档,它需要一些额外的,但可选的参数,以及更多。假设 arr 是一个任意的数组。
arr.reduce(function(){}, initialValue);
现在,让我们仔细看看回调函数,这是reduce()的第一个参数,它也需要两个参数。
在本文中,我们会称他们为acc,代表累积值;和item,表示正在访问的当前项目。
目前为止,我们的reduce()应该长成这样:
arr.reduce(function(acc, item){ /*函数内容*/ }, initialValue);
现在,让我们找出将这些acc和item的值。我们前面提到的,reduce是一个迭代结构的替换。它将减少将采取自定义回调函数,遍历该数组减少被调用。为了更进一步的了解reduce()的迭代过程,我们用conso.log()来看看:
var arr = [10, 20, 30, 60]; arr.reduce(function(acc, item){ console.log(acc, item); }, 0);
输出如下:
0 10 undefined 20 undefined 30 undefined 60
注意输出的数和数组中的元素数相同[ 60,20,30,10 ]。事实上,它打印出数组的元素!
因此,我们可以推断出,reduce()需要自定义回调并执行它对数组的每个元素。这样做时,它使当前的项目可用到自定义回调作为项目参数。但对于acc?我们看到,除了第一行,当item= 10时,它是不确定的。在第一行,对应的第一次迭代,其价值是相同的初值。总之,我们的acc累加器,不积累!但是,我们如何使它积累?让我们尝试执行这个:
var arr = [10, 20, 30, 60]; arr.reduce(function(acc, item){ console.log(acc, item); return acc; }, 0);
这一次的输出变化:
0 10 0 20 0 30 0 60
你可以看到,acc的值将保持不变。这是预期之中的,在自定义回调中,我们不改变acc的任何价值。我们返回任何减少,使在一个给定的迭代。但我们没有意识到的东西——acc当前迭代的值,将从自定义回调从以前的迭代的返回值。最终,当迭代结束,最终的价值将减少调用返回的ACC。
这只留下一个重要的部分,在我们的理解——执行上下文的值,或this!
var arr = [10, 20, 30, 60]; arr.reduce(function(acc, item){ console.log(acc, item, this); return acc; }, 0);
如果你是在严格的模式,它会返回不确定的值,这。否则,在浏览器中,它会指向这个窗口对象。我们可以覆盖和设置它在我们自己的,使用绑定?当然!只使用绑定与回调:
var arr = [10, 20, 30, 60]; arr.reduce(function(acc, item){ console.log(acc, item, this); return acc; }.bind(arr), 0);
我已经绑定数组arr本身;但你可以将它设置为在你的环境中的任何对象。
了解Reduce
让我们总结一下我们对这个减少函数的理解,便于参考:
- 减少以一个自定义回调函数作为其第一个参数,并将其作为第二个参数的一些初始值。
- 重要的是,我们不要忘记第二个参数,初始值;我们明确地设置它,而使用它。
- 要自定义回调的输入参数的累积值acc;和当前项目在数组中,项目。
- acc的下一次迭代的值将返回值在回调,在当前迭代。
- 使用reduce()整点是形成正确的acc;最后从reduce()回调。
你不用试着临时抱佛脚记住他们!相反,把它们应用在实际的代码中。
减少使用
让我们在一个数组中开始一个简单的数组操作,在一个数组中查找最大值。为了简单起见,我设它是一个整数数组。
为了形成一个解决方案,我们需要考虑如何形成acc和遍历数组。我觉得一个有用的方法:出一个制定,无论数组的大小或内容;ACC应该迄今最大值。比如:我的数组是[ 60,50,5,20 ]。经过两次迭代;项目将5和ACC应该是最大的(20,50)= 50。只有这样acc总是最大值。
var arr = [20, 50, 5, 60]; arr.reduce(function(acc, item){ return Math.max(acc, item); }, 0);
如果你想写得更精炼:
var arr = [20, 50, 5, 60]; arr.reduce(Math.max, 0);
但这不会工作,将返回NaN。原因是:acc 和 item 不仅仅是回调函数的参数。当你调用Math.max()时,max()会试图把它称为非数值的参数,导致结果为NaN。
那么,如果我的数组是由小于零的值组成呢?比如,arr = [ - 7、- 56、- 5、- 2 ]。返回的值是0,这是不存在于数组arr。相反,我们应该选择初始值的最低可能值。
var arr = [-20, -50, -5, -60]; arr.reduce(function(acc, item){ return Math.max(acc, item); }, -Infinity);
更多的reduce()
Reduce不只是一个函数为您提供工具来解决一些数学问题如矩阵阵,HCF的阵列,数组中的最小值。它是完全能够超越和超越。我们将在处理现在一些复杂的例子。比如:你希望扁平化嵌套数组。例如,我们可以使用这个数组来测试我们的代码。
var arr = [[1, 2, 3], ['cat', 'dog', ['fish', 'bird'], [[[]]]]];
这看起来足够复杂,开始与嵌套的数组,不同深度的空嵌套数组。输出应该是:
[1, 2, 3, 'cat', 'dog', 'fish', 'bird']
我们显然需要区分一个数组和一个元素。同时,acc应该在整个迭代形成数组;这意味着初始值将是一个空数组。在整个回调函数代码中,我们只简单地从项目中提取内容,它可以是一个嵌套的数组。我们将 Array.prototype.concat()它
与acc值。它的更好的使用concat()在 Array.prototype.push()
;因为push()改变原数组,而concat()创建一个新数组并返回它。因为我们不知道在任何一个给定的即时嵌套的水平;我们必须递归调用我们的自定义回调。的意思,我们已经在其他地方写它的名字里面reduce()称它。
var arr = [[1, 2, 3], ['cat', 'dog', ['fish', 'bird'], [[[]]]]]; function flattenArray(arr) { return arr.reduce(function(acc, item){ if(Array.isArray(item)){ return item.reduce(flattenArray, acc); } return acc.concat(item); // this does the ordering. If you want reverse ordered output, just reverse it! }, []) } // call it like this flattenArray(arr);
当然,这需要在递归函数中的一些背景;但这并不太难拿起,相比这个长的问题!但是注意,我们可以简单地写3-4行清洁功能保持一些简单的指导思想,做一些复杂的,可靠的。这是可读性和可维护性。
例如,如果你想改变或调整后的逻辑(说你想上的情况下,一些字符串或编码一些字符串);你可以很容易地确定在哪里改变。实际嵌套在中频条件下发生。和我们使用的减少调用有-它保持元素的顺序,因为它们是在数组中。我们要找出两个或两个以上数组的对称差异。它看起来令人畏惧;但你开始思考。最初的价值是什么?当然,我们正在形成一个数组;因此,它将是一个空数组[ ]开始。然后是acc ——因为我们的最终解决方案将包含一个diff ED阵列;它也将是一个数组。这将继续打桩的阵列遇到的对称差异,到目前为止。只要是清晰的,这个函数可以接受任意数量的数组;因此,我们必须把它们转换成一个数组的数组。
function symDiff(args){ // convert args to an Array var argsArray = Array.prototype.slice.call(arguments); // now do the reduce magic! argsArray.reduce(function(acc, item){ return acc .filter(function(itemInAcc){ return item.indexOf(itemInAcc) === -1; }) .concat(item.filter(function(itemInItem){ return acc.indexOf(itemInItem) === -1; })); }. []); }
是的,我知道。它看起来大。所以,让我们看看我们是否能够重构使其小。注意,两个过滤器功能都做同样的工作;除非用改变的参数对。让我们创建一个单独的函数,并用这些参数调用它两次。
function symDiff(args){ // convert args to an Array var argsArray = Array.prototype.slice.call(arguments); // now do the reduce magic! argsArray.reduce(function(acc, item){ var funWithFiltering = function(arr1, arr2){ return arr1.filter(function(itemInArr1){ return arr2.indexOf(itemInArr1) === -1; }); }; return funWithFiltering(acc, item).concat(funWithFiltering(item, acc)); }. []); }
这看起来更好。但仍然有一个问题。这将保持数组中的重复。如果这是不需要的,我们可以很容易地写另一个函数使用减少删除重复。
function removeDuplicates(arr){ arr.filter(item, index, self){ // Keep only the first instance of the array, as given by indexOf() // Remove other elements from Array return self.indexOf(item) === index; } }
我们不能再继续忽视。我一直在使用filter,而使用reduce更有远眺性,对吗?原因很简单——filter可以用reduce写的。事实上,任何数组操作,在理论上;可以实现reduce()。做给它一个尝试!用reduce的实现man和filter。你也必须照顾可选的参数。
结束了
哇,那是相当多!但我认为我已经做了一个强大的情况下,使用减少每当你想使用一个循环来完成它。要习惯它喜欢你的第一天性。一旦你得到一个问题在一些字符串或数组manipuation转化;开始写
return arr.reduce(function(acc, item){_}, _);
然后填补空白。当你使用reduce(),你在每一个元素与其他元素的相互作用方面的思考。你是acculumating从头到尾形成输出。
框架归来拥抱减少的原则,在网页设计中获得很高的知名度。还注意到另一个显着的功能-减少力量或指导你形成你的解决方案,而不改变任何现有的。例如,在最后一个示例中;我们被过滤和连接-但我们知道这将是工作;由于操作的第一集并没有改变任何的ACC或项目内的迭代。
初始参数是可选的,你不需要显式地提供它。如果你忽略了这个,对于第一次迭代,acc将数组中的第一项,项目将数组中的第二项。这就意味着我们可以写一笔阵效用只是忽略它。或者,我们不需要考虑-无穷大的情况下,在数组中找到最大值-它会工作得很好,如果我们删除初始值。但在一些复杂的情况下,最好是可视化和制定解决方案的一些基础-一些初始化。