Topik ini membahas empat cara membatasi akses anggota kelas, yaitu modifier public, private, dan protected, serta field #private milik JavaScript. Perbedaan terpentingnya terletak pada lapisan yang memberlakukan batasan, yaitu pemeriksa tipe TypeScript atau bahasa JavaScript itu sendiri. Setelah topik ini dipelajari, batasan akses dapat dipilih sesuai kebutuhan dan perilaku program hasil kompilasinya dapat diantisipasi.
Empat tingkat visibilitas#
Modifier akses adalah kata kunci yang mengatur dari mana anggota kelas boleh dibaca atau ditulis. Anggota tanpa modifier dianggap public, sehingga dapat diakses dari bagian mana pun. Modifier private membatasi akses pada kelas yang mendeklarasikannya, sedangkan protected membuka akses tambahan bagi kelas turunan. Field #private diawali tanda pagar dan merupakan mekanisme privatisasi milik bahasa JavaScript, bukan sekadar anotasi TypeScript.
| Bentuk penandaan | Kelas itu sendiri | Kelas turunan | Kode di luar kelas |
|---|---|---|---|
public, nilai bawaan | Boleh | Boleh | Boleh |
private | Boleh | Tidak | Tidak |
protected | Boleh | Boleh | Tidak |
#private | Boleh | Tidak | Tidak |
Tabel menunjukkan private dan #private menetapkan aturan akses yang sama, padahal keduanya bekerja pada lapisan yang berbeda. Modifier private, protected, dan public hanya diperhitungkan oleh pemeriksa tipe TypeScript. Sebaliknya, field #private diberlakukan oleh mesin JavaScript sehingga pelanggarannya terjadi saat program berjalan, bukan hanya saat kompilasi. Bagian berikut menunjukkan perbedaan itu dengan keluaran nyata.
Modifier juga dapat dipadukan dengan readonly pada field, misalnya private readonly, sehingga batasan akses dan batasan penulisan berlaku bersamaan. Pemilihan tingkat visibilitas sebaiknya dimulai dari yang paling ketat, lalu diperlonggar hanya ketika ada pemakai yang jelas. Kebiasaan ini mengurangi jumlah anggota yang terbuka tanpa pernah dimaksudkan menjadi pintu masuk.
private dan protected dalam praktik#
Berkas catatan-suhu.ts berikut memuat kelas dengan field privat untuk menyimpan data pengukuran dan field protected untuk identitas perangkat. Kelas turunan CatatanSuhuHarian membaca field protected tersebut pada metodenya, sedangkan field private tidak pernah diakses dari luar kelas.
class CatatanSuhu {
private daftarSuhu: number[] = [];
protected idPerangkat: string;
constructor(idPerangkat: string) {
this.idPerangkat = idPerangkat;
}
catat(nilai: number): void {
this.daftarSuhu.push(nilai);
}
rataRata(): number {
const total = this.daftarSuhu.reduce((a, b) => a + b, 0);
return total / this.daftarSuhu.length;
}
}
class CatatanSuhuHarian extends CatatanSuhu {
label(): string {
return `perangkat ${this.idPerangkat}`;
}
}
const harian = new CatatanSuhuHarian("SNS-07");
harian.catat(21.5);
harian.catat(22.5);
console.log(harian.label());
console.log(harian.rataRata());perangkat SNS-07
22Field private daftarSuhu hanya dipakai oleh catat() dan rataRata(), sehingga cara penyimpanan data bebas diubah tanpa memengaruhi pemakainya. Field protected idPerangkat sengaja dibuka bagi kelas turunan yang memakainya untuk menyusun label. Apabila kode di luar kelas mencoba membaca field privat, pemeriksa tipe menolak akses itu seperti pada berkas suhu-salah.ts berikut.
// tidak-dijalankan
class CatatanSuhu {
private daftarSuhu: number[] = [];
catat(nilai: number): void {
this.daftarSuhu.push(nilai);
}
}
const catatan = new CatatanSuhu();
catatan.daftarSuhu.push(20);# periksa tipe tanpa menghasilkan berkas keluaran
tsc --noEmit --pretty false suhu-salah.tssuhu-salah.ts(11,9): error TS2341: Property 'daftarSuhu' is private and only accessible within class 'CatatanSuhu'.Error TS2341 menyatakan properti bersifat private dan hanya dapat diakses dari dalam kelasnya. Pemisahan ini menjaga detail internal tetap bebas berubah, sedangkan bagian yang dipakai bersama dinyatakan secara eksplisit. Desain kelas turunan yang membutuhkan data internal induknya sebaiknya ditinjau ulang, karena membuka akses protected berarti mewariskan ketergantungan kepada seluruh turunan. Daftar anggota protected dengan demikian merupakan keputusan desain, bukan jalan pintas untuk menghindari penolakan pemeriksa tipe.
Type erasure pada private#
TypeScript menghapus seluruh anotasi tipe pada saat kompilasi, termasuk modifier akses. Proses penghapusan ini disebut type erasure, dan akibatnya modifier private tidak meninggalkan jejak apa pun pada program hasil kompilasi. Field yang dilindungi modifier itu berubah menjadi properti biasa yang dapat dilihat, disalin, dan dibaca oleh kode JavaScript mana pun. Berkas erasi.ts berikut membuktikannya dengan membaca field privat melalui fungsi utilitas yang mengeksekusi potongan kode JavaScript.
class LokerArsip {
private kodeLoker = "ARS-2024";
#logAkses: string[] = [];
catat(kegiatan: string): void {
this.#logAkses.push(kegiatan);
}
}
function bacaAman(objek: object, namaProperti: string): string {
try {
const pembaca = new Function("objek", `return objek.${namaProperti};`);
return String(pembaca(objek));
} catch (e) {
const err = e as Error;
return `${err.name}: ${err.message}`;
}
}
const loker = new LokerArsip();
loker.catat("loker dibuka petugas");
console.log(Object.keys(loker));
console.log(JSON.stringify(loker));
console.log(bacaAman(loker, "kodeLoker"));
console.log(bacaAman(loker, "#logAkses"));[ 'kodeLoker' ]
{"kodeLoker":"ARS-2024"}
ARS-2024
SyntaxError: Private field '#logAkses' must be declared in an enclosing classBaris pertama keluaran memuat hanya kodeLoker, sedangkan #logAkses tidak pernah muncul pada daftar kunci maupun pada JSON.stringify(). Baris ketiga menunjukkan kodeLoker terbaca langsung dari luar kelas tanpa penolakan apa pun, karena pada program berjalan field itu adalah properti biasa. Dengan demikian modifier private hanya dapat diandalkan pada saat kompilasi, dan bagian berikut menelaah sisi #private pada keluaran yang sama.
Fungsi utilitas bacaAman() pada contoh sengaja memakai new Function() agar aksesnya ditulis sebagai teks dan tidak ikut diperiksa tipe. Dengan cara itu pembacaan terjadi sepenuhnya pada saat program berjalan. Tanpa perantara semacam ini, pemeriksa tipe sudah menolak akses field privat sebelum program dijalankan.
#private sebagai field privat asli#
Field #private diatur oleh spesifikasi bahasa JavaScript sehingga tersedia tanpa TypeScript sama sekali. Pada hasil kompilasi, private kodeLoker menjadi properti biasa bernama kodeLoker, sedangkan #logAkses tetap ditulis dengan tanda pagarnya. Perintah berikut mengompilasi berkas erasi.ts lalu menjalankan hasilnya dengan Node, dan keluarannya menegaskan perilaku yang sama.
# kompilasi berkas lalu jalankan hasilnya dengan Node
tsc erasi.ts
node erasi.js[ 'kodeLoker' ]
{"kodeLoker":"ARS-2024"}
ARS-2024
SyntaxError: Private field '#logAkses' must be declared in an enclosing classBaris keempat adalah bukti pembedanya, karena akses #logAkses dari luar kelas tidak hanya ditolak pemeriksa tipe, tetapi juga menghasilkan SyntaxError pada saat program berjalan. Field #private juga tidak dapat dibaca oleh kelas turunan, sehingga pelindungannya lebih ketat dibandingkan private. Pilih private untuk penandaan internal kelas yang masih mengandalkan pemeriksaan tipe, dan pilih #private apabila penyembunyiannya wajib dijamin pada saat runtime.
Kesalahan umum#
- Berharap
privatemenyembunyikan data dari serialisasi.JSON.stringify()tetap menampilkan field privat hasil modifier TypeScript karena field itu properti biasa pada program berjalan. Gunakan#privateatau salin hanya field yang ingin ditampilkan sebelum diserialisasi. - Mengakses field
#privatedari luar kelas dan mengira perbaikannya cukup dilakukan pada lapisan tipe. Kesalahan ini bukan error tipe, melainkan SyntaxError yang menghentikan program saat berjalan. Pindahkan akses ke dalam metode kelas yang mendeklarasikan field tersebut. - Memilih
protectedagar anggota dapat diakses dari luar kelas. Modifier ini tetap menolak akses dari luar dan hanya membuka pintu bagi kelas turunan. Gunakanpublicatau sediakan metode antarmuka yang sengaja dibuka.
Ringkasan#
publicadalah nilai bawaan,privatemembatasi akses pada kelas itu sendiri, danprotectedmeluas ke kelas turunan.- Modifier TypeScript hilang bersama type erasure, sehingga field
privatetetap terbaca pada program hasil kompilasi. - Field
#privatediberlakukan oleh bahasa JavaScript, tidak muncul pada inspeksi objek, dan menimbulkan SyntaxError bila diakses dari luar kelas. - Pilihan modifier sebaiknya mengikuti lapisan jaminan yang dibutuhkan, yaitu pemeriksaan kompilasi atau keamanan runtime.
Referensi#
- Classes — TypeScript Handbook — Modifier private, protected, public, dan perbandingannya dengan field #private.
- Private elements — MDN Web Docs — Referensi field privat # pada JavaScript beserta aturan aksesnya.