Ada satu potongan sintaks JavaScript modern yang bakal terus kamu temui begitu mulai membaca kode beneran: tiga titik, .... Dia muncul saat menyalin array, menggabungkan dua objek, mengoper sederet argumen, atau mengumpulkan “sisanya yang lain” ke dalam satu variabel. Yang bikin bingung, tiga titik yang sama itu ternyata melakukan dua hal yang berlawanan, tergantung di mana kamu menulisnya. Di satu tempat dia membongkar sebuah kumpulan; di tempat lain dia justru mengumpulkan nilai-nilai yang berserakan. Artikel ini akan menuntaskan kebingungan itu.
Kedua perilaku tadi punya nama. Saat ... membuka sesuatu menjadi potongan-potongan terpisahnya, itu namanya operator spread. Saat dia mengumpulkan beberapa nilai jadi satu array, itu elemen rest. Simbolnya sama, tapi tugasnya kebalikan satu sama lain. Kita bahas satu per satu, dengan banyak contoh nyata, dan di akhir artikel kamu bakal selalu tahu sedang melihat yang mana.
Kalau kamu masih perlu membiasakan diri dengan struktur datanya sendiri, ada baiknya membaca dulu array dan objek serta fungsi — semua yang ada di sini berdiri langsung di atas keduanya.
Apa sih sebenarnya yang dilakukan tiga titik ini
Sebelum hafal namanya, pegang dulu satu ide sederhana. Sintaks ... selalu duduk di sebelah sebuah kumpulan atau variabel, dan arahnya ditentukan oleh konteks:
- Di sisi kanan sebuah assignment, atau di dalam
[ ]/{ }/ pemanggilan fungsi, dia menyebar (spread) — membuka sebuah kumpulan dan menjajarkan isinya satu per satu. - Di sisi kiri, di dalam daftar parameter fungsi atau sebuah pola destructuring, dia mengumpulkan (rest) — menciduk nilai-nilai sisa ke dalam array baru.
Cuma itu intinya. “Aku ini sedang menyusun sesuatu dari potongan-potongan, atau sedang menangkap potongan ke dalam wadah?” Jawab pertanyaan itu, maka kamu langsung tahu sedang spread atau rest. Yuk kita lihat keduanya beraksi.
Operator spread: membongkar sebuah kumpulan
Operator spread mengambil sebuah iterable — paling sering berupa array — lalu menyebarkan isinya seolah-olah kamu mengetik tiap elemen satu per satu. Tempat paling gampang untuk merasakannya adalah saat menyusun array:
const numbers = [1, 2, 3];
const more = [...numbers, 4, 5];
console.log(more); // [1, 2, 3, 4, 5]
...numbers bukan menaruh array-nya di dalam array baru — dia menuangkan isinya. Jadi more jadi [1, 2, 3, 4, 5], bukan [[1, 2, 3], 4, 5]. Perbedaan ini adalah inti dari spread: dia membuka satu lapisan.
Menyalin array
Salah satu pemakaian paling umum adalah membuat salinan dangkal (shallow copy) sebuah array. Karena spread menjajarkan elemen-elemen baru ke dalam array yang benar-benar baru, salinannya jadi terpisah dari aslinya:
const asli = ["a", "b", "c"];
const salinan = [...asli];
salinan.push("d");
console.log(asli); // ["a", "b", "c"] (tetap utuh)
console.log(salinan); // ["a", "b", "c", "d"]
Ini penting karena const salinan = asli tidak menyalin apa pun — kedua nama itu bakal menunjuk ke array yang sama persis, dan menambah isi yang satu otomatis mengubah yang lain. Spread memberimu array yang benar-benar berdiri sendiri.
Menggabungkan array
Mau menyatukan beberapa array sekaligus? Spread masing-masing di dalam sebuah array literal baru:
const depan = [1, 2];
const belakang = [3, 4];
const gabungan = [...depan, ...belakang];
console.log(gabungan); // [1, 2, 3, 4]
// kamu bisa menyelipkan nilai tambahan di mana saja
const dilengkapi = [0, ...depan, 99, ...belakang];
console.log(dilengkapi); // [0, 1, 2, 99, 3, 4]
Cara ini terbaca rapi dan kamu mengatur urutannya dengan presisi — jauh lebih enak ketimbang pendekatan lama dengan concat.
Spread pada objek
Spread juga jalan di objek (sejak ES2018), menyalin pasangan key-value-nya ke dalam objek baru. Inilah cara modern untuk mengkloning atau memperluas sebuah objek:
const user = { name: "John Doe", role: "editor" };
const updated = { ...user, role: "admin" };
console.log(updated); // { name: "John Doe", role: "admin" }
console.log(user); // { name: "John Doe", role: "editor" } (tetap utuh)
Di sini ...user menyalin semua property milik user ke objek baru, baru setelah itu role: "admin" diterapkan. Urutannya berpengaruh: key yang ditulis belakangan yang menang, jadi updated mempertahankan name tapi menimpa role. Objek user aslinya dibiarkan persis seperti semula — ini pola standar untuk “beri aku salinan objek ini dengan satu field diubah,” yang akan kamu pakai di mana-mana, mulai dari state React sampai payload API.
Salinan dari spread itu dangkal
Spread cuma menggandakan lapisan paling atas. Kalau ada nilai di dalam array atau objek yang ternyata berupa array atau objek lagi, salinannya tetap berbagi referensi bersarang yang sama dengan aslinya.
const a = { name: "John Doe", tags: ["x", "y"] };
const b = { ...a };
b.tags.push("z");
console.log(a.tags); // ["x", "y", "z"] ← yang asli ikut berubah!b memang dapat objek sendiri di lapisan atas, tapi b.tags dan a.tags masih array yang sama persis. Untuk salinan dalam (deep copy) yang benar-benar terpisah, kamu butuh kloning dalam yang disengaja (misalnya structuredClone(a) di lingkungan modern). Untuk data yang datar, salinan dangkal justru pas — kamu cuma perlu sadar di mana batasnya.
Spread ke dalam pemanggilan fungsi
Spread juga bekerja di tempat kamu memanggil fungsi, mengubah sebuah array jadi sederet argumen terpisah. Ini cocok banget untuk fungsi yang mengharapkan nilai satu-satu, seperti Math.max:
const nilai = [82, 96, 71, 88];
console.log(Math.max(...nilai)); // 96
// setara dengan Math.max(82, 96, 71, 88)
Math.max tidak menerima array — dia maunya argumen satu demi satu. Spread pada array tadi menyerahkan tiap elemen sebagai argumennya masing-masing. Sebelum sintaks ini ada, orang memakai Math.max.apply(null, nilai); spread melakukan hal yang sama tapi jauh lebih enak dibaca.
Rest: mengumpulkan nilai ke dalam array
Sekarang balik arahnya. Rest muncul di sisi penerima dan melakukan kebalikan dari spread: alih-alih membuka sebuah kumpulan, dia justru mengumpulkan nilai-nilai yang berserakan ke dalam satu array. Kamu akan menemuinya di dua tempat — parameter fungsi dan destructuring.
Rest parameter di dalam fungsi
Misalkan kamu ingin sebuah fungsi yang menjumlahkan berapa pun banyaknya angka yang diberikan — dua, lima, dua puluh, kamu nggak tahu dari awal. Sebuah rest parameter menangkap semua argumen ke dalam satu array sungguhan:
function jumlah(...nums) {
let total = 0;
for (const n of nums) {
total += n;
}
return total;
}
console.log(jumlah(1, 2, 3)); // 6
console.log(jumlah(10, 20, 30, 40)); // 100
console.log(jumlah()); // 0
Di dalam jumlah, nums adalah array biasa yang berisi setiap argumen yang dioper, jadi kamu bisa me-loop-nya, memanggil .length, memakai method array — apa saja. ... di sini mengumpulkan, persis kebalikan dari pemanggilan spread yang baru saja kita lihat.
Kamu juga boleh menaruh parameter tetap di depan, lalu membiarkan rest mengepel sisanya. Rest parameter harus berada di paling akhir:
function sapa(salam, ...names) {
return names.map((n) => `${salam}, ${n}!`);
}
console.log(sapa("Hai", "John Doe", "Jane Doe"));
// ["Hai, John Doe!", "Hai, Jane Doe!"]
salam mengambil argumen pertama; ...names mengumpulkan semua sisanya ke dalam array. Bersih dan jelas.
Rest parameter mengungguli objek arguments lama
JavaScript versi lama menyediakan objek khusus bernama arguments di dalam setiap fungsi untuk menjangkau argumen tambahan. Rest parameter lebih unggul di semua sisi yang penting: ...nums itu array betulan (jadi .map, .filter, .reduce langsung bisa dipakai), sedangkan arguments cuma objek yang mirip array dan harus kamu konversi dulu. Rest juga jalan di dalam arrow function, tempat arguments justru tidak ada sama sekali. Di kode baru, pilih rest parameter dan anggap saja arguments itu tidak ada.
Rest di dalam destructuring
Ide yang sama berlaku saat kamu menarik nilai keluar dari sebuah array atau objek. Rest menyambar “semua yang tidak aku sebut satu per satu.” Pada array:
const [pertama, kedua, ...sisanya] = [10, 20, 30, 40, 50];
console.log(pertama); // 10
console.log(kedua); // 20
console.log(sisanya); // [30, 40, 50]
pertama dan kedua mengambil dua item awal; ...sisanya mengumpulkan item-item yang tersisa ke dalam array baru. Ini cara yang rapi untuk memisahkan “kepala” dari “ekor” sebuah daftar.
Pada objek juga bisa, di mana rest mengumpulkan property yang tersisa ke dalam objek baru:
const orang = { name: "Jane Doe", age: 31, city: "Jakarta", role: "admin" };
const { name, ...rest } = orang;
console.log(name); // "Jane Doe"
console.log(rest); // { age: 31, city: "Jakarta", role: "admin" }
Satu kegunaan praktisnya: ini cara paling bersih untuk membuang sebuah property dari objek tanpa mengubah aslinya. Tarik keluar key yang mau kamu hilangkan, dan rest memegang semua sisanya.
// buang password sebelum mengirim objek user ke client
const { password, ...userAman } = userLengkap;
// userAman punya semua field kecuali password
Spread vs rest: cara membedakannya
Tiga titiknya sama, lalu bagaimana cara langsung tahu yang mana yang sedang kamu baca? Lihat dia ada di sisi mana dari sebuah aksi:
- Spread ada di sisi nilai/output. Dia berada di dalam sesuatu yang sedang kamu susun: array literal
[ ], objek literal{ }, atau argumen pada pemanggilan fungsi. Dia membongkar sebuah kumpulan. - Rest ada di sisi penerima. Dia berada di tempat nilai-nilai diterima: daftar parameter sebuah fungsi, atau sisi kiri sebuah assignment destructuring. Dia mengemas nilai-nilai jadi satu.
Baca saja sebagai pertanyaan: Apakah ini membuka sebuah kumpulan jadi potongan-potongan (spread), atau menangkap potongan-potongan ke dalam sebuah kumpulan (rest)? Posisinya selalu menjawabnya.
// SPREAD — membongkar, di sisi penyusunan
const all = [...a, ...b]; // di dalam array literal
fn(...args); // di dalam pemanggilan fungsi
// REST — mengumpulkan, di sisi penerima
function fn(...args) { } // di daftar parameter
const [head, ...tail] = list; // di pola destructuring
Satu simbol, dua peran — itu wajar
Memang terasa aneh kalau satu operator berarti dua hal yang berlawanan. Tapi perhatikan, keduanya tidak pernah bertabrakan: spread cuma masuk akal di tempat kamu menyusun sebuah nilai, dan rest cuma masuk akal di tempat kamu menerima sebuah nilai. Konteks tempat keduanya hidup tidak pernah tumpang tindih, jadi JavaScript (dan kamu) selalu bisa membedakan mana yang dimaksud. Bersandarlah pada aturan posisi tadi, dan kebingungannya hilang.
Beberapa pola nyata yang bakal sering kamu pakai
Idiom-idiom kecil ini muncul berulang kali, jadi sebaiknya kamu langsung kenal begitu melihatnya:
// 1. Kloning-lalu-ubah sebuah objek (ramah immutability)
const stateBaru = { ...state, loading: false };
// 2. Menambah item ke array tanpa mengubah aslinya
const denganBaru = [...items, itemBaru];
// 3. Mengubah iterable apa pun jadi array betulan (string → huruf, Set → array)
const huruf = [...("hello")]; // ["h", "e", "l", "l", "o"]
const unik = [...new Set([1, 1, 2, 3, 3])]; // [1, 2, 3]
// 4. Memberi fungsi argumen yang fleksibel
function catatSemua(label, ...pesan) {
pesan.forEach((m) => console.log(label, m));
}
Yang nomor tiga itu trik manis: spread pada sebuah Set adalah cara terpendek untuk menghapus duplikat dari array, karena Set otomatis membuang nilai kembar dan spread mengubahnya balik jadi array biasa.
Penutup
Tiga titik, dua tugas berlawanan — dan sekarang kamu selalu bisa membedakannya:
- Spread (
...) membuka sebuah kumpulan jadi potongan-potongan terpisah. Pakai di dalam array literal, objek literal, dan pemanggilan fungsi untuk menyalin, menggabungkan, dan membongkar:[...arr],{ ...obj },fn(...args),Math.max(...nums). - Rest (
...) mengumpulkan nilai-nilai yang berserakan jadi satu array. Pakai di parameter fungsi (function f(...args)) dan di destructuring (const [pertama, ...sisanya] = list) untuk menampung “semua yang lain.” - Posisinya yang memberitahu mana yang mana: sedang menyusun sebuah nilai → spread; sedang menerima nilai → rest.
- Salinan dari spread itu dangkal — array/objek bersarang tetap berbagi dengan aslinya. Pakai
structuredClonekalau kamu benar-benar butuh salinan dalam. - Spread adalah cara modern untuk update yang immutable: salin-dulu-baru-ubah, bukan mengubah di tempat.
Spread dan rest ada di mana-mana di JavaScript modern dan di framework yang dibangun di atasnya, jadi kelancaranmu di sini langsung membuahkan hasil. Keduanya berpasangan secara alami dengan destructuring — menarik nilai keluar dari array dan objek berdasarkan bentuknya — yang jadi materi paling pas untuk kamu kuasai berbarengan.