Typowy sposób na zapętlenie x
czasów w JavaScript to:
for (var i = 0; i < x; i++)
doStuff(i);
Ale nie chcę używać ++
operatora ani w ogóle mieć żadnych zmiennych zmiennych. Czy jest więc sposób w ES6 na zapętlenie x
czasów w inny sposób? Uwielbiam mechanizm Ruby:
x.times do |i|
do_stuff(i)
end
Coś podobnego w JavaScript / ES6? Mógłbym trochę oszukać i stworzyć własny generator:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
Oczywiście, że nadal używam i++
. Przynajmniej jest poza zasięgiem wzroku :), ale mam nadzieję, że w ES6 jest lepszy mechanizm.
Odpowiedzi:
DOBRZE!
Poniższy kod jest napisany przy użyciu składni ES6, ale można go równie łatwo napisać w ES5 lub nawet mniej. ES6 nie jest wymagane, aby utworzyć „mechanizm pętli x razy”
Jeśli nie potrzebujesz iteratora w wywołaniu zwrotnym , jest to najprostsza implementacja
Jeśli potrzebujesz iteratora , możesz użyć nazwanej funkcji wewnętrznej z parametrem licznika, aby wykonać iterację
Ale coś powinno czuć w tych ...
if
Instrukcje pojedynczej gałęzi są brzydkie - co się dzieje w drugiej gałęzi?undefined
- wskazanie nieczystej funkcji powodującej skutki uboczne"Czy nie ma lepszego sposobu?"
Jest. Przyjrzyjmy się najpierw naszej początkowej implementacji
Jasne, to proste, ale zwróć uwagę, jak po prostu dzwonimy
f()
i nic z tym nie robimy. To naprawdę ogranicza rodzaj funkcji, którą możemy powtarzać wielokrotnie. Nawet jeśli mamy dostępny iterator,f(i)
nie jest dużo bardziej wszechstronny.A jeśli zaczniemy od lepszego rodzaju procedury powtarzania funkcji? Może coś, co lepiej wykorzystuje dane wejściowe i wyjściowe.
Powtarzanie funkcji ogólnej
Powyżej zdefiniowaliśmy funkcję ogólną,
repeat
która pobiera dodatkowe dane wejściowe, które są używane do rozpoczęcia wielokrotnego stosowania pojedynczej funkcji.Wdrażanie
times
zrepeat
Cóż, teraz jest to łatwe; prawie cała praca jest już wykonana.
Ponieważ nasza funkcja przyjmuje
i
dane wejściowe i zwracai + 1
, działa to skutecznie jako nasz iterator, do którego przechodzimy zaf
każdym razem.Naprawiliśmy również naszą listę punktowaną problemów
if
instrukcjiundefined
Operator przecinka JavaScript,
Jeśli nie możesz zobaczyć, jak działa ostatni przykład, zależy to od twojej świadomości jednego z najstarszych toporów bojowych JavaScript; operator przecinek - krótko mówiąc, ocenia wyrażenia od lewej do prawej i zwraca wartość ostatniego wyrażenia ocenianej
W naszym powyższym przykładzie używam
co jest tylko zwięzłym sposobem pisania
Tail Call Optimization
Choć rekurencyjne implementacje są seksowne, w tym momencie byłoby nieodpowiedzialne, gdybym je polecał, biorąc pod uwagę, że żadna maszyna wirtualna JavaScript , o której myślę, nie obsługuje prawidłowej eliminacji wywołań ogonowych - do transpozycji używano babel, ale jest w stanie "zepsuty; zostanie ponownie zaimplementowany status przez ponad rok.
W związku z tym powinniśmy ponownie przyjrzeć się naszej implementacji,
repeat
aby była bezpieczna dla stosu.Poniższy kod nie używać zmiennych zmienny
n
ix
należy jednak pamiętać, że wszystkie mutacje są zlokalizowane wrepeat
funkcji - bez zmiany stanu (mutacje) są widoczne z zewnątrz funkcjiWiele z was będzie mówić „ale to nie działa!” - Wiem, zrelaksuj się. Możemy zaimplementować interfejs
loop
/ styl Clojurerecur
do pętli w przestrzeni stałej przy użyciu czystych wyrażeń ; nic z tegowhile
.Tutaj abstrahujemy
while
od naszejloop
funkcji - szuka ona specjalnegorecur
typu, aby utrzymać działanie pętli. Gdyrecur
napotkany zostanie inny typ, pętla jest zakończona i zwracany jest wynik obliczeniaźródło
g => g(g)(x)
). Czy istnieje korzyść z funkcji wyższego rzędu nad funkcją pierwszego rzędu, jak w moim rozwiązaniu?Korzystanie z operatora Spread ES2015 :
[...Array(n)].map()
Lub jeśli nie potrzebujesz wyniku:
Lub używając operatora ES2015 Array.from :
Array.from(...)
Zauważ, że jeśli potrzebujesz tylko powtórzenia ciągu, możesz użyć String.prototype.repeat .
źródło
Array.from(Array(10), (_, i) => i*10)
[...Array(10)].forEach(() => console.log('looping 10 times');
źródło
Array
służą klucze.[0..x]
w JS, który jest bardziej zwięzły niż w mojej odpowiedzi.Array.prototype.keys
iObject.prototype.keys
, ale na pierwszy rzut oka jest to mylące.Myślę, że najlepszym rozwiązaniem jest użycie
let
:Spowoduje to utworzenie nowej (
i
zmiennej ) zmiennej dla każdej oceny treści i zapewni, żei
zostanie ona zmieniona tylko w wyrażeniu inkrementacji w tej składni pętli, a nie z innego miejsca.To powinno wystarczyć imo. Nawet w czystych językach wszystkie operacje (lub przynajmniej ich interpretery) są zbudowane z prymitywów, które używają mutacji. Dopóki ma odpowiedni zakres, nie widzę, co w tym złego.
Powinieneś być w porządku
Wtedy jedynym wyborem jest użycie rekurencji. Możesz zdefiniować tę funkcję generatora również bez mutacji
i
:Ale wydaje mi się to przesada i może powodować problemy z wydajnością (ponieważ eliminacja ogonów nie jest dostępna
return yield*
).źródło
Ten fragment będzie
console.log
test
4 razy.źródło
Myślę, że to całkiem proste:
lub
źródło
Odpowiedź: 9 grudnia 2015
Osobiście uważam, że zaakceptowana odpowiedź jest zarówno zwięzła (dobra), jak i zwięzła (zła). Doceń, że to stwierdzenie może być subiektywne, więc przeczytaj tę odpowiedź i sprawdź, czy się zgadzasz, czy nie
Przykład podany w pytaniu był podobny do Rubiego:
Wyrażenie tego w JS za pomocą poniższych pozwoliłoby na:
Oto kod:
Otóż to!
Prosty przykład użycia:
Alternatywnie, postępując zgodnie z przykładami zaakceptowanej odpowiedzi:
Uwaga dodatkowa - Definiowanie funkcji zakresu
Podobnym / pokrewnym pytaniem, które wykorzystuje zasadniczo bardzo podobne konstrukcje kodu, może być wygodna funkcja Range w (rdzeniu) JavaScript, coś podobnego do funkcji zakresu podkreślenia.
Utwórz tablicę z n liczbami, zaczynając od x
Podkreślać
ES2015
Kilka alternatyw:
Demo przy użyciu n = 10, x = 1:
W krótkim teście, który przeprowadziłem, z każdym z powyższych uruchomionych milion razy przy użyciu naszego rozwiązania i funkcji doStuff, poprzednie podejście (Array (n) .fill ()) okazało się nieco szybsze.
źródło
Ta wersja spełnia wymóg niezmienności PO. Rozważ także użycie
reduce
zamiastmap
zależności od przypadku użycia.Jest to również opcja, jeśli nie przeszkadza ci mała mutacja w twoim prototypie.
Teraz możemy to zrobić
+1 do arcseldona za
.fill
sugestię.źródło
Oto kolejna dobra alternatywa:
Najlepiej, jak zauważył @Dave Morse w komentarzach, możesz również pozbyć się
map
wywołania, używając drugiego parametruArray.from
funkcji, takiego jak:źródło
Array.from
w MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Array.from({ length: label.length }, (_, i) => (...))
Oszczędza to tworzenie pustej tymczasowej tablicy tylko po to, aby rozpocząć wywołanie mapy.Nie jest to coś, czego bym uczył (lub kiedykolwiek używałbym w moim kodzie), ale oto rozwiązanie godne Codegolf bez mutowania zmiennej, bez potrzeby ES6:
Bardziej interesujący dowód słuszności koncepcji niż użyteczna odpowiedź.
źródło
Array.apply(null, {length: 10})
można być sprawiedliwymArray(10)
?Spóźniłem się na imprezę, ale ponieważ to pytanie często pojawia się w wynikach wyszukiwania, chciałbym tylko dodać rozwiązanie, które uważam za najlepsze pod względem czytelności, a jednocześnie nie jest długie (które jest idealne dla każdej bazy kodów IMO) . To mutuje, ale dokonałbym tego kompromisu dla zasad KISS.
źródło
times
zmiennej wewnątrz pętli. Być możecountdown
byłoby lepsze nazewnictwo. W przeciwnym razie najczystsza i najjaśniejsza odpowiedź na stronie.Ahaik, w ES6 nie ma mechanizmu podobnego do
times
metody Rubiego . Ale możesz uniknąć mutacji, używając rekurencji:Demo: http://jsbin.com/koyecovano/1/edit?js,console
źródło
Jeśli chcesz skorzystać z biblioteki, możesz również skorzystać z lodash
_.times
lub podkreślenia_.times
:Zwróć uwagę, że zwraca to tablicę wyników, więc bardziej przypomina ten rubin:
źródło
W paradygmacie funkcjonalnym
repeat
jest zwykle nieskończoną funkcją rekurencyjną. Aby go użyć, potrzebujemy leniwej oceny lub stylu zaliczania kontynuacji.Leniwy oceniał powtarzanie funkcji
Używam thunk (funkcji bez argumentów), aby osiągnąć leniwą ocenę w JavaScript.
Powtarzanie funkcji ze stylem przekazywania kontynuacji
CPS jest na początku trochę przerażający. Jednak zawsze według takiego samego schematu: Ostatni argument jest kontynuacją (funkcja), która powołuje się na swój własny podmiot:
k => k(...)
. Należy pamiętać, że CPS wywraca aplikację na lewą stronę, tj.take(8) (repeat...)
Staje się miejscem, wk(take(8)) (...)
którymk
jest częściowo zastosowanarepeat
.Wniosek
Oddzielając powtórzenie (
repeat
) od warunku zakończenia (take
) uzyskujemy elastyczność - rozdzielenie spraw aż do ich gorzkiego końca: Dźródło
Zalety tego rozwiązania
Wady - mutacja. Będąc tylko wewnętrznym, nie obchodzi mnie to, może inni też nie.
Przykłady i kod
Wersja TypeScipt
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
źródło
zajęcie się aspektem funkcjonalnym:
źródło
i
. Jaki jest powód, aby używać nawettimes
zwykłego staregofor
?var twice = times(2);
.for
dwa razy?i++
. Nie jest oczywiste, jak zawijanie czegoś niedopuszczalnego w funkcji sprawia, że jest ona lepsza.Generatory? Rekursja? Dlaczego tak bardzo nienawidzę mutacji? ;-)
Jeśli jest to dopuszczalne tak długo jak my „ukryć”, a potem po prostu zaakceptować zastosowanie operatora jednoargumentowego i możemy zachować rzeczy proste :
Tak jak w rubinie:
źródło
Odpowiedź @Tieme s opakowałem funkcją pomocniczą.
W języku TypeScript:
Teraz możesz biegać:
źródło
Ja to zrobiłem:
Stosowanie:
i
Zmienna zwraca ilość razy to zapętlone - przydatna, gdy trzeba by wstępnie kwotę X obrazów.źródło