Soal 1Apa yang terjadi saat kamu menjalankan kode yang berisi card.#balance = 0; di luar class, setelah sebuah console.log?
Private Field (#) dan Enkapsulasi
Private field dan private method memakai awalan # untuk mencegah pembacaan dan penulisan dari luar class. Membahas SyntaxError sebelum kode jalan dan bedanya # dengan konvensi _.
Menyimpan saldo kartu prabayar di _balance tidak melindunginya: cukup tulis card._balance = 999999 di kode halaman mana pun, value itu langsung masuk. Awalan _ hanyalah konvensi yang berarti "jangan diubah dari luar"; konvensi ini tidak mencegah assignment maupun pemanggilan.
Artikel ini membahas private field dan private method, yang tidak bisa dibaca atau ditulis dari luar class.
Menyembunyikan Saldo di Dalam Class — Private Field
Misalnya saldo hanya boleh berubah lewat charge, yang mengecek batas top up 20000 yen per transaksi. Dengan _balance, kalau kode lain yang membuat layar refund menulis card._balance += 30000, tidak ada yang menghentikannya, dan nominal yang melebihi batas masuk ke saldo.
Private field (field di dalam class yang namanya diawali #, dan hanya bisa dibaca dan ditulis dari dalam kurung kurawal class itu) dideklarasikan sebagai #balance = 0; lalu dipakai dengan menulis this.#balance.
# juga bagian dari nama, jadi this.balance dan this.#balance adalah dua nama yang berbeda.
class PrepaidCard {
#balance = 0; // Saldo. Tidak bisa dibaca atau ditulis dari luar class
charge(amount) {
if (amount > 20000) {
throw new Error(`Top up maksimal 20000 yen per transaksi: ${amount}`);
}
this.#balance += amount; // Di dalam class, jadi bisa diubah
}
get balance() { return this.#balance; } // Ke luar, hanya getter untuk membaca yang dibuka
}
const card = new PrepaidCard();
card.charge(5000); // Satu-satunya pintu untuk mengubah saldo adalah charge
card.charge(3000);
console.log(card.balance); // 8000
#balance = 0— nama yang hanya bisa dipakai di dalam kurung kurawal ini
- Kalau
amount > 20000, berhenti denganthrow - Hanya nominal yang lolos yang menjalankan
this.#balance += amount
return this.#balance— hanya mengembalikan value, tidak mengubahnya
card.charge(5000)— mengubah saldo lewat chargecard.balance— membaca 8000 lewat getter- Nama
#balancetidak bisa ditulis di sini
#balance hanya bisa ditulis di dalam kurung kurawal class. Bagi baris di luar, satu-satunya pintu untuk mengubah saldo adalah charge.Dengan enkapsulasi (desain yang menyatukan data dan kode yang mengubahnya di dalam class, lalu membatasi pemakaian dari luar hanya lewat pintu yang sudah ditentukan), satu-satunya cara mengubah saldo PrepaidCard ini adalah charge, dan satu-satunya cara membacanya adalah getter balance.
Nama # yang Belum Dideklarasikan Melempar SyntaxError
Deklarasikan nama # di body class, seperti #balance = 0; atau #balance;, sebelum memakainya. Kalau kamu meng-assign this.#balance = 0; di constructor tanpa deklarasi, hasilnya SyntaxError, walaupun barisnya ada di dalam class. Nama # tidak dibuat hanya dengan assignment.
Ditulis dari Luar, Berhenti Sebelum Kode Jalan — SyntaxError
Katakanlah kamu ingin mengatur saldo menjadi 999999 dari kode halaman untuk menguji sesuatu. Kalau kamu menulis card.#balance = 999999, seperti yang biasa kamu lakukan dengan _balance, baris itu gagal. Bahkan console.log di atasnya, yang seharusnya tampil lebih dulu, tidak menampilkan apa pun.
Menulis card.#balance di luar class melempar SyntaxError: Private field '#balance' must be declared in an enclosing class saat kode sedang dimuat, sama seperti mendeklarasikan ulang let. Pesan itu artinya "private field harus dideklarasikan di class yang membungkusnya".
class PrepaidCard {
#balance = 0;
charge(amount) { this.#balance += amount; }
get balance() { return this.#balance; }
}
console.log("Mulai pengujian"); // Baris ini juga tidak ditampilkan
const card = new PrepaidCard();
card.charge(5000);
// Dengan _balance, baris dijalankan dari atas, tapi nama # menghentikan kode saat sedang dimuat
card.#balance = 999999; // SyntaxError: Private field '#balance' must be declared in an enclosing class
console.log(card.balance);
Kalau kamu mendapat SyntaxError yang diawali Private field tanpa ada output sama sekali, cari nama # yang ditulis di luar class. Ubah baris yang mengganti value dari luar menjadi pemanggilan method class, seperti charge.
Menyembunyikan Langkah Internal dari Luar — Private Method
Misalnya kode yang mengurangi saldo kamu pindahkan dari pay ke method biasa bernama withdraw. Sekarang kode di luar class juga bisa memanggil card.withdraw(5000), melewati pengecekan saldo di pay, dan membuat saldonya minus.
Kalau kamu menjadikannya private method (method yang didefinisikan dengan awalan # dan hanya bisa dipanggil dari method di class yang sama dalam bentuk this.#withdraw(price)), langkah yang kamu pindahkan itu tidak bisa dipanggil dari luar. Kalau nanti nama atau parameternya diubah, bagian yang perlu diperbaiki tetap berada di dalam class.
class PrepaidCard {
#balance = 3000;
// Langkah yang hanya mengurangi saldo. Cukup atau tidaknya dicek dulu oleh pay
#withdraw(amount) { this.#balance -= amount; }
pay(price) {
if (price > this.#balance) {
return `Saldo tidak cukup: ${price}`;
}
this.#withdraw(price); // Di dalam class, jadi bisa dipanggil
return `Saldo setelah pembayaran: ${this.#balance}`;
}
}
const card = new PrepaidCard();
console.log(card.pay(480)); // Saldo setelah pembayaran: 2520
console.log(card.pay(5000)); // Saldo tidak cukup: 5000
console.log(typeof card.withdraw); // undefined (kalau dipanggil, TypeError)
pay(5000) langsung return di pengecekan saldo, jadi tidak pernah sampai ke #withdraw. Menulis card.#withdraw(5000) di luar class melempar SyntaxError saat kode dimuat, sama seperti #balance; TypeError di kolom kanan berasal dari pemanggilan nama lain, yaitu nama tanpa #.
Panggil Private Method dengan this.
Di dalam class pun, kalau kamu menghilangkan this. dan menulis #withdraw(price);, kodenya berhenti dengan SyntaxError: Unexpected identifier '#withdraw' sebelum apa pun dijalankan. Sama seperti private field, private method selalu dipanggil dalam bentuk this.#withdraw(price).
Apa yang Tersimpan di JSON — Beda _ dan #
Sekarang class kartu prabayar kamu ubah dari _balance menjadi #balance. Assignment dari luar memang jadi tercegah, tapi kalau selama ini instance disimpan sebagai string dengan JSON.stringify, saldo tidak lagi ada di data yang disimpan setelah perubahan itu.
Nama berawalan _ hanyalah property biasa yang kebetulan diawali _, jadi muncul di JSON.stringify. Field # tidak termasuk key property, jadi tidak ikut di hasilnya. Getter didefinisikan di class, bukan di instance, jadi juga bukan key, dan balance pun tidak muncul.
// Sebelum diubah: property biasa dengan awalan _
class LegacyPrepaidCard {
constructor() { this._balance = 3000; }
}
// Setelah diubah: private field dengan # dan getter untuk membacanya
class PrepaidCard {
#balance = 3000;
get balance() { return this.#balance; }
}
const legacyCard = new LegacyPrepaidCard();
const prepaidCard = new PrepaidCard();
console.log(JSON.stringify(legacyCard)); // {"_balance":3000}
console.log(JSON.stringify(prepaidCard)); // {}
console.log(JSON.stringify({ balance: prepaidCard.balance })); // {"balance":3000}
- Tidak ada key sama sekali (
get balance()adalah method yang ditulis di class) JSON.stringify(prepaidCard)menghasilkan{}
#balance— 3000- Hanya baris di dalam PrepaidCard yang bisa membacanya
#balance ada di instance, tapi bukan key property. JSON.stringify hanya membaca key property.Walaupun hasilnya {}, saldonya tidak hilang: prepaidCard.balance tetap membaca 3000. Saat menyimpan, oper object yang dibuat dari value getter, seperti di baris terakhir contoh. Tabel di bawah membandingkan tiga cara menulis yang dipakai di artikel ini.
| Cara menulis | Akses dari luar class | Hasil JSON.stringify |
|---|---|---|
| Nama dengan _ (_balance) | Bisa dibaca dan ditulis, tanpa cek | Muncul: {"_balance":3000} |
| Private field (#balance) | SyntaxError saat dimuat | Tidak muncul ({}) |
| Private method (#withdraw) | #withdraw(): SyntaxError / withdraw(): TypeError | Tidak muncul (method) |
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Pada card yang punya #withdraw, apa yang terjadi kalau kamu memanggil card.withdraw(500) dari luar?
Soal 3Apa hasil JSON.stringify untuk card yang punya #balance = 3000 dan hanya sebuah getter?