是否存在在ES6(ECMAScript 6)中循环x次而没有可变变量的机制?

at.*_*at. 132 javascript generator ecmascript-harmony ecmascript-6

x在JavaScript中循环时间的典型方法是:

for (var i = 0; i < x; i++)
  doStuff(i);
Run Code Online (Sandbox Code Playgroud)

但我不想使用++运算符或根本没有任何可变变量.那么,在ES6中,有x另一种方式循环时间吗?我喜欢Ruby的机制:

x.times do |i|
  do_stuff(i)
end
Run Code Online (Sandbox Code Playgroud)

在JavaScript/ES6中有类似的东西吗?我可以作弊并制造自己的发电机:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}
Run Code Online (Sandbox Code Playgroud)

我当然还在用i++.至少它看不见:),但我希望在ES6中有更好的机制.

Tie*_*eme 206

使用ES2015 Spread运算符:

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => {
  return i * 10;
});

// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);
Run Code Online (Sandbox Code Playgroud)

或者如果您不需要结果:

[...Array(10)].forEach((_, i) => {
  console.log(i);
});

// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));
Run Code Online (Sandbox Code Playgroud)

请注意,如果您只需要重复一个字符串,则可以使用 String.prototype.repeat.

const res = Array.from(Array(10)).map((_, i) => {
  return i * 10;
});

// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);
Run Code Online (Sandbox Code Playgroud)

  • 更好:`Array.from(Array(10),(_,i)=> i*10)` (25认同)
  • 所以你分配**整个**元素的N个元素只是为了扔掉它? (7认同)
  • 这应该是最好的答案.那么ES6!太棒了! (5认同)
  • 如果不需要迭代器(i),则可以同时排除键和值,以使其成为:`[... Array(10)]。forEach(()=&gt; console.log('循环10次' );` (3认同)
  • 有没有人解决过 Kugel 之前的评论?我想知道同样的事情 (2认同)
  • @sebpiq因为Array(10)函数返回一个长度设置为10的空数组实例。该数组实例本质上是在内存中分配的,但是是空的。如果您尝试对其进行 map() ,它将失败,因为数组为空。但是,当您尝试展开它时,展开运算符将返回与数组长度相同数量的项目。由于数组为空,因此这些项未定义(不存在),因此 spread 将为您提供 10 个元素 === 未定义。因此 (_, i) =&gt; {} 语法始终忽略第一个(始终未定义)参数。 (2认同)

Tha*_*you 137

好!

下面的代码是使用ES6语法编写的,但可以很容易地用ES5编写,甚至更少.ES6 不是创建"循环x次机制"的要求


如果在回调中不需要迭代器,那么这是最简单的实现

const times = x => f => {
  if (x > 0) {
    f()
    times (x - 1) (f)
  }
}

// use it
times (3) (() => console.log('hi'))

// or define intermediate functions for reuse
let twice = times (2)

// twice the power !
twice (() => console.log('double vision'))
Run Code Online (Sandbox Code Playgroud)

如果确实需要迭代器,可以使用带有计数器参数的命名内部函数来迭代

const times = n => f => {
  let iter = i => {
    if (i === n) return
    f (i)
    iter (i + 1)
  }
  return iter (0)
}

times (3) (i => console.log(i, 'hi'))
Run Code Online (Sandbox Code Playgroud)


如果你不喜欢学习更多的东西,请不要在这里阅读...

但是对于那些......

  • 单个分支if语句很难看 - 另一个分支上发生了什么?
  • 函数体中的多个语句/表达式 - 是否混合了程序问题?
  • 隐含地返回undefined- 表示不纯的,副作用的功能

"难道没有更好的方法吗?"

有.让我们首先重新审视我们的初始实施

// times :: Int -> (void -> void) -> void
const times = x => f => {
  if (x > 0) {
    f()               // has to be side-effecting function
    times (x - 1) (f)
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,这很简单,但请注意我们如何打电话f(),不做任何事情.这确实限制了我们可以重复多次的功能类型.即使我们有可用的迭代器,f(i)也不会更加通用.

如果我们从更好的函数重复过程开始怎么办?也许更好地利用输入和输出的东西.

通用函数重复

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// power :: Int -> Int -> Int
const power = base => exp => {
  // repeat <exp> times, <base> * <x>, starting with 1
  return repeat (exp) (x => base * x) (1)
}

console.log(power (2) (8))
// => 256
Run Code Online (Sandbox Code Playgroud)

上面,我们定义了一个泛型repeat函数,它接受一个额外的输入,用于启动单个函数的重复应用.

// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)

// is the same as ...
var result = f(f(f(x)))
Run Code Online (Sandbox Code Playgroud)

实现timesrepeat

那么现在这很容易; 几乎所有的工作都已完成.

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// times :: Int -> (Int -> Int) -> Int 
const times = n=> f=>
  repeat (n) (i => (f(i), i + 1)) (0)

// use it
times (3) (i => console.log(i, 'hi'))
Run Code Online (Sandbox Code Playgroud)

由于我们的函数i作为输入并返回i + 1,因此这有效地作为我们f每次传递的迭代器.

我们也修复了问题的清单

  • 没有更丑陋的单一分支if陈述
  • 单表达式主体表示很好的分离关注点
  • 没有更多的无用,隐含地返回 undefined

JavaScript逗号运算符,

如果你无法看到最后一个例子是如何工作的,那么这取决于你对JavaScript最古老的战斧之一的认识; 的逗号操作符 -总之,它从左至右计算表达式和返回最后计算的表达式的值

(expr1 :: a, expr2 :: b, expr3 :: c) :: c
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我正在使用

(i => (f(i), i + 1))
Run Code Online (Sandbox Code Playgroud)

这只是一种简洁的写作方式

(i => { f(i); return i + 1 })
Run Code Online (Sandbox Code Playgroud)

尾调用优化

由于递归实现的性感,在这一点上我推荐它们是不负责任的,因为没有我能想到的JavaScript VM支持正确的尾部调用消除 - babel用于转换它,但它已被"破坏;将重新实现"状态超过一年.

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
Run Code Online (Sandbox Code Playgroud)

因此,我们应该重新审视我们的实现,repeat以使其安全堆栈.

下面的代码确实使用了可变变量n,x但请注意所有突变都定位到repeat函数中 - 从函数外部看不到状态变化(突变)

// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
  {
    let m = 0, acc = x
    while (m < n)
      (m = m + 1, acc = f (acc))
    return acc
  }

// inc :: Int -> Int
const inc = x =>
  x + 1

console.log (repeat (1e8) (inc) (0))
// 100000000
Run Code Online (Sandbox Code Playgroud)

这将有很多人说"但这不起作用!" - 我知道,放松吧.我们可以使用纯表达式实现Clojure样式loop/ recur接口以进行常量空间循环; 没有那些东西.while

在这里,我们while使用我们的loop函数抽象出来- 它寻找一种特殊的recur类型来保持循环运行.recur遇到非类型时,循环结束并返回计算结果

const recur = (...args) =>
  ({ type: recur, args })
  
const loop = f =>
  {
    let acc = f ()
    while (acc.type === recur)
      acc = f (...acc.args)
    return acc
  }

const repeat = $n => f => x =>
  loop ((n = $n, acc = x) =>
    n === 0
      ? acc
      : recur (n - 1, f (acc)))
      
const inc = x =>
  x + 1

const fibonacci = $n =>
  loop ((n = $n, a = 0, b = 1) =>
    n === 0
      ? a
      : recur (n - 1, b, a + b))
      
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100))        // 354224848179262000000
Run Code Online (Sandbox Code Playgroud)

  • 似乎过于复杂(我特别困惑于`g => g(g)(x)`).高阶函数比第一阶函数有什么好处,比如我的解决方案? (19认同)
  • 该答案似乎已经被接受并且评分很高,因为它必须付出很多努力,但是我认为这不是一个好答案。该问题的正确答案是“否”。像您一样列出解决方法会很有帮助,但是在此之后立即声明有更好的方法。您为什么不把答案放在最前面呢?您为什么要解释逗号运算符?你为什么要提起Clojure?通常,为什么一个带有2个字符的答案的问题有很多切线?简单的问题不仅仅是用户就一些简洁的编程事实进行演示的平台。 (5认同)
  • @Timofey 这个答案是 2 年时间里多次编辑的汇编。我同意这个答案确实需要一些最终编辑,但您的编辑删除了太多。我会尽快重新审视它,认真考虑您的评论和编辑建议。 (3认同)

zer*_*kms 31

for (let i of Array(100).keys()) {
    console.log(i)
}
Run Code Online (Sandbox Code Playgroud)


Ber*_*rgi 24

我认为最好的解决方案是使用let:

for (let i=0; i<100; i++) …
Run Code Online (Sandbox Code Playgroud)

这将为i每个主体评估创建一个新的(可变)变量,并确保i仅在该循环语法中的增量表达式中更改,而不是从其他任何位置更改.

我可以欺骗自己的发电机.至少i++是看不见:)

这应该足够了.即使在纯语言中,所有操作(或至少是它们的解释器)都是使用使用变异的原语构建的.只要它适当的范围,我看不出有什么问题.

你应该没问题

function* times(n) {
  for (let i = 0; i < x; i++)
    yield i;
}
for (const i of times(5))
  console.log(i);
Run Code Online (Sandbox Code Playgroud)

但我不想使用++运算符或根本没有任何可变变量.

那么你唯一的选择是使用递归.您也可以在没有可变的情况下定义该生成器函数i:

function* range(i, n) {
  if (i >= n) return;
  yield i;
  return yield* range(i+1, n);
}
times = (n) => range(0, n);
Run Code Online (Sandbox Code Playgroud)

但这对我来说似乎有些过分,并且可能存在性能问题(因为尾部调用消除不可用return yield*).

  • 这很简单,而且一点都不像上面的许多答案那样分配数组 (2认同)

oem*_*era 24

这是另一个不错的选择:

Array.from({ length: 3}).map(...);
Run Code Online (Sandbox Code Playgroud)

最好,正如@Dave Morse 在评论中指出的那样,您还可以map通过使用Array.from函数的第二个参数来摆脱调用,如下所示:

Array.from({ length: 3 }, () => (...))
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案!一个小建议 - 您已经通过 Array.from 免费获得了所需的类似地图的功能: `Array.from({ length: label.length }, (_, i) =&gt; (...))` 这可以节省创建一个空的临时数组只是为了启动对地图的调用。 (5认同)
  • MDN 上的“Array.from”:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from (2认同)

Hos*_*rad 12

const times = 4;
new Array(times).fill().map(() => console.log('test'));
Run Code Online (Sandbox Code Playgroud)

这个片段将是console.log test4次.

  • @AamirAfridi您可以检查“浏览器兼容性”部分,还提供了一个polyfill:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill (2认同)

arc*_*don 11

答案:2015年12月9日

就个人而言,我发现接受的答案既简洁(好)又简洁(坏).欣赏这个陈述可能是主观的,所以请阅读这个答案,看看你是否同意或不同意

问题中给出的例子类似于Ruby:

x.times do |i|
  do_stuff(i)
end
Run Code Online (Sandbox Code Playgroud)

使用下面的JS表达这一点将允许:

times(x)(doStuff(i));
Run Code Online (Sandbox Code Playgroud)

这是代码:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};
Run Code Online (Sandbox Code Playgroud)

而已!

简单示例用法:

let cheer = () => console.log('Hip hip hooray!');

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!
Run Code Online (Sandbox Code Playgroud)

或者,按照接受的答案的例子:

let doStuff = (i) => console.log(i, ' hi'),
  once = times(1),
  twice = times(2),
  thrice = times(3);

once(doStuff);
//0 ' hi'

twice(doStuff);
//0 ' hi'
//1 ' hi'

thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'
Run Code Online (Sandbox Code Playgroud)

附注 - 定义范围功能

一个类似/相关的问题,使用基本上非常相似的代码结构,可能是(核心)JavaScript中有一个方便的Range函数,类似于下划线的范围函数.

从x开始创建一个包含n个数字的数组

下划线

_.range(x, x + n)
Run Code Online (Sandbox Code Playgroud)

ES2015

几种替代品:

Array(n).fill().map((_, i) => x + i)

Array.from(Array(n), (_, i) => x + i)
Run Code Online (Sandbox Code Playgroud)

使用n = 10,x = 1的演示:

> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Run Code Online (Sandbox Code Playgroud)

在一个快速测试中,我使用我们的解决方案和doStuff函数运行上述每个运行一百万次,前一种方法(Array(n).fill())证明稍微快一些.


Ger*_*ári 10

我认为这很简单:

[...Array(3).keys()]
Run Code Online (Sandbox Code Playgroud)

要么

Array(3).fill()
Run Code Online (Sandbox Code Playgroud)


J G*_*cia 10

我迟到了,但由于这个问题经常出现在搜索结果中,我只想添加一个我认为在可读性方面最好的解决方案,但不长(这对于任何代码库 IMO 都是理想的) . 它会变异,但我会为 KISS 原则做出权衡。

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0
Run Code Online (Sandbox Code Playgroud)

  • 感谢您在我只能描述为高阶 lambda 迷恋派对中发出理性的声音。我也最终在谷歌上的无伤大雅的第一次点击路径上结束了这个问答,并且很快我的理智就被这里的大多数答案亵渎了。您是列表中第一个我认为可以直接解决简单问题的解决方案。 (4认同)

Tom*_*Tom 7

Array(100).fill().map((_,i)=> console.log(i) );
Run Code Online (Sandbox Code Playgroud)

该版本满足OP对不变性的要求.也可以考虑使用reduce而不是map 根据您的使用情况.

如果您不介意原型中的一点突变,这也是一个选项.

Number.prototype.times = function(f) {
   return Array(this.valueOf()).fill().map((_,i)=>f(i));
};
Run Code Online (Sandbox Code Playgroud)

现在我们可以做到这一点

((3).times(i=>console.log(i)));
Run Code Online (Sandbox Code Playgroud)

.fill建议+1到arcseldon .


dol*_*ldt 6

不是我会教的东西(或者在我的代码中使用过),但是这里有一个代码高尔夫的解决方案而不需要改变变量,不需要ES6:

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})
Run Code Online (Sandbox Code Playgroud)

真的,更多的是一个有趣的概念验证,而不是一个有用的答案.