Bab 5: Fungsi Lanjutan
Kalau di bab sebelumnya kamu belajar bikin fungsi dasar, sekarang kita naik level. Bab ini tentang teknik-teknik canggih yang bikin fungsi JavaScript jadi super fleksibel. Ini yang bikin JavaScript beda dari bahasa lain.
5.1 Rekursi dan Stack
Apa Itu Rekursi?
Analogi: Bayangkan kamu berdiri di antara dua cermin. Kamu lihat pantulan dirimu yang memantulkan dirimu lagi, dan lagi, dan lagi... sampai akhirnya berhenti (karena cermin ada batasnya). Itulah rekursi — fungsi yang memanggil dirinya sendiri.
function hitungMundur(angka) {
console.log(angka);
if (angka <= 0) return; // titik berhenti (base case)
hitungMundur(angka - 1); // panggil diri sendiri
}
hitungMundur(5); // 5, 4, 3, 2, 1, 0Dua Komponen Wajib Rekursi
- Base case — kondisi berhenti (tanpa ini = infinite loop = crash)
- Recursive step — panggil diri sendiri dengan masalah yang lebih kecil
Contoh Klasik: Pangkat
// Iteratif (pakai loop)
function pangkatLoop(x, n) {
let hasil = 1;
for (let i = 0; i < n; i++) {
hasil *= x;
}
return hasil;
}
// Rekursif (panggil diri sendiri)
function pangkat(x, n) {
if (n === 1) return x; // base case
return x * pangkat(x, n - 1); // recursive step
}
console.log(pangkat(2, 4)); // 16
// Cara kerjanya:
// pangkat(2,4) = 2 * pangkat(2,3)
// pangkat(2,3) = 2 * pangkat(2,2)
// pangkat(2,2) = 2 * pangkat(2,1)
// pangkat(2,1) = 2 ← base case, mulai balikExecution Context Stack
Setiap kali fungsi dipanggil, JavaScript menyimpan "konteks" di tumpukan (stack):
pangkat(2,1) ← sedang jalan [paling atas]
pangkat(2,2) ← nunggu
pangkat(2,3) ← nunggu
pangkat(2,4) ← nunggu [paling bawah]
Begitu base case tercapai, stack "dibongkar" dari atas ke bawah. Makanya rekursi pakai memori — setiap panggilan nambah satu layer di stack.
Recursive Traversal (Jelajah Rekursif)
Rekursi paling berguna untuk data bersarang (nested):
const perusahaan = {
marketing: [
{ nama: "Andi", gaji: 5000000 },
{ nama: "Budi", gaji: 4500000 }
],
engineering: {
frontend: [{ nama: "Cici", gaji: 7000000 }],
backend: [{ nama: "Dedi", gaji: 7500000 }]
}
};
function totalGaji(departemen) {
if (Array.isArray(departemen)) {
// Base case: array karyawan → jumlahkan
return departemen.reduce((total, org) => total + org.gaji, 0);
}
// Recursive step: objek → jelajahi sub-departemen
let total = 0;
for (const subDept of Object.values(departemen)) {
total += totalGaji(subDept);
}
return total;
}
console.log(totalGaji(perusahaan)); // 24000000Linked List (Daftar Berantai)
Struktur data rekursif — setiap elemen punya value dan pointer ke elemen next:
const daftar = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: null // akhir daftar
}
}
};
// Cetak semua nilai (rekursif)
function cetakDaftar(daftar) {
console.log(daftar.value);
if (daftar.next) cetakDaftar(daftar.next);
}
cetakDaftar(daftar); // 1, 2, 3- Lupa base case = stack overflow (program crash)
- JavaScript punya batas stack (~10.000 panggilan). Jangan rekursi untuk data sangat besar
- Rekursi lebih lambat dari loop (karena overhead stack), tapi kodenya lebih bersih
Buat fungsi fibonacci(n) yang mengembalikan angka Fibonacci ke-n. Fibonacci: 1, 1, 2, 3, 5, 8, 13...
// fibonacci(1) = 1
// fibonacci(7) = 13
// Hint: fib(n) = fib(n-1) + fib(n-2), base case: fib(1) = fib(2) = 15.2 Rest Parameter dan Spread Syntax
Rest Parameter (...)
Analogi: Kamu pesan makanan di restoran. "Saya mau nasi goreng, terus... sisanya terserah chef." Rest parameter = "sisanya".
function totalBelanja(diskon, ...hargaBarang) {
// diskon = parameter pertama
// hargaBarang = ARRAY berisi semua argumen sisanya
const total = hargaBarang.reduce((sum, h) => sum + h, 0);
return total - diskon;
}
console.log(totalBelanja(5000, 20000, 15000, 30000)); // 60000
// diskon = 5000
// hargaBarang = [20000, 15000, 30000]Aturan: Rest parameter HARUS di posisi terakhir.
// ❌ SALAH
function salah(...angka, terakhir) {}
// ✅ BENAR
function benar(pertama, ...sisanya) {}Spread Syntax (...)
Kebalikan dari rest — membongkar array/objek jadi elemen terpisah.
const angka = [3, 7, 1, 9, 4];
// Tanpa spread — Math.max butuh argumen terpisah, bukan array
console.log(Math.max(angka)); // NaN 😢
// Dengan spread — array dibongkar jadi 3, 7, 1, 9, 4
console.log(Math.max(...angka)); // 9 🎉
// Gabung array
const buah = ["apel", "jeruk"];
const sayur = ["bayam", "wortel"];
const semua = [...buah, ...sayur, "tempe"];
// ["apel", "jeruk", "bayam", "wortel", "tempe"]Spread untuk Copy Objek/Array
// Copy array (shallow copy)
const asli = [1, 2, 3];
const salinan = [...asli];
salinan.push(4);
console.log(asli); // [1, 2, 3] — tidak berubah!
console.log(salinan); // [1, 2, 3, 4]
// Copy objek
const user = { nama: "Andi", umur: 25 };
const userBaru = { ...user, kota: "Jakarta" };
// { nama: "Andi", umur: 25, kota: "Jakarta" }...di parameter fungsi = rest (mengumpulkan)...di pemanggilan/literal = spread (membongkar)- Spread hanya shallow copy — objek nested tetap berbagi referensi
Buat fungsi gabungUnik(...arrays) yang menerima beberapa array dan mengembalikan satu array berisi semua elemen unik (tanpa duplikat).
// gabungUnik([1,2,3], [2,3,4], [4,5]) → [1,2,3,4,5]5.3 Scope Variabel dan Closure
Scope (Cakupan)
Analogi: Bayangkan rumah dengan kamar-kamar. Barang di kamar A tidak bisa diakses dari kamar B. Tapi barang di ruang tamu (global) bisa diakses dari mana saja.
// Scope global
const namaApp = "TokoKu";
function tampilkanProduk() {
// Scope lokal fungsi
const produk = "Sepatu";
console.log(namaApp); // ✅ Bisa akses global
console.log(produk); // ✅ Bisa akses lokal
}
console.log(namaApp); // ✅
console.log(produk); // ❌ Error! produk tidak ada di siniBlock Scope
Variabel let dan const hanya hidup di dalam {} tempat mereka dideklarasikan:
if (true) {
let rahasia = "abc123";
console.log(rahasia); // ✅ "abc123"
}
console.log(rahasia); // ❌ Error! Tidak ada di luar block
for (let i = 0; i < 3; i++) {
// i hanya ada di sini
}
console.log(i); // ❌ Error!Lexical Environment
Setiap kali kode berjalan (fungsi dipanggil, block {} dimasuki), JavaScript membuat Lexical Environment — semacam "tas" yang menyimpan variabel lokal + referensi ke environment luar.
┌─────────────────────────┐
│ Lexical Environment │
│ - variabel lokal │
│ - referensi ke outer → ─┼──→ [Environment luar]
└─────────────────────────┘
Closure — Konsep Paling Penting!
Analogi: Kamu punya kotak bekal dari rumah. Mau kamu bawa ke kantor, ke taman, ke mana pun — isi kotak tetap dari rumah. Closure = fungsi yang "membawa bekal" dari tempat dia dibuat.
function buatCounter() {
let hitungan = 0; // variabel "bekal"
return function() {
hitungan++;
return hitungan;
};
}
const counter = buatCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// hitungan "hidup" di dalam closure, tidak bisa diakses dari luar
// console.log(hitungan); // ❌ Error!Kenapa ini penting?
- Closure = cara bikin data privat di JavaScript
- Setiap panggilan
buatCounter()bikin closure BARU (independen)
const counterA = buatCounter();
const counterB = buatCounter();
console.log(counterA()); // 1
console.log(counterA()); // 2
console.log(counterB()); // 1 ← independen!Closure di Dunia Nyata
// Fungsi pembuat greeting
function buatGreeting(salam) {
return function(nama) {
return `${salam}, ${nama}!`;
};
}
const halo = buatGreeting("Halo");
const selamat = buatGreeting("Selamat pagi");
console.log(halo("Andi")); // "Halo, Andi!"
console.log(selamat("Budi")); // "Selamat pagi, Budi!"- Closure mengakses nilai terkini variabel, bukan salinan saat dibuat:
let nama = "Andi";
function sapa() {
console.log(`Hai, ${nama}`);
}
nama = "Budi"; // diubah sebelum fungsi dipanggil
sapa(); // "Hai, Budi" — bukan "Andi"!- Loop + closure = jebakan klasik:
// ❌ BUG: semua alert menampilkan 10
for (var i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 10, 10, 10, ... (10 kali)
// ✅ FIX: pakai let (setiap iterasi punya scope sendiri)
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2, 3, ..., 9Buat fungsi buatAkumulator(nilaiAwal) yang mengembalikan fungsi. Setiap kali fungsi itu dipanggil dengan angka, dia menambahkan angka tersebut ke total dan mengembalikan total baru.
const akum = buatAkumulator(10);
console.log(akum(5)); // 15
console.log(akum(3)); // 18
console.log(akum(7)); // 255.4 "var" yang Lama
Kenapa Perlu Tahu?
Kamu akan sering ketemu var di kode lama. Penting paham bedanya supaya tidak kena bug misterius.
Perbedaan var vs let/const
| Fitur | var | let/const |
|---|---|---|
| Scope | Function scope | Block scope |
| Hoisting | Ya (diangkat ke atas) | Ya tapi "dead zone" |
| Re-deklarasi | Boleh | Error |
Function Scope (Bukan Block Scope!)
if (true) {
var pesan = "Halo"; // var TIDAK peduli block!
}
console.log(pesan); // "Halo" ← masih bisa diakses!
// Bandingkan dengan let:
if (true) {
let pesan2 = "Hai";
}
// console.log(pesan2); // ❌ Error!Hoisting (Diangkat ke Atas)
console.log(x); // undefined (bukan Error!)
var x = 5;
// JavaScript "membaca" kode di atas seperti ini:
// var x; ← deklarasi diangkat ke atas
// console.log(x); ← undefined karena belum diisi
// x = 5; ← assignment tetap di tempatIIFE (Immediately Invoked Function Expression)
Dulu, sebelum ada let, programmer pakai trik ini untuk bikin scope:
// Pola IIFE — langsung jalan setelah dideklarasikan
(function() {
var rahasia = "abc123";
console.log(rahasia); // ✅
})();
// console.log(rahasia); // ❌ Error — terkurung di dalam IIFE- Jangan pakai
vardi kode baru — selalulet/const vardi dalamforloop bocor keluar:
for (var i = 0; i < 3; i++) {}
console.log(i); // 3 ← bocor!
for (let j = 0; j < 3; j++) {}
// console.log(j); // ❌ Error — amanTebak output kode ini (tanpa menjalankan):
var a = 1;
function test() {
console.log(a); // ?
var a = 2;
console.log(a); // ?
}
test();
console.log(a); // ?5.5 Objek Global
Apa Itu?
Objek global = "wadah" untuk semua yang bersifat global. Di browser namanya window, di Node.js namanya global (atau globalThis yang universal).
// Di browser:
var nama = "Andi"; // var bikin property di window!
console.log(window.nama); // "Andi"
// let/const TIDAK bikin property di window
let umur = 25;
console.log(window.umur); // undefinedglobalThis — Universal
// Bekerja di mana saja (browser, Node.js, dll)
globalThis.appNama = "TokoKu";
console.log(globalThis.appNama); // "TokoKu"Kapan Pakai Objek Global?
Hampir tidak pernah untuk menyimpan data. Tapi berguna untuk:
- Cek fitur browser:
if (window.Promise) - Polyfill:
if (!window.fetch) { window.fetch = ... }
- Jangan polusi global scope — bikin konflik nama
- Pakai module system (import/export) untuk berbagi data antar file
Buat polyfill sederhana: cek apakah globalThis.Array.prototype.at ada. Kalau tidak, tambahkan method at yang mengambil elemen array berdasarkan index (termasuk index negatif).
5.6 Function Object dan NFE
Fungsi = Objek
Di JavaScript, fungsi itu objek. Artinya fungsi punya property!
function sapa(nama) {
console.log(`Hai, ${nama}`);
}
// Property bawaan
console.log(sapa.name); // "sapa" — nama fungsi
console.log(sapa.length); // 1 — jumlah parameter
// Bisa tambah property sendiri
sapa.jumlahPanggilan = 0;Property name
// Function declaration
function halo() {}
console.log(halo.name); // "halo"
// Function expression
const selamat = function() {};
console.log(selamat.name); // "selamat" — diambil dari variabel!
// Method di objek
const obj = {
metode() {},
metode2: function() {}
};
console.log(obj.metode.name); // "metode"
console.log(obj.metode2.name); // "metode2"Property length
Menghitung jumlah parameter (tidak termasuk rest ...):
function f1(a) {}
function f2(a, b) {}
function f3(a, b, ...rest) {}
console.log(f1.length); // 1
console.log(f2.length); // 2
console.log(f3.length); // 2 — rest tidak dihitung!Custom Property (Pengganti Closure)
function buatCounter() {
function counter() {
return counter.hitungan++;
}
counter.hitungan = 0;
return counter;
}
const hitung = buatCounter();
console.log(hitung()); // 0
console.log(hitung()); // 1
console.log(hitung.hitungan); // 2 — bisa diakses dari luar!Bedanya dengan closure: property bisa diakses dari luar, closure tidak.
NFE (Named Function Expression)
// Function Expression biasa
const sapa = function(nama) {
console.log(`Hai, ${nama}`);
};
// Named Function Expression — punya nama internal
const sapa2 = function sapaDalam(nama) {
if (!nama) {
sapaDalam("Anonim"); // bisa panggil diri sendiri!
return;
}
console.log(`Hai, ${nama}`);
};
sapa2(); // "Hai, Anonim"
// sapaDalam(); // ❌ Error — nama hanya ada di dalam fungsifunction.lengthtidak menghitung parameter dengan default value- Property fungsi bukan variabel lokal — tidak ada hubungannya dengan closure
Buat fungsi buatCounter() yang mengembalikan fungsi counter. Counter harus punya method reset() untuk mengembalikan hitungan ke 0.
const counter = buatCounter();
console.log(counter()); // 0
console.log(counter()); // 1
counter.reset();
console.log(counter()); // 05.7 Sintaks "new Function"
Membuat Fungsi dari String
// Sintaks: new Function('param1', 'param2', 'body')
const jumlah = new Function('a', 'b', 'return a + b');
console.log(jumlah(3, 5)); // 8Kapan Dipakai?
Sangat jarang! Biasanya hanya ketika:
- Menerima kode dari server
- Membuat fungsi secara dinamis dari template
Perbedaan Penting: Tidak Punya Closure!
function biasa() {
const x = 10;
const fn = new Function('return x'); // ❌ tidak bisa akses x!
return fn();
}
// biasa(); // Error: x is not defined
function biasa2() {
const x = 10;
const fn = () => x; // ✅ closure normal
return fn();
}
console.log(biasa2()); // 10Fungsi dari new Function hanya bisa akses variabel global, bukan variabel lokal di sekitarnya.
- Hindari
new Functionkecuali benar-benar perlu - Mirip
eval()— potensi masalah keamanan - Tidak bisa di-optimize oleh engine karena body berupa string
Buat fungsi buatKalkulator(operasi) yang menerima string operasi ("+", "-", "*", "/") dan mengembalikan fungsi yang melakukan operasi tersebut. Gunakan new Function.
const kali = buatKalkulator("*");
console.log(kali(3, 4)); // 125.8 Scheduling: setTimeout dan setInterval
setTimeout — Jalankan Sekali Setelah Delay
Analogi: Kamu set alarm untuk 5 menit lagi. Alarm bunyi sekali, selesai.
// Sintaks: setTimeout(fungsi, delay_ms, ...argumen)
function sapa(nama) {
console.log(`Halo, ${nama}!`);
}
// Jalankan sapa setelah 2 detik (2000 ms)
setTimeout(sapa, 2000, "Andi");
// Setelah 2 detik: "Halo, Andi!"Membatalkan setTimeout
const timerId = setTimeout(() => console.log("Bom!"), 5000);
// Berubah pikiran — batalkan!
clearTimeout(timerId);
// "Bom!" tidak akan pernah munculsetInterval — Jalankan Berulang
Analogi: Alarm yang bunyi setiap 5 menit, terus-terusan sampai kamu matikan.
// Cetak waktu setiap 1 detik
const intervalId = setInterval(() => {
console.log(new Date().toLocaleTimeString());
}, 1000);
// Hentikan setelah 5 detik
setTimeout(() => {
clearInterval(intervalId);
console.log("Timer dihentikan");
}, 5000);setTimeout Rekursif (Alternatif setInterval)
// Lebih fleksibel — bisa atur delay berbeda tiap iterasi
let delay = 1000;
function polling() {
console.log("Cek server...");
// Kalau gagal, tunggu lebih lama
delay *= 2; // exponential backoff
setTimeout(polling, delay);
}
setTimeout(polling, delay);Keuntungan vs setInterval:
- Delay dihitung SETELAH fungsi selesai (bukan dari mulai)
- Bisa ubah delay secara dinamis
Zero Delay setTimeout
console.log("1 - sebelum");
setTimeout(() => console.log("2 - timeout"), 0);
console.log("3 - sesudah");
// Output:
// "1 - sebelum"
// "3 - sesudah"
// "2 - timeout" ← tetap terakhir! (masuk antrian)Meskipun delay = 0, fungsi tetap masuk antrian dan dijalankan setelah kode sinkron selesai.
setTimeoutdelay minimum, bukan tepat. Kalau CPU sibuk, bisa lebih lama- Di browser, nested setTimeout punya minimum delay 4ms setelah 5 panggilan
thishilang di setTimeout:
const user = {
nama: "Andi",
sapa() {
console.log(`Hai, ${this.nama}`);
}
};
setTimeout(user.sapa, 1000); // "Hai, undefined" 😱
// Fix:
setTimeout(() => user.sapa(), 1000); // "Hai, Andi" ✅Buat fungsi cetakBerurutan(pesan, delay) yang mencetak setiap karakter dari pesan satu per satu dengan jeda delay ms.
cetakBerurutan("Halo!", 300);
// H (setelah 300ms)
// a (setelah 600ms)
// l (setelah 900ms)
// o (setelah 1200ms)
// ! (setelah 1500ms)5.9 Decorator dan Forwarding (call/apply)
Apa Itu Decorator?
Analogi: Kamu punya kue polos. Decorator = menambah topping tanpa mengubah kue aslinya. Di programming, decorator = fungsi yang "membungkus" fungsi lain untuk menambah fitur.
Contoh: Caching Decorator
function lambat(x) {
// Simulasi operasi berat
console.log(`Menghitung ${x}...`);
return x * x;
}
function cachingDecorator(fn) {
const cache = new Map();
return function(x) {
if (cache.has(x)) {
console.log(`Dari cache: ${x}`);
return cache.get(x);
}
const hasil = fn(x); // panggil fungsi asli
cache.set(x, hasil);
return hasil;
};
}
const cepat = cachingDecorator(lambat);
console.log(cepat(5)); // "Menghitung 5..." → 25
console.log(cepat(5)); // "Dari cache: 5" → 25 (instan!)
console.log(cepat(3)); // "Menghitung 3..." → 9func.call() — Panggil dengan this Tertentu
function sapa() {
console.log(`Hai, ${this.nama}`);
}
const user1 = { nama: "Andi" };
const user2 = { nama: "Budi" };
sapa.call(user1); // "Hai, Andi"
sapa.call(user2); // "Hai, Budi"
// Dengan argumen:
function info(kota, umur) {
console.log(`${this.nama} dari ${kota}, umur ${umur}`);
}
info.call(user1, "Jakarta", 25); // "Andi dari Jakarta, umur 25"func.apply() — Sama tapi Argumen dalam Array
// call: argumen satu-satu
info.call(user1, "Jakarta", 25);
// apply: argumen dalam array
info.apply(user1, ["Jakarta", 25]);
// Hasilnya sama! Bedanya cuma cara passing argumenDecorator untuk Method (Pakai call)
const worker = {
nama: "Server",
proses(x) {
console.log(`${this.nama} memproses ${x}`);
return x * 2;
}
};
function cachingDecorator(fn) {
const cache = new Map();
return function(x) {
if (cache.has(x)) return cache.get(x);
const hasil = fn.call(this, x); // forward this!
cache.set(x, hasil);
return hasil;
};
}
worker.proses = cachingDecorator(worker.proses);
worker.proses(5); // "Server memproses 5" → 10
worker.proses(5); // dari cache → 10Forwarding Semua Argumen
function decorator(fn) {
return function() {
// Forward semua argumen + this
return fn.apply(this, arguments);
};
}
// Versi modern dengan rest/spread:
function decoratorModern(fn) {
return function(...args) {
return fn.call(this, ...args);
};
}- Decorator mengganti referensi fungsi — property asli hilang
argumentsbukan array sungguhan (tidak punya method array)- Hati-hati dengan
thissaat membungkus method
Buat delayDecorator(fn, ms) yang menunda eksekusi fungsi selama ms milidetik:
function sapa(nama) {
console.log(`Hai, ${nama}`);
}
const sapaLambat = delayDecorator(sapa, 2000);
sapaLambat("Andi"); // Setelah 2 detik: "Hai, Andi"5.10 Function Binding
Masalah: Kehilangan this
Analogi: Kamu kasih nomor telepon teman ke orang lain. Orang itu telepon, tapi yang angkat bukan temanmu — salah sambung! Itulah yang terjadi saat method "dilepas" dari objeknya.
const user = {
nama: "Andi",
sapa() {
console.log(`Hai, saya ${this.nama}`);
}
};
// Method dilepas dari objek
const fungsiSapa = user.sapa;
fungsiSapa(); // "Hai, saya undefined" 😱
// Atau di setTimeout
setTimeout(user.sapa, 1000); // "Hai, saya undefined" 😱Solusi 1: Wrapper Function
setTimeout(function() {
user.sapa(); // "Hai, saya Andi" ✅
}, 1000);
// Atau arrow function (lebih singkat):
setTimeout(() => user.sapa(), 1000); // ✅Kelemahan: Kalau user berubah sebelum timeout selesai, wrapper akan panggil objek yang baru.
Solusi 2: bind() — Kunci this Permanen
const sapaTerikat = user.sapa.bind(user);
sapaTerikat(); // "Hai, saya Andi" ✅
setTimeout(sapaTerikat, 1000); // "Hai, saya Andi" ✅
// Bahkan kalau user berubah:
user = { nama: "Budi" };
sapaTerikat(); // "Hai, saya Andi" ← tetap Andi! bind sudah kuncibind dengan Argumen (Partial Application)
function kali(a, b) {
return a * b;
}
// Kunci argumen pertama = 2
const kaliDua = kali.bind(null, 2);
console.log(kaliDua(3)); // 6 (= 2 * 3)
console.log(kaliDua(5)); // 10 (= 2 * 5)
// Kunci argumen pertama = 3
const kaliTiga = kali.bind(null, 3);
console.log(kaliTiga(4)); // 12 (= 3 * 4)Bind Semua Method Sekaligus
const user = {
nama: "Andi",
sapa() { console.log(`Hai, ${this.nama}`); },
bye() { console.log(`Bye, ${this.nama}`); }
};
// Bind semua method
for (const key in user) {
if (typeof user[key] === "function") {
user[key] = user[key].bind(user);
}
}
const { sapa, bye } = user; // destructure aman!
sapa(); // "Hai, Andi" ✅
bye(); // "Bye, Andi" ✅bindmembuat fungsi BARU — tidak mengubah yang asli- Fungsi yang sudah di-bind tidak bisa di-bind ulang (this sudah terkunci)
bindmenghilangkan custom property dari fungsi asli
Buat fungsi partial(fn, ...argsTetap) yang mirip bind tapi TANPA mengunci this:
const user = {
nama: "Andi",
sapa(waktu, pesan) {
console.log(`[${waktu}] ${this.nama}: ${pesan}`);
}
};
user.sapaPagi = partial(user.sapa, "08:00");
user.sapaPagi("Selamat pagi!"); // "[08:00] Andi: Selamat pagi!"5.11 Arrow Function (Revisited)
Bukan Sekadar Sintaks Pendek
Arrow function bukan cuma cara singkat menulis fungsi — mereka punya perilaku berbeda yang fundamental.
Tidak Punya this Sendiri
Arrow function mengambil this dari luar (lexical this):
const tim = {
nama: "Frontend",
anggota: ["Andi", "Budi", "Cici"],
tampilkan() {
// Arrow function mengambil this dari tampilkan()
this.anggota.forEach(anggota => {
console.log(`${anggota} - Tim ${this.nama}`);
});
}
};
tim.tampilkan();
// "Andi - Tim Frontend"
// "Budi - Tim Frontend"
// "Cici - Tim Frontend"
// Kalau pakai function biasa:
const tim2 = {
nama: "Backend",
anggota: ["Dedi", "Eka"],
tampilkan() {
this.anggota.forEach(function(anggota) {
console.log(`${anggota} - Tim ${this.nama}`); // ❌ this = undefined!
});
}
};Tidak Punya arguments
function biasa() {
console.log(arguments); // ✅ [1, 2, 3]
}
biasa(1, 2, 3);
const arrow = () => {
// console.log(arguments); // ❌ Error! Tidak ada arguments
};
// Solusi: pakai rest parameter
const arrowFix = (...args) => {
console.log(args); // ✅ [1, 2, 3]
};
arrowFix(1, 2, 3);Tidak Bisa Jadi Constructor
const Orang = (nama) => {
this.nama = nama;
};
// new Orang("Andi"); // ❌ TypeError: Orang is not a constructorTidak Punya super
Arrow function mengambil super dari fungsi luar (berguna di class inheritance — nanti di bab Class).
Kapan Pakai Arrow vs Function Biasa?
| Situasi | Pakai |
|---|---|
| Callback pendek (map, filter, forEach) | Arrow ✅ |
| Method di objek | Function biasa ✅ |
| Constructor | Function biasa ✅ |
Event handler yang butuh this = element | Function biasa ✅ |
Butuh arguments | Function biasa ✅ |
- Jangan pakai arrow untuk method objek:
const user = {
nama: "Andi",
// ❌ SALAH — arrow tidak punya this sendiri
sapa: () => {
console.log(`Hai, ${this.nama}`); // undefined!
},
// ✅ BENAR
sapa2() {
console.log(`Hai, ${this.nama}`); // "Andi"
}
};- Arrow function tidak bisa di-
bind,call, atauapplyuntuk mengubahthis— karena memang tidak punyathissendiri
Refactor kode ini agar bekerja dengan benar (gunakan pengetahuan tentang arrow function):
const kalkulator = {
nilai: 0,
tambah(angka) {
// Bug: setTimeout kehilangan this
setTimeout(function() {
this.nilai += angka;
console.log(`Nilai sekarang: ${this.nilai}`);
}, 1000);
}
};
kalkulator.tambah(5); // Harusnya: "Nilai sekarang: 5"Ringkasan Bab 5
| Konsep | Inti |
|---|---|
| Rekursi | Fungsi panggil diri sendiri, butuh base case |
| Rest/Spread | ... mengumpulkan (rest) atau membongkar (spread) |
| Closure | Fungsi "ingat" variabel dari tempat dia dibuat |
| var | Function scope, hoisting — hindari di kode baru |
| Objek Global | globalThis — wadah variabel global |
| Function Object | Fungsi = objek, punya .name, .length |
| new Function | Buat fungsi dari string — tanpa closure |
| setTimeout/setInterval | Jadwalkan eksekusi di masa depan |
| Decorator | Bungkus fungsi untuk tambah fitur |
| bind | Kunci this permanen + partial application |
| Arrow Function | Tanpa this, arguments, super sendiri |
Pesan penting: Closure dan this adalah dua konsep yang paling sering bikin bingung di JavaScript. Kalau kamu paham bab ini, kamu sudah melewati rintangan terbesar! 🎯
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.