Episode ini menuntaskan tiga konsep tersulit di JavaScript: rantai scope yang menentukan visibilitas variabel, closure yang membuat fungsi mengingat lingkungan lahirnya, dan empat aturan binding this beserta call, apply, serta bind. Ini fondasi untuk memahami kode produksi yang kompleks.

Episode 12 adalah puncak fase dua — tiga konsep yang saling terkait dan sering dianggap paling membingungkan di JavaScript: scope, closure, dan this. Di episode 4 kalian sudah melihat scope global, function, dan block secara singkat. Sekarang kita memperdalamnya: bagaimana rantai scope bekerja, mekanisme closure yang menjadi dasar banyak pola produksi, dan empat aturan binding this.
Mengapa tiga hal ini dibahas bersama? Karena semuanya tentang satu pertanyaan: saat kode berjalan, nilai atau variabel apa yang bisa diakses dari titik itu? Scope menjawab untuk variabel, closure mengeksploitasi jawaban itu, dan this menjawab untuk objek yang sedang aktif. Tiga konsep ini sering muncul di interview kerja, jadi pahami mekanismenya, bukan hafalannya.
Scope ditentukan saat kode ditulis, bukan saat dieksekusi — inilah arti lexical. Fungsi di dalam fungsi bisa mengakses variabel scope luarnya, tapi tidak sebaliknya:
const nama = "Global";
function luar() {
const namaLuar = "Di fungsi luar";
function dalam() {
const namaDalam = "Di fungsi dalam";
console.log(nama);
console.log(namaLuar);
console.log(namaDalam);
}
dalam();
}
luar();dalam bisa mengakses namaLuar dari luar dan nama dari global — ini rantai scope (scope chain). Setiap fungsi menambah satu lapisan ke atas mencari variabel. Sebaliknya, luar tidak bisa mengakses namaDalam milik dalam, dan global tidak bisa mengakses keduanya. Pencarian selalu naik, tidak pernah turun.
Closure terjadi ketika sebuah fungsi "membawa" variabel dari scope lahirnya, bahkan setelah scope itu selesai dieksekusi:
function buatPenghitung() {
let hitung = 0;
return function () {
hitung += 1;
return hitung;
};
}
const counter = buatPenghitung();
console.log(counter());
console.log(counter());
console.log(counter());buatPenghitung mengembalikan fungsi yang menambah hitung. Normalnya hitung mati setelah buatPenghitung selesai — tapi karena fungsi dalam menutup (closure) atasnya, hitung tetap hidup. Setiap pemanggilan counter() menghasilkan 1, 2, 3. Inilah mekanisme private state di JavaScript, dan dasar dari pola function factory di episode 8.
Jebakan klasik: membuat closure di dalam loop dengan var. Solusi modern adalah let, yang membuat variabel per iterasi:
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}let memberi i cakupan per iterasi, sehingga setiap callback menutup nilai yang berbeda: 1, 2, 3. Jika diganti var, semua callback menutup variabel yang sama dan semuanya mencetak 4. Ini alasan utama let menggantikan var di kode modern.
this di function biasa ditentukan oleh cara fungsi dipanggil. Empat aturannya:
this menunjuk ke global (atau undefined di strict mode) saat fungsi dipanggil berdiri sendiri.this menunjuk ke objek di depan tanda titik saat fungsi dipanggil sebagai method.call, apply, atau bind.new.const pengguna = {
nama: "Arman",
panggil: function () {
console.log(this.nama);
},
};
pengguna.panggil();pengguna.panggil() memakai implicit binding: karena dipanggil lewat pengguna, this menunjuk ke pengguna, sehingga hasilnya "Arman". Jika fungsinya dipisah dan dipanggil berdiri sendiri, this tidak lagi menunjuk ke pengguna.
Arrow function tidak punya this sendiri — dia mewarisi this dari scope terdekat yang memilikinya:
const tim = {
nama: "Squad Dev",
anggota: ["Arman", "Sari"],
perkenalan: function () {
this.anggota.forEach((nama) => {
console.log(`${nama} dari ${this.nama}`);
});
},
};
tim.perkenalan();perkenalan adalah function biasa sehingga this menunjuk ke tim. Arrow function di dalamnya mewarisi this tersebut — bukan membuat miliknya sendiri — sehingga this.nama tetap "Squad Dev". Kalau diganti function biasa, this di dalam callback akan lepas dari tim.
call dan apply memanggil fungsi segera dengan this yang kalian tentukan. Bedanya hanya di cara mengirim argumen: call satu per satu, apply lewat array:
function perkenalkan(kota, tahun) {
console.log(`${this.nama} dari ${kota}, sejak ${tahun}`);
}
const arman = { nama: "Arman" };
perkenalkan.call(arman, "Jakarta", 2020);
perkenalkan.apply(arman, ["Jakarta", 2020]);perkenalkan.call(arman, "Jakarta", 2020) dan versi apply dengan array menghasilkan output yang sama. Di sini this dipaksa menunjuk ke arman meskipun perkenalkan bukan method arman.
Berbeda dari call dan apply yang langsung memanggil, bind membuat fungsi baru dengan this yang terkunci:
const pengguna = {
nama: "Arman",
sapaan: function () {
return `Halo, ${this.nama}`;
},
};
const ambilSapaan = pengguna.sapaan;
const ambilSapaanKunci = pengguna.sapaan.bind(pengguna);
console.log(ambilSapaanKunci());ambilSapaan yang dipanggil berdiri sendiri kehilangan this — tapi ambilSapaanKunci yang dibuat dengan .bind(pengguna) selalu menunjuk ke pengguna apa pun cara memanggilnya. bind adalah solusi klasik ketika sebuah method dilepas dari objeknya, misalnya dikirim sebagai callback.
Episode 12 menuntaskan tiga konsep tersulit: rantai scope yang menentukan visibilitas variabel secara lexical, closure yang membuat fungsi mengingat lingkungan lahirnya dengan state privat, dan empat aturan binding this plus call, apply, serta bind untuk mengendalikannya.
Inti yang harus dibawa pulang:
let di loop memberi closure nilai per iterasi; var tidak.this ditentukan oleh cara fungsi dipanggil, bukan tempat ditulis.this sendiri; dia mewarisi dari atas.call dan apply memanggil langsung; bind membuat fungsi baru yang terkunci.Di episode 13 selanjutnya kita masuk ke fase tiga: DOM API dan manipulasi elemen — memilih elemen dengan selector, mengubah konten dan style, membuat elemen baru, serta mengelola class dan atribut. Inilah saat JavaScript mulai berinteraksi dengan halaman web sungguhan.