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

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

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

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

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

JavaScript/ES6 中有什么类似的东西吗?我可以作弊并制作自己的生成器:

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

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

当然,我仍然在使用.至少它:)看不到,但我希望ES6中有一个更好的机制。i++


答案 1

使用 ES2015 点差运算符

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

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

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

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

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

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

或者使用 ES2015 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);

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

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

答案 2

还行!

下面的代码是使用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'))

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

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

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

但是应该对那些人感到有些不对劲...

  • 单分支语句很丑陋 — 另一个分支上会发生什么?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)
  }
}

当然,这很简单,但请注意我们如何打电话,不要用它做任何事情。这确实限制了我们可以重复多次的函数类型。即使我们有可用的迭代器,也不是更通用的。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

上面,我们定义了一个泛型函数,该函数需要一个额外的输入,用于启动单个函数的重复应用。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)))

重复实现时间

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

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

由于我们的函数作为输入并返回,这有效地充当了我们每次传递给的迭代器。ii + 1f

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

  • 不再有丑陋的单分支语句if
  • 单一表达主体表示分离的关注点
  • 不再无用,隐式返回undefined

JavaScript 逗号运算符,

如果你在看到最后一个例子是如何工作的时遇到困难,这取决于你对JavaScript最古老的战斧之一的认识;逗号运算符 – 简而言之,它从左到右计算表达式并返回上次计算表达式的值

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

在上面的例子中,我使用

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

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

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

尾部呼叫优化

尽管递归实现很性感,但在这一点上,我推荐它们是不负责任的,因为我能想到的JavaScript VM都不支持适当的尾部调用消除 - babel用于转译它,但它已经处于“破碎;将重新实现“状态超过一年。

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

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

下面的代码确实使用了可变变量,但请注意,所有突变都局限于函数 - 从函数外部看不到状态变化(突变)。nxrepeat

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

这会让你很多人说“但那不起作用!”——我知道,放松一下。我们可以使用纯表达式实现 Clojure 样式 /接口,用于常量空间循环;没有这些东西。looprecurwhile

在这里,我们抽象出我们的函数 - 它寻找一种特殊的类型来保持循环运行。遇到非类型时,循环完成并返回计算结果whilelooprecurrecur

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