Menu

Closure JavaScript: Cara Kerja dan Mengapa Penting

Closure adalah fungsi yang mengingat variabel di sekitarnya. Berikut cara kerja closure di JavaScript, dengan contoh yang bisa dijalankan dan kasus penggunaan nyata.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Closure Adalah Fungsi yang Mengingat

Setiap kali kamu mendefinisikan fungsi di JavaScript, fungsi itu diam-diam menyimpan tautan ke variabel di sekitarnya. Saat fungsi itu berjalan nanti, bahkan di tempat yang sama sekali berbeda, ia tetap bisa melihat variabel-variabel tersebut. Itulah closure.

Demo paling singkat:

makeGreeter berjalan, mengembalikan fungsi di dalamnya, lalu selesai. Kamu mungkin mengira variabel lokalnya, name, akan hilang karena fungsinya sudah selesai. Tetapi fungsi di dalamnya masih memakai name, jadi JavaScript tetap menyimpannya. greetAda mengingat "Ada". greetBoris mengingat "Boris". Dua closure, dua nilai yang diingat secara terpisah.

Lexical Scope Adalah Asal Closure

Aturan di balik closure disebut lexical scope: sebuah fungsi bisa melihat variabel dari tempat ia ditulis, bukan dari tempat ia dipanggil. "Lexical" hanya berarti "berdasarkan posisinya di source code".

show mencetak "I'm outside", bukan "I'm inside caller". Fungsi itu ditulis di sebelah outer tingkat atas, jadi itulah yang dilihatnya. Memanggilnya dari tempat yang kebetulan punya outer sendiri tidak berpengaruh.

Closure hanyalah lexical scope yang bertahan lebih lama daripada fungsi luarnya. Variabelnya tidak hilang karena masih ada yang memegang referensi ke variabel itu.

Setiap Panggilan Mendapat Closure Sendiri

Setiap panggilan baru ke fungsi luar membuat variabel baru, dan fungsi dalam yang dikembalikan dari panggilan itu mengingat variabel tersebut. Inilah sebabnya greetAda dan greetBoris di atas tidak saling bertabrakan.

Contoh klasiknya adalah counter:

a dan b masing-masing memegang count sendiri. Tidak ada apa pun di luar fungsi yang dikembalikan yang bisa menyentuh variabel itu: count benar-benar privat. Ini bukan fitur bahasa yang kita nyalakan; ini muncul dengan sendirinya dari cara closure bekerja.

Variabel Privat Tanpa Class

Karena variabel yang terkurung hanya bisa dijangkau lewat fungsi yang dikembalikan, kamu bisa memakai closure untuk membangun objek kecil dengan state yang benar-benar privat:

balance bukan property pada objek yang dikembalikan. Ia hidup di dalam closure. Satu-satunya cara membaca atau mengubahnya adalah lewat method yang kamu sediakan. Class dengan field #private juga bisa melakukan ini, tetapi versi closure sudah ada puluhan tahun sebelumnya dan masih muncul di seluruh ekosistem.

Jebakan Klasik di Dalam Loop

Closure paling sering menjebak orang di dalam loop. Perhatikan apa yang terjadi dengan var:

Kamu mungkin mengharapkan 0, 1, 2. Yang kamu dapat 3, 3, 3. Inilah alasannya: var memiliki scope fungsi, jadi hanya ada satu i untuk seluruh loop. Ketiga closure menangkap variabel yang sama, dan saat mereka berjalan, loop sudah selesai dan i bernilai 3.

Ganti dengan let:

Sekarang yang tercetak 0, 1, 2. let memiliki scope blok: setiap iterasi loop membuat binding baru untuk i, jadi setiap closure menangkap nilainya sendiri. Inilah alasan terbesar untuk lebih memilih let daripada var.

Closure Menangkap Variabel, Bukan Nilai

Satu poin yang halus tetapi penting: closure memegang variabelnya, bukan potret nilainya saat fungsi itu didefinisikan.

printMessage membaca message saat berjalan, bukan saat dibuat. Kalau kamu ingin potret nilainya, salin dulu nilai itu ke variabel lokal. Pada dasarnya itulah yang dilakukan let di dalam loop for.

Pola Umum di Dunia Nyata: Once

Berikut utilitas kecil yang memakai closure untuk memastikan sebuah fungsi hanya berjalan satu kali:

called dan result adalah state privat yang hidup selama fungsi yang dikembalikan masih ada. Tanpa flag global, tanpa objek tambahan. Pola ini, yaitu helper kecil, state privat, dan closure, adalah salah satu hal paling berguna yang bisa dilakukan JavaScript.

Sedikit tentang Memori

Closure menjaga variabel yang ditangkapnya tetap hidup selama masih ada yang merujuk ke closure itu. Biasanya memang itu yang kamu inginkan, tetapi kalau kamu memasang closure pada sesuatu yang berumur panjang (seperti event listener DOM atau cache global) dan closure itu menangkap sesuatu yang besar, benda itu tidak bisa dibersihkan oleh garbage collector sampai closure-nya hilang.

function attach() {
    const hugeData = new Array(1_000_000).fill("...");
    document.addEventListener("click", () => {
        console.log(hugeData.length);
    });
}

Selama listener masih terpasang, hugeData tetap berada di memori. Lepaskan listener-nya (atau hindari menangkap apa yang tidak kamu butuhkan) dan referensinya akan terlepas. Kamu tidak perlu mengatur ini secara detail, cukup ketahui bahwa closure dan memori saling berkaitan.

Yang Perlu Kamu Ingat

  • Closure adalah sebuah fungsi beserta variabel yang dilihatnya saat ia didefinisikan.
  • Setiap panggilan ke fungsi luar membuat set variabel baru untuk closure di dalamnya.
  • Closure memberimu state privat tanpa perlu class.
  • Di dalam loop, pakai let agar setiap iterasi mendapat binding sendiri.
  • Closure menangkap variabelnya sendiri, bukan nilainya saat dibuat.

Berikutnya: Kata Kunci this

Closure menangani variabel di sekitar sebuah fungsi. Bagian berikutnya adalah pada objek apa sebuah fungsi dipanggil, yang di JavaScript dikendalikan oleh this, dan perilakunya sangat berbeda dari variabel yang ditangkap yang baru saja kita bahas.

Pertanyaan yang sering diajukan

Apa itu closure di JavaScript?

Closure adalah fungsi yang mengingat variabel dari scope tempat ia didefinisikan, bahkan setelah scope luar itu selesai berjalan. Secara teknis setiap fungsi di JavaScript adalah closure. Istilah ini biasanya muncul saat sebuah fungsi dikembalikan atau diberikan ke tempat lain dan tetap memakai variabel dari scope aslinya.

Mengapa closure berguna?

Closure memungkinkan sebuah fungsi membawa state privat bersamanya. Kamu mendapat data yang terikat ke sebuah fungsi tanpa perlu class atau variabel global. Penggunaan umumnya antara lain counter, callback yang hanya berjalan sekali, helper dengan memoization, dan menyembunyikan detail implementasi di balik API kecil.

Mengapa closure berperilaku aneh di dalam loop dengan var?

var memiliki scope fungsi, jadi setiap iterasi memakai variabel yang sama. Semua closure yang dibuat di dalam loop merujuk ke satu variabel itu, yang sudah bernilai akhir saat closure-closure itu berjalan. Pakai let sebagai gantinya: scope-nya blok, jadi setiap iterasi mendapat binding sendiri.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai