Belajar dengan membaca secara berurutan

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
Variabel yang Diingat oleh Closure
Level terluar (tidak di dalam kurung kurawal mana pun)
  • const countDownload = makeDownloadCounter() — menyimpan function yang dikembalikan
  • console.log(total) — namanya tidak terlihat, jadi ReferenceError
Kurung kurawal makeDownloadCounter
  • let total = 0 — tidak kembali ke 0 walaupun countDownload() dipanggil
  • return countUp — mengembalikan function itu sendiri, bukan value
Kurung kurawal countUp
  • 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 hitunganValue antar pemanggilanBisa ditimpa dari luar?
let di dalam function penghitungHilang tiap selesai; mulai lagi dari 0Tidak, namanya tak terlihat
let di level terluarTetap tersimpanYa, dari baris mana saja
let di function luar, return function dalamLanjut dari value sebelumnyaHanya lewat function yang dikembalikan

Video uji coba gratis bisa diputar paling banyak 3 kali. Deklarasikan sendiri variabelnya.

① Tulis makeTrialCounter, yang mengembalikan sebuah function. Sisa pemutarannya mulai dari 3; setiap kali function itu dipanggil, sisanya berkurang 1 lalu dikembalikan. Kalau sisanya sudah 0, jangan dikurangi lagi.

② Buat function uji coba dengan makeTrialCounter.

③ Panggil function dari ② sebanyak 4 kali, lalu tampilkan tiap hasilnya dalam bentuk "Sisa pemutaran: N".

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
Tiga Function dari Satu Definisi
Satu definisi:makePointCalculatorTeruskan (0.01)→ regularPointTeruskan (0.05)→ goldPointTeruskan (0.1)→ platinumPointrate yang diingat0.01rate yang diingat0.05rate yang diingat0.1Untuk 3000 yen:mengembalikan 30Untuk 3000 yen:mengembalikan 150Untuk 3000 yen:mengembalikan 300
Body setiap function berisi ekspresi yang sama; bedanya hanya 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.

Toko pusat dan cabang punya batas gratis ongkos kirim dan ongkos kirim yang berbeda. cartTotals sudah dideklarasikan.

① Tulis makeShippingFee, yang menerima batas gratis ongkos kirim dan ongkos kirimnya, lalu mengembalikan function yang menerima total belanja dan mengembalikan ongkos kirim.

② Buat function untuk toko pusat (gratis mulai 4000 yen, selain itu 500 yen) dan cabang (gratis mulai 3000 yen, selain itu 350 yen).

③ Teruskan setiap jumlah di cartTotals ke kedua function itu, lalu tampilkan "N yen → Toko pusat N yen / Cabang N yen".

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
Variabel Baru di Setiap Pemanggilan
makeStockReserver(10) dijalankanMembuat variabelrest = 10makeStockReserver(4) dijalankanMembuat rest = 4yang terpisahPanggilreserveShirt(3)dan reserveCap(1)Mengembalikan7 dan 3 secaraterpisah
Meskipun dari definisi yang sama, setiap function mendapat 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.

Terbitkan ID berurutan untuk pesanan dan retur, masing-masing dengan hitungan sendiri. Deklarasikan sendiri variabelnya.

① Tulis makeIdIssuer, yang menerima prefix lalu mengembalikan function yang setiap kali dipanggil mengembalikan ID berurutan — "prefix-1", "prefix-2", dan seterusnya.

② Buat satu function penerbit untuk ORD dan satu untuk RTN.

③ Terbitkan 2 ID pesanan dan 1 ID retur, lalu tampilkan masing-masing.

④ Terbitkan satu ID pesanan lagi, tampilkan, dan periksa apakah nomornya melanjutkan urutan sebelumnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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]
Apa yang Dibaca Function yang Mengingat Variabel Loop
var di dalamtanda kurung forSatu variabeluntuk semuaiterasiKetiga functionmembaca iyang samaMengembalikan[undefined]let di dalamtanda kurung forVariabel barudi tiap iterasiMasing-masingmengingat 0, 1, 2Mengembalikan[info]
Di sisi 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).

Buat function untuk setiap metode pembayaran yang menampilkan notifikasi konfirmasi. methods, labels, dan notifiers sudah dideklarasikan.

① Loop methods dengan let di dalam tanda kurung for.

② Di setiap iterasi, tambahkan ke notifiers sebuah function yang menerima nomor pesanan dan menampilkan notifikasi seperti "[Transfer bank] Pesanan A-1002 diterima", dengan nama metode pembayaran sebagai key.

③ Setelah loop, teruskan A-1002 ke bank dan A-1003 ke cod.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Kalau kamu memanggil countDownload() dua kali lalu menampilkan total di level terluar, apa yang terjadi?

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?