何时/为何使用map / reduce over for循环

时间:2015-04-15 01:50:15

标签: javascript

所以我第一次在JavaScript中进行了一些对象操作,我有一个问题,我想知道是否有人可以回答。

当我有一个我想要操作的对象时,我可以在一些嵌套for循环的范围内做一些事情,但是有一些内置于JavaScript的函数,比如map / reduce / filter,以及像lodash / underscore这样的库。

我认为后者(map / reduce / filter和库)是更好的练习,但我只是好奇为什么。

我正在做一些非常基本的对象操作,可以通过一些很好的循环来解决,以获取和更改对象中的正确键/值,但可以使用JS中的函数/库轻松完成。只是好奇它们如何变得更好 - 比如更好的性能/更清洁的代码/易用性/其他任何东西。

抱歉,没有代码。我非常感谢有人帮助我在这里了解更多。

编辑 - 所以从map()的例子中取出

我可以以javascript.map

为例
 var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
var reformattedArray = kvArray.map(function(obj){ 
var rObj = {};
rObj[obj.key] = obj.value;
return rObj;
});

我可以做类似

的事情
   var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
var reformattedArray = [];

for(var object in kvArray){
  //combine both values into object inside of kvArray[object]);
 };

更少的代码 - 但还有其他值得了解的好处吗?

8 个答案:

答案 0 :(得分:13)

我知道我回复了一个旧答案,但只想指出未来的读者。

Map reduce和filter函数来自函数式编程世界。

这些是Lisp,Haskell等语言中的第一类内置运算符(ml?)。 函数式语言倾向于在不可变数据上运行运算符,而不是让代码在数据上运行以对其进行操作(比如循环)。 因此,与提供for和while循环相比,它们提供了更简单但功能更强大的接口,如map,filter和reduce。

它还可以帮助他们满足其他要求,例如不变性等。这就是为什么地图会给你带回一张新地图而不是改变旧地图。从并发的角度来看,它们非常好,但在某些情况下它们可能会更慢。

这种方法通常可以减少多线程或高并发应用程序中的代码错误。 当多个参与者对同一条数据进行操作时,不变性有助于防止代码互相踩踏。

由于javascript试图通过提供函数式编程语言的某些功能来尝试部分功能,因此在它中实现map,filter和reduce功能可能也是有意义的。

YMMV取决于您使用所提供的工具所做的事情。

如果你的代码在for循环中运行得更好,那就去吧。

但是如果你发现异步代码咀嚼公共数据并且最终分裂你的头发试图调试循环。 打个招呼,映射,缩小和过滤。

我的2卢比

答案 1 :(得分:7)

这就像问我是否更喜欢篮球或足球。两者都有积极的一面。

如果你有10个开发人员看你的for循环,那么10个中的9个会立刻知道你在做什么。也许有一半人不得不查看map()方法是什么,但他们也会知道发生了什么。所以在这方面,for循环更容易让其他人阅读。

另一方面,map()将为您节省两到三行代码。

就性能而言,你会发现map()内部构建了类似于for循环的东西。如果通过大型迭代运行它们,在性能速度方面可能会看到几毫秒的差异;但它们永远不会被最终用户识别。

答案 2 :(得分:7)

.map()允许您通过迭代原始数组并允许您运行某种自定义转换函数来创建新数组。 .map()的输出是一个新数组。

var orig = [1,2,3,4,5];
var squares = orig.map(function(val) {
    return val * val;
});
console.log(squares);   // [1,4,9,16,25]

.reduce()允许您迭代累积单个结果或对象的数组。

var orig = [1,2,3,4,5];
var sum = orig.reduce(function(cum, val) {
    return cum + val;
}, 0);
console.log(sum);    // 15

这些是专门的迭代器。当这种类型的输出完全符合您的要求时,您可以使用它们。它们不如for循环灵活(例如,你不能像使用for循环那样在中间停止迭代),但它们对特定类型的操作的输入较少,对于了解它们的人来说,他们可能更容易看到代码的意图。

我自己没有测试.map().reduce()for循环的效果,但已经看到.forEach()的测试表明.forEach().forEach()在某些浏览器中实际上较慢这可能是因为使用for的循环的每次迭代都必须调用您的回调函数,而在普通的for循环中,您不必进行这样的函数调用(代码可以直接嵌入)那里)。在任何情况下,这种类型的性能差异实际上很少有意义,您通常应该使用哪种结构使代码更清晰,更容易维护。

如果你真的想要优化性能,你必须在像jsperf这样的工具中编写自己的测试用例,然后在多个浏览器中运行它,看看哪种做法最适合你的特定情况。


.reduce()循环的另一个优点是它可以用于类似数组的对象,例如HTMLCollection,它们不是实际的数组,因此没有像.map()和{{1这样的方法}}

答案 3 :(得分:2)

在搜索其他内容时遇到了麻烦。因此,即使适用于旧线程,也要尝试回答它,无论如何都适用。

如果您考虑性能和灵活性,“ for”循环总是胜过其他循环,只是因为它没有为每次迭代调用函数的开销,并且可以用于任何用途。

但是,使用forEach,map,reduce等功能还有其他好处(我们称它们为功能方法)。它主要是可读性,可维护性。

以下是for循环的一些弊端

  1. 将新变量引入范围,仅用于计数器/迭代
  2. 由于计数器变量的意外更改,很难调试错误。随着带有in循环的循环数的增加,这变得更加困难,并且犯错的机会也会增加
  3. 开发人员习惯于将循环变量用作i,j,k。一旦循环增加了某些代码行,就很容易丢失对代码正在执行哪个计数器和哪个内部循环的跟踪。
  4. 对于ES6,我们至少使用'let'引入了有限/本地范围。但是以前,for循环引入的变量具有函数作用域,从而导致更多的意外错误

为避免所有这些情况,建议在知道要执行的操作时使用诸如forEach,map,reduce之类的功能(不要忘记这些功能方法大多数都提供了不变性)。为了获得更好的代码和更简洁的代码,在性能方面做了一些牺牲。

使用ES6,语言本身支持大多数功能方法。它们已经过优化,我们不必依赖lodash之类的库(除非可以显着提高性能)。

答案 4 :(得分:0)

forEach():对每个数组元素执行一次提供的函数(回调)。不返回任何内容(未定义),但允许该回调方法使调用数组发生突变。

map():对每个数组元素执行一次提供的函数(回调),并使用执行结果创建一个新数组。它不能改变调用数组的内容。

结论 需要返回新数组时,请使用map();要更改原始数组时,请使用forEach()for

答案 5 :(得分:0)

刚刚碰到这一点,发现没有答案突出显示@Sql({"/employee_schema.sql", "/employee_test_data.sql"}) public class EmployeeTest { @Autowired private EmployeeRepository employeeRepository; @Test public void testLoadDataForTestClass() { assertEquals(3, employeeRepository.findAll().size()); } } for-loop之间在何时使用另一个上的重要区别:

  1. 有了map,您将无法摆脱map可以进行的迭代。

例如,您不能这样做

for-loop

答案 6 :(得分:0)

总结高阶数组方法(mapreducefilter 等 - 我将它们称为 HOM)之间的差异vs for 循环,包括其他几点:

  • 计数器变量:for 循环引入了引入错误的变量,例如:OBOE;块范围错误(由于 letvar 声明范围的差异而进一步复杂化)
  • 可用性:HOM 仅可用于数组对象 (Array.isArray(obj)); for 循环可用于实现迭代器协议(包括数组)的对象,
  • 提前退出:HOM 没有;循环对此有 breakreturn 语句,
  • 连续 async 迭代执行:在 HOM 中不可能。请注意,只有循环才能在控制台日志记录执行之间延迟:
// Delays for a number of milliseconds
const delay = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms));

const items = ['a', 'b', 'c'];
const printItem = async (item) => {
  await delay();
  console.log(item);
}

const testForLoopParallelism = async () => {
  for (const item of items) {
    await printItem(item);
  }
};
const testHigherOrderParallelism = () => {
  return Promise.all(items.map(async item => await printItem(item)));
}

const run = async () => {
  // Prints consecutively at a rate of ~1s, for a total of ~3s
  console.time('for');
  await testForLoopParallelism();
  console.timeEnd('for');

  // Prints all concurrently, for a total of ~1s
  console.time('HOM');
  await testHigherOrderParallelism();
  console.timeEnd('HOM');
};

run();

不太重要但值得注意:

  • 冗长:数组 HOM 可能比 for 循环短
  • 主观上更容易阅读:这对于 HOM 和 for ... in 循环都是有争议的
  • 主观上更好理解:对于 JavaScript 新手来说,循环可能比 HOM 更广为人知
  • 性能:可能存在性能差异,需要在高性能代码库中逐案考虑
  • 不变性辅助:可能有助于提供不变性 - 尽管可以使用 HOM 或 for 循环创建突变,例如,
items.map((item) => {
  items.push(item);
  return `${item}x`;
});

答案 7 :(得分:0)

mapreduce 等是类容器数据结构应该实现的函数,以便消费者可以使用它们而无需了解它们的内部结构。这些函数接受您的逻辑作为输入。这使您可以在不影响消费者的情况下对这些数据结构的内部进行更改。

map 优于 for 循环的真正原因是因为随着您的应用程序的发展,它们更容易开发。如果您的需求发生变化,您现在拥有一个对象,该怎么办?

import map from 'lodash/fp/map';
import mapValues from 'lodash/fp/mapValues';

const before = map(logic, data);
const after = mapValues(logic, data);

再说一次,如果您的需求发生变化,现在您有一棵树,该怎么办?好吧,现在您是一名优秀的开发人员,您意识到负责遍历树的代码应该与业务逻辑分离。它应该编写一次,彻底测试一次,由拥有数据结构的团队维护,并接受业务逻辑作为输入。这一切都是为了让消费者不必担心其内部结构。

相反,消费者应该这样做。

import { mapTreeValues } from 'tree'

const before = map(logic, data);
const after = mapTreeValues(logic, data);

长话短说,for 应该只被数据结构的所有者用来实现 mapreduce 等功能。这些功能不应该与业务逻辑耦合,相反,他们应该接受它作为输入。还记得单一职责原则吗?

旁白:为了抢先与自定义迭代器进行比较,这些高阶函数有利于提高组合的易用性。例如:

getUser().then(updateUser)
getUsers().then(map(updateUser))