Javascript 是否有一种机制可以在没有可变变量的情况下在 ES6(ECMAScript 6)中循环 x 次?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30452263/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:11:48  来源:igfitidea点击:

Is there a mechanism to loop x times in ES6 (ECMAScript 6) without mutable variables?

javascriptgeneratorecmascript-6ecmascript-harmony

提问by at.

The typical way to loop xtimes in JavaScript is:

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

for (var i = 0; i < x; i++)
  doStuff(i);

But I don't want to use the ++operator or have any mutable variables at all. So is there a way, in ES6, to loop xtimes another way? I love Ruby's mechanism:

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

x.times do |i|
  do_stuff(i)
end

Anything similar in JavaScript/ES6? I could kind of cheat and make my own generator:

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

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

for (var i of times(5)) {
  console.log(i);
}

Of course I'm still using i++. At least it's out of sight :), but I'm hoping there's a better mechanism in ES6.

当然我还在用i++。至少它不在视线范围内:),但我希望 ES6 中有更好的机制。

回答by Tieme

Using the ES2015 Spread operator:

使用ES2015 扩展运算符

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

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

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

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

Or if you don't need the result:

或者,如果您不需要结果:

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

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

Or using the ES2015 Array.from operator:

或者使用ES2015 Array.from 运算符

Array.from(...)

Array.from(...)

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);


Note that if you just need a string repeated you can use String.prototype.repeat.

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

console.log("0".repeat(10))
// 0000000000

回答by Thank you

OK!

好的!

The code below is written using ES6 syntaxes but could just as easily be written in ES5 or even less. ES6 is nota requirement to create a "mechanism to loop x times"

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



If you don't need the iterator in the callback, this is the most simple implementation

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

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'))

If you do need the iterator, you can use a named inner function with a counter parameter to iterate for you

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

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'))



Stop reading here if you don't like learning more things ...

如果您不喜欢学习更多东西,请停止阅读...

But something should feel off about those...

但是这些东西应该让人感觉有些不对劲……

  • single branch ifstatements are ugly — what happens on the other branch ?
  • multiple statements/expressions in the function bodies — are procedure concerns being mixed ?
  • implicitly returned undefined— indication of impure, side-effecting function
  • 单个分支if语句很难看——在另一个分支上会发生什么?
  • 函数体中的多个语句/表达式 -过程问题是否混合?
  • 隐式返回undefined— 指示不纯的、有副作用的函数

"Isn't there a better way ?"

“没有更好的办法吗?”

There is. Let's first revisit our initial implementation

有。让我们首先回顾一下我们最初的实现

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

Sure, it's simple, but notice how we just call f()and don't do anything with it. This really limits the type of function we can repeat multiple times. Even if we have the iterator available, f(i)isn't much more versatile.

当然,这很简单,但请注意我们如何只调用f()而不用它做任何事情。这确实限制了我们可以多次重复的函数类型。即使我们有可用的迭代器,f(i)也不是通用的多。

What if we start with a better kind of function repetition procedure ? Maybe something that makes better use of input and output.

如果我们从一种更好的函数重复程序开始呢?也许可以更好地利用输入和输出的东西。

Generic function repetition

泛型函数重复

// 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

Above, we defined a generic repeatfunction which takes an additional input which is used to start the repeated application of a single function.

上面,我们定义了一个通用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)))


Implementing timeswith repeat

实现timesrepeat

Well this is easy now; almost all of the work is already done.

现在这很容易;几乎所有的工作都已经完成。

// 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'))

Since our function takes ias an input and returns i + 1, this effectively works as our iterator which we pass to feach time.

由于我们的函数将i作为输入并返回i + 1,因此它可以有效地用作我们f每次传递给的迭代器。

We've fixed our bullet list of issues too

我们也修复了问题的项目符号列表

  • No more ugly single branch ifstatements
  • Single-expression bodies indicate nicely separated concerns
  • No more useless, implicitly returned undefined
  • 不再有丑陋的单分支if语句
  • 单表达式主体表示很好分离的关注点
  • 不再无用,隐式返回 undefined


JavaScript comma operator, the

JavaScript 逗号运算符,

In case you're having trouble seeing how the last example is working, it depends on your awareness of one of JavaScript's oldest battle axes; the comma operator– in short, it evaluates expressions from left to right and returnsthe value of the last evaluated expression

如果您在查看最后一个示例的工作方式时遇到问题,这取决于您对 JavaScript 最古老的战斗轴之一的认识;的逗号操作符-总之,它从左至右计算表达式和返回最后计算的表达式的值

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

In our above example, I'm using

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

(i => (f(i), i + 1))

which is just a succinct way of writing

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

(i => { f(i); return i + 1 })


Tail Call Optimisation

尾调用优化

As sexy as the recursive implementations are, at this point it would be irresponsible for me to recommend them given that no JavaScript VMI can think of supports proper tail call elimination – babel used to transpile it, but it's been in "broken; will reimplement" status for well over a year.

与递归实现一样性感,在这一点上,我推荐它们是不负责任的,因为我认为没有任何JavaScript VM支持正确的尾调用消除——babel 曾经用于转译它,但它已经“损坏;将重新实现” “状态超过一年。

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

As such, we should revisit our implementation of repeatto make it stack-safe.

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

The code below doesuse mutable variables nand xbut note that all mutations are localized to the repeatfunction – no state changes (mutations) are visible from outside of the function

下面的代码确实使用了可变变量nx但请注意,所有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

This is going to have a lot of you saying "but that's not functional !" – I know, just relax. We can implement a Clojure-style loop/recurinterface for constant-space looping using pure expressions; none of that whilestuff.

这会让很多人说“但这不起作用!” ——我知道,放松点。我们可以使用纯表达式为常量空间循环实现 Clojure 风格的loop/recur接口;没有那些东西。while

Here we abstract whileaway with our loopfunction – it looks for a special recurtype to keep the loop running. When a non-recurtype is encountered, the loop is finished and the result of the computation is returned

在这里,我们抽象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

回答by zerkms

for (let i of Array(100).keys()) {
    console.log(i)
}

回答by Bergi

I think the best solution is to use let:

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

for (let i=0; i<100; i++) …

That will create a new (mutable) ivariable for each body evaluation and assures that the iis only changed in the increment expression in that loop syntax, not from anywhere else.

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

I could kind of cheat and make my own generator. At least i++is out of sight :)

我可以作弊并制作自己的发电机。至少i++是看不见的:)

That should be enough imo. Even in pure languages all operations (or at least, their interpreters) are built from primitives that use mutation. As long as it is properly scoped, I cannot see what is wrong with that.

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

You should be fine with

你应该没问题

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

But I don't want to use the ++operator or have any mutable variables at all.

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

Then your only choice is to use recursion. You can define that generator function without a mutable ias well:

那么你唯一的选择就是使用递归。您也可以定义没有可变的生成器函数i

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

But that seems overkill to me and might have performance problems (as tail call elimination is not available for return yield*).

但这对我来说似乎有点矫枉过正,并且可能会出现性能问题(因为尾调用消除不适用于return yield*)。

回答by Gergely Fehérvári

I think it is pretty simple:

我认为这很简单:

[...Array(3).keys()]

or

或者

Array(3).fill()

回答by Hossam Mourad

const times = 4;
new Array(times).fill().map(() => console.log('test'));

This snippet will console.logtest4 times.

此代码段将出现console.logtest4 次。

回答by arcseldon

Answer: 09 December 2015

答案:2015 年 12 月 9 日

Personally, I found the accepted answer both concise (good) and terse (bad). Appreciate this statement might be subjective, so please read this answer and see if you agree or disagree

就个人而言,我发现公认的答案既简洁(好)又简洁(坏)。欣赏此声明可能是主观的,因此请阅读此答案,看看您是否同意或不同意

The example given in the question was something like Ruby's:

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

x.times do |i|
  do_stuff(i)
end

Expressing this in JS using below would permit:

使用以下在 JS 中表达这一点将允许:

times(x)(doStuff(i));

Here is the code:

这是代码:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};

That's it!

就是这样!

Simple example usage:

简单示例用法:

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

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!

Alternatively, following the examples of the accepted answer:

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

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'

Side note - Defining a range function

旁注 - 定义范围函数

A similar / related question, that uses fundamentally very similar code constructs, might be is there a convenient Range function in (core) JavaScript, something similar to underscore's range function.

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

Create an array with n numbers, starting from x

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

Underscore

下划线

_.range(x, x + n)

ES2015

ES2015

Couple of alternatives:

几个替代方案:

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

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

Demo using n = 10, x = 1:

使用 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 ]

In a quick test I ran, with each of the above running a million times each using our solution and doStuff function, the former approach (Array(n).fill()) proved slightly faster.

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

回答by Tom

Array(100).fill().map((_,i)=> console.log(i) );

This version satisifies the OP's requirement for immutability. Also consider using reduceinstead of mapdepending on your use case.

此版本满足 OP 对不变性的要求。还要考虑使用reduce而不是map取决于您的用例。

This is also an option if you don't mind a little mutation in your prototype.

如果您不介意原型中的一点点变化,这也是一个选择。

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

Now we can do this

现在我们可以这样做

((3).times(i=>console.log(i)));

+1 to arcseldon for the .fillsuggestion.

+1 Arcseldon 的.fill建议。

回答by doldt

Not something I would teach (or ever use in my code), but here's a codegolf-worthy solution without mutating a variable, no need for ES6:

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

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})

More of an interesting proof-of-concept thing than a useful answer, really.

实际上,与其说是一个有用的答案,不如说是一个有趣的概念验证。

回答by oemera

Here is another good alternative:

这是另一个不错的选择:

Array.from({ length: 3}).map(...);