【问题标题】:What is the advantage of using a function over loops?使用函数而不是循环有什么好处?
【发布时间】:2015-12-21 18:35:27
【问题描述】:

似乎函数式迭代器正在取代 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;
}

【问题讨论】:

  • 嗯,不是闭包,而是方法的作用? mapreduce 对数组执行特定的操作,它们不会像 for 循环那样迭代?
  • 简洁(使用 es6 的箭头函数会更好)
  • 如果你不喜欢它们,你不必使用它们,大多数事情都可以在没有新的(ish)数组方法的情况下完成。
  • 这里传递给.map()的匿名回调形成的闭包基本上与任何代码的工作方式无关。函数本身当然可以,但调用形成的闭包不会。

标签: javascript loops closures


【解决方案1】:

我不知道您为什么将使用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 ]

它结合了过滤器和地图。

更远一点,如果您打算编写一个迭代数组的生成器,并从每个元素产生一些计算,您将需要使用 for 循环,因为没有办法从内部产生forEach 回调:

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!!
}

【讨论】:

    【解决方案2】:

    这是一种范式转变。后者是命令式编程的一种形式,其中用户是创建计算机使用的数据的人。前者本质上是一种基于数学的更基于函数的方法,利用已经存在的数据(代码)。

    两者在理论上(处理)上没有优势,尽管应用于计算机的当前状态,但随着处理能力的增强,函数式编程变得更加有用。

    函数式编程允许一种基于数学的推理形式,强调输入和输出。由于函数是一流的数据类型,JavaScript 尤其擅长处理这种风格

    【讨论】:

      【解决方案3】:

      这里已经有了很好的答案。我会随着时间的推移添加一件事:当你以“命令式”/旧方式做事时,它往往会鼓励一种带有大量中间变量、可变事物以及“当我在迭代我还不如在相同的数据上做其他事情”,这是代码设计中最大的陷阱——为了可疑的性能提升而牺牲关注点分离。考虑这个例子

      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 var 中存储的内容,直到您阅读到 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);
      

      【讨论】:

      • ...大声清晰。在几乎只做 js 几年后,我最近做了一些 php,并且无法忍受回到“旧方式”?
      猜你喜欢
      • 2014-08-28
      • 1970-01-01
      • 2021-05-10
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 2022-08-03
      • 2015-05-19
      • 2016-05-26
      相关资源
      最近更新 更多