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

Типичный способ зацикливания x раз в JavaScript:

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

Но я не хочу использовать оператор ++ или вообще иметь какие-либо изменяемые переменные. Так есть ли способ в ES6 зациклить 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);
}

Конечно, я все еще использую i++. По крайней мере, это вне поля зрения :), но я надеюсь, что в ES6 есть лучший механизм.


person at.    schedule 26.05.2015    source источник
comment
Почему изменяемая переменная управления циклом является проблемой? Просто принцип?   -  person doldt    schedule 26.05.2015
comment
@doldt - я пытаюсь научить JavaScript, но я экспериментирую с откладыванием концепции изменяемых переменных на потом   -  person at.    schedule 26.05.2015
comment
Мы действительно уходим от темы, но вы уверены, что переход к генераторам ES6 (или любой другой новой концепции высокого уровня) является хорошей идеей, прежде чем они узнают об изменяемых переменных? :)   -  person doldt    schedule 26.05.2015
comment
@doldt - может быть, я экспериментирую. Применение функционального языкового подхода к JavaScript.   -  person at.    schedule 26.05.2015
comment
Используйте let, чтобы объявить эту переменную в цикле. Его область действия заканчивается циклом.   -  person ncmathsadist    schedule 23.07.2020


Ответы (21)


OK!

Приведенный ниже код написан с использованием синтаксиса 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, чтобы сделать ее безопасной для стека.

В приведенном ниже коде действительно используются изменяемые переменные n и x, но обратите внимание, что все мутации локализованы в функции 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

Это заставит многих из вас сказать: «Но это не работает!» - Я знаю, просто расслабься. Мы можем реализовать интерфейс loop/recur в стиле Clojure для циклов с постоянным пространством, используя чистые выражения; ничего из этого 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

person Mulan    schedule 26.05.2015
comment
Кажется слишком сложным (особенно меня смущает g => g(g)(x)). Есть ли преимущество у функции более высокого порядка над функцией первого порядка, как в моем решении? - person Pavlo; 26.05.2015
comment
этот ответ заставил меня узнать о существовании оператора запятой, имхо, его можно было бы сделать немного длиннее без его использования и понятнее для новичков, или, по крайней мере, комментарий был бы хорош, в любом случае это был замечательный ответ, спасибо ! - person Alfonso Pérez; 21.04.2017
comment
@AlfonsoPérez Я ценю это замечание. Я посмотрю, смогу ли я сделать небольшую подсказку где-нибудь ^_^ - person Mulan; 21.04.2017
comment
@naomik Прощай, TCO! Я опустошен. - person ; 10.11.2017
comment
@ftor Я так много работал над этим, что теперь для меня это не имеет большого значения - JavaScript находится под влиянием стольких разных лагерей, что он не может эффективно развиваться так, чтобы все были довольны. Я смотрю на Elm и ClojureScript (а теперь и на ReasonML) как на замену JavaScript в моих реальных проектах. На самом деле наслаждаюсь Elm до сих пор. - person Mulan; 10.11.2017
comment
Кажется, этот ответ принят и хорошо оценен, потому что он, должно быть, потребовал много усилий, но я не думаю, что это хороший ответ. Правильный ответ на вопрос - нет. Полезно перечислить обходной путь, как вы это сделали, но сразу после этого вы заявляете, что есть лучший способ. Почему бы вам просто не поместить этот ответ и удалить худший вверху? Зачем ты объясняешь запятые? Почему вы упомянули Clojure? Зачем вообще столько касательных к вопросу с 2х символьным ответом? Простые вопросы — это не просто платформа для пользователей, чтобы сделать презентацию с некоторыми интересными фактами о программировании. - person Sasha Kondrashov; 16.02.2019
comment
@Timofey Этот ответ представляет собой сборник нескольких правок за 2 года. Я согласен с тем, что этот ответ нуждается в окончательном редактировании, но ваши правки слишком много удалили. Я вернусь к нему в ближайшее время, принимая во внимание ваши комментарии и предложения по редактированию. - person Mulan; 16.02.2019
comment
Очень полезный курс по функциональному программированию. Но короткий ответ Array(N).keys() как в for (let i of Array(3).keys()){console.log(i)} - person Eran W; 16.09.2020

Используя оператор 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
person Tieme    schedule 24.05.2016
comment
Лучше: Array.from(Array(10), (_, i) => i*10) - person Bergi; 17.10.2016
comment
Если вам не нужен итератор (i), вы можете исключить как ключ, так и значение, чтобы сделать это: [...Array(10)].forEach(() => console.log('looping 10 times'); - person Sterling Bourne; 29.08.2017
comment
Итак, вы выделяете весь массив из N элементов только для того, чтобы его выбросить? - person Kugel; 30.11.2017
comment
Кто-нибудь обращался к предыдущему комментарию Кугеля? Я думал о том же самом - person Arman; 26.06.2018
comment
Если вы используете компилятор TypeScript для работы с ES5, [...Array(10)] будет выводиться как Array.Slice(10), что не приведет к повторению. Я предлагаю использовать Array.Apply, как предлагается в следующем решении, чтобы обойти эту проблему (также работает в IE 9+) stackoverflow.com/a/26707922/2035501. Обратите внимание, что синтаксис распространения ES6 не вызывает проблем при использовании babel в качестве транспилятора. - person Andrew; 04.01.2019
comment
@Kugel, этот метод очень полезен; например, когда вы хотите run немного function or code-block n number of time. - person Aakash; 11.04.2019
comment
интересно...почему без спреда не работает? Почему Array(10).map((_, i) => i * 10) не работает? - person sebpiq; 20.01.2020
comment
Вот функция-оболочка для этого ответа в TypeScript: export const mapN = <T>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn()). Затем запустите mapN(3, () => 'hello') - person Andries; 06.05.2020
comment
@sebpiq Поскольку функция Array (10) возвращает пустой экземпляр массива с длиной, равной 10. Экземпляр массива по существу выделен в памяти, но пуст. Если вы попытаетесь применить к нему функцию map(), она потерпит неудачу, потому что массив пуст. Однако, когда вы пытаетесь распространить его, оператор распространения вернет то же количество элементов, что и длина массива. Поскольку массив пуст, эти элементы не определены (не существуют), поэтому распространение даст вам 10 элементов === undefined. Следовательно, синтаксис (_, i) => {} всегда игнорирует первый (постоянно не определенный) параметр. - person Xunnamius; 22.05.2020

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

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

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

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

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

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

function* times(n) {
  for (let i = 0; i < n; 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*).

person Bergi    schedule 26.05.2015
comment
Это просто и точно и не выделяет массив, как многие ответы выше. - person Kugel; 03.12.2017
comment
@Kugel Второй может быть размещен в стеке, хотя - person Bergi; 03.12.2017
comment
Хороший вопрос, не уверен, что здесь будет работать оптимизация хвостового вызова @Bergi - person Kugel; 04.12.2017

Вот еще хороший вариант:

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

Предпочтительно, как отметил в комментариях @Dave Morse, вы также можете избавиться от вызова map, используя второй параметр функции Array.from следующим образом:

Array.from({ length: 3 }, () => (...))
person oemera    schedule 04.12.2019
comment
Array.from в MDN: developer.mozilla.org/ en-US/docs/Web/JavaScript/Reference/ - person Purplejacket; 11.05.2020
comment
Это должен быть принятый ответ! Одно маленькое предложение — вы уже получаете необходимую функциональность, подобную карте, бесплатно с помощью Array.from: Array.from({ length: label.length }, (_, i) => (...)) Это избавляет от создания пустого временного массива только для запуска вызова карты. - person Dave Morse; 11.06.2020
comment
Это должен быть принятый ответ! - person lngs; 14.07.2021

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

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

person Hossam Mourad    schedule 26.11.2017
comment
Какова поддержка заполнения? - person Aamir Afridi; 15.12.2017
comment
@AamirAfridi Вы можете проверить раздел совместимости браузера, там также есть полифилл: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ - person Hossam Mourad; 17.12.2017

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

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

or

Array(3).fill()
person Gergely Fehérvári    schedule 04.05.2017

Ответ: 9 декабря 2015 г.

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

Пример, приведенный в вопросе, был чем-то вроде 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'

Примечание: определение функции диапазона

Аналогичный/связанный вопрос, в котором используются принципиально очень похожие конструкции кода, может заключаться в том, есть ли удобная функция Range в (основном) 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()) оказался немного быстрее.

person arcseldon    schedule 09.12.2015

Я опаздываю на вечеринку, но поскольку этот вопрос часто появляется в результатах поиска, я просто хотел бы добавить решение, которое я считаю лучшим с точки зрения удобочитаемости, но не длинным (что идеально подходит для любой кодовой базы IMO) . Он мутирует, но я бы пошел на компромисс ради принципов KISS.

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0
person J Garcia    schedule 27.11.2019
comment
Спасибо за то, что вы были голосом разума на вечеринке, которую я могу описать только как лямбда-фетиш-вечеринку высшего порядка. Я тоже оказался в этом вопросе и ответе после безобидного первого попадания на путь Google, и мое здравомыслие быстро осквернилось большинством ответов здесь. Ваш - первый в списке, который я бы счел простым решением простой проблемы. - person Martin Devillers; 12.04.2020
comment
Единственная проблема заключается в том, что это немного нелогично, если вы хотите использовать переменную times внутри цикла. Возможно, countdown было бы лучшим именем. В противном случае, самый чистый и ясный ответ на странице. - person Tony Brasunas; 21.07.2020

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.

person Tom    schedule 22.01.2016
comment
Отклонено как метод заполнения не поддерживается в IE, Opera или PhantomJS - person morhook; 13.07.2016

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

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

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

person doldt    schedule 26.05.2015
comment
Разве Array.apply(null, {length: 10}) не может быть просто Array(10)? - person Pavlo; 26.05.2015
comment
@Павло, на самом деле нет. Array(10) создаст массив длиной 10, но без определенных в нем ключей, что делает конструкцию forEach непригодной для использования в этом случае. Но на самом деле это можно упростить, если вы не используете forEach, см. ответ zerkms (хотя он использует ES6!). - person doldt; 26.05.2015
comment
творческий @doldt, но я ищу что-то обучаемое и простое. - person at.; 26.05.2015

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

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

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

x.times.map { |i|
  doStuff(i)
}
person ronen    schedule 16.10.2016

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

person Pavlo    schedule 26.05.2015

В функциональной парадигме 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
);

Я использую преобразователь (функция без аргументов) для ленивой оценки в Javascript.

Повторение функции со стилем передачи продолжения

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

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

CPS поначалу немного пугает. Однако он всегда следует одной и той же схеме: последний аргумент — это продолжение (функция), которая вызывает собственное тело: k => k(...). Обратите внимание, что CPS выворачивает приложение наизнанку, т.е. take(8) (repeat...) становится k(take(8)) (...), где k — это частично примененное repeat.

Заключение

Отделяя повторение (repeat) от условия завершения (take), мы получаем гибкость - разделение проблем до самого конца :D

person Community    schedule 07.02.2017

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

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

person whitneyland    schedule 27.11.2017

Самый простой способ, который я могу придумать для создания списка/массива в диапазоне

Array.from(Array(max-min+1), (_, index) => index+min)

person mestea    schedule 06.01.2021

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

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);
});
person Nina Scholz    schedule 26.05.2015
comment
обращаясь к функциональному аспекту, а затем используя императивный цикл с изменяемым i. В чем тогда причина даже использовать times вместо старого доброго for? - person zerkms; 26.05.2015
comment
повторно использовать как var twice = times(2);. - person Nina Scholz; 26.05.2015
comment
Так почему бы просто не использовать for дважды? - person zerkms; 26.05.2015
comment
я не боюсь использовать для. вопрос был в том, чтобы не использовать переменную. но результатом всегда является какая-то кеширующая переменная. - person Nina Scholz; 26.05.2015
comment
было что-то, чтобы не использовать вариабеле --- и вы все еще используете его - i++. Не очевидно, как обертывание чего-то неприемлемого в функцию делает ее лучше. - person zerkms; 27.05.2015

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

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

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

Так же, как в рубине:

> (3).times(console.log)
0
1
2
person conny    schedule 14.03.2017
comment
Большой палец вверх за простоту, большой палец вниз за то, что слишком много рубинового стиля с обезьяньим патчем. Просто скажи нет этим плохим плохим обезьянам. - person mrm; 02.05.2017
comment
@mrm это исправление обезьяны, разве это не просто случай расширения? Принять и расширить :) - person conny; 31.05.2017
comment
Нет. Добавление функций в Number (или String, или Array, или любой другой класс, который вы не создавали) по определению является либо полифиллами, либо обезьяньими патчами — и даже полифиллы не рекомендуются. Прочтите определения «обезьяний пластырь», «полифилл» и рекомендуемой альтернативы «понифилл». Это то, что ты хочешь. - person mrm; 31.05.2017
comment
Чтобы расширить число, вы должны сделать: class SuperNumber extends Number { times(fn) { for (let i = 0; i ‹ this; i ++) { fn(i); } } } - person Alexander; 10.09.2017

Я обернул ответ @Tieme вспомогательной функцией.

В машинописном языке:

export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())

Теперь вы можете запустить:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
person Andries    schedule 06.05.2020

Я это сделал:

function repeat(func, times) {
    for (var i=0; i<times; i++) {
        func(i);
    }
}

Применение:

repeat(function(i) {
    console.log("Hello, World! - "+i);
}, 5)

/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/

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

person Nanoo    schedule 23.07.2020

Я просто положу это сюда. Если вы ищете компактную функцию без использования массивов и у вас нет проблем с изменчивостью/неизменяемостью:

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

person    schedule
comment
Это работает, так что это здорово! Но это немного уродливо в том смысле, что требуется дополнительная работа, и это не то, для чего используются клавиши Array. - person at.; 26.05.2015
comment
@в. конечно. Но я не уверен, что есть синоним haskell для [0..x] в JS, более краткий, чем в моем ответе. - person zerkms; 26.05.2015
comment
Вы можете быть правы, что нет ничего более краткого, чем это. - person at.; 26.05.2015
comment
Хорошо, я понимаю, почему это работает, учитывая разницу между Array.prototype.keys и Object.prototype.keys, но на первый взгляд это сбивает с толку. - person Mark Reed; 27.01.2016
comment
Я бы предположил, что это быстрее, чем лучший ответ, и менее подвержено максимальным ошибкам переполнения рекурсии. - person cchamberlain; 04.08.2016
comment
@cchamberlain с TCO в ES2015 (хотя нигде не реализовано?) Это может быть меньше беспокойства, но на самом деле :-) - person zerkms; 05.08.2016
comment
Зачем вам выделять массив только для того, чтобы его выбросить? - person Kugel; 30.11.2017
comment
@Kugel, потому что ОП создал искусственную проблему, которая требует этого? - person zerkms; 30.11.2017
comment
Это выделит место для массива. Это настолько близко к обычному циклу for, что нет смысла использовать этот подход. - person X_Trust; 23.05.2019
comment
@X_Trust вопрос не об оптимизации, а об изучении новых языковых функций. - person zerkms; 24.05.2019