Callback di chapter sebelumnya membuka pertanyaan yang belum kita jawab tuntas: ketika function dijalankan, variabel-variabel mana saja yang boleh dibacanya? Aturan yang mengatur hal itu disebut scope — wilayah keberlakuan sebuah nama. Pemahaman scope bukan teori kering: hampir seluruh bug “variable is not defined” dan sebagian besar kebingungan saat membaca kode orang lain berakar di sini.
JavaScript mengenal tiga lingkar scope yang bersarang: global, function, dan block. Dari tiga lingkar itu lahir dua konsekuensi penting yang kita bedah di chapter ini — lexical scope, yang menentukan bahwa function membaca variabel dari tempat ia ditulis, dan shadowing, yang membuat nama sama bisa hidup di dua lingkar berbeda. Keduanya akan terasa familier dari chapter-chapter sebelumnya; bedanya, kini kita menguji aturannya secara sengaja, bukan sekadar menaati sintaks.
Tiga Lingkar Scope #
Berkas berikut menguji kelima hal sekaligus: variabel global yang dibaca function, variabel dalam function yang tidak terlihat dari luar, variabel dalam blok if, function dalam yang membaca variabel function luarnya, dan nama lokal yang menutupi nama global:
// jelajah-scope.js
// Global scope, function scope, block scope, lexical scope, dan shadowing.
// --- 1. global scope: terbaca dari dalam function mana pun ---
const tarifPajak = 11; // dalam persen
function hitungPajak(harga) {
return (harga * tarifPajak) / 100; // membaca variabel global
}
console.log("Pajak 100000:", hitungPajak(100000));
// --- 2. function scope: let/const di dalam function tak terlihat dari luar ---
function hitungSubtotal(harga, jumlah) {
const subtotal = harga * jumlah;
return subtotal;
}
console.log("Subtotal:", hitungSubtotal(50000, 3));
try {
console.log(subtotal); // tidak dikenal di luar function
} catch (error) {
console.log(error.name, "-", error.message);
}
// --- 3. block scope: let/const terkurung di dalam blok if ---
if (true) {
const pesanBlok = "hanya hidup di blok ini";
console.log("Di dalam blok:", pesanBlok);
}
try {
console.log("Di luar blok:", pesanBlok);
} catch (error) {
console.log(error.name, "-", error.message);
}
// --- 4. lexical scope: function dalam membaca variabel function luarnya ---
function hitungDiskon(harga) {
const batasDiskon = 100000;
function dapatDiskon() {
return harga >= batasDiskon; // membaca kedua variabel di luarnya
}
if (dapatDiskon()) {
return harga - harga * 0.1;
}
return harga;
}
console.log("Harga akhir A:", hitungDiskon(150000));
console.log("Harga akhir B:", hitungDiskon(80000));
// --- 5. shadowing: nama lokal menutupi nama global ---
const statusKirim = "global";
function cekStatus() {
const statusKirim = "lokal"; // menutupi statusKirim global
return statusKirim;
}
console.log("Dalam function:", cekStatus());
console.log("Di luar function:", statusKirim);Output yang Dihasilkan:
Pajak 100000: 11000
Subtotal: 150000
ReferenceError - subtotal is not defined
Di dalam blok: hanya hidup di blok ini
ReferenceError - pesanBlok is not defined
Harga akhir A: 135000
Harga akhir B: 80000
Dalam function: lokal
Di luar function: globalMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 5 dan 7–9 (
tarifPajakdanhitungPajak):tarifPajakditulis di tingkat atas berkas — lingkar global. Function mana pun di berkas yang sama boleh membacanya; itulah mengapa perhitungan di baris 8 berhasil. Nilai konfigurasi yang dipakai banyak function — tarif, batas, konstanta domain — memang tempatnya di sini. - Baris 14–24 (
hitungSubtotaldan percobaan pembacaannya):subtotaldiciptakan di dalam function. Begitu function selesai, lingkarnya tertutup: pembacaan dari luar (baris 21) memunculkanReferenceError: subtotal is not defined. Inilah function scope — setiap function membentuk wilayahnya sendiri. - Baris 27–35 (blok
if):letdanconstjuga menghormati batas blok kurung kurawal, bahkan tanpa function.pesanBloklahir dan terbaca di dalamif, lalu tidak dikenal di luarnya. - Baris 38–49 (
hitungDiskondandapatDiskon): function dalam boleh membaca seluruh variabel lingkar luarnya.dapatDiskonmembacaharga(parameterhitungDiskon) sekaligusbatasDiskon(variabel lokalnya) tanpa satu pun dari keduanya dikirim sebagai parameter. - Baris 55–63 (
statusKirimganda): dua variabel bernama sama hidup berdampingan. Di dalamcekStatus, deklarasi lokal menutupi yang global —return statusKirimmengirim"lokal". Di luar function, nama global tetap utuh dengan nilai"global". Peristiwa tertutupnya nama dari luar disebut shadowing.
Lexical Scope: Mengikuti Tempat Penulisan #
Kata “lexical” merujuk ke letak teks: lingkar variabel ditentukan oleh di mana kode ditulis, bukan oleh dari mana function dipanggil. dapatDiskon ditulis di dalam hitungDiskon, maka selamanya membaca variabel lingkar hitungDiskon — kapan pun dan dari mana pun ia dipanggil. Aturan ini membuat pembacaan kode bisa dilakukan dari teksnya saja: cukup naikkan pandangan ke kurung kurawal yang melingkupi, dan daftar variabel yang boleh diakses langsung terbaca. Kita tidak perlu menjalankan program untuk mengetahui lingkar sebuah variabel; cukup membaca susunan kodenya.
Rantai pembacaannya mengalir ke arah luar saja: function dalam membaca lingkar luarnya, lingkar luarnya membaca lingkar yang lebih luar, hingga global. Arah sebaliknya tidak pernah terjadi — kode global tidak bisa membaca variabel di dalam function, persis seperti yang dibuktikan baris 21.
Nama function sendiri tunduk pada aturan yang sama. Function dalam yang dideklarasikan di dalam function luar hanya dikenal di dalam lingkar itu:
function luarnya() {
function dalamnya() {
return "halo dari dalam";
}
return typeof dalamnya;
}
console.log("Di dalam luarnya:", luarnya());
console.log("Di luar:", typeof dalamnya);Output yang Dihasilkan:
Di dalam luarnya: function
Di luar: undefinedDari dalam luarnya, nama dalamnya terbaca sebagai function. Dari tingkat atas berkas, typeof dalamnya melaporkan undefined — function dalam itu tidak pernah terdaftar di lingkar global. Deklarasi function dalam yang disembunyikan seperti ini adalah cara lazim menata kode: helper yang hanya dipakai satu function tidak perlu mengotori nama global.
Shadowing dan Jebakannya #
Shadowing sah secara bahasa dan kadang berguna, tetapi membawa risiko yang nyata: dua nama identik yang merujuk dua hal berbeda. Bentuknya tidak terbatas pada function; blok if pun bisa membayangi variabel luarnya:
const judul = "global";
if (true) {
const judul = "blok";
console.log("Dalam blok:", judul);
}
console.log("Luar blok:", judul);Output yang Dihasilkan:
Dalam blok: blok
Luar blok: globalDua judul hidup di dua lingkar berbeda: yang dalam hanya berlaku selama blok berjalan, lalu yang global kembali berlaku setelahnya. Bentuk paling licik terjadi lewat parameter — const angka = 7; global dengan parameter angka akan membuat pembaca menebak variabel mana yang sedang dimaksud:
const angka = 7;
function duaKaliAngka(angka) {
return angka * 2;
}
console.log(duaKaliAngka(5));
console.log(angka);Output yang Dihasilkan:
10
7Di dalam function, angka berarti 5 (parameter), bukan 7 (global); di luar function, angka tetap 7. Tidak ada error dan tidak ada peringatan — nilai yang dihitung function bergantung sepenuhnya pada argument yang dikirim, dan nama global yang tampak di layar bisa jadi bukan yang dipakai function.
Sampai di sini, aturan scope sudah lengkap: tiga lingkar, arah pembacaan dari dalam ke luar, dan nama yang bisa menutupi. Sebelum menutup, satu gambaran praktis tentang manfaat terbesarnya: karena setiap function membentuk lingkarnya sendiri, dua function bebas memakai nama lokal yang sama tanpa saling mengganggu:
function luasA() {
const hasil = 100;
return hasil;
}
function luasB() {
const hasil = 250;
return hasil;
}
console.log(luasA() + luasB());Output yang Dihasilkan:
350Dua variabel hasil hidup di dua lingkar terpisah, dan penjumlahannya 350 — tidak ada tabrakan nama, tidak ada nilai yang tertimpa. Tanpa function scope, program besar akan habis waktu hanya untuk mencari nama variabel yang belum dipakai. Dengan batas yang tegas seperti ini, kita bebas menamai variabel lokal sesuai maksudnya di tempat masing-masing.
Namun ada satu kasus yang belum tersentuh — apa yang terjadi bila function dalam itu justru dikirim keluar dari lingkarnya? Apakah variabel lingkar luarnya ikut terbawa? Jawabannya ya, dan fenomena itu bernama closure — topik chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
jelajah-scope.js
Unduh berkas// jelajah-scope.js
// Global scope, function scope, block scope, lexical scope, dan shadowing.
// --- 1. global scope: terbaca dari dalam function mana pun ---
const tarifPajak = 11; // dalam persen
function hitungPajak(harga) {
return (harga * tarifPajak) / 100; // membaca variabel global
}
console.log("Pajak 100000:", hitungPajak(100000));
// --- 2. function scope: let/const di dalam function tak terlihat dari luar ---
function hitungSubtotal(harga, jumlah) {
const subtotal = harga * jumlah;
return subtotal;
}
console.log("Subtotal:", hitungSubtotal(50000, 3));
try {
console.log(subtotal); // tidak dikenal di luar function
} catch (error) {
console.log(error.name, "-", error.message);
}
// --- 3. block scope: let/const terkurung di dalam blok if ---
if (true) {
const pesanBlok = "hanya hidup di blok ini";
console.log("Di dalam blok:", pesanBlok);
}
try {
console.log("Di luar blok:", pesanBlok);
} catch (error) {
console.log(error.name, "-", error.message);
}
// --- 4. lexical scope: function dalam membaca variabel function luarnya ---
function hitungDiskon(harga) {
const batasDiskon = 100000;
function dapatDiskon() {
return harga >= batasDiskon; // membaca kedua variabel di luarnya
}
if (dapatDiskon()) {
return harga - harga * 0.1;
}
return harga;
}
console.log("Harga akhir A:", hitungDiskon(150000));
console.log("Harga akhir B:", hitungDiskon(80000));
// --- 5. shadowing: nama lokal menutupi nama global ---
const statusKirim = "global";
function cekStatus() {
const statusKirim = "lokal"; // menutupi statusKirim global
return statusKirim;
}
console.log("Dalam function:", cekStatus());
console.log("Di luar function:", statusKirim);