Pada chapter Closures di section Functions, kita membongkar mekanismenya: function yang dikirim keluar dari function pembuatnya membawa serta lingkaran variabelnya, sehingga variabel lokal tetap hidup selama function pembawanya masih dirujuk. Waktu itu polanya diuji dengan penghitung klik dan rekening sederhana. Pertanyaannya sekarang: untuk apa kemampuan itu dipakai dalam pekerjaan nyata?
Jawabannya terkumpul dalam tiga pola yang dipakai terus-menerus di kode JavaScript profesional. Counter menyimpan hitungan yang hanya bisa disentuh oleh function tertentu. Memoization menyimpan hasil perhitungan mahal dalam cache agar tidak dihitung ulang. Encapsulation menyembunyikan data detail dari pemakainya, membuka akses hanya lewat antarmuka yang disediakan. Ketiganya bertumpu pada satu sifat yang sama: variabel yang hidup di dalam closure dan tidak terjangkau dari luar.
Counter: State yang Hanya Milik Satu Function #
Penghitung adalah rekap singkat sekaligus pola terkecil dari ketiganya. Versi di berkas contoh menambahkan satu kelenturan dibanding versi di chapter Closures: langkah kenaikannya menjadi parameter, sehingga satu template bisa membuat penghitung dengan ukuran langkah berbeda:
// memo-fibonacci.js
// Closure dalam praktik: counter, memoization, dan encapsulation.
// --- 1. counter: state pribadi yang terus bertambah ---
function buatPenghitung(langkah = 1) {
let hitungan = 0;
return function () {
hitungan += langkah;
return hitungan;
};
}
const hitungPanggilan = buatPenghitung();
console.log(hitungPanggilan());
console.log(hitungPanggilan());
console.log(hitungPanggilan());
// --- 2. tanpa cache: fibonacci menghitung ulang terus ---
let panggilanNaif = 0;
function fibonacciNaif(n) {
panggilanNaif += 1;
if (n <= 1) {
return n;
}
return fibonacciNaif(n - 1) + fibonacciNaif(n - 2);
}
console.log(fibonacciNaif(20));
console.log(panggilanNaif);
// --- 3. memoization: cache Map tersembunyi dalam closure ---
function buatFibonacci() {
const cache = new Map([
[0, 0],
[1, 1],
]);
let panggilan = 0;
function fibonacci(n) {
panggilan += 1;
if (cache.has(n)) {
return cache.get(n);
}
const hasil = fibonacci(n - 1) + fibonacci(n - 2);
cache.set(n, hasil);
return hasil;
}
function jumlahPanggilan() {
return panggilan;
}
return { fibonacci, jumlahPanggilan };
}
const alatFibonacci = buatFibonacci();
console.log(alatFibonacci.fibonacci(20));
console.log(alatFibonacci.jumlahPanggilan());
console.log(alatFibonacci.fibonacci(20));
console.log(alatFibonacci.jumlahPanggilan());
// --- 4. encapsulation: cache tidak terjangkau dari luar ---
console.log(alatFibonacci.cache);Output yang Dihasilkan:
1
2
3
6765
21891
6765
39
6765
40
undefinedMari kita telaah baris-baris pentingnya:
- Baris 5–16 (
buatPenghitung):hitunganhidup di lingkar function luar, dan hanya function dalam yang bisa menaikkannya. Tiga pemanggilan menghasilkan1,2,3— hitungan terus bertambah antar-pemanggilan, persis perilaku closure yang kita buktikan di section Functions. - Baris 19–30 (versi tanpa cache):
fibonacciNaifadalah rekursi klasik dari chapter Recursion. Setiap pemanggilan menaikkanpanggilanNaif, dan hasilnya mencolok: menghitungfibonacciNaif(20)yang bernilai6765membutuhkan21891pemanggilan. Penyebabnya, nilai sepertifibonacciNaif(18)dihitung berulang-ulang oleh rantai pemanggilan yang berbeda. - Baris 33–38 (persiapan cache):
buatFibonaccimenciptakancacheberupa Map dari section Collections, yang sudah berisi dua nilai dasar —0dan1. Map ini hidup di dalam closure, bukan di variabel global. - Baris 40–48 (
fibonaccidengan cache): setiap pemanggilan mencatat hitungan, memeriksacache.has(n)lebih dulu, dan langsung mengembalikan hasil daricache.get(n)bila sudah ada. Baru bila belum ada, hasil dihitung lewat rekursi lalu disimpan dengancache.set. - Baris 54 (objek yang dikembalikan): pola yang sama dengan rekening di chapter Closures — dua method yang menutup state yang sama, dikembalikan dalam satu object.
fibonaccimengerjakan hitungan,jumlahPanggilanmembaca penghitung. - Baris 58–59 (ukur dengan hitungan, bukan waktu): pemanggilan pertama
fibonacci(20)tetap menghasilkan6765, tetapi kini hanya39pemanggilan tercatat. Mengukur jumlah pemanggilan jauh lebih andal daripada mengukur waktu, yang berfluktuasi antar-mesin. - Baris 60–61 (pemanggilan kedua): pemanggilan yang sama untuk kedua kalinya hanya menambah satu hitungan — total
40— karena seluruh jawabannya sudah menunggu di cache. - Baris 64 (uji encapsulation):
alatFibonacci.cachemenghasilkanundefined. Cache dan penghitung sama sekali tidak menempel pada objek kembalian; satu-satunya jalan masuk adalah dua method yang disediakan.
Fleksibilitas parameter langkah pada counter bisa dibuktikan dalam dua baris — penghitung yang melompat lima sekali naik:
const hitungGelas = buatPenghitung(5);
console.log(hitungGelas());
console.log(hitungGelas());Output yang Dihasilkan:
5
10Template yang sama, wadah hitungan yang berbeda: hitungGelas tidak menyentuh hitungan milik hitungPanggilan, karena setiap pemanggilan buatPenghitung menciptakan lingkaran variabelnya sendiri.
Cara Membaca Angka 39 #
Dari 39 pemanggilan itu, hanya sebagian yang benar-benar menghitung. Rinciannya: 19 pemanggilan menghitung nilai baru — yaitu nilai 2 sampai 20, karena 0 dan 1 sudah dipersiapkan di cache — dan 20 pemanggilan sisanya langsung menjawab dari cache, mulai dari nilai 0 dan 1 yang diminta fibonacci(2), sampai operand kedua fibonacci(n - 2) yang nilainya baru saja selesai dihitung. Dibanding 21891 pemanggilan pada versi tanpa cache, penurunannya drastis — dan semakin besar n, semakin lebar jurangnya.
Cache semacam ini sah sepanjang kontraknya jelas: memoization hanya boleh dipasang pada function murni, karena hasil yang di-cache hanya bisa dipercaya bila masukan sama selalu menghasilkan keluaran sama. Dari syarat itu turun tiga kaidah kapan memoization layak dipasang. Pertama, perhitungannya mahal — rekursi berlapis, pencarian berat, atau transformasi besar. Kedua, masukannya berulang: data yang sama diminta berkali-kali oleh bagian program berbeda, seperti daftar produk yang dihitung ulang setiap permintaan pelanggan. Ketiga, ruang masukannya tidak tak terbatas — cache fibonacci hanya bisa menampung satu jawaban untuk setiap angka, tetapi cache atas koordinat pembaca GPS akan menumpuk tanpa henti.
Memoization juga bukan satu-satunya jalan mempercepat: kadang perbaikan algoritma lebih berdampak daripada menyimpan hasil. Nilai terbesarnya adalah kombinasi keduanya — algoritma yang sudah benar, dengan hasil yang kerap diulang tak perlu dihitung dua kali.
Encapsulation: Antarmuka yang Dikendalikan #
Baris 64 adalah puncak cerita closure pada chapter ini. Data cache dan panggilan memang hidup di memori, tetapi tidak ada ekspresi apa pun dari luar yang bisa membaca atau menimpanya — tidak ada properti yang bisa disentuh, tidak ada variabel global yang bisa ditimpa. Pemakai hanya diberi dua pintu: fibonacci untuk menghitung dan jumlahPanggilan untuk mengaudit. Perubahan perilaku ke depan — misalnya membatasi ukuran cache — cukup dilakukan di dalam buatFibonacci, tanpa memperbaiki satu pun pemakainya.
Encapsulation lewat closure dan encapsulation lewat object berproperti bekerja dengan janji yang berbeda. Properti biasa terbuka siapa saja, sehingga aturan “jangan sentuh” hanya hidup sebagai kesepakatan; closure menutup aksesnya secara teknis — kesepakatan dan mekanisme berjalan searah. Untuk state yang benar-benar sensitif, seperti cache internal atau data kerja, beda itu menentukan.
Counter, memoization, dan encapsulation sama-sama mengelola state. Namun kerangka functional sebenarnya berambisi sebaliknya: seminimal mungkin state yang berubah, semaksimal mungkin aliran data yang jelas. Chapter berikutnya membahas gaya penulisan yang menonjolkan aliran itu — declarative programming.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
memo-fibonacci.js
Unduh berkas// memo-fibonacci.js
// Closure dalam praktik: counter, memoization, dan encapsulation.
// --- 1. counter: state pribadi yang terus bertambah ---
function buatPenghitung(langkah = 1) {
let hitungan = 0;
return function () {
hitungan += langkah;
return hitungan;
};
}
const hitungPanggilan = buatPenghitung();
console.log(hitungPanggilan());
console.log(hitungPanggilan());
console.log(hitungPanggilan());
// --- 2. tanpa cache: fibonacci menghitung ulang terus ---
let panggilanNaif = 0;
function fibonacciNaif(n) {
panggilanNaif += 1;
if (n <= 1) {
return n;
}
return fibonacciNaif(n - 1) + fibonacciNaif(n - 2);
}
console.log(fibonacciNaif(20));
console.log(panggilanNaif);
// --- 3. memoization: cache Map tersembunyi dalam closure ---
function buatFibonacci() {
const cache = new Map([
[0, 0],
[1, 1],
]);
let panggilan = 0;
function fibonacci(n) {
panggilan += 1;
if (cache.has(n)) {
return cache.get(n);
}
const hasil = fibonacci(n - 1) + fibonacci(n - 2);
cache.set(n, hasil);
return hasil;
}
function jumlahPanggilan() {
return panggilan;
}
return { fibonacci, jumlahPanggilan };
}
const alatFibonacci = buatFibonacci();
console.log(alatFibonacci.fibonacci(20));
console.log(alatFibonacci.jumlahPanggilan());
console.log(alatFibonacci.fibonacci(20));
console.log(alatFibonacci.jumlahPanggilan());
// --- 4. encapsulation: cache tidak terjangkau dari luar ---
console.log(alatFibonacci.cache);