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

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中有更好的机制。


当前回答

答案:2015年12月9日

就我个人而言,我发现公认的答案既简洁(好)又简洁(坏)。欣赏这个说法可能是主观的,所以请阅读这个答案,看看你是否同意

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

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

在JS中使用下面的方法来表达这一点将允许:

times(x)(doStuff(i));

代码如下:

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

就是这样!

简单的示例用法:

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

times(3)(cheer);

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

或者,下面是被接受的答案的例子:

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'

边注-定义一个范围函数

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

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

下划线

_.range(x, x + n)

ES2015

一些替代方案:

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

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

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

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

其他回答

OK!

下面的代码是使用ES6语法编写的,但也可以用ES5或更少的语法编写。ES6不要求创建“循环x次的机制”


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

Const times = x => f => { If (x > 0) { f () 乘以(x - 1) (f) } } //使用它 Times (3) (() => console.log('hi')) //或为重用定义中间函数 令twice =乘以(2) //两倍的幂! Twice (() => console.log('double vision'))

如果确实需要迭代器,可以使用带counter形参的命名内部函数为您迭代

Const times = n => f => { if = I => { 如果(i === n)返回 f(我) Iter (i + 1) } 返回iter (0) } Times (3) (i => console.log(i, 'hi'))


如果你不想学习更多的东西,请停止阅读这里…

但是有些东西应该让人感觉不舒服……

如果语句是丑陋的单个分支-在另一个分支上发生了什么? 函数体中的多个语句/表达式——是否混合了过程关注点? 隐式返回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. Int -> (a -> a 常量repeat = n => f => x => { If (n > 0) 返回(n - 1) (f) (f (x)) 其他的 返回x } // power:: Int -> Int -> Int Const power = base => exp => { //重复<exp>次,<base> * <x>,从1开始 返回repeat (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. Int -> (a -> a 常量repeat = n => f => x => { If (n > 0) 返回(n - 1) (f) (f (x)) 其他的 返回x } // times:: Int -> (Int -> Int) -> Int Const times = n=> f=> 重复(n) (i => (f(i), i + 1)) (0) //使用它 Times (3) (i => console.log(i, 'hi'))

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

我们也修正了我们的项目列表问题

不再有丑陋的单个分支if语句 单一表达式体表示良好分离的关注点 没有更多无用的,隐式返回未定义


JavaScript逗号运算符

如果你看不清最后一个例子是如何工作的,这取决于你对JavaScript最古老的战斧之一的认识;逗号操作符——简而言之,它从左到右计算表达式,并返回最后求值的表达式的值

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

在上面的例子中,我使用

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

这是一种简洁的写法吗

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

尾部呼叫优化

尽管递归实现很吸引人,但在这一点上,我推荐它们是不负责任的,因为我能想到的JavaScript VM都不支持正确的尾部调用消除- babel用来编译它,但它已经在“broken;将在一年多的时间里重新实施“状态”。

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

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

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

// repeat:: Int -> (a -> a) -> (a -> a) Const repeat = n => f => x => { 令m = 0, acc = x 当(m < n) (m = m + 1, acc = f (acc)) 返回acc } // inc:: Int -> Int Const inc = x => X + 1 Console.log (repeat (1e8) (inc) (0)) / / 100000000

你们很多人都会说"那根本没用" -我知道,放松点。我们可以使用纯表达式实现clojure风格的循环/递归接口,用于常量空间循环;没有那些while之类的东西。

这里我们对循环函数进行了抽象——它寻找一个特殊的递归类型来保持循环运行。当遇到非递归类型时,循环结束并返回计算结果

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

答案:2015年12月9日

就我个人而言,我发现公认的答案既简洁(好)又简洁(坏)。欣赏这个说法可能是主观的,所以请阅读这个答案,看看你是否同意

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

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

在JS中使用下面的方法来表达这一点将允许:

times(x)(doStuff(i));

代码如下:

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

就是这样!

简单的示例用法:

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

times(3)(cheer);

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

或者,下面是被接受的答案的例子:

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'

边注-定义一个范围函数

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

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

下划线

_.range(x, x + n)

ES2015

一些替代方案:

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

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

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

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

我认为这很简单:

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

or

Array(3).fill()

此解决方案的优点

最容易阅读/使用(我觉得) 返回值可以用作和,也可以忽略 普通es6版本,也链接到TypeScript版本的代码

缺点 ——突变。只是内在我不在乎,也许其他人也不在乎。

示例和代码

times(5, 3)                       // 15    (3+3+3+3+3)

times(5, (i) => Math.pow(2,i) )   // 31    (1+2+4+8+16)

times(5, '<br/>')                 // <br/><br/><br/><br/><br/>

times(3, (i, count) => {          // name[0], name[1], name[2]
    let n = 'name[' + i + ']'
    if (i < count-1)
        n += ', '
    return n
})

function times(count, callbackOrScalar) {
    let type = typeof callbackOrScalar
    let sum
    if (type === 'number') sum = 0
    else if (type === 'string') sum = ''

    for (let j = 0; j < count; j++) {
        if (type === 'function') {
            const callback = callbackOrScalar
            const result = callback(j, count)
            if (typeof result === 'number' || typeof result === 'string')
                sum = sum === undefined ? result : sum + result
        }
        else if (type === 'number' || type === 'string') {
            const scalar = callbackOrScalar
            sum = sum === undefined ? scalar : sum + scalar
        }
    }
    return sum
}

TypeScipt版本 https://codepen.io/whitneyland/pen/aVjaaE?editors=0011

我把它放在这里。如果你正在寻找一个不使用数组的紧凑函数,并且你对可变性/不可变性没有问题:

var g =x=>{/*your code goes here*/x-1>0?g(x-1):null};