小编典典

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

all

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

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

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

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

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


阅读 57

收藏
2022-06-13

共1个答案

小编典典

好的!

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

实施times_repeat

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

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

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

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

  • 不再有丑陋的单分支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以使其堆栈安全。

下面的代码 确实 使用了可变变量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

这会让很多人说“但这不起作用!” ——我知道,放轻松。 我们可以使用纯表达式 为常量空间循环实现 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
2022-06-13