Scope dan Closure JavaScript: Tempat Variabel Hidup dan Cara Fungsi Mengingatnya

Scope menentukan variabel mana yang bisa dibaca kodemu; closure membuat sebuah fungsi mengingat variabel tempat ia dilahirkan. Kuasai keduanya, dan sebagian besar perilaku JavaScript berhenti terasa seperti sihir.

Diterbitkan 5 Agustus 20269 menit bacaOleh ACY Partner Indonesia
Scope dan closure JavaScript — kotak variabel bertingkat dan fungsi yang mengingat
300 × 250Slot Iklan TersediaPasang iklan Anda di sini

Ada dua pertanyaan yang diam-diam mengatur begitu banyak perilaku JavaScript: di mana sebuah kode boleh membaca variabel tertentu, dan berapa lama variabel itu tetap hidup? Jawaban pertanyaan pertama adalah scope. Jawaban pertanyaan kedua — begitu kamu mulai melibatkan fungsi — membawamu langsung ke closure, salah satu fitur yang paling sering bikin orang bingung sampai akhirnya tiba-tiba “klik”.

Kabar baiknya, kedua konsep ini sebenarnya nggak sulit asalkan kamu berhenti menghafal aturan dan mulai membayangkan apa yang sebenarnya terjadi. Di akhir artikel ini kamu bakal bisa melihat sepotong kode, melacak variabel mana saja yang bisa dijangkau tiap barisnya, dan menjelaskan kenapa sebuah counter yang kamu tulis tiga baris lalu masih mengingat angka pribadinya sendiri. Kita akan banyak mengandalkan apa yang sudah kamu tahu soal fungsi dan variabel, jadi kalau dua hal itu masih goyah, baca sekilas dulu ya.

Apa arti scope sebenarnya

Scope adalah kumpulan variabel yang terlihat — bisa dipanggil namanya — dari sebuah titik di dalam kodemu. Nggak semua variabel tersedia di mana-mana. JavaScript menarik garis-garis batas, dan sebuah variabel cuma ada di dalam batas tempat ia dideklarasikan.

Bayangkan seperti ruangan di dalam sebuah gedung. Kalau kamu berdiri di sebuah ruangan kecil, kamu bisa melihat semua isi ruangan itu plus semua isi ruangan yang lebih besar di sekelilingnya — tapi orang di ruangan sebelah nggak bisa melihat apa yang ada di mejamu. Kode bekerja persis begitu: area yang di dalam bisa memandang ke luar, tapi area yang di luar nggak bisa mengintip ke dalam.

Ada tiga jenis scope yang perlu kamu kenali namanya:

  • Global scope — dideklarasikan di luar semua fungsi dan blok. Terlihat di mana-mana.
  • Function scope — dideklarasikan di dalam sebuah fungsi. Cuma terlihat di dalam fungsi itu.
  • Block scope — dideklarasikan di dalam sebuah blok { } (misalnya if, for, atau sekadar sepasang kurung kurawal). Cuma terlihat di dalam blok itu. Aturan ini berlaku untuk let dan const, tapi tidak untuk var.
const namaApp = "ACY Partner Indonesia"; // global

function sapa() {
  const pesan = "Selamat datang"; // function scope
  console.log(pesan);    // ✅ jalan
  console.log(namaApp);  // ✅ jalan — bisa memandang ke luar ke global
}

sapa();
console.log(namaApp);  // ✅ "ACY Partner Indonesia"
console.log(pesan);    // ❌ ReferenceError: pesan is not defined

pesan hidup di dalam sapa. Begitu kamu mencoba membacanya dari luar, JavaScript sama sekali nggak tahu apa yang kamu maksud — nama itu memang nggak ada di luar sana.

Block scope: let dan const vs var

Ini satu potong sejarah scope yang benar-benar perlu kamu tahu, karena kode lama dan kode baru berperilaku berbeda. Sebelum 2015, var adalah satu-satunya cara mendeklarasikan variabel, dan var sama sekali nggak peduli batas blok. Dia cuma menghormati scope fungsi.

function cek() {
  if (true) {
    var x = 10;   // var mengabaikan blok { }
    let y = 20;   // let terkurung di dalam blok
  }
  console.log(x);  // ✅ 10  — var bocor keluar dari blok if
  console.log(y);  // ❌ ReferenceError — let tetap di dalam
}

Perhatikan baik-baik: x dan y dideklarasikan di baris yang bersebelahan, di dalam if yang sama. Tapi x lolos keluar dari blok sementara y tidak. Itulah inti masalah var dalam satu contoh — dia longgar soal di mana ia tinggal, dan kelonggaran itu melahirkan bug yang susah dilacak.

Pakai const dulu, baru let, hindari var

Di JavaScript modern, deklarasikan semuanya dengan const lebih dulu. Kalau kamu memang butuh menugaskan ulang variabel itu nanti, baru ganti yang satu itu jadi let. var hampir nggak pernah kamu butuhkan — perilakunya yang mengabaikan blok justru lebih banyak bikin kaget ketimbang membantu. Membiasakan diri menyimpan var cuma untuk “jangan, kecuali lagi merawat kode lama” diam-diam menghapus satu kategori bug penuh dari pekerjaanmu.

Jebakan var di dalam loop yang klasik

Korban paling terkenal dari scope var yang longgar adalah loop yang membuat fungsi. Karena tiap iterasi memakai var i yang sama, semua fungsi akhirnya membaca nilai terakhirnya:

// Dengan var — ketiganya mencetak 3
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// mencetak: 3, 3, 3

// Dengan let — i baru di setiap iterasi
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// mencetak: 0, 1, 2

Dengan let, tiap putaran loop mendapat i yang benar-benar baru, jadi tiap fungsi menangkap angka yang berbeda. Perbedaan satu ini saja sudah jadi alasan cukup untuk meninggalkan var. (Jangan pusingkan dulu kenapa setTimeout-nya jalan belakangan — yang penting di sini adalah i mana yang dilihat tiap fungsi.)

Scope chain: cara JavaScript menemukan variabel

Saat kodemu memakai sebuah variabel, JavaScript perlu mencari tahu yang mana yang kamu maksud. Caranya dengan menyusuri scope chain: dia mengecek scope saat ini dulu, dan kalau namanya nggak ada di situ, dia melangkah ke luar ke scope di sekelilingnya, lalu ke luar lagi, terus sampai ke global. Yang cocok pertama kali itulah yang menang. Kalau sudah sampai global dan tetap nggak ketemu, kamu dapat ReferenceError.

const level = "global";

function luar() {
  const level = "luar";

  function dalam() {
    console.log(level);  // "luar"
  }

  dalam();
}

luar();

Di dalam dalam, JavaScript mencari level. Variabel itu nggak dideklarasikan di dalam, jadi dia melangkah keluar ke luar, menemukan level = "luar" di sana, lalu berhenti. level yang global nggak pernah sempat dilirik, karena pencariannya berhenti di kecocokan pertama. Pencarian yang cuma mengarah ke luar inilah mesin di balik semua hal lain di artikel ini.

Pencariannya cuma ke luar, nggak pernah ke dalam

Sebuah fungsi bisa membaca variabel dari scope-scope yang membungkusnya, tapi nggak pernah dari scope yang bersarang di dalamnya, dan nggak pernah dari fungsi yang sederajat. Kalau dalam mendefinisikan sebuah variabel, luar nggak bisa melihatnya. Rantai ini jalur satu arah yang menunjuk keluar menuju global — ingat arah ini, dan kebanyakan teka-teki “kok ini undefined?” bakal terpecahkan sendiri.

Closure: fungsi yang mengingat

Di sinilah semuanya menyatu. Closure adalah apa yang kamu dapat ketika sebuah fungsi tetap memegang variabel dari scope tempat ia diciptakan, bahkan setelah scope luar itu selesai dijalankan. Fungsi itu “menutup” (closes over) variabel-variabel tadi dan membawanya seperti ransel.

Itu kedengarannya abstrak, jadi mari kita lihat langsung:

function buatPenyapa(nama) {
  // `nama` hidup di scope buatPenyapa
  return function () {
    console.log("Halo, " + nama + "!");
  };
}

const sapaJohn = buatPenyapa("John Doe");
const sapaJane = buatPenyapa("Jane Doe");

sapaJohn();   // "Halo, John Doe!"
sapaJane();   // "Halo, Jane Doe!"

Coba berhenti sejenak dan perhatikan keanehannya. Pada saat kamu memanggil sapaJohn(), fungsi buatPenyapa sebenarnya sudah lama selesai — dia jalan, mengembalikan nilai, lalu keluar. Biasanya, begitu sebuah fungsi keluar, variabel lokalnya ikut lenyap. Tapi fungsi yang di dalam tadi masih tahu bahwa nama itu "John Doe". Itulah closure: fungsi yang dikembalikan menyimpan tautan hidup ke nama tempat ia dilahirkan, jadi variabelnya tetap bertahan melewati “kematian” fungsi induknya.

Dan karena tiap panggilan ke buatPenyapa menciptakan scope yang baru, sapaJohn dan sapaJane mengingat nama yang berbeda. Keduanya nggak berbagi state — tiap closure punya salinan pribadinya sendiri.

Counter pribadi (closure yang bakal benar-benar kamu pakai)

Contoh yang ada di buku teks, dan kebetulan memang berguna betulan, adalah sebuah counter yang menyembunyikan hitungannya dari bagian kode lainnya:

function buatCounter() {
  let hitung = 0; // pribadi — nggak ada yang dari luar bisa menyentuhnya langsung

  return {
    naik() {
      hitung++;
      return hitung;
    },
    reset() {
      hitung = 0;
    },
  };
}

const counter = buatCounter();
console.log(counter.naik());  // 1
console.log(counter.naik());  // 2
console.log(counter.naik());  // 3
counter.reset();
console.log(counter.naik());  // 1

hitung terkunci di dalam buatCounter. Nggak ada counter.hitung yang bisa dibaca atau nggak sengaja ditimpa dari luar — satu-satunya cara mengubahnya adalah lewat method naik dan reset yang diberikan kepadamu. Closure memberimu privasi data: sebuah nilai yang bertahan di antara pemanggilan, tapi cuma bisa disentuh lewat pintu yang terkontrol. Inilah fondasi dari banyak sekali pola nyata, mulai dari caching, modul, sampai event handler yang mengingat pengaturannya.

Closure menangkap variabelnya, bukan potretnya

Bagian ini sering bikin orang keliru: closure memegang referensi hidup ke variabelnya, bukan salinan beku dari nilainya saat closure dibuat. Kalau variabelnya berubah belakangan, closure ikut melihat nilai yang baru. Justru karena itulah hitung di atas terus naik dari panggilan ke panggilan, bukan kembali ke nilai awalnya — closure dan scope asalnya menatap hitung yang sama, bukan dua hitung yang terpisah.

Kenapa closure penting di kode nyata

Closure itu bukan sekadar trik sulap — kamu sebenarnya sudah memakainya terus-menerus, sering kali tanpa menyebut namanya. Beberapa tempat dia muncul setiap hari:

  • Event handler yang perlu mengingat sesuatu dari saat ia dipasang:
function pasangTombol(label) {
  const tombol = document.querySelector("#simpan");
  tombol.addEventListener("click", () => {
    // menutup `label` — masih tersedia saat klik terjadi nanti
    console.log("Diklik: " + label);
  });
}
  • Pabrik fungsi (function factory) — membuat fungsi-fungsi khusus dari satu fungsi umum:
function kaliDengan(faktor) {
  return (n) => n * faktor;
}

const kaliDua = kaliDengan(2);
const kaliTiga = kaliDengan(3);

console.log(kaliDua(5));   // 10
console.log(kaliTiga(5));  // 15

kaliDua selamanya mengingat faktor = 2; kaliTiga mengingat faktor = 3. Pabrik yang sama, dua fungsi dengan dua ingatan berbeda yang sudah tertanam. Begitu kamu mengenali pola ini, kamu bakal melihatnya di mana-mana di kode nyata — ini cara yang rapi untuk mengatur perilaku sejak awal.

Jebakan yang umum: variabel bocor jadi global

Satu jebakan terakhir yang menghubungkan scope kembali ke bug sehari-hari. Kalau kamu menugaskan nilai ke sebuah variabel yang nggak pernah kamu deklarasikan, di kode non-strict JavaScript diam-diam membuatnya sebagai variabel global — jauh di luar scope tempat kamu ingin ia tinggal:

function setup() {
  config = { siap: true };  // ❌ tanpa let/const/var — jadi global!
}

setup();
console.log(config.siap);   // true — config bocor ke scope global

Kamu mungkin bermaksud config lokal untuk setup. Tapi kenyataannya sekarang dia terlihat di mana-mana, siap bertabrakan dengan apa pun yang juga bernama config. Selalu deklarasikan variabelmu, dan pasang "use strict"; di bagian atas file — strict mode mengubah kesalahan diam-diam ini jadi ReferenceError yang nyaring, supaya kamu langsung menangkapnya.

Penutup

Sekarang kamu sudah paham dua ide yang diam-diam mengatur cara variabel JavaScript berperilaku:

  • Scope adalah tempat sebuah variabel terlihat. Ada global, function, dan block scope. let dan const menghormati batas blok; var cuma menghormati scope fungsi dan bocor keluar dari blok — jadi pakai const sebagai default, pakai let kalau perlu menugaskan ulang, dan tinggalkan var.
  • JavaScript menemukan variabel dengan menyusuri scope chain — dari scope saat ini ke luar menuju global, mengambil yang cocok pertama. Pencariannya nggak pernah ke dalam atau ke samping.
  • Closure adalah fungsi yang mengingat variabel dari scope tempat ia diciptakan, bahkan setelah scope itu selesai. Dia menangkap variabelnya sendiri, bukan potret dari nilainya.
  • Closure memberimu state pribadi (pola counter), menggerakkan event handler dan pabrik fungsi, dan sebenarnya sudah berjalan di mana-mana di kodemu.
  • Selalu deklarasikan variabelmu supaya nggak tanpa sengaja bocor jadi global; strict mode membantu menangkap kesalahan ini.

Scope dan closure adalah jenis topik yang, begitu “klik”, langsung membuat selusin perilaku lain tiba-tiba masuk akal. Pasangan yang pas untuk topik ini adalah keyword this — satu lagi aturan soal konteks yang bikin orang bingung karena alasan serupa, dan jadi bahasan artikel berikutnya. Dengan scope dan closure sudah di tangan, kamu siap untuk itu.

Tag:javascriptfrontendscopeclosuremenengah
728 × 90Slot Iklan TersediaPasang iklan Anda di sini

Artikel Terkait

Lihat Semua Artikel

Artikel yang Mungkin Kamu Suka

Apa Itu Framework Frontend? — chip kode dengan tag komponen
Frontend / Dasar

Apa Itu Framework Frontend?

Penjelasan ramah untuk pemula tentang apa itu framework frontend, kenapa mereka ada, masalah apa yang mereka selesaikan dibanding JavaScript biasa, dan kapan kamu memang sebaiknya memakainya.

20 Sep 20268 menit baca
Sampul fundamentals frontend bertuliskan Apa Itu Frontend dan HTML + CSS + JS
Frontend / Dasar

Apa Itu Frontend Development?

Panduan ramah pemula tentang frontend development: bagian website yang kamu lihat dan klik secara langsung. Pahami cara kerja HTML, CSS, dan JavaScript, serta bedanya frontend dengan backend.

20 Sep 20268 menit baca
Ilustrasi sampul untuk Apa yang Dikerjakan Frontend Developer
Frontend / Dasar

Apa yang Dikerjakan Frontend Developer: Panduan untuk Pemula

Penjelasan ramah pemula tentang apa yang sebenarnya dikerjakan frontend developer setiap hari: mengubah desain jadi antarmuka yang berfungsi, membangun UI yang bisa dipakai ulang, sampai urusan responsif, aksesibilitas, dan kecepatan.

20 Sep 20269 menit baca
Kartu judul bertuliskan Styling dan Interaktivitas di atas latar biru gelap ACY Partner
Frontend / Dasar

Cara Kerja Styling dan Interaktivitas

Penjelasan ramah pemula soal bagaimana CSS menyulap HTML polos jadi tampilan rapi, dan bagaimana JavaScript menambahkan perilaku — pola berpikir struktur, gaya, perilaku saat membangun halaman web.

20 Sep 20268 menit baca
Tiga lapisan front-end menyatu di satu halaman web
Frontend / Dasar

Cara HTML, CSS, dan JavaScript Bekerja Bersama

Panduan praktis untuk pemula: bikin satu tombol kecil, beri struktur dengan HTML, tampilan dengan CSS, dan perilaku dengan JavaScript, lalu lihat ketiga lapisan ini bekerja sama di halaman nyata.

20 Sep 20268 menit baca
Sampul ikhtisar praktik terbaik frontend
Frontend / Dasar

Ikhtisar Praktik Terbaik Frontend

Peta ringkas tentang apa itu frontend yang baik — HTML bermakna, CSS rapi, JavaScript secukupnya, desain responsif, performa, dan progressive enhancement, lengkap dengan arahan untuk mendalaminya.

20 Sep 20268 menit baca