Pola closure adalah bentuk-bentuk kode yang menjadikan lingkungan variabel terbawa sebagai wadah data, lalu membuka aksesnya hanya melalui fungsi tertentu. Tiga hal dibahas di sini, yaitu state privat, factory function, dan riwayat perulangan var beserta bug yang menyertainya. State privat berarti data yang tersimpan selama program berjalan dan disembunyikan di balik method, sedangkan factory function membuat dan mengembalikan object baru setiap kali dipanggil. Ketiganya bertumpu pada mekanisme closure yang sama, hanya penerapannya yang melebar.
State privat pada object dompet#
Object dompet berikut menyimpan saldonya di lingkungan variabel, bukan di properti object. Fungsi buatDompet menerima saldo awal, mendeklarasikan saldo, lalu mengembalikan object berisi dua method yang sama-sama membaca dan mengubah saldo. Dari luar, satu-satunya cara mengubah saldo adalah memanggil kedua method itu.
// Object dompet dengan saldo yang hanya bisa diubah lewat methodnya.
function buatDompet(saldoAwal) {
let saldo = saldoAwal;
return {
deposito(jumlah) {
saldo = saldo + jumlah;
return saldo;
},
tarik(jumlah) {
if (jumlah > saldo) {
return "saldo tidak cukup";
}
saldo = saldo - jumlah;
return saldo;
},
};
}
const dompet = buatDompet(100000);
console.log("setelah deposito 50000:", dompet.deposito(50000));
console.log("setelah tarik 20000:", dompet.tarik(20000));
console.log("properti dompet.saldo:", dompet.saldo);
dompet.saldo = 0;
console.log("setelah tarik 50000:", dompet.tarik(50000));setelah deposito 50000: 150000
setelah tarik 20000: 130000
properti dompet.saldo: undefined
setelah tarik 50000: 80000Keluaran memperlihatkan alur yang wajar, deposito 50000 menaikkan saldo menjadi 150000, lalu tarik 20000 menyisakan 130000. Baris ketiga adalah bukti kerahasiaannya, properti dompet.saldo terbaca sebagai undefined karena saldo memang tidak pernah menjadi properti. Nilai saldo hidup di lingkungan yang dibawa kedua method. Penugasan dompet.saldo = 0 tidak menimbulkan error, JavaScript hanya menambahkan properti baru bernama saldo ke object, sedangkan saldo aslinya tidak tersentuh. Tarik 50000 sesudahnya tetap dihitung dari 130000 dan menyisakan 80000, tanda bahwa penugasan tadi tidak berpengaruh.
Manfaat pola ini baru terasa ketika program bertambah besar. Bila saldo dijadikan properti biasa, baris mana pun boleh menulis dompet.saldo = -50000 dan tidak ada satu titik pun yang mengatur perubahannya. Dengan akses yang hanya lewat method, aturan seperti batas penarikan cukup ditulis satu kali di dalam method, dan setiap pemanggil dari bagian mana pun mengikuti aturan yang sama. Prinsip yang sama dipakai oleh modul ES, yaitu menyembunyikan detail di balik antarmuka yang sempit dan terjaga.
Factory function, satu pembuat banyak object#
Bentuk buatDompet di atas sebenarnya sudah merupakan factory function, karena ia mengembalikan object baru setiap dipanggil. Manfaat besarnya bukan pada satu object, melainkan pada banyak object yang dapat lahir dari satu fungsi pembuat tanpa duplikasi kode. Contoh berikut membuat konfigurasi antrean untuk dua loket sekaligus, dengan data yang tersimpan di lingkungan masing-masing.
// Factory function, satu pembuat untuk banyak object konfigurasi.
function buatKonfigurasiAntrean(namaLoket) {
const nilai = { batasAntrean: 20, durasiLayananDetik: 120 };
return {
namaLoket: namaLoket,
dapatkan(kunci) {
return nilai[kunci];
},
tetapkan(kunci, angkaBaru) {
if (angkaBaru < 1) {
return "nilai harus minimal 1";
}
nilai[kunci] = angkaBaru;
return nilai[kunci];
},
};
}
const loket1 = buatKonfigurasiAntrean("Loket 1");
const loket2 = buatKonfigurasiAntrean("Loket 2");
console.log(loket1.namaLoket, loket1.dapatkan("batasAntrean"), "antrean");
console.log(loket2.namaLoket, loket2.dapatkan("durasiLayananDetik"), "detik");
console.log(loket1.tetapkan("durasiLayananDetik", 150));
console.log("loket1 durasi baru:", loket1.dapatkan("durasiLayananDetik"));
console.log("loket2 durasi tetap:", loket2.dapatkan("durasiLayananDetik"));
console.log(loket1.tetapkan("batasAntrean", 0));Loket 1 20 antrean
Loket 2 120 detik
150
loket1 durasi baru: 150
loket2 durasi tetap: 120
nilai harus minimal 1Kedua loket lahir dari fungsi yang sama dengan nilai awal yang sama, 20 antrean dan 120 detik per layanan. Penggantian nilai hanya terjadi lewat method tetapkan, dan di sanalah aturan ditaruh. Penugasan batasAntrean bernilai 0 ditolak dengan pesan karena berada di bawah batas wajar. Pergantian durasi pada loket1 tidak berimbas sedikit pun pada loket2, karena setiap pemanggilan buatKonfigurasiAntrean membentuk lingkungan barunya sendiri. Pola seperti ini menggantikan penulisan object literal yang berulang-ulang, dan gagasannya berlanjut ke sintaks kelas pada modul object dan kelas.
Riwayat var dalam perulangan#
Riwayat var menyisakan perilaku yang sering ditemui pada kode lama, yaitu semua callback dalam perulangan mencetak nilai yang sama. Wujud masalahnya sederhana, sebuah perulangan memasang callback timer pada setiap putaran, lalu callback yang terpasang mencetak variabel perulangan itu. Berkas berikut menjalankan versi var dan versi let berdampingan agar keluarannya dapat dibandingkan langsung.
// Perbandingan var dan let dalam perulangan ber-callback timer.
// Blok var hanya pembanding sejarah, kode modern memakai let.
console.log("-- var, satu variabel untuk semua putaran");
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log("callback var mencetak", i);
}, 0);
}
console.log("-- let, satu lingkup per putaran");
for (let n = 0; n < 3; n++) {
setTimeout(function () {
console.log("callback let mencetak", n);
}, 0);
}-- var, satu variabel untuk semua putaran
-- let, satu lingkup per putaran
callback var mencetak 3
callback var mencetak 3
callback var mencetak 3
callback let mencetak 0
callback let mencetak 1
callback let mencetak 2Versi var mencetak 3 sebanyak tiga kali karena seluruh callback berbagi satu variabel i yang sama, dan pada saat timer berjalan perulangan sudah tuntas sehingga i berada di nilai akhirnya. Variabel var memang berlaku di seluruh fungsi, satu kotak untuk semua putaran. Versi let mencetak 0, 1, lalu 2 karena JavaScript membentuk lingkup baru untuk setiap putaran, sehingga setiap callback membawa lingkungan berisi nilai putarannya sendiri. Bila kode lama menampilkan gejala semua callback bernilai sama, penyebabnya hampir selalu pola var ini.
Sebelum let tersedia, pemrogram menutup setiap putaran dengan fungsi yang langsung dipanggil. Pola ini dikenal sebagai IIFE (Immediately Invoked Function Expression, fungsi yang dipanggil segera setelah dideklarasikan). Bentuknya terlihat pada potongan berikut, cukup dikenali agar kode lama tetap terbaca dan tidak perlu ditiru.
// tidak-dijalankan
// Sebelum let tersedia, lingkup per putaran dibentuk dengan fungsi
// yang dipanggil segera. Pola ini sudah tidak diperlukan.
for (var i = 0; i < 3; i++) {
(function (posisi) {
setTimeout(function () {
console.log("callback IIFE mencetak", posisi);
}, 0);
})(i);
}Pola IIFE bekerja dengan memanggil fungsi itu segera pada setiap putaran, sehingga posisi menjadi parameter milik pemanggilan itu saja dan setiap callback membawa nilai putarannya masing-masing. Cara ini memang menyelesaikan masalahnya, tetapi tanda kurung ganda di akhirnya mudah terlewat saat dibaca. Sesudah let dan scope blok menjadi standar, perulangan sudah membentuk lingkungan per putaran dengan sendirinya, sehingga IIFE kehilangan gunanya pada kasus ini dan tinggal sebagai penanda era pada kode lama.
Ringkasan#
- State privat menyembunyikan data di lingkungan variabel dan membuka aksesnya hanya lewat method, seperti saldo pada object dompet.
- Factory function mengembalikan object baru setiap dipanggil, dan setiap object membawa lingkungannya sendiri sehingga datanya saling terpisah.
- Perulangan dengan
varmembagi satu variabel untuk semua callback sehingga semuanya membaca nilai akhir, sedangkanletmembentuk satu lingkup per putaran. - IIFE adalah pola lama untuk membentuk lingkup per putaran dan sudah tidak diperlukan pada kode modern.
Referensi#
- let — MDN Web Docs — referensi resmi yang menjelaskan lingkup baru untuk setiap putaran perulangan.
- Closures — MDN Web Docs — panduan resmi closure berikut contoh method yang membungkus data.
- Closure — The Modern JavaScript Tutorial — bab dengan contoh penghitung dan perbandingan perulangan.