Soal 1Apa yang terjadi kalau kamu menampilkan unit di baris setelah if (true) { const unit = "yen"; }?
Scope dan Hoisting — Dari Mana dan Kapan Variabel Bisa Dibaca
Scope menentukan dari mana variabel bisa dibaca, dan hoisting mendaftarkan deklarasi sebelum kode berjalan. Termasuk shadowing, TDZ, dan alasan tidak memakai var.
Value yang dihitung di dalam kurung kurawal tidak bisa dipakai dari luar kurung kurawal itu — eksekusinya langsung berhenti. Hal yang sama terjadi saat sebuah baris membaca nilai pengaturan yang baru dideklarasikan di baris bawahnya. Tanpa memahami aturannya, sulit menebak dari mana dan mulai kapan sebuah nama bisa dipakai.
Artikel ini membahas scope, yang menentukan dari mana nama yang sudah dideklarasikan bisa dibaca, dan hoisting, yang mendaftarkan deklarasi sebelum kode dijalankan.
Variabel yang Hanya Bisa Dipakai di Dalam Kurung Kurawal — Block Scope
Misalnya kamu ingin menampilkan di halaman keranjang berapa yen lagi yang perlu dibelanjakan supaya dapat gratis ongkos kirim. Selisihnya bisa dihitung di dalam if yang mengecek gratis ongkos kirim, tapi kalau value itu ditampilkan di luar kurung kurawalnya, eksekusi berhenti dengan ReferenceError.
Scope (batas yang menentukan dari mana sebuah nama bisa dibaca) dibatasi oleh kurung kurawal. Batas satu pasang kurung kurawal disebut block scope, dan variabel yang dideklarasikan dengan const atau let hanya bisa dibaca di dalam block scope terdalam yang membungkus deklarasinya.
const cartTotal = 4800; // Level terluar, tidak di dalam kurung kurawal mana pun
function checkShipping(total) {
const freeLine = 5000; // Hanya di dalam kurung kurawal checkShipping
if (total < freeLine) {
const shortage = freeLine - total; // Hanya di dalam kurung kurawal if
console.log(`Kurang ${shortage} yen lagi`); // Kurang 200 yen lagi
}
// Variabel yang dideklarasikan di luar bisa dibaca dari dalam
console.log(cartTotal); // 4800
// console.log(shortage); // ReferenceError: shortage is not defined
}
checkShipping(cartTotal);
const cartTotal = 4800— bisa dibaca dari mana saja di filecheckShipping(cartTotal)— memanggil function dari level terluar
const freeLine = 5000— hanya bisa dibaca di dalam kurung kurawal inicartTotaldari luar tetap bisa dibacaconsole.log(shortage)— tidak terbaca setelah keluar dari if, jadiReferenceError
const shortage = 200— hanya bisa dibaca di dalam kurung kurawal inifreeLinedancartTotalsama-sama bisa dibaca
if tidak bisa dibaca lagi begitu keluar dari kurung kurawalnya. Arah membacanya cuma satu: dari dalam ke luar.Level terluar yang tidak dibungkus kurung kurawal apa pun adalah global scope, dan variabel di sana bisa dibaca dari mana saja. Body function juga diapit kurung kurawal, jadi freeLine tidak bisa dibaca dari luar function. Batas yang dibentuk kurung kurawal function disebut function scope.
Deklarasi di Dalam Didahulukan — Shadowing
Saat menambahkan function yang menghitung harga dengan tarif pajak yang lebih rendah, bisa saja kamu mendeklarasikan variabel taxRate di dalamnya, padahal nama itu sudah dipakai variabel di luar. Kalau kamu tidak tahu value mana yang dipakai, kamu tidak bisa melacak kenapa hasil hitungannya meleset.
Kalau di scope dalam kamu mendeklarasikan nama yang sama dengan nama di scope luar, di dalam kurung kurawal itu yang dipakai adalah deklarasi dalam. Ini disebut shadowing (deklarasi di dalam menutupi variabel di luar yang namanya sama), dan terjadi baik di kurung kurawal if maupun kurung kurawal function.
const taxRate = 0.1; // Tarif pajak yang ditetapkan di luar
function withTax(price) {
return Math.floor(price * (1 + taxRate));
}
function withReducedTax(price) {
const taxRate = 0.08; // Nama yang sama, dideklarasikan di dalam function
return Math.floor(price * (1 + taxRate));
}
console.log(withTax(1000)); // 1100
console.log(withReducedTax(1000)); // 1080
console.log(taxRate); // 0.1 (yang di luar tidak berubah)
taxRate yang sama menemukan deklarasi yang berbeda tergantung dari mana kamu membacanya. Deklarasi di dalam tidak mengubah value di luar.Nama dicari mulai dari kurung kurawal terdalam yang membungkus baris yang membacanya. Di dalam withReducedTax, pencarian berhenti begitu 0.08 ditemukan, jadi 0.1 di luar tidak pernah dibaca. Satu nama yang berisi value berbeda di tiap function membingungkan pembaca kode. Di kode kamu sendiri, pakai nama yang berbeda, misalnya reducedTaxRate.
Tanpa const, yang Di-assign Justru Variabel di Luar
Kalau kamu menulis taxRate = 0.08; di dalam function tanpa const, tidak ada variabel baru yang dibuat — baris itu justru meng-assign taxRate di luar. Kalau yang di luar adalah const, eksekusi berhenti dengan TypeError, tapi kalau let, value-nya tertimpa, dan pemanggilan withTax(1000) setelahnya pun ikut mengembalikan 1080.
Tidak Bisa Dibaca Sebelum Baris Deklarasinya — Hoisting dan TDZ
Misalnya kamu ingin mengumpulkan nilai pengaturan seperti persentase poin atau ongkos kirim di baris-baris bawah, setelah kode yang memakainya. Seperti yang sudah kamu lihat di artikel tentang function, deklarasi function tetap bisa dipanggil walaupun ditulis di bawah, tapi membaca nilai pengaturan let atau const sebelum baris deklarasinya akan menghentikan eksekusi dengan ReferenceError.
Sebelum mulai menjalankan sebuah scope, JavaScript lebih dulu mendaftarkan nama-nama deklarasi di dalamnya. Ini disebut hoisting (deklarasi didaftarkan sebelum kode dijalankan). Deklarasi function mendaftarkan nama dan body-nya sekaligus, jadi bisa dipanggil dari baris di atas deklarasinya. let dan const hanya mendaftarkan namanya; value-nya baru masuk saat baris deklarasi dijalankan.
// Body function membaca pointRate saat function dipanggil
function calcPoint(amount) {
return Math.floor(amount * pointRate);
}
// Kalau dipanggil di sini, baris deklarasi pointRate belum dijalankan
// console.log(calcPoint(3200));
// → ReferenceError: Cannot access 'pointRate' before initialization
let pointRate = 0.05; // Nilai pengaturan dikumpulkan di bawah
console.log(calcPoint(3200)); // 160 (bisa dibaca karena baris deklarasinya sudah dijalankan)
let dijalankan, value-nya belum ada, meskipun dibaca dari function yang ditulis di atasnya. Bisa dibaca atau tidak ditentukan oleh urutan eksekusi, bukan oleh posisi penulisannya.Rentang sejak masuk ke scope sampai baris deklarasi let dijalankan disebut TDZ (Temporal Dead Zone — periode ketika sebuah nama tidak bisa dibaca sampai baris deklarasinya dijalankan). Kalau pesan error-nya is not defined, nama itu tidak ada di scope. Kalau before initialization, namanya ada, tapi kamu membacanya sebelum baris deklarasinya dijalankan.
Didaftarkan Lebih Awal sebagai undefined — Alasan Tidak Memakai var
Di artikel atau library lama, kamu akan menemukan variabel yang dideklarasikan dengan var. Saat kamu menyalin kode yang sudah jalan, var bisa ikut terbawa. Tapi var berbeda dari const dan let dalam dua hal: batas berlakunya variabel, dan apa yang terjadi saat dibaca sebelum deklarasinya.
Deklarasi var juga didaftarkan lebih awal, tapi saat itu juga langsung diisi undefined. Jadi kalau dirujuk di baris di atas deklarasinya, tidak terjadi error — eksekusi lanjut ke baris berikutnya dengan undefined. Batas berlakunya juga bukan kurung kurawal, melainkan seluruh function scope yang membungkusnya.
function checkStock(orderCount) {
// Dibaca sebelum deklarasi pun tidak berhenti; isinya undefined
console.log(stock); // undefined
var stock = 12;
console.log(stock); // 12
if (orderCount <= stock) {
var status = "Siap dikirim"; // Dideklarasikan dengan var
let note = "Dikirim dari gudang A"; // Dideklarasikan dengan let
}
// var berlaku di seluruh kurung kurawal function, jadi bisa dibaca di luar if
console.log(status); // Siap dikirim
// console.log(note); // ReferenceError: note is not defined
}
checkStock(3);
if yang sama, value var masih bisa dibaca di baris setelah kurung kurawalnya berakhir. Dengan let, eksekusi berhenti di baris yang membacanya di luar batasnya.Untuk var, satu nama hanya punya satu variabel di setiap function, jadi mendeklarasikan ulang nama yang sama dengan var di dalam if tidak menghasilkan shadowing — variabel yang sama ditimpa. Tabel di bawah menunjukkan, untuk tiap cara deklarasi, apa yang masuk saat nama didaftarkan sebelum kode berjalan, dan hasilnya kalau dibaca di baris di atas deklarasi.
| Cara deklarasi | Yang masuk saat didaftarkan | Dibaca di atas deklarasi |
|---|---|---|
| var stock = 12 | Nama dan undefined | Lanjut dengan undefined |
| let stock = 12 | Hanya nama (tanpa value) | Berhenti dengan ReferenceError |
| const stock = 12 | Hanya nama (tanpa value) | Berhenti dengan ReferenceError |
Mengganti var dengan let Membuat Beberapa Baris Berhenti
Kalau kamu mengganti var dengan let di kode lama, baris yang membaca variabel sebelum deklarasinya, atau di luar if, akan berhenti dengan ReferenceError. Baris-baris itu selama ini membaca undefined atau value dari kurung kurawal lain. Untuk memperbaikinya, pindahkan deklarasi ke atas baris yang membacanya, di dalam kurung kurawal yang juga membungkus baris itu.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Di bagian atas file, kamu menulis function show yang mengembalikan rate. Lalu kamu memanggilnya di baris setelah let rate = 0.1;. Apa yang dikembalikan?
Soal 3Kalau kamu menulis console.log(count); di baris sebelum var count = 3;, apa yang ditampilkan?