Kenapa ini penting
Di JavaScript, function adalah first-class citizen. Artinya function bisa disimpan di variabel, dikirim sebagai argument ke function lain, dan dikembalikan sebagai hasil function. Kemampuan ini membuka pola-pola yang sangat kuat dan dipakai di mana-mana: event klik tombol (callback), pengolahan data (map, filter, reduce), function yang "mengingat" data (closure), dan penyelesaian masalah bercabang seperti folder di dalam folder (rekursi). Kalau nanti kamu belajar React atau Node.js, pola-pola ini muncul di hampir setiap file.
Konsep inti
Callback
Callback adalah function yang dikirim sebagai argument ke function lain, untuk dipanggil nanti oleh function penerima. Analogi: kamu memesan makanan di restoran dan meninggalkan nomor HP. Restoran yang akan "memanggil balik" (call back) saat makanan siap. Kamu tidak perlu menunggu di depan dapur.
Contoh nyata callback: setTimeout(fn, 1000) memanggil fn setelah 1 detik, tombol.addEventListener("click", fn) memanggil fn saat tombol diklik, dan array.forEach(fn) memanggil fn untuk setiap elemen.
Perhatikan bedanya: jalankan(sapa) mengirim function-nya, sedangkan jalankan(sapa()) mengirim hasil pemanggilannya.
Higher-order function (HOF)
Higher-order function adalah function yang menerima function sebagai argument, mengembalikan function, atau keduanya. setTimeout, forEach, map, filter, dan reduce semuanya HOF. HOF memisahkan "apa yang dilakukan" dari "bagaimana mengulangnya", sehingga kode lebih pendek dan maksudnya lebih jelas.
Trio pengolah array: map, filter, reduce
Ketiganya tidak mengubah array asli. Mereka menghasilkan nilai baru.
| Method | Fungsi | Callback mengembalikan | Hasil |
|---|---|---|---|
map |
Mengubah setiap elemen | Elemen baru | Array baru, panjang sama |
filter |
Menyaring elemen | true (simpan) / false (buang) |
Array baru, panjang sama atau lebih pendek |
reduce |
Menggabungkan semua elemen jadi satu nilai | Akumulator baru | Satu nilai (angka, object, array, dan sebagainya) |
Analogi dapur: kamu punya sekeranjang buah.
map= mengupas setiap buah. Jumlah buahnya tetap, bentuknya berubah.filter= membuang buah yang busuk. Jumlahnya berkurang, buahnya tetap.reduce= memblender semuanya menjadi segelas jus. Banyak buah menjadi satu hasil.
reduce(callback, nilaiAwal): callback menerima (akumulator, elemen) dan mengembalikan akumulator baru. Selalu berikan nilai awal (misalnya 0 untuk penjumlahan). Tanpa nilai awal, reduce pada array kosong melempar error.
Ketiganya bisa dirangkai (chaining): data.filter(...).map(...).reduce(...).
Method HOF lain yang sering dipakai: forEach (jalankan sesuatu untuk setiap elemen, tanpa hasil), find (elemen pertama yang cocok), some (adakah yang cocok?), every (apakah semua cocok?), dan sort dengan function pembanding.
Closure
Closure terjadi ketika sebuah function "mengingat" variabel dari scope tempat ia dibuat, bahkan setelah function luarnya selesai dijalankan. Analogi: ransel. Function dalam membawa ransel berisi variabel dari lingkungan asalnya ke mana pun ia pergi.
Kegunaan closure:
- Menyimpan state pribadi yang tidak bisa diubah sembarangan dari luar, misalnya penghitung atau saldo.
- Function factory: function yang membuat function lain dengan konfigurasi berbeda, misalnya
buatPengali(2)danbuatPengali(10).
Rekursi
Rekursi adalah function yang memanggil dirinya sendiri untuk menyelesaikan versi masalah yang lebih kecil. Setiap rekursi wajib punya:
- Base case: kondisi berhenti yang langsung me-return tanpa memanggil diri sendiri.
- Recursive case: memanggil diri sendiri dengan masalah yang lebih kecil dan mendekati base case.
Analogi: boneka matryoshka (boneka Rusia bertumpuk). Untuk menemukan boneka terkecil, buka boneka, lalu ulangi proses yang sama pada boneka di dalamnya, sampai menemukan boneka yang tidak bisa dibuka lagi (base case).
Tanpa base case, function memanggil dirinya terus sampai memori tumpukan panggilan habis: RangeError: Maximum call stack size exceeded. Rekursi sangat cocok untuk data bertingkat seperti struktur folder, komentar berbalas, atau menu bertingkat.
Contoh kode
Callback dan higher-order function:
// Callback sederhana
function prosesPesanan(nama, callback) {
console.log(`Memproses pesanan ${nama}...`);
callback(nama);
}
function kirimNotif(nama) {
console.log(`Notifikasi: pesanan ${nama} siap!`);
}
prosesPesanan("Kopi Susu", kirimNotif);
// → Memproses pesanan Kopi Susu...
// → Notifikasi: pesanan Kopi Susu siap!
// Callback anonim dengan arrow
prosesPesanan("Teh", (n) => console.log(`${n} diantar ke meja 3`));
// → Memproses pesanan Teh...
// → Teh diantar ke meja 3
// setTimeout: callback dipanggil nanti
setTimeout(() => console.log("Muncul setelah 1 detik"), 1000);
console.log("Muncul duluan");
// → Muncul duluan
// → (1 detik kemudian) Muncul setelah 1 detik
// Catatan: kalau seluruh blok ini dijalankan sekaligus, output "Langkah" di bawah
// juga tercetak SEBELUM pesan setTimeout, karena setTimeout menunggu 1 detik.
// HOF yang mengembalikan function
function ulangi(n, aksi) {
for (let i = 0; i < n; i++) aksi(i);
}
ulangi(3, (i) => console.log("Langkah", i + 1));
// → Langkah 1, Langkah 2, Langkah 3
map, filter, reduce, dan teman-temannya:
const produk = [
{ nama: "Kaos", harga: 80000, stok: 12 },
{ nama: "Topi", harga: 45000, stok: 0 },
{ nama: "Jaket", harga: 250000, stok: 4 },
{ nama: "Kaus kaki", harga: 15000, stok: 30 },
];
// map: ambil nama saja
const daftarNama = produk.map((p) => p.nama);
console.log(daftarNama); // → [ 'Kaos', 'Topi', 'Jaket', 'Kaus kaki' ]
// map: harga setelah diskon 10%
const hargaDiskon = produk.map((p) => ({ nama: p.nama, harga: p.harga * 0.9 }));
console.log(hargaDiskon[0]); // → { nama: 'Kaos', harga: 72000 }
// filter: hanya yang stoknya ada
const tersedia = produk.filter((p) => p.stok > 0);
console.log(tersedia.length); // → 3
// reduce: total nilai stok gudang (harga × stok)
const nilaiGudang = produk.reduce((total, p) => total + p.harga * p.stok, 0);
console.log(nilaiGudang); // → 2410000
// Chaining: total harga barang tersedia di bawah 100 ribu
const totalMurah = produk
.filter((p) => p.stok > 0 && p.harga < 100000)
.map((p) => p.harga)
.reduce((a, b) => a + b, 0);
console.log(totalMurah); // → 95000
// reduce untuk mengelompokkan
const nilai = [90, 72, 85, 60, 95];
const kelompok = nilai.reduce(
(acc, n) => {
if (n >= 75) acc.lulus.push(n);
else acc.remedial.push(n);
return acc;
},
{ lulus: [], remedial: [] }
);
console.log(kelompok); // → { lulus: [ 90, 85, 95 ], remedial: [ 72, 60 ] }
// find, some, every, sort
console.log(produk.find((p) => p.harga > 100000).nama); // → Jaket
console.log(produk.some((p) => p.stok === 0)); // → true
console.log(produk.every((p) => p.harga > 10000)); // → true
const termurah = [...produk].sort((a, b) => a.harga - b.harga);
console.log(termurah.map((p) => p.nama)); // → [ 'Kaus kaki', 'Topi', 'Kaos', 'Jaket' ]
// Array asli tidak berubah
console.log(produk[0].nama); // → Kaos
Closure dan rekursi:
// Closure: penghitung dengan state pribadi
function buatPenghitung() {
let hitungan = 0; // tidak bisa diakses langsung dari luar
return {
tambah: () => ++hitungan,
lihat: () => hitungan,
};
}
const klik = buatPenghitung();
klik.tambah();
klik.tambah();
console.log(klik.lihat()); // → 2
console.log(klik.hitungan); // → undefined (benar-benar pribadi)
const klikLain = buatPenghitung(); // closure baru, hitungan sendiri
console.log(klikLain.lihat()); // → 0
// Function factory
const buatPengali = (faktor) => (x) => x * faktor;
const kaliDua = buatPengali(2);
const kaliSepuluh = buatPengali(10);
console.log(kaliDua(7), kaliSepuluh(7)); // → 14 70
// Rekursi: faktorial n! = n × (n-1)!
function faktorial(n) {
if (n <= 1) return 1; // base case
return n * faktorial(n - 1); // recursive case
}
console.log(faktorial(5)); // → 120 (5 × 4 × 3 × 2 × 1)
// Rekursi pada data bertingkat: hitung total file di semua folder
const folder = {
nama: "proyek",
file: 2,
isi: [
{ nama: "src", file: 5, isi: [{ nama: "utils", file: 3, isi: [] }] },
{ nama: "docs", file: 1, isi: [] },
],
};
function totalFile(f) {
let total = f.file;
for (const sub of f.isi) {
total += totalFile(sub); // panggil diri sendiri untuk subfolder
}
return total;
}
console.log(totalFile(folder)); // → 11
Kesalahan umum
- Memanggil callback saat mengirimnya.
setTimeout(sapa(), 1000)langsung menjalankansapadan mengirim hasilnya. Kirim function-nya:setTimeout(sapa, 1000). - Lupa
returndi callbackmap/filteryang memakai{ }. Hasilnya array berisiundefinedatau array kosong. reducetanpa nilai awal. Pada array kosong munculTypeError: Reduce of empty array with no initial value. Selalu berikan nilai awal.- Memakai
maphanya untuk efek samping. Kalau tidak butuh array hasil, pakaiforEachataufor...of. - Rekursi tanpa base case atau masalah yang tidak mengecil. Muncul
RangeError: Maximum call stack size exceeded. Pastikan setiap panggilan mendekati kondisi berhenti. sort()tanpa function pembanding untuk angka.[10, 9, 1].sort()menghasilkan[1, 10, 9]karena diurutkan sebagai teks. Gunakan(a, b) => a - b. Ingat juga bahwasortmengubah array asli, jadi salin dulu dengan[...arr].
Latihan
- Mudah, Dari
[1, 2, 3, 4, 5, 6], buat array kuadrat dari bilangan genap saja memakaifilterdanmap. Hasil:[4, 16, 36]. - Sedang, Diberikan
const transaksi = [{ jenis: "masuk", jumlah: 500000 }, { jenis: "keluar", jumlah: 120000 }, { jenis: "keluar", jumlah: 80000 }, { jenis: "masuk", jumlah: 200000 }]. Hitung saldo akhir dengan satureduce(masuk menambah, keluar mengurangi). - Menantang, (a) Buat closure
buatDompet(saldoAwal)yang mengembalikan methodsetor(x),tarik(x)(tolak kalau saldo kurang, kembalikan pesan), dancekSaldo(). (b) Buat function rekursifjumlahDigit(n)yang menjumlahkan digit sebuah bilangan bulat positif, misalnyajumlahDigit(1234)menghasilkan10.
Lihat solusi
1.
const hasil = [1, 2, 3, 4, 5, 6]
.filter((n) => n % 2 === 0)
.map((n) => n ** 2);
console.log(hasil); // → [ 4, 16, 36 ]
2.
const transaksi = [
{ jenis: "masuk", jumlah: 500000 },
{ jenis: "keluar", jumlah: 120000 },
{ jenis: "keluar", jumlah: 80000 },
{ jenis: "masuk", jumlah: 200000 },
];
const saldo = transaksi.reduce(
(acc, t) => (t.jenis === "masuk" ? acc + t.jumlah : acc - t.jumlah),
0
);
console.log(saldo); // → 500000
3.
// (a) Closure dompet
function buatDompet(saldoAwal) {
let saldo = saldoAwal;
return {
setor(x) {
saldo += x;
return saldo;
},
tarik(x) {
if (x > saldo) return "Saldo tidak cukup";
saldo -= x;
return saldo;
},
cekSaldo: () => saldo,
};
}
const dompet = buatDompet(100000);
console.log(dompet.setor(50000)); // → 150000
console.log(dompet.tarik(200000)); // → Saldo tidak cukup
console.log(dompet.tarik(30000)); // → 120000
console.log(dompet.cekSaldo()); // → 120000
// (b) Rekursi jumlah digit
function jumlahDigit(n) {
if (n < 10) return n; // base case: satu digit
return (n % 10) + jumlahDigit(Math.floor(n / 10)); // digit terakhir + sisanya
}
console.log(jumlahDigit(1234)); // → 10
console.log(jumlahDigit(9)); // → 9
Ringkasan
- Function di JavaScript adalah first-class: bisa disimpan, dikirim, dan dikembalikan.
- Callback = function yang dikirim untuk dipanggil nanti. HOF = function yang menerima atau mengembalikan function.
mapmengubah setiap elemen,filtermenyaring,reducemenggabungkan menjadi satu nilai. Ketiganya tidak mengubah array asli.- Closure membuat function mengingat variabel dari scope asalnya, berguna untuk state pribadi dan function factory.
- Rekursi wajib punya base case dan masalah yang terus mengecil.