使用函数与循环相比的优势是什么?

3

看起来JS中的功能迭代器正在取代for循环的使用。

与for/while循环相比,传递函数(如mapreduce)的优点是什么?

var numbers = [1, 4, 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});

var doubles = [];
for (i = 0; i < numbers.length; i++) { 
    doubles[i] = numbers[i] * 2;
}

4
嗯,重点不是闭包,而是方法的作用?mapreduce对数组有特定的操作,它们不仅仅像for循环一样迭代。 - adeneo
1
简洁性(通过es6的箭头函数变得更好) - raina77ow
如果你不喜欢它们,你不必使用它们,大多数事情都可以在没有这些新的数组方法的情况下完成。 - adeneo
1
这里通过传递给.map()的匿名回调函数形成的闭包与代码的运行方式基本无关。当然,函数本身是有关系的,但是调用时形成的闭包并不相关。 - Pointy
3个回答

8

我不知道为什么你会把使用map叫作"闭包"。闭包是完全不同的东西。map是高阶函数,即定义为一个操作(接受或返回)函数的函数。这种编程风格可以粗略地称作"函数式"。

map这样的函数有优点和缺点。正如一位评论者所指出的,它更加紧凑:

function sum(array) {
  var sum = 0;
  for (var i = 0; i < array.length; i++) sum += array[i];
  return sum;
}

对比。

function sum(array) {
  return array.reduce(add);
}

其中,addfunction add(a, b) { return a + b; }

更加紧凑意味着更加易读,同时也减少了出错的可能性。使用名为 add 的函数还可以增强可读性;我们可以很容易地直观地理解这个操作是将数组元素相加。

基本上,所有数组函数都有对应的 for 循环等价形式,但需要设置更多的变量并编写更多的逻辑。例如,map

function map(array, fn) {
  var result = [];
  for (var i = 0; i < array.length; i++) result.push(fn(array[i]));
  return result;
}

这段代码可以更加简洁地写成array.map(fn)

在许多情况下,我们可能已经定义了所需的元素映射或元素过滤函数。在这种情况下,我们可以直接使用这些函数与mapreduce等方法一起使用。

map及其相关方法的优点在于它们对于稀疏数组也很友好,例如:

var a = [];
a[1000000] = 1;

现在我们将每个元素都加倍:
function double(array) {
  var result = [];
  for (var i = 0; i < array.length; i++) result.push(array[i] * 2); 
  return result;
}

这个循环执行一百万次,并返回一个由NaN填充的数组。相比之下,
array.map(elt => elt*2)

这个操作仅对位于位置1000000的单个元素进行操作,并返回稀疏数组,如人们所希望的。

函数式风格还为灵活性开辟了额外的可能性。假设我们想要推广乘法的概念。我可以编写一个高阶函数来创建一个函数,该函数将某个值乘以特定的因子:

function multiply(n) {
  return function(x) {
    return n * x;
  };
}

现在我可以写代码了。
array.map(multiply(2))

这种简明和表达能力的要求在for循环中难以实现。使用forEachmap等方式可能会比for循环慢,如果您的代码在一个紧密的循环中运行一百万次,这可能是一个问题。但在实际应用中,这很少是一个问题。更好的做法是优先考虑代码的可读性和紧凑性。
然而,并没有人强迫你使用mapfilter。在ES7或任何其它版本中,您可以使用数组推导式来更加可读地完成相同的任务:
[ for (i of array) if (i % 2) i + 1 ]

它结合了filter和map,用于it技术相关内容。

如果你计划编写一个循环遍历数组并从每个元素中产生一些计算的生成器,那么你需要使用for循环,因为没有办法在forEach回调中产生yield:

function *double(array) {
  for (var i = 0; i < array.length; i++) yield array[i]*2;
}

function *double(array) {
  array.forEach(elt => yield elt*2); // DOESN'T WORK!!
}

4
这是一种范式转变。后者是一种命令式编程形式,用户创建数据,计算机消耗这些数据。前者基本上是更加功能化的方法,源自于数学,利用已经存在的数据(代码)。
理论上两者并没有处理优劣之分,但随着处理能力的不断提高,函数式编程在当前计算机状态下变得更加有用。
函数式编程允许一种基于数学的推理方式,强调输入和输出。特别地,JavaScript 由于函数作为一级数据类型而擅长处理这种风格。

4

这里已经有了一些很好的答案。我想补充一件事情,随着时间的推移我开始欣赏的是:当你按照 "命令式" / 旧方式进行操作时,它往往会鼓励采用大量中间变量、可变的东西以及 “当我正在迭代时,我可能会在同一数据上做这个其他事情” 的编程风格,这是代码设计中最大的陷阱之一 - 牺牲关注点分离来换取可疑的性能提升。请看以下示例:

const numbers = [1,4,9,16];
let sum = 0;
let onlyEvenNumbers = [];
for(i = 0; i < numbers.length; i++) {
  sum += numbers[i];
  if(numbers[i] % 2 == 0) {
    onlyEvenNumbers.push(numbers[i]);
  }
}

我认为这是不好的,因为你几乎没有获得任何性能提升(如果有的话),而且for循环没有明确的单一目的。当然,在这种简单情况下可能没问题,但它可能会变得非常复杂和难以理解。此外,一开始并不清楚onlyEvenNumbers变量中存储了什么,直到你读到for循环底部 - 再一次,这里可能没问题,但如果for循环变得很大,它可能会变得令人困惑。 函数式方法胜利了(还要注意的是,事物不仅可以是const,而且在构建后甚至不会被改变):

const numbers = [1,4,9,16];
const sum = numbers.reduce((acc, val) => acc + val);
const onlyEvenNumbers = numbers.filter(num => num % 2 == 0);

清晰明了地说,我最近做了一些PHP,之前几年几乎只做JavaScript,回到“旧的方式”真是受不了。 - Daniel

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