Scope adalah jangkauan tempat sebuah nama variabel dikenali oleh kode. JavaScript membentuk scope baru setiap kali sebuah fungsi atau blok ditulis, lalu mencari nama yang belum dikenalinya dengan menyusuri scope-scope di luarnya. Aturan pencarian itu mengikat sejak kode ditulis, dan di situlah letak kata leksikal pada judul topik ini. Setelah mekanismenya jelas, letak setiap variabel dapat ditentukan hanya dengan membaca posisi penulisannya, mulai dari fungsi bersarang yang membaca variabel lingkup luarnya sampai nama yang menutupi nama lain.
Tiga tingkat scope: global, fungsi, dan blok#
Scope global adalah lingkup teratas sebuah berkas, nama yang dideklarasikan di sana dapat dibaca dari bagian mana pun di berkas itu. Setiap fungsi membentuk scope barunya sendiri, berisi parameternya dan variabel yang dideklarasikan di dalam tubuhnya. Blok yang diapit kurung kurawal, seperti isi if atau isi perulangan, juga membentuk scope tersendiri bagi const dan let. Tiga tingkat ini tersusun seperti kotak di dalam kotak, dan kode di tingkat dalam selalu dapat membaca nama milik tingkat luarnya.
Berkas berikut menampakkan ketiganya dalam satu program kecil.
// Tiga tingkat scope, dari global sampai blok di dalam fungsi.
const namaAplikasi = "Penghitung Antrean";
function catatAntrean() {
const namaLoket = "Loket 2";
{
const jumlahAntrean = 7;
console.log(namaAplikasi, "di", namaLoket, "mencatat", jumlahAntrean, "antrean");
}
try {
console.log(jumlahAntrean);
} catch (e) {
console.log("di luar blok:", e.message);
}
}
catatAntrean();Penghitung Antrean di Loket 2 mencatat 7 antrean
di luar blok: jumlahAntrean is not definedKeluaran memperlihatkan dua arah yang berbeda. Baris pertama berhasil membaca namaAplikasi dari scope global, namaLoket dari scope fungsinya, dan jumlahAntrean dari scope bloknya, karena membaca ke arah luar selalu diperbolehkan. Baris kedua adalah kebalikannya, kode di tubuh fungsi mencoba membaca jumlahAntrean yang lahir di dalam blok, dan JavaScript menolak dengan ReferenceError bertuliskan jumlahAntrean is not defined. Arah pandangnya satu jalur, dari dalam bebas melihat ke luar, dari luar tertutup melihat ke dalam.
Fungsi bersarang dan rantai scope#
Fungsi yang dideklarasikan di dalam fungsi lain dapat membaca seluruh nama dari lingkup luarnya, berlapis sampai ke scope global. Rangkaian berlapis ini disebut rantai scope, dan pencariannya selalu dimulai dari scope terdalam tempat nama itu dipakai. Bila nama tidak ditemukan di sana, JavaScript naik satu tingkat ke lingkup luarnya, lalu berlanjut ke atas sampai nama ditemukan atau scope global pun habis. Pencarian tidak pernah bergerak sebaliknya, kode di lingkup luar tidak pernah menjangkau nama di lingkup dalam.
Berkas berikut menghitung biaya cetak dengan fungsi bersarang, dan parameternya pun ikut terbaca dari dalam.
// Fungsi bersarang membaca variabel lingkup luarnya, pencariannya ke luar.
const tarifDasar = 3000;
function hitungBiaya(jumlahCetakan) {
const potongan = 500;
function biayaAkhir() {
return tarifDasar * jumlahCetakan - potongan;
}
return biayaAkhir();
}
console.log("4 lembar:", hitungBiaya(4));
console.log("10 lembar:", hitungBiaya(10));4 lembar: 11500
10 lembar: 29500Ketika biayaAkhir dijalankan, ia membutuhkan tiga nama sekaligus. potongan dan jumlahCetakan tidak ada di lingkupnya, sehingga pencarian naik ke hitungBiaya dan menemukan keduanya di sana. tarifDasar pun tidak ada di tingkat itu, sehingga pencarian naik lagi sampai menemukannya di scope global. Setiap pemanggilan hitungBiaya menjalankan pencarian yang sama dari awal. Keluarannya sesuai hitungan, 3000 dikali 4 dikurangi 500 sama dengan 11500, begitu pula 10 lembar menjadi 29500.
Diagram berikut menggambar rantai tiga tingkat itu beserta arah pencariannya.
Nama ditebak saat kode ditulis#
JavaScript menentukan milik siapa sebuah nama dengan membaca posisi kode di teks program, bukan dengan melihat dari mana fungsi dipanggil. Sifat inilah yang disebut leksikal, keputusannya mengikat sejak kode ditulis, jauh sebelum pemanggilan pertama terjadi. Konsekuensinya konsisten, fungsi selalu melihat lingkup tempat ia ditulis, meskipun dipanggil dari lingkup yang sama sekali berbeda. Berkas berikut membuktikannya dengan fungsi kecil yang dipanggil dari dua tempat.
// Lingkup fungsi ditentukan tempat kodenya ditulis, bukan tempat dipanggil.
const kata = "versi global";
function pembaca() {
return kata;
}
function dipanggilDariSini() {
const kata = "versi lokal";
console.log("dipanggil dari dalam:", pembaca());
}
dipanggilDariSini();
console.log("dipanggil dari luar:", pembaca());dipanggil dari dalam: versi global
dipanggil dari luar: versi globalFungsi pembaca ditulis di scope global, sehingga kata di dalam tubuhnya merujuk ke kata milik global. Pemanggilan pertama terjadi dari dalam dipanggilDariSini, padahal di sana ada kata lokal berisi versi lokal, tetapi keluaran tetap versi global karena tempat pemanggilan tidak ikut menentukan. Pemanggilan kedua dari luar menghasilkan keputusan yang sama. Sekali aturan ini melekat, posisi setiap nama dapat ditelusuri dengan mata tanpa menjalankan program. Kata leksikal pada nama topik ini merujuk tepat ke sana, ke teks yang ditulis, bukan ke keadaan program saat berjalan.
Shadowing, nama dalam menutupi nama luar#
Deklarasi baru dengan nama yang sama di lingkup dalam akan menutupi deklarasi luar selama berada di dalam lingkupnya, perilaku ini disebut shadowing. Kedua nama itu bukan salinan yang terhubung, melainkan dua variabel yang berbeda dengan penyimpanan terpisah. Mengubah salah satunya tidak memberi dampak apa pun pada yang lain. Contoh berikut memakai nama kapasitas di dua tingkat sekaligus.
// Shadowing, variabel dalam menutupi variabel luar yang bernama sama.
const kapasitas = 100;
function laporkanGudang() {
const kapasitas = 40;
console.log("kapasitas di dalam fungsi:", kapasitas);
}
laporkanGudang();
console.log("kapasitas global:", kapasitas);kapasitas di dalam fungsi: 40
kapasitas global: 100Di dalam laporkanGudang, deklarasi kapasitas bernilai 40 mendahului kapasitas global, sehingga baris pertama membaca 40. Pencarian nama memang berhenti pada kecocokan pertama yang ditemukannya, dan kecocokan itu ada di lingkup terdalam. Sesudah fungsi selesai, kapasitas global kembali terbuka dan baris terakhir membaca 100 tanpa terpengaruh. Shadowing sah menurut bahasa dan kadang dipakai dengan sengaja, tetapi dua nama identik dengan nilai berbeda kerap membingungkan pembaca kode, sehingga penamaan yang berbeda lebih aman bila kedua nilai memang berbeda makna. Kebiasaan menamai variabel secara spesifik, misalnya kapasitasGudang untuk yang global, menghapus keraguan semacam ini sejak awal.
Pembanding singkat, var berlaku di seluruh fungsi#
Kata kunci var yang lama mengikuti aturan yang berbeda dari const dan let. var mengabaikan blok, sehingga variabelnya berlaku di seluruh fungsi tempat ia ditulis, bukan pada kurung kurawalnya. Satu contoh kecil berikut cukup sebagai pembanding, tanpa perlu ditiru pola penulisannya.
function ujiVar() {
if (true) {
var pesan = "terlihat di luar blok if";
}
console.log("var:", pesan);
}
ujiVar();var: terlihat di luar blok ifBagi const dan let, blok if menjadi batas jangkauan, tetapi pesan yang dideklarasikan dengan var lolos keluar dan berhasil dibaca setelah blok berakhir. Perilaku ini membuat batas antarlingkup kabur pada program yang panjang, dan itulah alasan var digantikan sepenuhnya oleh const dan let pada kode modern. Cukup mengenalinya agar kode lama tetap terbaca.
Ringkasan#
- Scope adalah jangkauan tempat nama variabel dikenali, dan JavaScript membentuknya pada tiga tingkat yang umum, yaitu global, fungsi, dan blok.
- Fungsi bersarang membaca nama lingkup luarnya lewat rantai scope yang dicari berlapis ke luar, dan pencarian berhenti pada kecocokan pertama.
- Keputusan milik siapa sebuah nama diambil secara leksikal, dari posisi kode saat ditulis, tidak peduli dari mana fungsinya dipanggil.
- Shadowing menutupi nama luar dengan deklarasi baru bernama sama di lingkup dalam, sedangkan
varberlaku di seluruh fungsi dan kini hanya menjadi pembanding.
Referensi#
- Scope — MDN Web Docs — definisi scope beserta tingkat-tingkatnya pada glosarium resmi MDN.
- You Don’t Know JS Yet — Kyle Simpson — deretan buku gratis yang membedah scope leksikal dan closure secara mendalam.