Apakah ada mekanisme untuk mengulang x kali dalam ES6 (ECMAScript 6) tanpa variabel yang dapat berubah?

157

Cara khas untuk mengulang xkali dalam JavaScript adalah:

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

Tetapi saya tidak ingin menggunakan ++operator atau memiliki variabel yang bisa berubah-ubah sama sekali. Jadi apakah ada cara, di ES6, untuk mengulang xkali dengan cara lain? Saya suka mekanisme Ruby:

x.times do |i|
  do_stuff(i)
end

Adakah yang serupa dengan JavaScript / ES6? Saya bisa menipu dan membuat generator sendiri:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}

Tentu saja saya masih menggunakan i++. Setidaknya tidak terlihat :), tapi saya berharap ada mekanisme yang lebih baik di ES6.

di.
sumber
3
Mengapa variabel kontrol loop yang dapat diubah menjadi masalah? Hanya sebuah prinsip?
doldt
1
@doldt - Saya mencoba mengajar JavaScript, tapi saya bereksperimen dengan menunda konsep variabel yang bisa berubah sampai nanti
pada.
5
Kami benar-benar di luar topik di sini, tetapi apakah Anda yakin bahwa beralih ke generator ES6 (atau konsep baru, tingkat tinggi lainnya) adalah ide yang baik sebelum mereka belajar tentang variabel yang bisa berubah? :)
doldt
5
@doldt - mungkin, saya bereksperimen. Mengambil pendekatan bahasa fungsional ke JavaScript.
di
Gunakan biarkan untuk mendeklarasikan variabel itu dalam loop. Lingkupnya berakhir dengan loop.
ncmathsadist

Jawaban:

156

BAIK!

Kode di bawah ini ditulis menggunakan sintaks ES6 tetapi bisa dengan mudah ditulis dalam ES5 atau bahkan kurang. ES6 bukan persyaratan untuk membuat "mekanisme untuk mengulang x kali"


Jika Anda tidak memerlukan iterator di callback , ini adalah implementasi paling sederhana

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'))

Jika Anda benar-benar membutuhkan iterator , Anda dapat menggunakan fungsi dalam bernama dengan parameter penghitung untuk beralih untuk Anda

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'))


Berhenti membaca di sini jika Anda tidak suka mempelajari lebih banyak hal ...

Tetapi sesuatu harus merasa tidak enak tentang itu ...

  • ifpernyataan cabang tunggal jelek - apa yang terjadi di cabang lain?
  • banyak pernyataan / ekspresi dalam badan-badan fungsi - apakah masalah prosedur dicampur?
  • dikembalikan secara implisit undefined- indikasi fungsi tidak murni dan efek samping

"Apakah tidak ada cara yang lebih baik?"

Ada. Pertama mari kita meninjau kembali implementasi awal kita

// times :: Int -> (void -> void) -> void
const times = x => f => {
  if (x > 0) {
    f()               // has to be side-effecting function
    times (x - 1) (f)
  }
}

Tentu, ini sederhana, tetapi perhatikan bagaimana kita menelepon f()dan tidak melakukan apa-apa dengannya. Ini benar-benar membatasi jenis fungsi yang dapat kami ulangi berkali-kali. Bahkan jika kita memiliki iterator yang tersedia, f(i)tidak jauh lebih fleksibel.

Bagaimana jika kita mulai dengan prosedur pengulangan fungsi yang lebih baik? Mungkin sesuatu yang lebih baik menggunakan input dan output.

Pengulangan fungsi generik

// 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

Di atas, kami mendefinisikan repeatfungsi generik yang mengambil input tambahan yang digunakan untuk memulai aplikasi berulang fungsi tunggal.

// 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)))

Melaksanakan timesdenganrepeat

Nah ini mudah sekarang; hampir semua pekerjaan sudah dilakukan.

// 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'))

Karena fungsi kita mengambil isebagai input dan kembali i + 1, ini secara efektif berfungsi sebagai iterator kita yang kita lewati fsetiap waktu.

Kami telah memperbaiki daftar masalah kami juga

  • Tidak ada lagi ifpernyataan cabang tunggal yang jelek
  • Badan ekspresi tunggal menunjukkan kekhawatiran yang dipisahkan dengan baik
  • Tidak ada lagi yang tidak berguna, dikembalikan secara implisit undefined

Operator koma JavaScript,

Jika Anda kesulitan melihat bagaimana contoh terakhir bekerja, itu tergantung pada kesadaran Anda tentang salah satu sumbu pertempuran JavaScript tertua; yang operator koma - singkatnya, itu mengevaluasi ekspresi dari kiri ke kanan dan mengembalikan nilai dari ekspresi dievaluasi terakhir

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

Dalam contoh di atas, saya menggunakan

(i => (f(i), i + 1))

yang hanya cara penulisan yang ringkas

(i => { f(i); return i + 1 })

Optimasi Panggilan Ekor

Seberapa seksi implementasi rekursif ini, pada titik ini tidak bertanggung jawab bagi saya untuk merekomendasikan mereka mengingat bahwa tidak ada JavaScript VM yang dapat saya pikirkan mendukung eliminasi panggilan ekor yang tepat - babel digunakan untuk mentransmisikannya, tetapi sudah dalam "rusak; akan reimplement "status lebih dari setahun.

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

Karena itu, kita harus meninjau kembali implementasi kita repeatuntuk membuatnya menjadi stack-safe.

Kode di bawah ini memang menggunakan variabel yang bisa berubah ndan xtetapi perhatikan bahwa semua mutasi dilokalkan ke repeatfungsi - tidak ada perubahan status (mutasi) yang terlihat dari luar fungsi

// 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

Ini akan membuat banyak dari Anda mengatakan "tapi itu tidak berfungsi!" - Aku tahu, santai saja. Kita dapat mengimplementasikan gaya-Clojure loop/ recurantarmuka untuk perulangan ruang konstan menggunakan ekspresi murni ; tidak ada yang seperti whileitu.

Di sini kita mengabstraksi whiledengan loopfungsi kita - mencari recurtipe khusus untuk menjaga loop tetap berjalan. Ketika non- recurtype ditemukan, loop selesai dan hasil perhitungan dikembalikan

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

Terima kasih
sumber
24
Tampaknya terlalu rumit (saya sangat bingung dengan g => g(g)(x)). Apakah ada manfaat dari fungsi tingkat tinggi daripada fungsi tingkat pertama, seperti dalam solusi saya?
Pavlo
1
@naomik: terima kasih telah meluangkan waktu untuk mengirim tautan. sangat dihargai.
Pineda
1
@ AlfonsoPérez Saya menghargai ucapannya. Saya akan melihat apakah saya dapat memberikan sedikit petunjuk di sana di suatu tempat ^ _ ^
Terima kasih
1
@naomik Perpisahan TCO ! Saya hancur.
10
Tampaknya jawaban ini diterima dan diberi peringkat tinggi karena pasti membutuhkan banyak usaha, tetapi saya tidak berpikir itu jawaban yang baik. Jawaban yang benar untuk pertanyaan itu adalah "tidak". Sangat membantu untuk membuat daftar penyelesaian masalah seperti yang Anda lakukan, tetapi segera setelah itu Anda menyatakan bahwa ada cara yang lebih baik. Mengapa Anda tidak hanya meletakkan jawaban itu dan menghapus yang lebih buruk di bagian atas? Mengapa Anda menjelaskan operator koma? Mengapa Anda membesarkan Clojure? Mengapa, secara umum, begitu banyak garis singgung untuk pertanyaan dengan jawaban 2 karakter? Pertanyaan sederhana bukan hanya platform bagi pengguna untuk melakukan presentasi pada beberapa fakta pemrograman yang rapi.
Timofey 'Sasha' Kondrashov
266

Menggunakan operator Penyebaran ES2015 :

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => {
  return i * 10;
});

// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);

Atau jika Anda tidak membutuhkan hasilnya:

[...Array(10)].forEach((_, i) => {
  console.log(i);
});

// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));

Atau menggunakan operator 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);

Perhatikan bahwa jika Anda hanya perlu sebuah string diulang Anda dapat menggunakan String.prototype.repeat .

console.log("0".repeat(10))
// 0000000000
Ikat aku
sumber
26
Lebih baik:Array.from(Array(10), (_, i) => i*10)
Bergi
6
Ini harus menjadi jawaban terbaik. Jadi ES6! Luar biasa!
Gergely Fehérvári
3
Jika Anda tidak membutuhkan iterator (i), Anda dapat mengecualikan kunci dan nilai untuk membuatnya:[...Array(10)].forEach(() => console.log('looping 10 times');
Sterling Bourne
9
Jadi, Anda mengalokasikan seluruh array elemen N hanya untuk membuangnya?
Kugel
2
Adakah yang membahas komentar sebelumnya oleh Kugel? Saya bertanya-tanya hal yang sama
Arman
37
for (let i of Array(100).keys()) {
    console.log(i)
}
zerkms
sumber
Ini bekerja, jadi itu hebat! Tetapi agak jelek dalam arti bahwa pekerjaan ekstra diperlukan dan ini bukan untuk apa Arraykunci digunakan.
di
@di. memang. Tapi saya tidak yakin ada sinonim haskell untuk [0..x]JS lebih ringkas daripada jawaban saya.
zerkms
Anda mungkin benar bahwa tidak ada yang lebih ringkas dari ini.
di
OK, saya mengerti mengapa ini bekerja mengingat perbedaan antara Array.prototype.keysdan Object.prototype.keys, tetapi pada awalnya membingungkan.
Mark Reed
1
@cchamberlain dengan TCO di ES2015 (tidak diterapkan di mana pun?) mungkin kurang diperhatikan, tetapi memang :-)
zerkms
29

Saya pikir solusi terbaik adalah menggunakan let:

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

Itu akan membuat ivariabel (bisa berubah) baru untuk setiap evaluasi tubuh dan memastikan bahwa ihanya diubah dalam ekspresi kenaikan dalam sintaks loop itu, bukan dari tempat lain.

Saya bisa menipu dan membuat generator sendiri. Setidaknya i++tidak terlihat :)

Itu seharusnya cukup imo. Bahkan dalam bahasa murni semua operasi (atau setidaknya, juru bahasa mereka) dibangun dari primitif yang menggunakan mutasi. Selama ini memiliki cakupan yang tepat, saya tidak bisa melihat apa yang salah dengan itu.

Anda harus baik-baik saja dengan

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

Tetapi saya tidak ingin menggunakan ++operator atau memiliki variabel yang bisa berubah-ubah sama sekali.

Maka satu-satunya pilihan Anda adalah menggunakan rekursi. Anda dapat mendefinisikan fungsi generator tanpa bisa berubah ijuga:

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

Tapi itu tampaknya terlalu berat bagi saya dan mungkin memiliki masalah kinerja (karena eliminasi panggilan ekor tidak tersedia untuk return yield*).

Bergi
sumber
1
Saya suka opsi ini - bagus dan sederhana!
DanV
2
Ini sederhana dan to the point dan tidak mengalokasikan array seperti banyak jawaban di atas
Kugel
@ Kugel Yang kedua mungkin mengalokasikan pada stack, meskipun
Bergi
Poin bagusnya tidak yakin apakah optimisasi panggilan-ekor akan berfungsi di sini @Bergi
Kugel
13
const times = 4;
new Array(times).fill().map(() => console.log('test'));

Cuplikan ini akan console.log test4 kali.

Hossam Mourad
sumber
Apa dukungan untuk mengisi?
Aamir Afridi
2
@AamirAfridi Anda dapat memeriksa bagian kompatibilitas Browser, ada juga polyfill yang disediakan: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Hossam Mourad
12

Saya pikir ini sangat sederhana:

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

atau

Array(3).fill()
Gergely Fehérvári
sumber
11

Jawaban: 09 Desember 2015

Secara pribadi, saya menemukan jawaban yang diterima baik singkat (baik) dan singkat (buruk). Menghargai pernyataan ini mungkin subjektif, jadi silakan baca jawaban ini dan lihat apakah Anda setuju atau tidak

Contoh yang diberikan dalam pertanyaan adalah sesuatu seperti Ruby:

x.times do |i|
  do_stuff(i)
end

Mengekspresikan ini dalam JS menggunakan di bawah ini akan memungkinkan:

times(x)(doStuff(i));

Ini kodenya:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};

Itu dia!

Contoh penggunaan sederhana:

let cheer = () => console.log('Hip hip hooray!');

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!

Atau, ikuti contoh jawaban yang diterima:

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'

Catatan samping - Menentukan fungsi rentang

Pertanyaan serupa / terkait, yang menggunakan konstruksi kode yang secara fundamental sangat mirip, mungkin adakah fungsi Range yang nyaman dalam JavaScript (inti), sesuatu yang mirip dengan fungsi rentang garis bawah.

Buat array dengan n angka, mulai dari x

Menggarisbawahi

_.range(x, x + n)

ES2015

Beberapa alternatif:

Array(n).fill().map((_, i) => x + i)

Array.from(Array(n), (_, i) => x + i)

Demo menggunakan 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 ]

Dalam tes cepat saya berlari, dengan masing-masing di atas berjalan satu juta kali masing-masing menggunakan fungsi solusi dan doStuff kami, pendekatan sebelumnya (Array (n) .fill ()) terbukti sedikit lebih cepat.

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

Versi ini memenuhi persyaratan OP untuk kekekalan. Juga mempertimbangkan menggunakan reducebukannyamap tergantung pada kasus penggunaan Anda.

Ini juga merupakan pilihan jika Anda tidak keberatan sedikit mutasi dalam prototipe Anda.

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

Sekarang kita bisa melakukan ini

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

+1 ke arcseldon untuk .fillsaran.

Tom
sumber
Terpilih, karena metode pengisian tidak didukung di IE atau Opera atau PhantomJS
morhook
8

Berikut adalah alternatif lain yang baik:

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

Lebih disukai, seperti yang ditunjukkan @Dave Morse dalam komentar, Anda juga dapat menyingkirkan mappanggilan, dengan menggunakan parameter Array.fromfungsi kedua seperti:

Array.from({ length: 3 }, () => (...))
oemera
sumber
2
Ini harus menjadi jawaban yang diterima! Satu saran kecil - Anda sudah mendapatkan fungsionalitas seperti peta yang Anda butuhkan secara gratis dengan Array.from: Array.from({ length: label.length }, (_, i) => (...)) Ini menghemat pembuatan array sementara yang kosong hanya untuk memulai panggilan ke peta.
Dave Morse
7

Bukan sesuatu yang akan saya ajarkan (atau pernah digunakan dalam kode saya), tapi inilah solusi codegolf-layak tanpa mengubah variabel, tidak perlu untuk ES6:

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

Lebih dari sekadar bukti konsep yang menarik daripada jawaban yang berguna, sungguh.

doldt
sumber
Tidak bisakah Array.apply(null, {length: 10})adil Array(10)?
Pavlo
1
@Pavlo, sebenarnya, tidak. Array (10) akan membuat array dengan panjang 10, tetapi tanpa kunci yang didefinisikan di dalamnya, yang membuat forEach construct tidak dapat digunakan dalam kasus ini. Tapi memang itu bisa disederhanakan jika Anda tidak menggunakan forEach, lihat jawaban zerkms (yang menggunakan ES6!).
doldt
@doldt kreatif, tapi saya mencari sesuatu yang bisa diajar dan sederhana.
di
5

Saya terlambat ke pesta, tetapi karena pertanyaan ini sering muncul dalam hasil pencarian, saya hanya ingin menambahkan solusi yang saya anggap terbaik dalam hal keterbacaan sementara tidak lama (yang ideal untuk IMO basis kode apa pun) . Itu bermutasi, tapi aku akan menukar prinsip KISS.

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0
J Garcia
sumber
3
Terima kasih telah menjadi suara nalar dalam apa yang hanya bisa saya gambarkan sebagai pesta jimat lambda tingkat tinggi. Saya juga berakhir dengan T&J ini mengikuti jalur hit-on-Google-pertama yang tidak berbahaya dan dengan cepat kewarasan saya dinodai oleh sebagian besar jawaban di sini. Milik Anda adalah yang pertama dalam daftar yang akan saya pertimbangkan sebagai solusi langsung untuk masalah langsung.
Martin Devillers
Satu-satunya masalah dengan ini adalah itu sedikit berlawanan dengan intuisi jika Anda ingin menggunakan timesvariabel di dalam loop. Mungkin countdownakan menjadi penamaan yang lebih baik. Kalau tidak, jawaban paling bersih dan paling jelas di halaman.
Tony Brasunas
3

Afaik, tidak ada mekanisme dalam ES6 mirip dengan timesmetode Ruby . Tetapi Anda dapat menghindari mutasi dengan menggunakan rekursi:

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

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

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

Demo: http://jsbin.com/koyecovano/1/edit?js,console

Pavlo
sumber
Saya suka pendekatan ini, saya suka rekursi. Tapi saya ingin sesuatu yang lebih sederhana untuk menampilkan loop pengguna JavaScript baru.
di
3

Jika Anda ingin menggunakan perpustakaan, ada juga tanda bawaan_.times atau garis bawah_.times :

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

Perhatikan ini mengembalikan array hasil, jadi benar-benar seperti ruby ​​ini:

x.times.map { |i|
  doStuff(i)
}
ronen
sumber
2

Dalam paradigma fungsional repeatbiasanya merupakan fungsi rekursif tak terbatas. Untuk menggunakannya kita perlu evaluasi malas atau gaya kelanjutan lewat.

Malas mengevaluasi pengulangan fungsi

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

Saya menggunakan thunk (fungsi tanpa argumen) untuk mencapai evaluasi malas di Javascript.

Berfungsi pengulangan dengan gaya passing berkelanjutan

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 sedikit menakutkan pada awalnya. Namun, selalu mengikuti pola yang sama: Argumen terakhir adalah kelanjutan (fungsi), yang memanggil tubuhnya sendiri: k => k(...). Harap dicatat bahwa CPS mengubah aplikasi keluar, yaitu take(8) (repeat...)menjadi k(take(8)) (...)tempat ksebagian diterapkanrepeat .

Kesimpulan

Dengan memisahkan repetisi ( repeat) dari kondisi terminasi ( take) kita mendapatkan fleksibilitas - pemisahan masalah hingga akhir yang pahit: D


sumber
1

Keuntungan dari solusi ini

  • Mudah dibaca / digunakan (imo)
  • Nilai pengembalian dapat digunakan sebagai jumlah, atau hanya diabaikan
  • Versi es6 polos, juga tautan ke versi kode TypeScript

Kekurangan - Mutasi. Menjadi internal saja saya tidak peduli, mungkin beberapa yang lain juga tidak.

Contoh dan Kode

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
}

Versi TypeScipt
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011

Whitneyland
sumber
0

menangani aspek fungsional:

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);
});
Nina Scholz
sumber
5
"menangani aspek fungsional" dan kemudian menggunakan imperative loop dengan yang bisa berubah i. Apa alasan untuk menggunakan timeslebih dari yang biasa for?
zerkms
gunakan kembali seperti var twice = times(2);.
Nina Scholz
Jadi mengapa tidak digunakan saja fordua kali?
zerkms
saya tidak takut menggunakannya. pertanyaannya adalah sesuatu untuk tidak menggunakan variabel. tetapi hasilnya selalu semacam caching alias variabel.
Nina Scholz
1
"Adalah sesuatu yang tidak menggunakan variabel" --- dan Anda masih menggunakannya - i++. Tidak jelas bagaimana membungkus sesuatu yang tidak dapat diterima dalam suatu fungsi membuatnya lebih baik.
zerkms
0

Generator? Pengulangan? Kenapa begitu banyak yang mau mutatin? ;-)

Jika itu dapat diterima selama kita "menyembunyikan" itu, maka cukup terima penggunaan operator unary dan kita dapat menjaga hal-hal sederhana :

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

Sama seperti di ruby:

> (3).times(console.log)
0
1
2
conny
sumber
2
Jempol ke atas: "Mengapa harus banyak omong tentang mutatin?"
Sarreph
1
Jempol untuk kesederhanaan, jempol ke bawah untuk gaya ruby ​​terlalu banyak dengan monkeypatch. Katakan saja tidak pada monyet-monyet jahat itu.
mrm
1
@ mrm apakah ini "tambalan monyet", bukankah ini hanya perpanjangan saja? Rangkul & lanjutkan :)
conny
Tidak. Menambahkan fungsi ke Angka (atau String atau Array atau kelas lain yang tidak Anda buat), menurut definisi, adalah polyfill atau patch monyet - dan bahkan polyfill tidak direkomendasikan. Baca definisi "tambalan monyet", "polyfill," dan alternatif yang disarankan, "ponyfill." Itu yang kamu mau.
mrm
Untuk memperluas Angka Anda akan melakukan: kelas SuperNumber meluas Angka {kali (fn) {untuk (biarkan i = 0; i <ini; i ++) {fn (i); }}}
Alexander
0

Saya membungkus jawaban @Tieme dengan fungsi pembantu.

Dalam TypeScript:

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

Sekarang Anda dapat menjalankan:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
Andries
sumber
0

Saya membuat ini:

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

Pemakaian:

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
*/

The ivariabel mengembalikan jumlah kali itu telah dilingkarkan - berguna jika Anda perlu untuk preload x jumlah gambar.

Nanoo
sumber