wykonaj <coś> N razy (składnia deklaratywna)

97

Czy w Javascript można łatwo napisać coś takiego:

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

Może jakakolwiek biblioteka obsługująca podobną składnię?

Aktualizacja: wyjaśnienie - chciałbym something()nazywać się odpowiednio 1,2 i 3 razy dla każdej iteracji elementu tablicy

BreakPhreak
źródło
2
Powiedziałbym, że nie ma takiej funkcji w JS i jest to 5 najczęściej brakujących funkcji. Jest to bardziej przydatne do testowania oprogramowania niż cokolwiek innego.
Alexander Mills,

Odpowiedzi:

48

Ta odpowiedź opiera się Array.forEach, bez żadnej biblioteki, tylko na natywnej wanilii .

Aby zadzwonić w zasadzie something()3 razy, użyj:

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

biorąc pod uwagę następującą funkcję:

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

Wylot będzie

something
something
something

Aby odpowiedzieć na to pytanie, oto sposób wykonania połączenia odpowiednio something()1, 2 i 3 razy:

Jest rok 2017, możesz użyć ES6:

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

lub w starym dobrym ES5:

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

W obu przypadkach wylew będzie

Wylot będzie

something

something
something

something
something
something

(raz, potem dwa razy, potem 3 razy)

Vinyll
źródło
18
Jest to niepoprawne, ponieważ nie odpowiada tej części pytania: „Chciałbym, aby coś () zostało nazwane 1,2 i 3 razy”. Użycie tego kodu somethingjest wywoływane tylko 3 razy, należy go wywołać 6 razy.
Ian Newson
Więc myślę, że została wybrana jako najlepsza odpowiedź, ponieważ może to być dobry, czystszy początek.
vinyll
3
Możesz także użyć [...Array(i)]lub Array(i).fill(), w zależności od potrzeb dla rzeczywistych indeksów.
Guido Bouman
Jeśli nie interesują Cię żadne przekazane argumenty, użyj.forEach(something)
kvsm
88

Po prostu użyj pętli:

var times = 10;
for(var i=0; i < times; i++){
    doSomething();
}
ahren
źródło
3
Dziękuję Ci! Chciałbym skorzystać z deklaratywnej składni (tak jak Jasmine itp.)
BreakPhreak
racja, ale funkcjonalna składnia deklaratywna pętli for byłaby również znacznie lepsza
Alexander Mills
73

Możliwa alternatywa ES6.

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

A jeśli chcesz, aby było to „nazwane odpowiednio 1,2 i 3 razy”.

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

Aktualizacja:

Zaczerpnięte z fill-arrays-with-undefined

Wydaje się, że jest to bardziej zoptymalizowany sposób tworzenia początkowej tablicy, zaktualizowałem to również, aby używać drugiej funkcji mapy parametrów sugerowanej przez @ felix-eve.

Array.from({ length: 3 }, (x, i) => {
  something();
});
nverba
źródło
3
Powinienem to ostrzec, mówiąc, że jest to w porządku, jeśli po prostu szybko tworzysz skrypt, ale wydajność jest okropna, więc prawdopodobnie nie używaj go do intensywnej rekurencji lub w ogóle w produkcji.
nverba
Jeśli wybierasz się na ES6, możesz użyć map () zamiast forEach ()
Andy Ford,
3
Jeśli celem jest zwięzłość (a właściwie, nawet jeśli nie jest), przekaż funkcję zamiast ją wywoływać:Array.from(Array(3)).forEach(something)
kvsm
1
Działa również z renderowaniem wyrażeń React
Josh Sharkey
4
Array.from()ma opcjonalny drugi parametr mapFn, który pozwala na wykonanie funkcji mapowania na każdym elemencie tablicy, więc nie ma potrzeby używania forEach. Możesz po prostu zrobić:Array.from({length: 3}, () => somthing() )
Felix Eve
18

Skoro wspominasz o podkreśleniu:

Zakładając, że fjest to funkcja, którą chcesz wywołać:

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

da rade. Na przykład za pomocą f = function (x) { console.log(x); }dostaniesz na konsoli: 0 0 1 0 1 2

ggozad
źródło
Rzeczywiście, myślałem, że chcesz separacji.
ggozad
2
_(3).times(function(n){return n;});powinien załatwić sprawę. Zobacz dokumentację tutaj.
Chip
18

Z lodash :

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

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

Lub jeśli chcesz coś zrobić N razy :

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

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

Skorzystaj z tego łącza, aby lodashzainstalować

Chociaż
źródło
15

Utwórz tablicę i fillwszystkie elementy za pomocą metody undefinedso mapmogą działać:

Array.fill nie obsługuje IE

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

Jeśli chcesz, aby powyższe stwierdzenie było bardziej „deklaratywne”, moje obecnie oparte na opiniach rozwiązanie byłoby następujące:


Korzystanie ze starej (odwrotnej) pętli:

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

Lub jako deklaratywne „podczas” :

vsync
źródło
1
Dla spokoju uruchomiłem funkcję uuid 50k razy, aby upewnić się, że nigdy nie powieliła uuid. Więc sprofilowałem górną pętlę względem dołu tylko dla kopnięć, po prostu działając w środku normalnego ładowania strony przy użyciu narzędzi dla programistów chrome, jeśli nie jestem głupi, myślę, że jego ~ 1,2 miliarda porównuje użycie Array.indexOf () plus generowanie 50k uuidów. newschool = 1-5561,2 ms 2-5426,8 ms | oldschool = 1st-4966.3ms / 2nd-4929.0ms Morał z tej historii, jeśli nie jesteś w przedziale miliarda +, nigdy nie zauważysz różnicy, wykonując te 200, 1k, a nawet 10k razy, aby coś zrobić. Pomyślałem, że ktoś może być tak ciekawy jak ja.
rifi2k
To prawda i jest znane od wielu lat. Przedstawiono różne podejścia nie dla korzyści związanych z szybkością, ale dla obsługi starszych przeglądarek.
vsync
3
Oczywiście każdy, kto czyta ten wątek, wie, że nie przedstawiłeś przykładów porównujących ich szybkość. Po prostu zdarzyło mi się użyć ich do przeprowadzenia małego testu i pomyślałem, że podzielę się informacjami, które ktoś w przyszłości może uznać za interesujące. Nie mam racji, ponieważ tak naprawdę nie odpowiadałem na pytanie tylko wyświetlając informacje i przypominając, aby nie przejmować się prędkością pętli, kiedy robisz tylko kilka rzeczy, które i tak kończą się w ciągu kilku ms. Nie jest to tak naprawdę znane, ponieważ ten sam test rok temu w ie może być o 50% wolniejszy, ponieważ przeglądarki zmieniają się cały czas.
rifi2k
10

Możesz również zrobić to samo z destrukturyzacją w następujący sposób

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

lub jeśli potrzebujesz index

[...Array(3)].forEach(( _, index) => console.log('do something'));
Ozay Duman
źródło
8

Jeśli nie możesz użyć Underscorejs, możesz to zaimplementować samodzielnie. Dołączając nowe metody do prototypów Number i String, można to zrobić w następujący sposób (używając funkcji strzałek 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) );

Musisz po prostu utworzyć wyrażenie funkcyjne (o dowolnej nazwie) i przypisać je do dowolnej nazwy właściwości (na prototypach), do której chcesz uzyskać dostęp jako:

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;
Andreas Bergström
źródło
1
Musiałbym ponownie zbadać, jak źle jest łatać prototyp, ale zwykle jest w porządku
Alexander Mills
2

Istnieje fantastyczna biblioteka o nazwie Ramda, która jest podobna do Underscore i Lodash, ale ma większe możliwości.

const R = require('ramda');

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

Ramda zawiera wiele przydatnych funkcji. Zobacz dokumentację Ramda

Jan Bodnar
źródło
Uwielbiam tę bibliotekę jako nowoczesne i eleganckie rozwiązanie FP.
momocow
1

Możesz użyć length of array do wykonania zadania, ile razy.

var arr = [1,2,3];

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

lub

 var arr = [1,2,3];

 do
 {


 }
 while (i++ < arr.length);
Adil
źródło
1
times = function () {
    var length = arguments.length;
    for (var i = 0; i < length ; i++) {
        for (var j = 0; j < arguments[i]; j++) {
            dosomthing();
        }
    }
}

Możesz to nazwać tak:

times(3,4);
times(1,2,3,4);
times(1,3,5,7,9);
XU3352
źródło
+1 - Wykorzystuje natywną zdolność JavaScript do wywoływania funkcji ze zmienną ilością parametrów. Nie potrzeba dodatkowej biblioteki. Niezłe rozwiązanie
RustyTheBoyRobot
1
// calls doSomething 42 times
Array( 42 ).join( "x" ).split( "" ).forEach( doSomething );

i

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

lub (przez https://stackoverflow.com/a/20066663/275501 )

Array.apply(null, {length: 42}).forEach( doSomething );
goofballLogic
źródło
1
var times = [1,2,3];

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

Korzystanie z jQuery .each()

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

LUB

var x = [1,2,3];

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

EDYTOWAĆ

Możesz zrobić jak poniżej z czystym JS:

var times = [1,2,3];
times.forEach(function(i) {
   // do something
});
thecodeparadox
źródło
0

Po prostu użyj zagnieżdżonej pętli (może być zawarta w funkcji)

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

Następnie nazwij to tak:

times( doSomething, [1,2,3] );
Sirko
źródło
0

Wszystkie te odpowiedzi są dobre i dobre, a IMO @Andreas jest najlepsze, ale wiele razy w JS musimy robić rzeczy asynchronicznie, w takim przypadku async obejmuje:

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

Te funkcje „czasów” nie są zbyt przydatne w przypadku większości kodu aplikacji, ale powinny być przydatne do testowania.

Alexander Mills
źródło
0
const loop (fn, times) => {
  if (!times) { return }
  fn()
  loop(fn, times - 1)
}

loop(something, 3)
Goro
źródło
0

Biorąc pod uwagę funkcję something:

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

timesDo Arrayprototypu dodano nową metodę :

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

Ten kod:

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

Wyprowadza to:

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

Która, jak sądzę, odpowiada na twoje zaktualizowane pytanie (5 lat później), ale zastanawiam się, jak przydatne jest posiadanie tej pracy na tablicy? Czy efekt nie byłby taki sam jak wywołanie [6].times(something), które z kolei można zapisać jako:

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

(chociaż użycie _zmiennej jako śmieciowej prawdopodobnie spowoduje przebicie lodash lub podkreślenie, jeśli jej używasz)

szkatułka
źródło
1
Dodawanie własnych metod do natywnego obiektu JS jest uważane za złą praktykę.
Lior Elrom
Możesz użyć letjak w, for (let _ of Array(6)) something()aby zapobiec bicie lodash poza przynajmniej przez.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Array.from (ES6)

function doSomthing() {
    ...
}

Użyj go w ten sposób:

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

Lub

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

Lub

// array start counting from 1
Array.from({ length }, (v, i) => ++i).forEach(doSomthing);
Lior Elrom
źródło
0

Korzystanie Array.fromi .forEach.

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

SeregPie
źródło
0

Zakładając, że możemy użyć jakiejś składni ES6, takiej jak operator spreadu, będziemy chcieli zrobić coś tyle razy, ile będzie suma wszystkich liczb w kolekcji.

W tym przypadku, jeśli czasy są równe [1,2,3], całkowita liczba razy wyniesie 6, tj. 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

Ten post powinien być pomocny, jeśli logika stojąca za konstruowaniem i rozpowszechnianiem tablicy nie jest oczywista.

IliasT
źródło
0

Implementacja TypeScript:

Dla tych z Was, którzy są zainteresowani, jak wdrożyć String.timesi Number.timesw sposób, który jest typu bezpieczne i współpracuje z thisArg, tutaj ya go:

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(() => {})
}

Link do Playground TypeScript z kilkoma przykładami można znaleźć tutaj

W tym poście zastosowano rozwiązania opublikowane przez: Andreas Bergström , vinyll , Ozay Duman i SeregPie

Noah Anderson
źródło