Есть ли механизм для цикла x раз в ES6 (ECMAScript 6) без изменяемых переменных?

Типичным способом цикла 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 появится лучший механизм.

Ответ 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'))

Ответ 2

Использование оператора 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);

Или если вам не нужен результат:

[...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

Ответ 3

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

Ответ 4

Я думаю, что лучшим решением является использование let:

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

Это создаст новую переменную i для каждой оценки тела и гарантирует, что i будет изменяться только в выражении приращения в синтаксисе цикла, а не где-либо еще.

Я мог бы обмануть и создать свой собственный генератор. По крайней мере, i++ скрывается:)

Это должно быть достаточно imo. Даже на чистых языках все операции (или, по крайней мере, их переводчики) построены из примитивов, которые используют мутацию. Пока это правильно, я не вижу, что с этим не так.

Вы должны быть в порядке с

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

Но я не хочу использовать оператор ++ или иметь какие-либо изменяемые переменные вообще.

Тогда ваш единственный выбор - использовать рекурсию. Вы можете определить эту функцию генератора без изменяемого i:

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

Но это кажется излишним для меня и может иметь проблемы с производительностью (поскольку устранение хвостового вызова недоступно для return yield*).

Ответ 5

Ответ: 09 декабря 2015

Лично я нашел принятый ответ как кратким (хорошим), так и кратким (плохим). Оцените это утверждение, возможно, субъективным, поэтому, пожалуйста, прочитайте этот ответ и посмотрите, согласны ли вы или не согласны

Пример, заданный в вопросе, был чем-то вроде Ruby's:

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()) оказался немного быстрее.

Ответ 6

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

Этот фрагмент будет console.log test 4 раза.

Ответ 7

Я думаю, это довольно просто:

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

или

Array(3).fill()

Ответ 8

Не то, что я буду преподавать (или когда-либо использовать в своем коде), но здесь достойное codegolf решение без изменения переменной, не нужно ES6:

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

На самом деле больше интересного доказательства концепции, чем полезный ответ.

Ответ 9

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

Эта версия удовлетворяет требованиям OP для неизменяемости. Также рассмотрите возможность использования reduce вместо map в зависимости от вашего варианта использования.

Это также вариант, если вы не возражаете против небольшой мутации в прототипе.

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

Теперь мы можем сделать это

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

+1 к arcseldon для предложения .fill.

Ответ 10

Afaik, в ES6 нет механизма, аналогичного методу Ruby times. Но вы можете избежать мутации, используя рекурсию:

let times = (i, cb, l = i) => {
  if (i === 0) return;

  cb(l - i);
  times(i - 1, cb, l);
}

times(5, i => doStuff(i));

Демо: http://jsbin.com/koyecovano/1/edit?js,console

Ответ 11

Если вы хотите использовать библиотеку, также lodash _.times или подчеркивание _.times:

_.times(x, i => {
   return doStuff(i)
})

Обратите внимание, что это возвращает массив результатов, поэтому он больше похож на этот рубин:

x.times.map { |i|
  doStuff(i)
}

Ответ 12

В функциональной парадигме repeat обычно есть бесконечная рекурсивная функция. Чтобы использовать его, нам нужен ленивый метод оценки или продолжения прохождения.

Ложное вычисление функции повторения

const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

Ответ 13

Преимущества этого решения

  • Проще всего читать/использовать (imo)
  • Возвращаемое значение может использоваться как сумма или просто игнорируется
  • Версия Plain 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

Ответ 14

обращаясь к функциональному аспекту:

function times(n, f) {
    var _f = function (f) {
        var i;
        for (i = 0; i < n; i++) {
            f(i);
        }
    };
    return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
    console.log('in parts: ' + v);
});
times(6, function (v) {
    console.log('complete: ' + v);
});

Ответ 15

Генераторы? Рекурсия? Почему так много хатина на мутатине?; -)

Если это приемлемо, если мы "спрячем" его, то просто примите использование унарного оператора, и мы можем держать вещи простыми:

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

Как и в ruby:

> (3).times(console.log)
0
1
2