Map Constructed Array on JavaScript The Right Way

Array ya… Tentu saja bisa, tetapi mungkin kamu belum tahu caranya yang benar.


Studi kasus: membuat array

Sebagai programmer JavaScript yang sudah sering melakukan deklarasi array dan mengisinya dengan elemen, pasti kalian sudah tidak asing lagi dengan penggunaan perulangan for. Ambil salah satu contoh, kita ingin membuat array yang berisikan nomor berurutan dari 0 sampai 2017:

const arr = [];

for (let i = 0; i < 2018; i++) {
    arr[i] = i;
}

Yap. Cara di atas benar adanya dan kita semua setuju itu adalah salah satu cara yang dapat dilakukan oleh programmer… emm… awam?

Mari kita setuju pada hal lain berikut ini: keringkasan kode adalah impian semua programmer. Bagi saya, dan mungkin kalian juga, melihat deklarasi array dengan perulangan for-loop di atas membuat tangan kita gatal. Melihat kapabilitas fungsional JavaScript sekarang ini, simplisitas kode bisa menjadi sahabat kita dengan mudah. Dengan adanya fungsi orde tinggi (higher-order function) seperti forEach, map, filter, dan reduce, siapa sih yang masing menggunakan cara for-loop yang kuno? Hail declarative functional programming!

Namun tetap saja, bagi yang masih berkutat di dunia dasar pemrograman JavaScript, cara di atas baik-baik saja dan bisa tetap kamu gunakan. Ya, secara teknis memang begitu. Tetapi percayalah, segera setelah kamu mengetahui ajaibnya fungsi orde tinggi, kamu akan berpikir hal yang satu ini: “pasti ada cara yang lebih baik!”

Di dalam kepala saya, solusi sederhana yang terpikirkan adalah: “Oh iya ya! Gimana kalau kita membuat array kosong dengan panjang (length) 2018 elemen lalu kita map array itu dengan setiap indeks!” Di JavaScript, hal ini bisa ditempuh dengan constructed array, yang berarti array yang dibangun dengan constructor Array.

Untuk contoh studi kasus ini, cara membuat array kosong dengan indeks sebanyak 2018 yaitu:

const arr = Array(2018);

Sekarang kita sudah mempunyai array persis dengan yang kita inginkan. Hal berikutnya yang kita butuhkan adalah map setiap indeks ke setiap elemen kosongnya.

const arr = Array(2018).map((_, i) => i);
console.log(arr[0]);  // undefined

Loh… gimana ini? Harusnya kan outputnya 0, tapi kok ini malah undefined?

Penjelasan

Ada hal yang ingin saya tekankan di sini, bahwa hal itu terjadi karena memang begitulah adanya, secara teknis. Jika kamu teliti lebih dalam dengan menelusuri kejanggalan tersebut pada console web browser, kamu bisa melihat bahwa array JavaScript pada dasarnya adalah sebuah objek, dengan bilangan yang menjadi key. Sebagai contoh:

['a', 'b', 'c']

pada dasarnya sama dengan objek berikut:

{
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
}

Ketika kamu mengakses elemen indeks 0 dalam array, sebenarnya kamu hanya mengakses property objek yang key-nya adalah 0, and nothing else. Hal ini perlu kamu catat, karena ketika kamu mengumpamakan array sebagai objek dalam hubungannya dengan cara fungsi orde tinggi ini diimplementasikan, penyebab masalah ini menjadi sangat masuk akal.

Ketika kamu membuat array baru dengan constructor Array, objek array baru tercipta dengan property length diatur ke nilai yang kamu berikan, selain itu ia hanyalah sebuah ruang ‘kosong’. Tidak ada key indeks dalam representasi objeknya.

{
    // tidak ada key indeks!
    length: 2018
}

Kamu akan mendapati nilai undefined ketika mencoba mengakses nilai array tersebut pada indeks 0, tetapi bukan berarti nilai undefined itu disimpan pada indeks 0, itu memanglah perilaku default dalam JavaScript: mengembalikan nilai undefined jika kamu mencoba mengakses nilai suatu objek untuk key yang tidak ada.

Fungsi iterasi seperti map, reduce, filter, dan forEach melakukan iterasi terhadap key indeks pada objek array dari 0 ke length, tetapi tereksekusinya callback yang disertakan pada fungsi tersebut tergantung kepada ada atau tidaknya key indeks pada objek. Ini menjelaskan mengapa callback tidak pernah dipanggil dan tidak ada yang terjadi ketika kita memanggil fungsi map pada array—karena tidak ada key indeks!

Solusi

Sebagaimana yang telah diterangkan sebelumnya, yang kita butuhkan adalah sebuah array yang representasi objek internalnya berisi key untuk setiap angka dari 0 hingga length. Cara terbaik untuk melakukan ini adalah dengan menyebarkan (spread) array ke dalam array kosong. Kita bisa menggunakan sintaks spread (...) untuk melakukannya.

const arr = [...Array(2018)].map((_, i) => i);

console.log(arr[0]);  // 0

Menyebarkan array ke dalam array kosong akan menghasilkan array yang diisi dengan nilai undefined pada setiap indeksnya.

console.log([...Array(2018)]);

/* 
{
    0: undefined,
    1: undefined,
    2: undefined,
    ...
    2017: undefined,
    length: 2018
}
*/

Ini karena proses yang dilakukan oleh operator spread lebih sederhana daripada fungsi map. Yang dilakukan hanyalah loop melalui array (atau objek iterable lainnya) dari 0 ke length dan membuat key indeks baru ke dalam array yang diberikan dengan nilai yang di-return dari array yang menyebar pada indeks terkait. Mengingat bahwa JavaScript mengembalikan nilai undefined dari array hasil penyebaran dari masing-masing indeksnya (ingat, hal ini terjadi secara default karena array tidak memiliki key indeks untuk nilai yang bersangkutan), kita berujung dengan mendapatkan array baru yang sebenarnya diisi dengan key indeks, dan oleh karena itu objek tersebut dapat di-map (dan juga di-filter, di-reduce, dan di-forEach).

Simpulan

Berdasarkan pengetahuan yang ditemukan bahwa array di JavaScript direpresentasikan secara internal sebagai sebuah objek, kita telah menemukan berbagai cara memanipulasi array, dan mengetahui cara terbaik untuk membuat array dengan panjang elemen yang diinginkan yang diisi dengan nilai apapun yang kita butuhkan.


Akhir kata, sekian tulisan dari saya, dan terima kasih bagi pembaca yang telah memperhatikan sehingga memperoleh ilmu baru ini. Semoga bermanfaat ya! 👍