Materi 1 · Modul 0
Kenalan sama JavaScript
Satu-satunya bahasa yang bisa dijalankan langsung oleh browser — dan sekarang jauh lebih dari itu.
JavaScript itu apa
JavaScript adalah bahasa pemrograman yang awalnya dibuat untuk membuat halaman web jadi interaktif — tombol yang merespons klik, form yang tervalidasi sebelum dikirim, konten yang berubah tanpa reload halaman. Berbeda dari PHP (yang dijalankan di server), JavaScript aslinya dijalankan langsung di browser pengunjung.
Dua tempat JavaScript berjalan
| Tempat | Fungsinya |
|---|---|
| Browser | Tempat asal JavaScript — mengatur tampilan, merespons klik, mengambil data tanpa reload halaman. |
| Node.js | Menjalankan JavaScript di luar browser — untuk membuat server, script otomasi, command line tool. |
Handbook ini fokus pada JavaScript di browser — dasar bahasa, DOM, event, dan cara mengambil data. Kalau nanti tertarik memakai JavaScript untuk backend, ada handbook Node.js + TypeScript terpisah yang membahasnya mendalam.
Menjalankan JavaScript pertama
Cara paling cepat mencoba JavaScript: buka browser, tekan F12 (atau klik kanan → Inspect), buka tab Console, lalu ketik langsung di sana.
console.log("Halo dari JavaScript!");
// tampil: Halo dari JavaScript!
2 + 3;
// tampil: 5Catatan — console.log() adalah cara paling sering dipakai untuk "melihat" apa yang terjadi di kode — menampilkan nilai ke console browser. Kamu akan memakainya terus-menerus, terutama saat debugging (mengetahui kenapa sesuatu tidak berjalan seperti harapan).
Menyisipkan JavaScript ke HTML
<!DOCTYPE html>
<html>
<head>
<title>Halaman Pertama</title>
</head>
<body>
<h1>Halo!</h1>
<!-- taruh <script> sebelum </body> — supaya HTML di atasnya sudah dimuat dulu -->
<script>
console.log("Halaman sudah dimuat");
alert("Selamat datang!"); // kotak dialog kecil di browser
</script>
</body>
</html>Hati-hati — Letak tag <script> penting. Kalau diletakkan di <head> tanpa penanganan khusus, JavaScript bisa mencoba mengakses elemen HTML yang belum ada saat script itu berjalan (karena browser membaca dari atas ke bawah). Kebiasaan aman untuk pemula: selalu taruh <script> tepat sebelum </body>.
File JavaScript terpisah
Untuk kode yang lebih dari beberapa baris, pisahkan ke file .js sendiri — lebih rapi dan bisa dipakai ulang di banyak halaman.
console.log("Ini dari file terpisah");<body>
<h1>Halo!</h1>
<script src="script.js"></script>
</body>Komentar
// komentar satu baris
/*
komentar
beberapa baris
*/Yang akan kamu pelajari
- Modul 0–1 — dasar bahasa: variabel, tipe data, kondisi, perulangan, fungsi, array, objek.
- Modul 2 — DOM: mengubah tampilan halaman, merespons klik dan input pengguna.
- Modul 3 — asynchronous: mengambil data dari server tanpa membuat halaman "macet".
- Modul 4 — JavaScript modern: class, module, dan fitur ES6+ yang dipakai di kode masa kini.
Setelah handbook ini selesai, ada studi kasus yang membangun sebuah game kecil di browser — cara seru mempraktikkan semuanya sekaligus.
Materi 2 · Modul 0
Variabel dan tipe data
Cara menyimpan nilai, dan jenis-jenis nilai yang bisa disimpan.
let, const, dan var
let nama = "Kaos Onlenkan"; // bisa diubah nanti
nama = "Kaos Polos"; // ✅ boleh
const pajak = 0.11; // TIDAK bisa diubah setelah diisi
pajak = 0.12; // ❌ TypeError: Assignment to constant variable
var lama = "gaya lama"; // cara lama, HINDARI di kode baruCatatan — Aturan praktis: pakai const secara default untuk semua variabel. Ganti ke let hanya kalau kamu memang tahu nilainya perlu berubah nanti (seperti penghitung di perulangan). Hindari var sepenuhnya di kode baru — dia punya perilaku aneh soal scope (dibahas di Materi 7) yang sudah digantikan let/const sejak 2015.
Tipe data primitif
const teks = "Halo"; // string
const angka = 42; // number — JS TIDAK membedakan int/float, semua "number"
const desimal = 3.14; // juga number
const benar = true; // boolean
const kosong = null; // sengaja "tidak ada nilai"
let belumDiisi; // undefined — belum diberi nilai sama sekali
console.log(typeof teks); // "string"
console.log(typeof angka); // "number"Hati-hati — null dan undefined mirip tapi beda maksud: undefined berarti "variabel ada tapi belum diisi apa pun" (JavaScript yang memberi nilai ini secara otomatis), null berarti "sengaja dikosongkan" (kamu yang menulisnya). Keduanya sering jadi sumber bug kalau tidak dicek dengan hati-hati — dibahas lebih lanjut di Materi 3.
Template literal: cara modern menggabungkan string
const nama = "Nus";
const umur = 28;
// cara lama: menyambung dengan +
const pesanLama = "Halo, " + nama + "! Umurmu " + umur + " tahun.";
// cara modern: template literal — pakai backtick ` , bukan kutip biasa
const pesan = `Halo, ${nama}! Umurmu ${umur} tahun.`;
// ${...} bisa berisi EKSPRESI apa pun, bukan cuma variabel
const info = `Tahun depan kamu ${umur + 1} tahun.`;
// bisa multi-baris langsung, tanpa perlu \n
const alamat = `Jl. Merdeka No. 1
Probolinggo, Jawa Timur`;Catatan — Template literal (pakai tanda backtick ` `) adalah cara yang jauh lebih disukai dibanding menyambung string dengan + — lebih mudah dibaca dan lebih jarang salah. Kamu akan memakainya di hampir semua tempat yang membutuhkan teks dinamis mulai sekarang.
Konversi tipe
Number("42"); // 42 (number)
Number("abc"); // NaN (Not a Number — konversi gagal)
String(42); // "42" (string)
Boolean(0); // false
Boolean(""); // false
Boolean("halo"); // true
parseInt("42px"); // 42 — ambil angka di depan, abaikan sisanya
parseFloat("3.14em"); // 3.14Latihan — Buat variabel nama, kota, dan tahunLahir. Hitung umur perkiraan (tahun sekarang dikurangi tahun lahir), lalu tampilkan kalimat lengkap memakai template literal, misalnya "Nus dari Probolinggo, perkiraan umur 28 tahun."
Materi 3 · Modul 0
Operator dan kondisi
Membandingkan nilai, dan membuat program mengambil keputusan.
Operator perbandingan: == vs ===
"5" == 5; // true — JS mengonversi tipe dulu sebelum membandingkan
"5" === 5; // false — beda tipe (string vs number), TIDAK dianggap sama
0 == false; // true — mengejutkan!
0 === false; // false — beda tipe
null == undefined; // true
null === undefined; // falseHati-hati — Ini salah satu jebakan paling terkenal di JavaScript. == ("loose equality") mengonversi tipe dulu sebelum membandingkan, menghasilkan kejutan seperti 0 == false bernilai true. === ("strict equality") membandingkan nilai dan tipe tanpa konversi apa pun. Selalu pakai === dan !==, hampir tanpa pengecualian.
Nilai yang dianggap "falsy"
Saat sebuah nilai dipakai dalam kondisi (seperti if), JavaScript otomatis menganggapnya true atau false. Ada 6 nilai yang selalu dianggap falsy — semua nilai lain dianggap truthy.
// enam nilai FALSY — semuanya dianggap "false" dalam kondisi:
false, 0, "", null, undefined, NaN
// SEMUA yang lain TRUTHY, termasuk yang mengejutkan:
"0" // truthy! (string berisi karakter, walau isinya "0")
"false" // truthy! (string tidak kosong)
[] // truthy! (array kosong tetap truthy)
{} // truthy! (objek kosong tetap truthy)Catatan — Ini penting dipahami karena kamu akan sering menulis if (nilai) untuk mengecek "apakah ada isinya". Untuk array/objek kosong, cek panjangnya secara eksplisit (if (array.length > 0)) — jangan mengandalkan truthy/falsy untuk itu, karena hasilnya tidak seperti dugaan.
if / else if / else
const nilai = 78;
if (nilai >= 90) {
console.log("A");
} else if (nilai >= 80) {
console.log("B");
} else if (nilai >= 70) {
console.log("C");
} else {
console.log("Belum lulus");
}Operator logika: &&, ||, !
stok > 0 && aktif === true; // AND — keduanya harus true
stok === 0 || diskontinu; // OR — salah satu cukup
!aktif; // NOT — kebalikannya
// && dan || sering dipakai untuk nilai default (dibahas lebih dalam di Materi 21)
const nama = inputNama || "Tamu"; // kalau inputNama falsy, pakai "Tamu"Ternary dan nullish coalescing
// ternary: kondisi ? nilai_true : nilai_false
const status = stok > 0 ? "Tersedia" : "Habis";
// nullish coalescing (??) — beda dari || !
const qty = 0;
qty || 10; // 10 — karena 0 itu FALSY, dianggap "tidak ada"
qty ?? 10; // 0 — ?? hanya peduli null/undefined, 0 tetap dipakaiCatatan — ?? (nullish coalescing) berbeda penting dari ||: || mengganti semua nilai falsy (termasuk 0 dan "") dengan nilai cadangan, sedangkan ?? hanya mengganti kalau nilainya benar-benar null atau undefined. Untuk kasus seperti qty/stok yang boleh bernilai 0 secara sah, ?? jauh lebih aman.
switch
switch (status) {
case "lunas":
console.log("Sudah dibayar");
break; // WAJIB — tanpa ini, lanjut ke case berikutnya
case "pending":
console.log("Menunggu pembayaran");
break;
default:
console.log("Status tidak dikenal");
}Latihan — Buat variabel suhu (angka). Tulis kondisi bertingkat: kalau di atas 35 tampilkan "Panas", 25–35 "Nyaman", di bawah 25 "Dingin". Uji juga contoh == vs === di console dengan beberapa pasangan nilai berbeda tipe, amati hasilnya.
Materi 4 · Modul 0
Perulangan
Menjalankan kode berulang kali — dasar untuk memproses daftar data.
for
for (let i = 1; i <= 5; i++) {
console.log(`Baris ${i}`);
}
// Baris 1, Baris 2, ... Baris 5while dan do-while
let stok = 5;
while (stok > 0) {
console.log(`Sisa stok: ${stok}`);
stok--;
}
let n = 0;
do {
console.log("Jalan minimal sekali");
n++;
} while (n < 3);Hati-hati — Pastikan kondisi while pada akhirnya jadi false, atau program terjebak perulangan tak berujung dan tab browser bisa membeku. Selalu ada sesuatu di dalam loop yang mengubah kondisinya.
for...of: perulangan untuk array
const produk = ["Kaos", "Hoodie", "Topi"];
for (const item of produk) {
console.log(item);
}
// Kaos, Hoodie, Topi
// butuh index juga? pakai entries()
for (const [index, item] of produk.entries()) {
console.log(`${index}: ${item}`);
}Catatan — for...of adalah cara paling umum di JavaScript modern untuk mengulang isi array — lebih ringkas dari for biasa dan tidak butuh mengurus index manual. Nanti di Materi 9 kamu akan lihat array juga punya method khusus (forEach, map) yang bahkan lebih sering dipakai lagi.
for...in: perulangan untuk objek
const produk = { nama: "Kaos", harga: 85000, stok: 24 };
for (const kunci in produk) {
console.log(`${kunci}: ${produk[kunci]}`);
}
// nama: Kaos, harga: 85000, stok: 24Hati-hati — for...in untuk objek, for...of untuk array — jangan tertukar. Memakai for...in pada array bisa jalan tapi memberi hasil yang tidak diharapkan (index sebagai string, bukan nilai array), jadi hindari.
break dan continue
for (const item of produk) {
if (item.stok === 0) {
continue; // lewati item ini, lanjut ke berikutnya
}
if (item.nama === "Selesai") {
break; // hentikan seluruh perulangan
}
console.log(item.nama);
}Latihan — Buat array angka berisi 10 bilangan bulat. Pakai for...of untuk menjumlahkan hanya angka genap, lalu tampilkan totalnya. Coba juga versi dengan continue untuk melewati angka ganjil.
Materi 5 · Modul 0
Array dasar
Struktur data untuk menyimpan daftar nilai — akan kamu pakai di hampir setiap program.
Membuat dan mengakses array
const produk = ["Kaos", "Hoodie", "Topi"];
produk[0]; // "Kaos" — index mulai dari 0
produk[2]; // "Topi"
produk.length; // 3
produk[produk.length - 1]; // "Topi" — item terakhir
// array boleh campur tipe, walau jarang dianjurkan
const campur = ["teks", 42, true, null];Menambah dan menghapus item
const produk = ["Kaos", "Hoodie"];
produk.push("Topi"); // tambah di akhir → ["Kaos","Hoodie","Topi"]
produk.unshift("Jaket"); // tambah di awal → ["Jaket","Kaos","Hoodie","Topi"]
produk.pop(); // hapus & kembalikan item TERAKHIR
produk.shift(); // hapus & kembalikan item PERTAMA
produk[0] = "Kaos Polos"; // ubah nilai di posisi tertentu
// splice: hapus/sisipkan di posisi manapun
produk.splice(1, 1); // dari index 1, hapus 1 item
produk.splice(1, 0, "Baru"); // di index 1, hapus 0, SISIPKAN "Baru"Catatan — push/pop bekerja di akhir array (cepat). unshift/shift bekerja di awal (lebih lambat untuk array besar, karena semua item lain harus "geser"). splice paling fleksibel tapi juga paling mudah salah pakai — selalu cek urutan parameternya: (mulaiDariIndex, jumlahYangDihapus, ...itemBaru).
Mencari di dalam array
const angka = [3, 1, 4, 1, 5, 9];
angka.includes(4); // true — apakah ada?
angka.indexOf(4); // 2 — posisi pertama, -1 kalau tidak ada
angka.join(", "); // "3, 1, 4, 1, 5, 9" — gabung jadi string
const produk = [{ nama: "Kaos", harga: 85000 }, { nama: "Topi", harga: 45000 }];
produk.find(p => p.nama === "Topi"); // objek Topi, atau undefined kalau tidak ada
produk.findIndex(p => p.harga > 50000); // index item pertama yang cocokJangan khawatir soal sintaks p => p.nama === "Topi" — itu arrow function, dibahas lengkap di Materi 6. Untuk sekarang, cukup tahu: itu cara memberi tahu find "syarat" yang dicari.
Mengurutkan array
const angka = [3, 1, 4, 1, 5, 9];
angka.sort(); // ❌ jebakan: sort() default membandingkan sebagai STRING!
// hasil: [1, 1, 3, 4, 5, 9] — kebetulan benar di sini, tapi coba [10, 2, 1]:
[10, 2, 1].sort(); // [1, 10, 2] — SALAH secara numerik!
// ✅ untuk angka, SELALU beri fungsi pembanding
[10, 2, 1].sort((a, b) => a - b); // [1, 2, 10] — menaik
[10, 2, 1].sort((a, b) => b - a); // [10, 2, 1] — menurunHati-hati — Jebakan klasik: .sort() tanpa argumen mengurutkan berdasarkan representasi string, bukan nilai numerik — [10, 2, 1].sort() menghasilkan [1, 10, 2], bukan [1, 2, 10]. Untuk array angka, selalu berikan fungsi pembanding seperti (a, b) => a - b.
Latihan — Buat array 6 nama produk. Tambahkan satu produk baru di akhir, hapus produk pertama, lalu urutkan array itu berdasarkan abjad (.sort() tanpa argumen sebenarnya cocok untuk string). Cetak hasil akhirnya.
Materi 6 · Modul 1
Fungsi
Tiga cara menulis fungsi di JavaScript, dan kapan memakai yang mana.
Function declaration
function sapa(nama) {
return `Halo, ${nama}!`;
}
sapa("Nus"); // "Halo, Nus!"Catatan — Function declaration bisa dipanggil sebelum ditulis di file (disebut hoisting) — JavaScript "mengangkat" definisinya ke atas secara otomatis. Ini beda dari function expression dan arrow function di bawah, yang harus didefinisikan dulu sebelum dipanggil.
Function expression
const sapa = function(nama) {
return `Halo, ${nama}!`;
};
sapa("Nus");Arrow function — paling sering dipakai di kode modern
const sapa = (nama) => {
return `Halo, ${nama}!`;
};
// kalau isinya cuma satu ekspresi return, bisa disingkat:
const sapaSingkat = nama => `Halo, ${nama}!`; // tanpa {}, tanpa return
// tanpa parameter: kurung tetap wajib
const acakAngka = () => Math.random();
// lebih dari satu parameter: kurung wajib
const jumlahkan = (a, b) => a + b;Hati-hati — Kalau isi arrow function langsung {} yang ingin mengembalikan sebuah objek (bukan blok kode), bungkus objeknya dengan kurung: () => ({ nama: "Kaos" }). Tanpa kurung, { nama: "Kaos" } akan disalahartikan sebagai blok kode, bukan objek.
Parameter default dan rest parameter
// nilai default — dipakai kalau argumen tidak diberikan
function formatHarga(angka, prefix = "Rp ") {
return prefix + angka.toLocaleString("id-ID");
}
formatHarga(85000); // "Rp 85.000"
formatHarga(85000, "IDR "); // "IDR 85.000"
// rest parameter — kumpulkan argumen sebanyak apa pun jadi array
function jumlahkanSemua(...angka) {
return angka.reduce((total, n) => total + n, 0);
}
jumlahkanSemua(1, 2, 3, 4); // 10Kapan pakai yang mana
| Bentuk | Kapan cocok |
|---|---|
| Function declaration | Fungsi utama yang berdiri sendiri, terutama kalau perlu dipanggil dari mana saja di file. |
| Arrow function | Fungsi pendek, callback (dibahas Materi 9 & 16), dan sebagian besar kode modern. |
| Function expression | Jarang dipakai secara eksplisit — arrow function biasanya lebih ringkas untuk kasus yang sama. |
Latihan — Buat fungsi hitungDiskon(harga, persen) dengan arrow function, yang mengembalikan harga setelah diskon. Beri persen nilai default 10. Panggil dengan dan tanpa argumen kedua, bandingkan hasilnya.
Materi 7 · Modul 1
Scope dan closure
Di mana variabel bisa diakses — dan satu konsep JavaScript yang paling sering membingungkan pemula, tapi sangat berguna sekali dipahami.
Block scope: let/const vs var
if (true) {
let pesan = "di dalam blok";
var pesanLama = "juga di dalam blok";
}
console.log(pesanLama); // "juga di dalam blok" — var BOCOR keluar blok!
console.log(pesan); // ❌ ReferenceError: pesan is not definedCatatan — Ini salah satu alasan utama menghindari var: dia tidak menghormati batas blok {} — variabelnya "bocor" ke luar. let dan const terkurung rapi di dalam blok tempat mereka dideklarasikan, sesuai intuisi kebanyakan orang.
Function scope
const pesan = "di luar";
function coba() {
// bisa MEMBACA variabel dari luar (scope luar terlihat dari dalam)
console.log(pesan); // "di luar"
const lokal = "cuma di sini";
}
coba();
console.log(lokal); // ❌ ReferenceError — variabel di dalam fungsi TIDAK terlihat dari luarClosure: fungsi yang "mengingat" scope-nya
Closure adalah saat sebuah fungsi tetap mengingat variabel dari tempat dia dibuat, bahkan setelah fungsi luarnya selesai berjalan. Terdengar abstrak — mari lihat contoh nyata.
function buatCounter() {
let hitung = 0; // variabel ini "terkunci" di dalam closure
return function() {
hitung++;
return hitung;
};
}
const counter1 = buatCounter();
counter1(); // 1
counter1(); // 2
counter1(); // 3 — hitung TERUS bertambah, "diingat" antar pemanggilan
const counter2 = buatCounter();
counter2(); // 1 — counter BARU, hitung-nya sendiri, terpisah dari counter1Catatan — Setiap kali buatCounter() dipanggil, dia membuat variabel hitung yang baru dan fungsi dalam yang "menempel" padanya. Fungsi dalam itu terus punya akses ke hitung miliknya sendiri, walau buatCounter() sudah selesai dijalankan. Inilah closure — dan ini pola yang sangat umum untuk membuat data privat (dibahas lagi lewat sudut pandang class di Materi 22).
Kasus nyata: closure untuk data privat
function buatRekening(saldoAwal) {
let saldo = saldoAwal; // TIDAK bisa diakses langsung dari luar
return {
setor: (jumlah) => { saldo += jumlah; },
tarik: (jumlah) => {
if (jumlah > saldo) throw new Error("Saldo tidak cukup");
saldo -= jumlah;
},
cekSaldo: () => saldo,
};
}
const rekening = buatRekening(100000);
rekening.setor(50000);
rekening.cekSaldo(); // 150000
// rekening.saldo tidak ada — saldo betul-betul tersembunyi, hanya bisa lewat methodLatihan — Buat fungsi buatKeranjang() yang mengembalikan objek dengan method tambah(nama, harga), totalHarga(), dan daftarItem() — semuanya memakai closure untuk menyimpan array item secara privat (tidak bisa diakses langsung dari luar).
Materi 8 · Modul 1
Objek
Struktur data untuk mengelompokkan informasi terkait — akan sangat sering kamu pakai, terutama saat bekerja dengan data dari server.
Membuat dan mengakses objek
const produk = {
nama: "Kaos Onlenkan",
harga: 85000,
stok: 24,
aktif: true,
};
produk.nama; // "Kaos Onlenkan" — dot notation, paling umum
produk["harga"]; // 85000 — bracket notation, untuk key dinamis
const kunci = "stok";
produk[kunci]; // 24 — bracket notation WAJIB kalau key-nya variabelCatatan — Pakai dot notation (produk.nama) sebagai default — lebih ringkas dan lebih jelas dibaca. Pakai bracket notation (produk["nama"]) hanya kalau nama key-nya berasal dari variabel, atau mengandung karakter yang tidak valid sebagai nama biasa (spasi, tanda hubung).
Menambah, mengubah, menghapus properti
const produk = { nama: "Kaos", harga: 85000 };
produk.stok = 24; // tambah properti baru
produk.harga = 90000; // ubah nilai yang sudah ada
delete produk.stok; // hapus properti
"nama" in produk; // true — cek apakah key adaObjek di dalam objek, dan array di dalam objek
const pesanan = {
id: 1,
pelanggan: { nama: "Rifki", email: "rifki@mail.com" }, // objek bersarang
item: [ // array bersarang
{ nama: "Kaos", qty: 2 },
{ nama: "Topi", qty: 1 },
],
};
pesanan.pelanggan.nama; // "Rifki"
pesanan.item[0].nama; // "Kaos"
pesanan.item.length; // 2Ini persis bentuk data yang akan kamu terima dari server lewat API (dibahas di Modul 3) — objek dan array bersarang seperti ini adalah bentuk paling umum data di web modern.
Destructuring: mengambil banyak nilai sekaligus
const produk = { nama: "Kaos", harga: 85000, stok: 24 };
// tanpa destructuring
const nama1 = produk.nama;
const harga1 = produk.harga;
// dengan destructuring — lebih ringkas, nama variabel HARUS sama dengan nama key
const { nama, harga } = produk;
console.log(nama, harga); // "Kaos" 85000
// beri nama variabel berbeda
const { nama: namaProduk } = produk;
// nilai default kalau key tidak ada
const { diskon = 0 } = produk; // 0, karena produk tidak punya key "diskon"Catatan — Destructuring akan sangat sering kamu pakai — terutama saat mengambil beberapa field dari hasil fetch() (Materi 19) atau parameter fungsi. Pola function tampilkan({ nama, harga }) { ... } (destructuring langsung di parameter) sangat umum di kode React dan JavaScript modern lainnya.
Spread operator untuk objek
const produk = { nama: "Kaos", harga: 85000 };
// salin objek, sambil menimpa satu field — TIDAK mengubah objek asli
const produkDiskon = { ...produk, harga: 70000 };
// { nama: "Kaos", harga: 70000 }
// gabung dua objek
const gabungan = { ...produk, stok: 24 };Hati-hati — Spread (...) hanya menyalin secara dangkal (shallow copy): kalau objeknya punya properti berupa objek/array lain di dalamnya, properti bersarang itu masih dibagikan (bukan disalin) antara objek asli dan salinannya. Untuk kebutuhan sederhana ini jarang jadi masalah, tapi penting diketahui saat mengubah data bersarang.
Latihan — Buat objek siswa dengan nama, nilai (array angka), dan alamat (objek dengan kota dan kodePos). Pakai destructuring untuk mengambil nama dan kota (dari dalam alamat) dalam satu baris masing-masing. Buat salinan objek siswa dengan nilai tambahan baru memakai spread.
Materi 9 · Modul 1
Array method: map, filter, reduce
Tiga method yang akan kamu pakai lebih sering dari perulangan biasa — cara "deklaratif" mengolah data.
forEach: menjalankan sesuatu untuk tiap item
const produk = ["Kaos", "Hoodie", "Topi"];
produk.forEach((item, index) => {
console.log(`${index}: ${item}`);
});forEach mirip for...of, tapi ditulis sebagai method. Bedanya dengan tiga method berikutnya: forEach tidak mengembalikan array baru — cocok kalau tujuanmu cuma "melakukan sesuatu" untuk tiap item (seperti console.log), bukan membuat data baru.
map: mengubah tiap item jadi sesuatu yang lain
const harga = [10000, 25000, 5000];
// map SELALU mengembalikan array BARU, dengan jumlah item SAMA
const hargaKenaPajak = harga.map(h => h * 1.11);
// [11100, 27750, 5550]
const produk = [{ nama: "Kaos", harga: 85000 }, { nama: "Topi", harga: 45000 }];
const namaSaja = produk.map(p => p.nama);
// ["Kaos", "Topi"]Catatan — map tidak mengubah array aslinya — dia selalu menghasilkan array baru. Ini penting: harga di atas tetap [10000, 25000, 5000] setelah map dijalankan. Kebiasaan "tidak mengubah data asli" ini disebut immutability, dan jadi fondasi penting terutama kalau nanti kamu belajar React.
filter: mengambil hanya yang lolos syarat
const produk = [
{ nama: "Kaos", stok: 24 },
{ nama: "Hoodie", stok: 0 },
{ nama: "Topi", stok: 5 },
];
// filter mengembalikan array BARU berisi item yang membuat fungsinya return true
const tersedia = produk.filter(p => p.stok > 0);
// [{ nama: "Kaos", stok: 24 }, { nama: "Topi", stok: 5 }]reduce: meringkas array jadi satu nilai
const keranjang = [
{ nama: "Kaos", harga: 85000, qty: 2 },
{ nama: "Topi", harga: 45000, qty: 1 },
];
// reduce(fungsi, nilaiAwal)
// fungsi menerima (akumulator, itemSaatIni), harus RETURN akumulator baru
const total = keranjang.reduce((total, item) => {
return total + (item.harga * item.qty);
}, 0); // 0 = nilai awal akumulator
// total = 215000Hati-hati — reduce adalah yang paling sering membingungkan pemula, karena fungsinya harus selalu return nilai akumulator baru di tiap panggilan — lupa return di dalam reduce adalah bug yang sangat umum. Kalau bingung, mulai dengan pertanyaan: "nilai awal apa, dan bagaimana tiap item mengubah nilai itu?"
Merangkai method (chaining)
const produk = [
{ nama: "Kaos", harga: 85000, stok: 24 },
{ nama: "Hoodie", harga: 210000, stok: 0 },
{ nama: "Topi", harga: 45000, stok: 5 },
];
// filter dulu (hanya yang tersedia), lalu map (ambil nama saja)
const namaTersedia = produk
.filter(p => p.stok > 0)
.map(p => p.nama);
// ["Kaos", "Topi"]
// filter, lalu reduce — total nilai inventori yang tersedia
const totalNilai = produk
.filter(p => p.stok > 0)
.reduce((total, p) => total + (p.harga * p.stok), 0);Merangkai filter, map, dan reduce berurutan adalah pola yang sangat umum — dibaca dari atas ke bawah, tiap baris mengambil hasil dari baris sebelumnya dan mengubahnya lebih lanjut.
Latihan — Dari array produk seperti contoh di atas, tulis satu rangkaian method yang: menyaring produk dengan harga di bawah 100000, mengubahnya jadi array string "nama - Rp harga", lalu tampilkan semuanya. Tambahkan juga reduce terpisah untuk menghitung total stok semua produk.
Materi 10 · Modul 1
this dan konteks
Salah satu bagian JavaScript yang paling sering membingungkan — nilai this berubah tergantung bagaimana fungsi dipanggil.
this di dalam method objek
const produk = {
nama: "Kaos",
harga: 85000,
info() {
// this MERUJUK ke objek yang memanggil method ini
return `${this.nama}: Rp ${this.harga}`;
},
};
produk.info(); // "Kaos: Rp 85000" — this === produkJebakan klasik: this hilang saat method "dilepas"
const ambilInfo = produk.info; // method "dilepas" dari objeknya
ambilInfo(); // ❌ Error — this bukan lagi produk, jadi undefined
// kasus nyata: dipakai sebagai callback event (Materi 12)
button.addEventListener("click", produk.info); // this JADI SALAH di dalam info()Hati-hati — this ditentukan oleh bagaimana fungsi dipanggil, bukan di mana fungsi itu ditulis. Kalau sebuah method "dilepas" dari objeknya (disimpan ke variabel, atau diteruskan sebagai callback), this di dalamnya tidak lagi merujuk ke objek asalnya. Ini sumber bug yang sangat umum bagi pemula.
Arrow function: this yang "tidak berubah"
const produk = {
nama: "Kaos",
tags: ["promo", "baru"],
// function biasa di dalam forEach — this DI DALAMNYA bukan produk lagi!
tampilkanSalah() {
this.tags.forEach(function(tag) {
console.log(this.nama, tag); // ❌ this.nama = undefined
});
},
// arrow function TIDAK punya this sendiri — dia "meminjam" this dari sekitarnya
tampilkanBenar() {
this.tags.forEach((tag) => {
console.log(this.nama, tag); // ✅ this.nama = "Kaos", sesuai harapan
});
},
};Catatan — Ini alasan besar kenapa arrow function begitu populer: mereka tidak punya this sendiri, jadi otomatis "mewarisi" this dari fungsi di sekitarnya (mirip cara kerja closure di Materi 7). Aturan praktis yang aman: pakai method shorthand (seperti info() {...}) untuk method langsung di objek, dan arrow function untuk callback di dalamnya.
call, apply, bind (sekilas)
const ambilInfo = produk.info.bind(produk); // "kunci" this ke produk secara permanen
ambilInfo(); // sekarang bekerja dengan benar, di mana pun dipanggil.bind() adalah cara eksplisit "mengunci" nilai this sebuah fungsi. Kamu tidak akan sering menulis ini secara manual (arrow function biasanya lebih praktis), tapi baik untuk tahu istilahnya kalau ditemui di kode orang lain.
Latihan — Buat objek timer dengan properti detik: 0 dan method mulai() yang memakai setInterval (fungsi bawaan yang mengulang tiap sekian milidetik) untuk menambah this.detik tiap detik dan menampilkannya. Coba dulu dengan function biasa di dalam setInterval (amati this jadi salah), lalu perbaiki dengan arrow function.
Materi 11 · Modul 2
DOM: memilih dan mengubah elemen
Inilah yang membuat JavaScript istimewa untuk web — mengubah halaman yang sedang dilihat pengguna, secara langsung.
Apa itu DOM
DOM (Document Object Model) adalah representasi HTML halaman sebagai objek yang bisa diakses dan diubah lewat JavaScript. Browser membaca HTML-mu, membangun struktur pohon elemen dari situ, dan JavaScript bisa "berbicara" dengan pohon itu — menambah, mengubah, atau menghapus apa pun secara langsung, tanpa reload halaman.
Memilih elemen
// querySelector — cara PALING umum, memakai selector CSS
const judul = document.querySelector("h1");
const tombol = document.querySelector(".btn-utama"); // class
const form = document.querySelector("#form-login"); // id
// querySelectorAll — ambil SEMUA yang cocok, hasilnya NodeList
const semuaTombol = document.querySelectorAll("button");
semuaTombol.forEach(btn => console.log(btn));Catatan — querySelector memakai selector CSS yang sama seperti di stylesheet — kalau kamu sudah paham cara menulis .class atau #id di CSS, kamu sudah tahu cara memilih elemen di sini. querySelector mengembalikan elemen pertama yang cocok, querySelectorAll mengembalikan semua.
Mengubah isi dan atribut
const judul = document.querySelector("h1");
judul.textContent = "Judul Baru"; // ubah teks (aman, tidak parse HTML)
judul.innerHTML = "Judul <b>Tebal</b>"; // bisa sisipkan HTML — hati-hati, dibahas di bawah
const gambar = document.querySelector("img");
gambar.src = "foto-baru.jpg";
gambar.setAttribute("alt", "Deskripsi gambar");
gambar.getAttribute("src"); // baca nilai atributHati-hati — innerHTML mem-parse teks yang kamu berikan sebagai HTML sungguhan. Kalau teks itu berasal dari input pengguna (komentar, nama, pencarian) dan kamu masukkan mentah lewat innerHTML, penyerang bisa menyisipkan <script> berbahaya — serangan yang sama seperti XSS di handbook PHP. Untuk menampilkan teks apa adanya, selalu pakai textContent, bukan innerHTML.
Mengubah style dan class
const kotak = document.querySelector(".kotak");
// style langsung (untuk kasus sederhana)
kotak.style.backgroundColor = "red";
kotak.style.display = "none"; // sembunyikan elemen
// classList — cara yang LEBIH DISUKAI, biarkan CSS mengurus tampilan
kotak.classList.add("aktif");
kotak.classList.remove("tersembunyi");
kotak.classList.toggle("terbuka"); // tambah kalau belum ada, hapus kalau sudah ada
kotak.classList.contains("aktif"); // true / falseCatatan — Mengubah style langsung lewat .style berguna untuk kasus sederhana, tapi cara yang lebih disukai untuk tampilan yang lebih kompleks: definisikan tampilannya di CSS sebagai sebuah class, lalu JavaScript cukup classList.add/remove/toggle class itu. Ini memisahkan "logika" (JS) dari "tampilan" (CSS) dengan rapi.
Menjelajahi elemen terkait
const item = document.querySelector(".item");
item.parentElement; // elemen induk
item.children; // elemen anak langsung
item.nextElementSibling; // elemen "saudara" setelahnya
item.previousElementSibling; // elemen "saudara" sebelumnya
// mencari di dalam elemen tertentu, bukan seluruh halaman
const daftar = document.querySelector(".daftar-produk");
daftar.querySelectorAll("li"); // hanya <li> DI DALAM .daftar-produkLatihan — Buat file HTML sederhana dengan sebuah <h1>, tiga <p>, dan satu <button>. Lewat JavaScript: ubah teks <h1>, tambahkan class ke paragraf pertama, ubah warna latar tombol, dan tampilkan jumlah paragraf yang ada dengan console.log.
Materi 12 · Modul 2
Event: merespons interaksi pengguna
Klik, ketik, submit form — sini caranya JavaScript "mendengarkan" apa yang dilakukan pengguna.
addEventListener
const tombol = document.querySelector("#tombol-suka");
tombol.addEventListener("click", () => {
console.log("Tombol diklik!");
});Catatan — addEventListener adalah cara standar menghubungkan kode dengan aksi pengguna. Parameter pertama nama event-nya ("click", "submit", "input", dll), parameter kedua fungsi yang dijalankan saat event itu terjadi. Fungsi ini sering disebut event handler atau callback.
Objek event dan event.target
tombol.addEventListener("click", (event) => {
console.log(event.target); // elemen yang di-klik
console.log(event.type); // "click"
});
// event delegation: satu listener untuk banyak elemen anak
const daftar = document.querySelector(".daftar-produk");
daftar.addEventListener("click", (event) => {
if (event.target.matches(".tombol-hapus")) {
console.log("Tombol hapus diklik, di item:", event.target.closest(".item"));
}
});Catatan — Event delegation (contoh kedua) adalah pola penting: daripada memasang listener di tiap tombol hapus satu-satu (yang mungkin dibuat dinamis, Materi 13), pasang satu listener di elemen induknya, lalu cek event.target untuk tahu elemen mana sebenarnya yang di-klik. Ini bekerja bahkan untuk elemen yang ditambahkan setelah listener dipasang.
Event umum lainnya
const input = document.querySelector("input");
input.addEventListener("input", (e) => {
console.log("Nilai sekarang:", e.target.value); // setiap kali mengetik
});
input.addEventListener("focus", () => console.log("Input fokus"));
input.addEventListener("blur", () => console.log("Input kehilangan fokus"));
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") console.log("Tombol Escape ditekan");
});
window.addEventListener("resize", () => console.log("Ukuran window berubah"));| Event | Terjadi saat |
|---|---|
click | Elemen diklik. |
input | Nilai input berubah (tiap ketikan) — beda dari change. |
change | Nilai berubah DAN elemen kehilangan fokus (cocok untuk dropdown, checkbox). |
submit | Form dikirim (dibahas Materi 14). |
keydown/keyup | Tombol keyboard ditekan/dilepas. |
preventDefault: mencegah perilaku bawaan browser
const link = document.querySelector("a");
link.addEventListener("click", (event) => {
event.preventDefault(); // cegah browser pindah halaman
console.log("Link diklik, tapi tidak pindah halaman");
});Ini dibahas lebih lengkap di Materi 14 untuk form — preventDefault() mencegah aksi bawaan browser (seperti submit form yang me-reload halaman) supaya kamu bisa menanganinya sendiri lewat JavaScript.
Latihan — Buat tiga tombol dengan class .warna dan atribut data-warna berbeda (merah, hijau, biru). Pasang satu event listener (pakai event delegation) di elemen pembungkusnya yang mengubah warna latar <body> sesuai tombol yang diklik, memakai event.target.dataset.warna.
Materi 13 · Modul 2
Membuat elemen secara dinamis
Menampilkan data (seperti daftar produk) tanpa menulis HTML-nya manual satu per satu.
createElement dan appendChild
const daftar = document.querySelector(".daftar-produk");
const item = document.createElement("li");
item.textContent = "Kaos Polos";
item.classList.add("item-produk");
daftar.appendChild(item); // masukkan ke dalam <ul class="daftar-produk">Menampilkan array data sebagai elemen HTML
Pola yang sangat umum: kamu punya array objek (data produk), dan perlu menampilkannya sebagai daftar HTML. Kombinasikan map (Materi 9) dengan pembuatan elemen.
const produk = [
{ nama: "Kaos", harga: 85000 },
{ nama: "Hoodie", harga: 210000 },
{ nama: "Topi", harga: 45000 },
];
const daftar = document.querySelector(".daftar-produk");
function renderProduk(daftarProduk) {
daftar.innerHTML = ""; // kosongkan dulu, supaya tidak menumpuk tiap render ulang
daftarProduk.forEach(p => {
const li = document.createElement("li");
li.innerHTML = `
<span>${p.nama}</span>
<span>Rp ${p.harga.toLocaleString("id-ID")}</span>
`;
daftar.appendChild(li);
});
}
renderProduk(produk);Catatan — Pola "kosongkan lalu render ulang semuanya" (innerHTML = "" lalu forEach) sederhana dan cukup baik untuk data yang tidak terlalu besar. Untuk aplikasi yang lebih kompleks dengan update yang sangat sering, framework seperti React (dibahas di handbook terpisah) menangani pembaruan tampilan secara jauh lebih efisien — tapi memahami pola dasar ini penting sebelum memakai framework apa pun.
Template literal untuk HTML yang lebih kompleks
function renderProduk(daftarProduk) {
daftar.innerHTML = daftarProduk.map(p => `
<li class="item-produk">
<h3>${p.nama}</h3>
<p>Rp ${p.harga.toLocaleString("id-ID")}</p>
<button data-id="${p.id}" class="tombol-hapus">Hapus</button>
</li>
`).join("");
}Hati-hati — Membangun HTML lewat template literal + innerHTML itu praktis, tapi jangan pernah menyisipkan teks dari input pengguna langsung ke dalamnya tanpa dibersihkan dulu — sama seperti bahaya XSS di Materi 11. Kalau p.nama berasal dari input pengguna yang belum divalidasi, pertimbangkan membuat elemen dengan createElement + textContent untuk bagian itu, bukan innerHTML langsung.
Latihan — Dari array produk seperti contoh, buat fungsi renderProduk() yang menampilkan tiap produk sebagai kartu (div) berisi nama, harga terformat, dan tombol "Hapus" dengan data-id. Pasang event delegation (Materi 12) di pembungkusnya supaya klik "Hapus" menghapus item itu dari array lalu me-render ulang.
Materi 14 · Modul 2
Form dan validasi di browser
Menangani input pengguna sebelum data dikirim ke server — respons instan, tanpa menunggu roundtrip ke backend.
Menangani submit form
<form id="form-kontak">
<input type="text" name="nama">
<input type="email" name="email">
<button type="submit">Kirim</button>
</form>const form = document.querySelector("#form-kontak");
form.addEventListener("submit", (event) => {
event.preventDefault(); // cegah reload halaman (perilaku default form)
const nama = form.nama.value; // akses input lewat name-nya
const email = form.email.value;
console.log("Data form:", { nama, email });
});Catatan — event.preventDefault() wajib di sini — tanpa itu, browser akan reload halaman saat form dikirim (perilaku HTML bawaan), dan semua JavaScript-mu terputus di tengah jalan. Ini pola dasar untuk apa pun yang disebut "Single Page Application" — form dan navigasi ditangani JavaScript, bukan reload halaman penuh.
Validasi manual sebelum diproses
form.addEventListener("submit", (event) => {
event.preventDefault();
const nama = form.nama.value.trim();
const email = form.email.value.trim();
const errors = {};
if (nama === "") errors.nama = "Nama wajib diisi";
if (!email.includes("@")) errors.email = "Email tidak valid";
// tampilkan error di halaman
document.querySelectorAll(".error").forEach(el => el.textContent = "");
Object.entries(errors).forEach(([field, pesan]) => {
const errorEl = document.querySelector(`[data-error="${field}"]`);
if (errorEl) errorEl.textContent = pesan;
});
if (Object.keys(errors).length > 0) return; // berhenti di sini kalau ada error
console.log("Form valid, kirim data:", { nama, email });
});Validasi bawaan HTML5
<input type="email" required> <!-- wajib diisi, harus format email -->
<input type="text" minlength="3" maxlength="50"> <!-- panjang karakter -->
<input type="number" min="1" max="100"> <!-- rentang angka -->
<input type="text" pattern="[0-9]{10,13}"> <!-- regex, untuk nomor HP -->Catatan — Browser modern sudah bisa memvalidasi banyak hal secara otomatis lewat atribut HTML seperti required, pattern, min/max — tanpa satu baris JavaScript pun. Ini bagus sebagai lapisan pertama (cepat, tanpa kode), tapi tidak menggantikan validasi di server (Materi 12 handbook PHP) — pengguna selalu bisa melewati validasi browser lewat cara lain (devtools, request manual).
Latihan — Buat form registrasi sederhana (nama, email, password) dengan validasi JavaScript: nama tidak boleh kosong, email harus mengandung "@", password minimal 8 karakter. Tampilkan pesan error di bawah tiap field yang gagal, dan pesan sukses kalau semua valid (tanpa benar-benar mengirim data ke mana pun).
Materi 15 · Modul 2
localStorage: menyimpan data di browser
Membuat data "bertahan" walau halaman di-refresh atau browser ditutup — tanpa server sama sekali.
Menyimpan dan membaca data
// localStorage HANYA menyimpan STRING
localStorage.setItem("nama", "Nus");
localStorage.getItem("nama"); // "Nus"
localStorage.removeItem("nama"); // hapus satu key
localStorage.clear(); // hapus SEMUA data localStorage untuk domain iniCatatan — Data di localStorage bertahan bahkan setelah tab atau browser ditutup — beda dari variabel JavaScript biasa yang hilang begitu halaman di-refresh. Datanya khusus per domain/situs (situs lain tidak bisa membaca localStorage situsmu), dan tersimpan murni di komputer pengguna, tidak pernah otomatis terkirim ke server mana pun.
Menyimpan objek/array: JSON.stringify dan JSON.parse
const keranjang = [
{ nama: "Kaos", harga: 85000, qty: 2 },
{ nama: "Topi", harga: 45000, qty: 1 },
];
// objek/array HARUS diubah jadi string JSON dulu sebelum disimpan
localStorage.setItem("keranjang", JSON.stringify(keranjang));
// saat dibaca kembali, ubah balik dari string JSON jadi objek/array
const keranjangTersimpan = JSON.parse(localStorage.getItem("keranjang"));
console.log(keranjangTersimpan[0].nama); // "Kaos"Hati-hati — localStorage.getItem() mengembalikan null kalau key-nya belum pernah disimpan — JSON.parse(null) akan menghasilkan null juga (bukan error), tapi tetap perlu ditangani. Kebiasaan aman: const data = JSON.parse(localStorage.getItem("keranjang")) ?? []; — pakai ?? (Materi 3) untuk nilai cadangan kalau belum ada data tersimpan.
Kasus nyata: keranjang yang bertahan setelah refresh
function ambilKeranjang() {
return JSON.parse(localStorage.getItem("keranjang")) ?? [];
}
function simpanKeranjang(keranjang) {
localStorage.setItem("keranjang", JSON.stringify(keranjang));
}
function tambahKeKeranjang(produk) {
const keranjang = ambilKeranjang();
keranjang.push(produk);
simpanKeranjang(keranjang);
}
tambahKeKeranjang({ nama: "Kaos", harga: 85000 });
// keranjang sekarang tersimpan — refresh halaman, datanya TETAP adalocalStorage vs sessionStorage vs cookie
| Mekanisme | Bertahan sampai |
|---|---|
localStorage | Dihapus manual — bertahan tanpa batas waktu, bahkan setelah browser ditutup. |
sessionStorage | Tab ditutup — sintaks persis sama dengan localStorage, cuma masa hidupnya lebih pendek. |
| Cookie | Bisa diatur kedaluwarsanya, dan (beda dari dua di atas) otomatis terkirim ke server di setiap request — dibahas di handbook PHP Materi 13. |
Catatan — Pakai localStorage untuk preferensi pengguna atau data sementara yang murni untuk kenyamanan browser (tema gelap/terang, keranjang belanja sebelum checkout). Untuk data yang harus aman dan tersinkron di berbagai perangkat (riwayat pesanan sungguhan, data akun), itu harus tersimpan di server/database — bukan localStorage, yang bisa dihapus pengguna kapan saja dan tidak dibagikan antar perangkat.
Latihan — Buat halaman dengan input teks dan tombol "Simpan Catatan". Setiap catatan yang disimpan ditambahkan ke daftar yang tampil di halaman, dan disimpan ke localStorage sebagai array. Pastikan saat halaman di-refresh, catatan-catatan yang sudah disimpan tetap muncul (dimuat ulang dari localStorage saat halaman pertama dibuka).
Materi 16 · Modul 3
Callback dan masalahnya
Kenapa JavaScript butuh cara khusus menangani operasi yang butuh waktu — dan kenapa cara paling awal (callback) punya masalah.
JavaScript itu single-threaded
JavaScript menjalankan kode satu per satu, berurutan — tidak bisa melakukan dua hal bersamaan di thread yang sama. Masalahnya: kalau ada operasi yang butuh waktu (mengambil data dari server, membaca file besar), menunggu operasi itu secara langsung akan membuat seluruh halaman "membeku" — tombol tidak bisa diklik, animasi berhenti, sampai operasi itu selesai.
setTimeout: mensimulasikan operasi yang butuh waktu
console.log("1. Mulai");
setTimeout(() => {
console.log("3. Ini jalan SETELAH 2 detik");
}, 2000);
console.log("2. Ini langsung jalan, TIDAK menunggu");
// urutan output: 1, 2, lalu (setelah 2 detik) 3Catatan — Ini inti dari asynchronous: setTimeout tidak menghentikan kode lain untuk menunggu — dia "menjadwalkan" fungsi untuk jalan nanti, lalu JavaScript lanjut ke baris berikutnya. Fungsi yang dijadwalkan itu (parameter pertama) disebut callback — fungsi yang dipanggil belakangan, setelah sesuatu selesai.
Callback untuk operasi berurutan: awal dari masalah
function ambilUser(id, callback) {
setTimeout(() => {
callback({ id, nama: "Nus" });
}, 1000);
}
function ambilPesanan(userId, callback) {
setTimeout(() => {
callback([{ id: 1, total: 85000 }]);
}, 1000);
}
// kalau ambilPesanan BUTUH hasil dari ambilUser dulu...
ambilUser(1, (user) => {
console.log("User:", user);
ambilPesanan(user.id, (pesanan) => {
console.log("Pesanan:", pesanan);
// kalau ada langkah ketiga, keempat... semakin dalam BERSARANG
});
});Hati-hati — Pola di atas, kalau diperpanjang untuk banyak langkah berurutan, membentuk apa yang dijuluki "callback hell" — kode yang menjorok makin dalam ke kanan, sulit dibaca, dan sulit menangani error di tiap tingkat. Ini masalah nyata yang membuat JavaScript menambahkan Promise (Materi 17) sebagai solusi yang jauh lebih rapi.
Menangani error di gaya callback
// konvensi lama: callback pertama untuk error, kedua untuk hasil
function ambilData(id, callback) {
setTimeout(() => {
if (id < 0) {
callback(new Error("ID tidak valid"), null);
return;
}
callback(null, { id, nama: "Kaos" });
}, 500);
}
ambilData(1, (error, data) => {
if (error) {
console.error("Gagal:", error.message);
return;
}
console.log("Berhasil:", data);
});Pola ini masih dipakai di beberapa API lama (termasuk sebagian API bawaan Node.js), tapi kode modern hampir selalu memakai Promise atau async/await — dibahas dua materi berikutnya.
Latihan — Tulis fungsi tungguDetik(detik, callback) yang memanggil callback setelah sekian detik (pakai setTimeout). Panggil tiga kali berurutan bersarang (1 detik, lalu 1 detik lagi, lalu 1 detik lagi), tampilkan pesan berbeda tiap tahap — rasakan sendiri betapa dalamnya kode "menjorok" untuk sekadar tiga langkah berurutan.
Materi 17 · Modul 3
Promise
Cara modern merepresentasikan "sesuatu yang akan selesai nanti" — solusi rapi untuk masalah di Materi 16.
Apa itu Promise
Promise adalah objek yang mewakili sebuah operasi yang belum selesai, tapi akan selesai — entah berhasil (resolved) atau gagal (rejected). Alih-alih memberi callback langsung, fungsi yang asynchronous sekarang mengembalikan sebuah Promise.
function ambilUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) {
reject(new Error("ID tidak valid"));
return;
}
resolve({ id, nama: "Nus" });
}, 1000);
});
}
// .then() untuk hasil sukses, .catch() untuk error
ambilUser(1)
.then(user => console.log("User:", user))
.catch(error => console.error("Gagal:", error.message));Catatan — resolve(nilai) dipanggil kalau operasi berhasil — nilainya akan diterima oleh .then(). reject(error) dipanggil kalau gagal — errornya akan ditangkap oleh .catch(). Kamu jarang menulis new Promise(...) manual seperti ini sendiri — biasanya kamu memakai Promise dari fungsi bawaan browser seperti fetch() (Materi 19), yang sudah mengembalikan Promise.
Merangkai .then() menyelesaikan masalah callback hell
ambilUser(1)
.then(user => {
console.log("User:", user);
return ambilPesanan(user.id); // return Promise lain — otomatis "disambung"
})
.then(pesanan => {
console.log("Pesanan:", pesanan);
})
.catch(error => {
// SATU catch menangkap error dari LANGKAH MANA PUN di atas
console.error("Terjadi kesalahan:", error.message);
});Bandingkan dengan versi callback bersarang di Materi 16 — di sini setiap langkah rata (tidak menjorok makin dalam), dan cukup satu .catch() di akhir untuk menangkap error dari langkah manapun.
Promise.all: menjalankan banyak operasi bersamaan
// kalau operasi-operasi ini TIDAK saling bergantung, jalankan BERSAMAAN
Promise.all([
ambilUser(1),
ambilUser(2),
ambilUser(3),
]).then(([user1, user2, user3]) => {
console.log("Ketiganya sudah selesai:", user1, user2, user3);
});
// total waktu ≈ waktu operasi TERLAMA, bukan jumlah semuanyaHati-hati — Kalau kamu punya beberapa operasi yang tidak saling bergantung (tidak butuh hasil satu sama lain), jalankan dengan Promise.all(), bukan berurutan dengan .then() berantai. Menjalankan tiga ambilUser secara berurutan (masing-masing 1 detik) butuh total 3 detik; dengan Promise.all, ketiganya jalan bersamaan dan total waktunya cuma sekitar 1 detik.
Latihan — Ubah fungsi ambilData dari latihan Materi 16 (gaya callback) menjadi versi yang mengembalikan Promise. Rangkai dengan .then() untuk memanggilnya tiga kali berurutan (tanpa bersarang, rata seperti contoh di atas), lalu tambahkan .catch() di akhir.
Materi 18 · Modul 3
async/await
Cara menulis kode asynchronous yang terlihat seperti kode biasa yang berurutan — sintaks paling disukai di JavaScript modern.
await: "tunggu" sebuah Promise selesai
// fungsi yang memakai await HARUS ditandai "async"
async function tampilkanUser() {
const user = await ambilUser(1); // "tunggu" sampai Promise ini selesai
console.log("User:", user); // baris ini BARU jalan setelah await selesai
}
tampilkanUser();Catatan — await membuat kode terlihat seperti berjalan berurutan (baris demi baris), padahal di baliknya tetap asynchronous — JavaScript tidak "membeku" menunggu, dia hanya menjeda fungsi async itu saja sampai Promise-nya selesai, lalu lanjut. Ini murni gula sintaksis di atas Promise (Materi 17) — keduanya bekerja dengan mekanisme yang sama persis.
Menyambungkan beberapa await berurutan
async function tampilkanSemua() {
const user = await ambilUser(1);
console.log("User:", user);
const pesanan = await ambilPesanan(user.id);
console.log("Pesanan:", pesanan);
}Bandingkan dengan versi .then() berantai di Materi 17 — dengan async/await, kodenya persis seperti menulis fungsi sinkron biasa. Inilah kenapa gaya ini jauh lebih disukai di kode JavaScript modern.
try/catch untuk menangani error
async function tampilkanUser() {
try {
const user = await ambilUser(-1); // ID tidak valid — akan reject
console.log("User:", user);
} catch (error) {
console.error("Gagal mengambil user:", error.message);
}
}Hati-hati — Dengan async/await, error ditangkap dengan try/catch biasa (Materi 10 handbook PHP punya konsep yang sama) — bukan .catch(). Kalau lupa membungkus await dengan try/catch, error yang terjadi akan "diam" tak tertangani (atau memunculkan unhandled promise rejection di console), bukan langsung terlihat sebagai bug yang jelas.
Promise.all tetap dipakai bersama await
async function ambilSemuaUser() {
const [user1, user2, user3] = await Promise.all([
ambilUser(1),
ambilUser(2),
ambilUser(3),
]);
console.log(user1, user2, user3);
}Promise.all tetap dipakai untuk operasi paralel (Materi 17) — await di sini cuma "menunggu" hasil gabungan dari Promise.all, bukan menggantikannya.
Latihan — Tulis ulang latihan Materi 17 (tiga panggilan ambilData berurutan) memakai async/await dan try/catch. Bandingkan keterbacaannya dengan versi .then(). Uji juga skenario error (panggil dengan id negatif) dan pastikan pesan error tertangkap dengan benar.
Materi 19 · Modul 3
fetch API: mengambil data dari server
Fungsi bawaan browser untuk berkomunikasi dengan server — inilah yang membuat halaman bisa menampilkan data tanpa reload.
GET request dasar
async function ambilProduk() {
const respons = await fetch("https://api.tokosaya.com/produk");
const data = await respons.json(); // urai body respons sebagai JSON
console.log(data);
}Catatan — fetch() mengembalikan Promise yang resolve begitu header respons diterima — belum termasuk body-nya. Karena itu butuh await respons.json() sebagai langkah kedua, untuk membaca dan mengurai isi respons sebagai JSON. Ini dua langkah await terpisah yang sering terlewat oleh pemula.
Mengecek status respons
async function ambilProduk() {
const respons = await fetch("https://api.tokosaya.com/produk");
if (!respons.ok) { // true untuk status 200-299, false untuk 404/500/dll
throw new Error(`Gagal mengambil data: ${respons.status}`);
}
return await respons.json();
}Hati-hati — Jebakan penting: fetch() hanya reject (masuk ke .catch()/try-catch) kalau ada masalah jaringan (tidak ada internet, server tidak bisa dihubungi). Respons dengan status 404 atau 500 tetap dianggap "berhasil" oleh fetch sendiri — kamu harus secara eksplisit mengecek respons.ok atau respons.status, seperti contoh di atas.
POST request: mengirim data
async function buatProduk(produk) {
const respons = await fetch("https://api.tokosaya.com/produk", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(produk), // objek HARUS diubah jadi string JSON
});
if (!respons.ok) {
const error = await respons.json();
throw new Error(error.message ?? "Gagal menyimpan produk");
}
return await respons.json();
}
buatProduk({ nama: "Kaos", harga: 85000 });Catatan — Ini persis bentuk API yang dibahas di handbook Node.js/NestJS dan Go — fetch adalah sisi klien yang berbicara dengan endpoint seperti itu. Header Content-Type: application/json memberitahu server bahwa body yang dikirim berformat JSON, dan JSON.stringify() mengubah objek JavaScript jadi teks JSON yang bisa dikirim lewat jaringan.
Kasus nyata: menampilkan data dari server ke halaman
async function muatDanTampilkanProduk() {
const daftar = document.querySelector(".daftar-produk");
daftar.innerHTML = "Memuat...";
try {
const { data } = await ambilProduk(); // destructuring, asumsi API balas { data: [...] }
daftar.innerHTML = data.map(p => `
<li>${p.nama} — Rp ${p.harga.toLocaleString("id-ID")}</li>
`).join("");
} catch (error) {
daftar.innerHTML = `<li>Gagal memuat produk: ${error.message}</li>`;
}
}Ini menggabungkan hampir semua yang dipelajari sejauh ini: fetch (asynchronous), destructuring (Materi 8), map (Materi 9), template literal (Materi 2), dan manipulasi DOM (Materi 11).
Latihan — Pakai API publik gratis untuk latihan: https://jsonplaceholder.typicode.com/users. Tulis fungsi async yang mengambil data itu dengan fetch, lalu menampilkan nama dan email tiap user sebagai daftar di halaman HTML. Tangani kasus kalau fetch gagal (misalnya salah ketik URL) dengan pesan error yang jelas.
Materi 20 · Modul 3
Pola loading, error, dan retry
Menyempurnakan pengalaman pengguna saat menunggu data — tiga keadaan yang harus selalu ditangani.
Tiga keadaan yang selalu ada
Setiap kali kamu mengambil data dari server, ada tiga kemungkinan keadaan: sedang memuat, berhasil, atau gagal. Aplikasi yang terasa "matang" selalu menangani ketiganya secara eksplisit, bukan cuma kasus berhasil.
async function muatProduk() {
const kontainer = document.querySelector(".produk-kontainer");
// 1. keadaan LOADING
kontainer.innerHTML = '<p class="loading">Memuat produk...</p>';
try {
const respons = await fetch("/api/produk");
if (!respons.ok) throw new Error(`Status ${respons.status}`);
const { data } = await respons.json();
// 2. keadaan BERHASIL (dengan sub-kasus: kosong vs ada isi)
if (data.length === 0) {
kontainer.innerHTML = '<p class="kosong">Belum ada produk</p>';
return;
}
kontainer.innerHTML = data.map(p => `<div>${p.nama}</div>`).join("");
} catch (error) {
// 3. keadaan GAGAL
kontainer.innerHTML = `
<p class="error">Gagal memuat produk: ${error.message}</p>
<button onclick="muatProduk()">Coba Lagi</button>
`;
}
}Catatan — Perhatikan ada empat kemungkinan sebenarnya: loading, berhasil-dengan-data, berhasil-tapi-kosong, dan gagal. Aplikasi yang terasa buruk biasanya karena mengabaikan salah satu dari empat ini — layar putih kosong saat loading, atau tidak ada penjelasan sama sekali saat request gagal.
Timeout: membatasi berapa lama menunggu
async function fetchDenganTimeout(url, batasWaktu = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), batasWaktu);
try {
const respons = await fetch(url, { signal: controller.signal });
clearTimeout(timer);
return respons;
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Permintaan terlalu lama, coba lagi");
}
throw error;
}
}Tanpa batas waktu, fetch bisa "menggantung" tanpa batas kalau server tidak merespons sama sekali — AbortController adalah cara bawaan browser untuk membatalkan request setelah waktu tertentu.
Mencegah klik ganda saat memproses
async function simpanProduk(data) {
const tombol = document.querySelector("#tombol-simpan");
tombol.disabled = true; // cegah klik berulang saat sedang proses
tombol.textContent = "Menyimpan...";
try {
await fetch("/api/produk", { method: "POST", body: JSON.stringify(data) });
tombol.textContent = "Tersimpan!";
} catch (error) {
tombol.textContent = "Gagal, coba lagi";
} finally {
tombol.disabled = false;
}
}Hati-hati — Tanpa tombol.disabled = true, pengguna yang tidak sabar bisa mengklik tombol submit berkali-kali sebelum request pertama selesai — mengirim data duplikat ke server. Ini bug yang sangat umum tapi mudah dicegah dengan menonaktifkan tombol selama proses berlangsung.
Latihan — Ambil fungsi muatDanTampilkanProduk dari latihan Materi 19. Tambahkan penanganan tiga keadaan penuh (loading, kosong, error dengan tombol "Coba Lagi"). Uji tiap keadaan: matikan koneksi internet sebentar untuk menguji error, dan coba dengan API yang sengaja mengembalikan array kosong.
Materi 21 · Modul 4
Fitur ES6+ yang sering dipakai
Merangkum dan melengkapi fitur modern JavaScript yang belum dibahas eksplisit sejauh ini.
Spread untuk array
const a = [1, 2, 3];
const b = [4, 5];
const gabungan = [...a, ...b]; // [1, 2, 3, 4, 5]
const salinan = [...a]; // salinan BARU, bukan referensi ke array asli
const denganTambahan = [...a, 4, 5]; // [1, 2, 3, 4, 5]
// mencari nilai terbesar/terkecil dari array
Math.max(...a); // 3Destructuring array
const koordinat = [10, 20, 30];
const [x, y, z] = koordinat; // x=10, y=20, z=30
const [pertama, , ketiga] = koordinat; // lewati elemen kedua dengan koma kosong
// pola sangat umum: tukar nilai dua variabel
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1 — tanpa variabel sementaraOptional chaining: akses properti yang mungkin tidak ada
const user = { nama: "Nus" }; // tidak punya properti "alamat"
user.alamat.kota; // ❌ TypeError: Cannot read properties of undefined
user.alamat?.kota; // ✅ undefined — tidak error, "berhenti dengan aman" di titik yang tidak ada
// berguna untuk data dari server yang strukturnya tidak selalu pasti
const kotaPengguna = respons?.data?.user?.alamat?.kota ?? "Tidak diketahui";Catatan — ?. (optional chaining) adalah penyelamat saat bekerja dengan data dari API (Materi 19) yang strukturnya bisa saja tidak lengkap. Digabung dengan ?? (Materi 3) untuk nilai cadangan, kombinasi ini adalah pola yang sangat umum di kode JavaScript modern untuk mengakses data bersarang dengan aman.
Shorthand untuk objek
const nama = "Kaos";
const harga = 85000;
// cara lama
const produkLama = { nama: nama, harga: harga };
// shorthand — kalau nama variabel SAMA dengan nama key, cukup tulis sekali
const produk = { nama, harga }; // sama persis hasilnya
// computed property name — key dari variabel
const kunciDinamis = "stok";
const data = { [kunciDinamis]: 24 }; // { stok: 24 }Set dan Map: struktur data tambahan
// Set — kumpulan nilai UNIK, tanpa duplikat
const tagUnik = new Set(["promo", "baru", "promo"]);
tagUnik.size; // 2 — duplikat otomatis dibuang
[...tagUnik]; // ["promo", "baru"] — ubah balik jadi array kalau perlu
// Map — mirip objek, tapi key-nya boleh tipe APA PUN (bukan cuma string)
const cache = new Map();
cache.set("user-1", { nama: "Nus" });
cache.get("user-1"); // { nama: "Nus" }
cache.has("user-1"); // trueUntuk kebanyakan kasus, array dan objek biasa sudah cukup — Set berguna spesifik saat kamu butuh menghapus duplikat dengan cepat, Map saat butuh key selain string.
Latihan — Buat objek respons yang mensimulasikan data API tidak lengkap (misalnya { data: { user: null } }). Pakai optional chaining dan ?? untuk mengambil respons.data.user.nama dengan aman, dengan nilai cadangan "Tamu" kalau tidak ada. Buat juga array berisi nama-nama dengan duplikat, lalu pakai Set untuk mendapatkan daftar nama unik.
Materi 22 · Modul 4
Class dan OOP di JavaScript
Cara modern menulis kode berorientasi objek — sintaks lebih rapi di atas closure (Materi 7) yang sudah kamu pahami.
Class dasar
class Produk {
constructor(nama, harga, stok = 0) {
this.nama = nama;
this.harga = harga;
this.stok = stok;
}
// method — TANPA kata kunci "function"
hargaFormat() {
return `Rp ${this.harga.toLocaleString("id-ID")}`;
}
tersedia() {
return this.stok > 0;
}
}
const kaos = new Produk("Kaos Polos", 85000, 12);
kaos.nama; // "Kaos Polos"
kaos.hargaFormat(); // "Rp 85.000"
kaos.tersedia(); // trueCatatan — class di JavaScript sebenarnya adalah "gula sintaksis" di atas mekanisme lama yang disebut prototype — tapi kamu tidak perlu memahami detail itu untuk memakainya secara efektif. Sintaksnya sengaja dibuat familiar bagi siapa pun yang pernah memakai OOP di bahasa lain (seperti PHP di handbook sebelumnya — constructor di JS setara __construct di PHP, sama-sama dipanggil otomatis saat object dibuat).
Private field
class Rekening {
#saldo = 0; // tanda # di depan = PRIVATE, tidak bisa diakses dari luar class
setor(jumlah) {
this.#saldo += jumlah;
}
tarik(jumlah) {
if (jumlah > this.#saldo) {
throw new Error("Saldo tidak cukup");
}
this.#saldo -= jumlah;
}
get saldo() { // getter — diakses seperti properti biasa, bukan dipanggil ()
return this.#saldo;
}
}
const akun = new Rekening();
akun.setor(100000);
akun.saldo; // 100000 — dibaca TANPA kurung, berkat getter
// akun.#saldo; ← ❌ SyntaxError dari luar classIni persis konsep enkapsulasi (private) di handbook PHP Materi 17 — data disembunyikan, hanya bisa diubah lewat method yang mengontrol aturannya.
Inheritance dengan extends
class Pegawai {
constructor(nama, gajiPokok) {
this.nama = nama;
this.gajiPokok = gajiPokok;
}
totalGaji() {
return this.gajiPokok;
}
}
class Manajer extends Pegawai {
constructor(nama, gajiPokok, tunjangan) {
super(nama, gajiPokok); // panggil constructor induk
this.tunjangan = tunjangan;
}
// override method induk
totalGaji() {
return this.gajiPokok + this.tunjangan;
}
}
const manajer = new Manajer("Nus", 8000000, 3000000);
manajer.totalGaji(); // 11000000Catatan — extends dan super() di JavaScript bekerja persis sama konsepnya dengan extends dan parent::__construct() di handbook PHP Materi 18 — kalau kamu sudah nyaman dengan OOP di PHP, class di JavaScript akan terasa sangat familiar.
Latihan — Buat class Keranjang dengan private field #item (array), method tambah(produk), hapus(index), dan getter total yang menjumlahkan harga * qty semua item. Buat beberapa instance dan uji semua methodnya.
Materi 23 · Modul 4
Module: memecah kode ke banyak file
Cara modern mengatur kode JavaScript jadi file-file terpisah yang saling terhubung.
export dan import
// export terpisah untuk tiap hal yang ingin dibagikan
export function formatRupiah(angka) {
return `Rp ${angka.toLocaleString("id-ID")}`;
}
export const PAJAK = 0.11;
export class Produk {
constructor(nama, harga) {
this.nama = nama;
this.harga = harga;
}
}import { formatRupiah, PAJAK, Produk } from "./helpers.js";
const kaos = new Produk("Kaos", 85000);
console.log(formatRupiah(kaos.harga * (1 + PAJAK)));export default: satu hal utama per file
export default class Produk {
// ...
}// import default TIDAK pakai kurung kurawal, dan namanya bebas kamu pilih
import Produk from "./produk.js";
import ApaSaja from "./produk.js"; // nama variabel ini bebas, tetap merujuk export default yang samaCatatan — Pakai export default untuk satu hal utama yang mewakili file itu (biasanya satu class atau satu fungsi besar). Pakai export biasa (tanpa default) untuk beberapa hal kecil yang saling terkait dalam satu file, seperti kumpulan fungsi pembantu.
Mengaktifkan module di HTML
<!-- type="module" WAJIB supaya import/export bisa dipakai -->
<script type="module" src="main.js"></script>Hati-hati — Tanpa type="module", browser tidak mengizinkan sintaks import/export dan akan memunculkan error sintaks. Efek samping lain dari type="module": script otomatis dijalankan dalam mode strict, dan variabel di dalamnya tidak otomatis menjadi global — cocok untuk mencegah tabrakan nama antar file.
Membuka file module langsung (file://) sering gagal
Catatan penting — Kalau kamu membuka file HTML dengan module langsung lewat file:// di browser (klik dua kali file-nya), import sering gagal karena aturan keamanan browser (CORS). Solusinya: jalankan server lokal sederhana — php -S localhost:8000 (dari handbook PHP) juga bisa dipakai untuk ini, atau ekstensi seperti "Live Server" di VS Code.
Latihan — Pecah kode class Keranjang dari latihan Materi 22 ke file keranjang.js tersendiri dengan export default. Buat main.js yang meng-import dan memakainya. Jalankan lewat server lokal (bukan buka file langsung) dan pastikan tidak ada error di console.
Materi 24 · Modul 4 · Penutup
Penutup dan langkah selanjutnya
Merangkum perjalanan dari console.log pertama sampai siap membangun aplikasi interaktif sungguhan.
Yang sudah kamu kuasai
- Modul 0 — dasar bahasa: variabel, tipe data, kondisi, perulangan, array.
- Modul 1 — fungsi, scope, closure, objek, array method (map/filter/reduce),
this. - Modul 2 — DOM: memilih dan mengubah elemen, event, membuat elemen dinamis, form, localStorage.
- Modul 3 — asynchronous penuh: callback, Promise, async/await, fetch API, penanganan loading/error.
- Modul 4 — JavaScript modern: fitur ES6+, class/OOP, module.
Kebiasaan menulis JavaScript yang baik
constsecara default,letkalau perlu, hindarivarsepenuhnya (Materi 2).- Selalu
===, bukan==— kecuali kamu punya alasan spesifik memakai konversi otomatis (Materi 3). - Arrow function untuk callback, terutama saat butuh
thisyang konsisten (Materi 10). - Selalu tangani tiga keadaan saat mengambil data: loading, berhasil, gagal (Materi 20).
- Jangan pernah
innerHTMLdengan input pengguna mentah — risiko XSS sama seperti di PHP (Materi 11, 13).
Ke mana setelah ini
Langkah lanjut yang wajar — Dengan fondasi JavaScript ini, dua arah yang masuk akal: React (ada handbook terpisah yang membahasnya dari nol) untuk membangun antarmuka yang lebih kompleks dan terstruktur daripada manipulasi DOM manual, atau Node.js + TypeScript (juga ada handbook terpisah) untuk memakai JavaScript di sisi server. Keduanya membangun di atas persis fondasi yang baru saja kamu kuasai — closure, Promise, class, module — jadi tidak ada yang sia-sia dipelajari di sini.
Sekarang, waktunya mempraktikkan semuanya sekaligus dengan cara yang seru: ada studi kasus terpisah yang membangun sebuah game kecil di browser dari nol — memakai canvas, game loop, keyboard input, collision detection, dan localStorage untuk skor tertinggi. Semua konsep di handbook ini — fungsi, objek, class, event, closure — akan dipakai bersamaan dalam satu project yang bisa langsung kamu mainkan.