сделать <что-то> N раз (декларативный синтаксис)

97

Есть ли способ в Javascript легко написать что-то вроде этого:

[1,2,3].times do {
  something();
}

Может быть, любая библиотека, которая может поддерживать подобный синтаксис?

Обновление: чтобы уточнить - я хотел бы, something()чтобы меня вызывали 1,2 и 3 раза соответственно для каждой итерации элемента массива

BreakPhreak
источник
2
Я бы сказал, что в JS нет такой функции, и это одна из пяти недостающих функций. Это очень полезно для тестирования программного обеспечения, больше всего на свете.
Александр Миллс

Ответы:

48

Этот ответ основан на Array.forEachнативной ванили , без какой-либо библиотеки .

Чтобы позвонить something()3 раза, используйте:

[1,2,3].forEach(function(i) {
  something();
});

учитывая следующую функцию:

function something(){ console.log('something') }

Выход будет

something
something
something

Чтобы ответить на эти вопросы, вот способ сделать звонок something()1, 2 и 3 раза соответственно:

Это 2017 год, вы можете использовать ES6:

[1,2,3].forEach(i => Array(i).fill(i).forEach(_ => {
  something()
}))

или в старом добром ES5:

[1,2,3].forEach(function(i) {
  Array(i).fill(i).forEach(function() {
    something()
  })
}))

В обоих случаях выход будет

Выход будет

something

something
something

something
something
something

(один раз, затем дважды, затем 3 раза)

винилл
источник
18
Это неверно, потому что не отвечает этой части вопроса: «Я бы хотел, чтобы something () вызывали 1,2 и 3 раза». Использование этого кода somethingвызывается только 3 раза, его следует вызывать 6 раз.
Ян Ньюсон,
Тогда я думаю, это был лучший ответ, так как это может быть хорошее начало.
vinyll
3
Вы также можете использовать [...Array(i)]или Array(i).fill(), в зависимости от ваших потребностей для фактических индексов.
Гвидо Боуман,
Если вас не интересуют переданные аргументы, используйте.forEach(something)
kvsm 09
88

Просто используйте цикл:

var times = 10;
for(var i=0; i < times; i++){
    doSomething();
}
Ahren
источник
3
Спасибо! Я хотел бы извлечь выгоду из декларативного синтаксиса (как у Jasmine и т. Д.)
BreakPhreak
верно, но функциональный декларативный синтаксис цикла также был бы намного лучше
Александр Миллс
73

Возможная альтернатива ES6.

Array.from(Array(3)).forEach((x, i) => {
  something();
});

Причем, если хотите, «вызывается 1,2 и 3 раза соответственно».

Array.from(Array(3)).forEach((x, i) => {
  Array.from(Array(i+1)).forEach((x, i2) => {
    console.log(`Something ${ i } ${ i2 }`)
  });
});

Обновить:

Взято из fill-array-with-undefined

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

Array.from({ length: 3 }, (x, i) => {
  something();
});
Нверба
источник
3
Я должен предупредить об этом, сказав, что это нормально, если вы просто быстро что-то пишете, но производительность ужасна, поэтому, вероятно, не используйте его для интенсивной рекурсии или в производстве вообще.
nverba
Если вы собираетесь использовать ES6, вы можете использовать map () вместо forEach ()
Энди Форд,
3
Если лаконичность является целью (а на самом деле, даже если это не так), передайте функцию вместо ее вызова:Array.from(Array(3)).forEach(something)
kvsm 09
1
Также работает с рендерингом выражения реакции.
Джош Шарки
4
Array.from()имеет необязательный второй параметр mapFn, который позволяет вам выполнять функцию карты для каждого элемента массива, поэтому нет необходимости использовать forEach. Вы можете просто сделать:Array.from({length: 3}, () => somthing() )
Felix Eve
18

Поскольку вы упоминаете Underscore:

Предполагается, fчто это функция, которую вы хотите вызвать:

_.each([1,2,3], function (n) { _.times(n, f) });

сделает свое дело. Например, с f = function (x) { console.log(x); }, вы попадете на консоль: 0 0 1 0 1 2

ггозад
источник
В самом деле, я думал, ты хочешь разлуки.
ggozad
2
_(3).times(function(n){return n;});должен сделать свое дело. Смотрите документацию здесь.
Чип
18

С lodash :

_.each([1, 2, 3], (item) => {
   doSomeThing(item);
});

//Or:
_.each([1, 2, 3], doSomeThing);

Или, если вы хотите сделать что-то N раз :

const N = 10;
_.times(N, () => {
   doSomeThing();
});

//Or shorter:
_.times(N, doSomeThing);

Обратитесь к этой ссылке для lodashустановки

То
источник
15

Создайте массив и fillвсе элементы с undefinedтаким mapметодом могут работать:

Array.fill не имеет поддержки IE

// run 5 times:
Array(5).fill().map((item, i)=>{ 
   console.log(i) // print index
})

Если вы хотите сделать вышеупомянутое более «отрицательным», мое текущее решение, основанное на мнении, будет:


Используя старый (обратный) цикл:

// run 5 times:
for( let i=5; i--; )
   console.log(i) 

Или как декларативное «пока» :

vsync
источник
1
Для спокойствия я запускал функцию uuid 50 тысяч раз, чтобы убедиться, что она никогда не дублирует uuid. Поэтому я профилировал верхний цикл и нижний просто для удовольствия, просто работая в середине нормальной загрузки страницы с помощью инструментов chrome dev, если я не тупой, я думаю, что его ~ 1,2 миллиарда сравнивает с использованием Array.indexOf () плюс создание 50k uuids. newschool = 1st-5561.2ms 2nd-5426.8ms | oldschool = 1st-4966.3ms / 2nd-4929.0ms Мораль истории, если u не находится в диапазоне от миллиарда +, вы никогда не заметите разницы, запустив эти 200, 1k, даже 10k раз, чтобы что-то сделать. Подумал, что кому-то может быть любопытно вроде меня.
rifi2k
Это правильно и известно уже много лет. Различные подходы были представлены не для повышения скорости, а для поддержки старых браузеров.
vsync
3
Очевидно, что каждый, кто читает эту ветку, знает, что вы не приводили примеры для сравнения их скорости. Я просто случайно использовал их для проведения небольшого теста и решил поделиться некоторой информацией, которая может быть интересна кому-то в будущем. Я не совсем прав, потому что на самом деле я не отвечал на вопрос, просто отображая информацию и давая напоминание, чтобы не терять скорость цикла, когда вы делаете только несколько вещей, которые в любом случае закончатся через пару мс. На самом деле это тоже не известно, потому что тот же тест, проведенный год назад, может оказаться на 50% медленнее, потому что браузеры меняются все время.
rifi2k
10

Вы также можете сделать то же самое с деструктуризацией следующим образом

[...Array(3)].forEach( _ => console.log('do something'));

или если вам нужен индекс

[...Array(3)].forEach(( _, index) => console.log('do something'));
Озай Думан
источник
8

Если вы не можете использовать Underscorejs, вы можете реализовать его самостоятельно. Прикрепив новые методы к прототипам Number и String, вы можете сделать это следующим образом (используя стрелочные функции ES6):

// With String
"5".times( (i) => console.log("number "+i) );

// With number variable
var five = 5;
five.times( (i) => console.log("number "+i) );

// With number literal (parentheses required)
(5).times( (i) => console.log("number "+i) );

Вам просто нужно создать выражение функции (с любым именем) и назначить его любому имени свойства (на прототипах), к которому вы хотите получить доступ, как:

var timesFunction = function(callback) {
  if (typeof callback !== "function" ) {
    throw new TypeError("Callback is not a function");
  } else if( isNaN(parseInt(Number(this.valueOf()))) ) {
    throw new TypeError("Object is not a valid number");
  }
  for (var i = 0; i < Number(this.valueOf()); i++) {
    callback(i);
  }
};

String.prototype.times = timesFunction;
Number.prototype.times = timesFunction;
Андреас Бергстрём
источник
1
Мне бы пришлось заново исследовать, насколько плохо
Александр Миллс
2

Существует фантастическая библиотека Ramda, похожая на Underscore и Lodash, но более мощная.

const R = require('ramda');

R.call(R.times(() => {
    console.log('do something')
}), 5);

Ramda содержит множество полезных функций. См. Документацию Ramda

Ян Боднар
источник
Мне нравится эта библиотека как современное и элегантное решение FP.
momocow
1

Вы можете использовать длину массива для выполнения вашей задачи количество раз.

var arr = [1,2,3];

for(var i=0; i < arr.length; i++){
    doSomething();
}

или

 var arr = [1,2,3];

 do
 {


 }
 while (i++ < arr.length);
Адиль
источник
1

ты можешь использовать

Array.forEach

пример:

function logArrayElements(element, index, array) {  
    console.log("a[" + index + "] = " + element);  
}  
[2, 5, 9].forEach(logArrayElements)

или с jQuery

$.each([52, 97], function(index, value) { 
  alert(index + ': ' + value); 
});

http://api.jquery.com/jQuery.each/

Мачу
источник
Похоже, forEachподдерживается только в IE версии 9: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Бруно,
1
times = function () {
    var length = arguments.length;
    for (var i = 0; i < length ; i++) {
        for (var j = 0; j < arguments[i]; j++) {
            dosomthing();
        }
    }
}

Вы можете назвать это так:

times(3,4);
times(1,2,3,4);
times(1,3,5,7,9);
XU3352
источник
+1 - это использует встроенную способность JavaScript вызывать функции с переменным количеством параметров. Никакой дополнительной библиотеки не требуется. Хорошее решение
RustyTheBoyRobot
1
// calls doSomething 42 times
Array( 42 ).join( "x" ).split( "" ).forEach( doSomething );

а также

// creates 42 somethings
var somethings = Array( 42 ).join( "x" ).split( "" ).map( () => buildSomething(); );

или (через https://stackoverflow.com/a/20066663/275501 )

Array.apply(null, {length: 42}).forEach( doSomething );
goofballLogic
источник
1
var times = [1,2,3];

for(var i = 0; i < times.length;  i++) {
  for(var j = 0; j < times[i];j++) {
     // do something
  }
}

Использование jQuery .each()

$([1,2,3]).each(function(i, val) {
  for(var j = 0; j < val;j++) {
     // do something
  }
});

ИЛИ

var x = [1,2,3];

$(x).each(function(i, val) {
  for(var j = 0; j < val;j++) {
     // do something
  }
});

РЕДАКТИРОВАТЬ

Вы можете сделать, как показано ниже, на чистом JS:

var times = [1,2,3];
times.forEach(function(i) {
   // do something
});
кодпарадокс
источник
0

Просто используйте вложенный цикл (возможно, заключенный в функцию)

function times( fct, times ) {
  for( var i=0; i<times.length; ++i ) {
    for( var j=0; j<times[i]; ++j ) {
      fct();
    }
  }
}

Тогда просто назовите это так:

times( doSomething, [1,2,3] );
Сирко
источник
0

Все эти ответы хороши и хороши, и IMO @Andreas - лучший, но много раз в JS нам приходится делать что-то асинхронно, в этом случае async охватывает вас:

http://caolan.github.io/async/docs.html#times

const async = require('async');

async.times(5, function(n, next) {
    createUser(n, function(err, user) {
        next(err, user);
    });
}, function(err, users) {
    // we should now have 5 users
});

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

Александр Миллс
источник
0
const loop (fn, times) => {
  if (!times) { return }
  fn()
  loop(fn, times - 1)
}

loop(something, 3)
Горо
источник
0

Учитывая функцию something:

function something() { console.log("did something") }

И timesв Arrayпрототип добавлен новый метод :

Array.prototype.times = function(f){
  for(v of this) 
    for(var _ of Array(v))
      f();
}

Этот код:

[1,2,3].times(something)

Выводит это:

did something
did something
did something
did something
did something
did something

Что, я думаю, отвечает на ваш обновленный вопрос (5 ​​лет спустя), но мне интересно, насколько полезно работать с массивом? Разве эффект не будет таким же, как при вызове [6].times(something), который, в свою очередь, можно записать как:

for(_ of Array(6)) something();

(хотя использование _в качестве нежелательной переменной, вероятно, приведет к сбою lodash или подчеркивания, если вы его используете)

пикс
источник
1
Считается плохой практикой добавлять собственные методы к собственному объекту JS.
Лиор Элром,
Вы можете использовать letas in, for (let _ of Array(6)) something()чтобы предотвратить затирание lodash за пределами, по крайней мере, для.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Array.from (ES6)

function doSomthing() {
    ...
}

Используйте это так:

Array.from(Array(length).keys()).forEach(doSomthing);

Или

Array.from({ length }, (v, i) => i).forEach(doSomthing);

Или

// array start counting from 1
Array.from({ length }, (v, i) => ++i).forEach(doSomthing);
Лиор Элром
источник
0

Использование Array.fromи .forEach.

let length = 5;
Array.from({length}).forEach((v, i) => {
  console.log(`#${i}`);
});

SeregPie
источник
0

Предполагая, что мы можем использовать какой-то синтаксис ES6, такой как оператор распространения, мы захотим сделать что-то столько раз, сколько сумма всех чисел в коллекции.

В этом случае, если times равно [1,2,3], общее количество раз будет 6, т.е. 1 + 2 + 3.

/**
 * @param {number[]} times
 * @param {cb} function
 */
function doTimes(times, cb) {
  // Get the sum of all the times
  const totalTimes = times.reduce((acc, time) => acc + time);
  // Call the callback as many times as the sum
  [...Array(totalTimes)].map(cb);
}

doTimes([1,2,3], () => console.log('something'));
// => Prints 'something' 6 times

Этот пост должен быть полезен, если логика построения и распространения массива не очевидна.

Илиаст
источник
0

Реализация TypeScript:

Для тех из вас , кто заинтересован в том , как реализовать String.timesи Number.timesтаким образом , что типобезопасен и работает с thisArg, вот я идти:

declare global {
    interface Number {
        times: (callbackFn: (iteration: number) => void, thisArg?: any) => void;
    }
    interface String {
        times: (callbackFn: (iteration: number) => void, thisArg?: any) => void;
    }
}

Number.prototype.times = function (callbackFn, thisArg) {
    const num = this.valueOf()
    if (typeof callbackFn !== "function" ) {
        throw new TypeError("callbackFn is not a function")
    }
    if (num < 0) {
        throw new RangeError('Must not be negative')
    }
    if (!isFinite(num)) {
        throw new RangeError('Must be Finite')
    }
    if (isNaN(num)) {
        throw new RangeError('Must not be NaN')
    }

    [...Array(num)].forEach((_, i) => callbackFn.bind(thisArg, i + 1)())
    // Other elegant solutions
    // new Array<null>(num).fill(null).forEach(() => {})
    // Array.from({length: num}).forEach(() => {})
}

String.prototype.times = function (callbackFn, thisArg) {
    let num = parseInt(this.valueOf())
    if (typeof callbackFn !== "function" ) {
        throw new TypeError("callbackFn is not a function")
    }
    if (num < 0) {
        throw new RangeError('Must not be negative')
    }
    if (!isFinite(num)) {
        throw new RangeError('Must be Finite')
    }
    // num is NaN if `this` is an empty string 
    if (isNaN(num)) {
        num = 0
    }

    [...Array(num)].forEach((_, i) => callbackFn.bind(thisArg, i + 1)())
    // Other elegant solutions
    // new Array<null>(num).fill(null).forEach(() => {})
    // Array.from({length: num}).forEach(() => {})
}

Ссылку на TypeScript Playground с некоторыми примерами можно найти здесь

В этом посте реализованы решения, опубликованные: Andreas Bergström , vinyll , Ozay Duman и SeregPie.

Ной Андерсон
источник