Soal 1Kalau kamu memanggil countDownload() dua kali lalu menampilkan total di level terluar, apa yang terjadi?
Closure — Function yang Mengingat Variabel di Luarnya
Closure adalah function yang mengingat variabel dari scope luar. Termasuk counter yang bertahan antar pemanggilan, function factory, dan function di dalam loop.
Kadang kamu ingin sebuah function menyimpan value yang bertambah setiap kali dipanggil, misalnya jumlah tampilan sebuah halaman. Kalau menghitungnya dengan variabel biasa, value-nya tidak bertahan, atau justru bisa ditimpa dari baris mana saja.
Artikel ini membahas closure, yaitu function yang mengingat variabel dari scope luar, dan function factory, yaitu function yang mengembalikan function dengan pengaturan yang sudah tertanam.
Menyimpan Value Antar Pemanggilan — Dasar Closure
Misalnya kamu ingin menghitung berapa kali sebuah dokumen diunduh. Kalau let dideklarasikan di dalam function penghitungnya, value-nya kembali ke 0 setiap kali dipanggil. Kalau dideklarasikan di level terluar, value-nya memang bertahan, tapi variabelnya bisa ditimpa dari baris mana saja.
Dengan closure (function yang mengingat variabel dari scope luar), kamu bisa menyimpan value antar pemanggilan sekaligus mencegah baris di luar menimpanya. Kalau kamu mendeklarasikan variabel di dalam sebuah function lalu me-return function yang membaca dan menulis variabel itu, function yang dikembalikan akan membawa variabel itu keluar bersamanya.
function makeDownloadCounter() {
let total = 0; // Variabel yang hanya ada di dalam makeDownloadCounter
const countUp = () => {
total = total + 1; // Mengubah total di luar
return total;
};
return countUp; // Mengembalikan function itu sendiri
}
const countDownload = makeDownloadCounter(); // Membuat satu counter
console.log(countDownload()); // 1
console.log(countDownload()); // 2
console.log(countDownload()); // 3 (melanjutkan dari value sebelumnya)
// console.log(total); // ReferenceError: total is not defined
const countDownload = makeDownloadCounter()— menyimpan function yang dikembalikanconsole.log(total)— namanya tidak terlihat, jadiReferenceError
let total = 0— tidak kembali ke 0 walaupuncountDownload()dipanggilreturn countUp— mengembalikan function itu sendiri, bukan value
total = total + 1— mengubah total di luar- Masih bisa membaca dan menulis total ini setelah function luar selesai dijalankan
countUp dikembalikan ke level terluar dan tetap mengingat total di luarnya. Meskipun function-nya sudah selesai, variabel yang diingat tetap ada.Function yang di dalam bernama countUp dipanggil dengan nama countDownload di luar. Yang diserahkan return adalah function itu sendiri, jadi variabel penerimanya bisa diberi nama apa pun yang cocok dengan tempat pemakaiannya. Tabel di bawah membandingkan tempat menaruh value hitungan, apakah value-nya bertahan antar pemanggilan, dan apakah bisa ditimpa.
| Tempat value hitungan | Value antar pemanggilan | Bisa ditimpa dari luar? |
|---|---|---|
| let di dalam function penghitung | Hilang tiap selesai; mulai lagi dari 0 | Tidak, namanya tak terlihat |
| let di level terluar | Tetap tersimpan | Ya, dari baris mana saja |
| let di function luar, return function dalam | Lanjut dari value sebelumnya | Hanya lewat function yang dikembalikan |
Membuat Function dengan Pengaturan Tertanam — Function Factory
Misalnya persentase poin berbeda untuk tiap level member. Kalau persentasenya diteruskan sebagai argument di setiap perhitungan, persentase level lain bisa saja tertukar, dan kamu jadi menulis persentase yang sama berulang kali. Di sini, lebih baik persentasenya ditentukan sekali di awal.
Dengan function factory (function yang menerima pengaturan lalu mengembalikan function yang mengingat pengaturan itu), persentasenya cukup diteruskan sekali. Closure juga mengingat argument, jadi setiap function yang dikembalikan terus membaca rate yang dipakai saat function itu dibuat.
function makePointCalculator(rate) {
// rate adalah argument. Function yang dikembalikan terus mengingatnya
return (amount) => Math.floor(amount * rate);
}
const regularPoint = makePointCalculator(0.01); // Member reguler dapat 1%
const goldPoint = makePointCalculator(0.05); // Member gold dapat 5%
const platinumPoint = makePointCalculator(0.1); // Member platinum dapat 10%
console.log(regularPoint(3000)); // 30
console.log(goldPoint(3000)); // 150
console.log(platinumPoint(3000)); // 300
// Tanpa variabel, langsung hitung dengan 2 pasang tanda kurung
console.log(makePointCalculator(0.05)(5000)); // 250
rate yang diingat masing-masing. Setiap function terus membaca rate yang kamu teruskan, kecuali ada yang me-reassign-nya.Pada makePointCalculator(0.05)(5000), pasangan tanda kurung pertama membuat function yang mengingat persentasenya, dan pasangan kedua meneruskan jumlah belanja lalu langsung menghitungnya. Kalau tanda kurungnya hanya satu pasang, yang dikembalikan adalah function, bukan angka hasil perhitungan.
Variabel Baru Setiap Kali Dibuat — Factory dan let
Misalnya kamu ingin mengurangi sisa stok untuk setiap produk. Kalau hanya ada satu variabel bersama untuk pengurangannya, memesan kaos ikut mengurangi sisa stok topi. Setiap produk perlu punya value sendiri.
Seperti makeDownloadCounter di awal artikel ini, kalau kamu mendeklarasikan let di dalam function yang mengembalikan function, variabel itu dibuat baru setiap kali function luar dipanggil. Function-function yang dikembalikan tidak berbagi variabel, jadi meskipun berasal dari definisi yang sama, masing-masing hanya membaca dan menulis variabelnya sendiri.
function makeStockReserver(stock) {
let rest = stock; // Dibuat baru di setiap pemanggilan
return (count) => {
rest = rest - count;
return rest;
};
}
const reserveShirt = makeStockReserver(10);
const reserveCap = makeStockReserver(4);
console.log(reserveShirt(3)); // 7
console.log(reserveCap(1)); // 3
console.log(reserveShirt(2)); // 5 (hanya stok kaos yang berkurang)
console.log(reserveCap(1)); // 2
rest sendiri. Mengurangi yang satu tidak mengubah sisa stok yang lain.reserveShirt(2) di console.log ketiga hanya mengurangi stok kaos dari 7 menjadi 5, dan stok topi tetap 3. reserveCap(1) berikutnya mengembalikan 2, yang menunjukkan bahwa function topi juga terus mengingat hitungannya sendiri.
Memanggil Factory Lagi Membuat Hitungan Mulai dari Awal
Kalau kamu menulis makeStockReserver(10)(3) dua kali, keduanya mengembalikan 7. Itu karena setiap pemanggilan membuat rest terpisah yang mulai dari 10. Supaya sisa stok yang sudah berkurang terbawa ke pemesanan berikutnya, simpan function yang dikembalikan ke variabel seperti reserveShirt, lalu panggil variabel itu.
Membuat Function di Dalam Loop — let Terpisah di Setiap Iterasi
Misalnya kamu ingin menyiapkan sekumpulan function untuk menampilkan log, satu untuk setiap level log. Kalau function dibuat di dalam loop lalu disimpan di array, function-function itu baru dipanggil setelah loop selesai. Value iterasi mana yang diingat tiap function bergantung pada cara kamu mendeklarasikan variabel loop-nya.
Kursus ini tidak memakai var, tapi perbedaannya dengan let jadi jelas kalau keduanya dibandingkan. Dengan let di dalam tanda kurung for, variabel baru dibuat di setiap iterasi, dan function yang dibuat di iterasi itu mengingat variabel tersebut. var memakai satu variabel yang sama untuk semua iterasi, jadi setiap function membaca value yang tersisa setelah loop selesai.
const levels = ["info", "warn", "error"];
// var memakai satu variabel yang sama di semua iterasi
const varLoggers = [];
for (var i = 0; i < levels.length; i++) {
varLoggers.push(() => `[${levels[i]}]`);
}
console.log(varLoggers[0]()); // [undefined]
// let membuat variabel baru di setiap iterasi
const letLoggers = [];
for (let j = 0; j < levels.length; j++) {
letLoggers.push(() => `[${levels[j]}]`);
}
console.log(letLoggers[0]()); // [info]
console.log(letLoggers[2]()); // [error]
var, ketiga function membaca variabel yang sama; di sisi let, masing-masing membaca value iterasinya sendiri. Apa yang diingat function bergantung pada apakah variabel baru dibuat di setiap iterasi.Sisi var menghasilkan [undefined] karena i bernilai 3 begitu loop selesai, dan levels[3] tidak ada. Di sisi let, levels[j] juga dibaca saat function dipanggil, tapi j yang dibaca adalah variabel yang dibuat untuk iterasi itu, jadi value tiap iterasi masih tersimpan.
Closure Tidak Mengingat Value Saat Function Dibuat
Yang diingat closure adalah variabelnya itu sendiri, dan yang dibaca adalah value variabel itu pada saat dipanggil. Kalau kamu membuat function yang membaca let rate = 0.05; lalu me-reassign rate = 0.1;, function itu juga ikut menghitung dengan 0.1. Untuk pengaturan yang ingin kamu kunci, teruskan sebagai argument factory, seperti makePointCalculator(0.05).
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2reserveCap dimulai dengan stok 4. Setelah reserveShirt mengurangi 3, kamu meneruskan 1 ke reserveCap. Apa yang dikembalikan?
Soal 3Kalau function yang dibuat di dalam for (var i = 0; ...) dipanggil setelah loop selesai, berapa nilai i?