Getter, setter, static

Fitur kelas modern, termasuk field privat dengan tanda pagar.

Daftar isi

Fitur kelas tidak berhenti pada metode biasa. Pasangan get dan set menyajikan perhitungan sebagai properti, static menempelkan utilitas pada kelasnya, dan field privat dengan awalan # menyembunyikan data dari luar. Topik ini membangun ketiganya di atas domain akun tabungan dan sensor suhu. Bagian penutupnya membandingkan field privat dengan konvensi garis bawah, lalu menunjukkan pola standar getter di atas field privat. Semua contoh berupa berkas JavaScript utuh yang dijalankan dengan Node.js.

Getter dan setter#

Pasangan get dan set mendefinisikan properti terhitung, yaitu properti yang dibaca dan ditulis seperti properti biasa tetapi di belakangnya menjalankan fungsi. Getter dipanggil sekadar dengan membaca propertinya, tanpa tanda kurung, sedangkan setter dipanggil lewat penugasan biasa. Bentuk ini cocok untuk nilai yang dihitung dari data lain dan untuk aturan yang harus berlaku setiap kali nilai diubah.

js getter-setter.js
// get dan set memperlakukan hasil perhitungan seperti properti biasa.

class SensorSuhu {
  constructor(celsius) {
    this.celsius = celsius;
  }

  get fahrenheit() {
    return this.celsius * 1.8 + 32;
  }

  set fahrenheit(nilai) {
    if (typeof nilai !== "number" || Number.isNaN(nilai)) {
      throw new TypeError("Nilai suhu harus berupa angka");
    }
    this.celsius = (nilai - 32) / 1.8;
  }
}

const sensor = new SensorSuhu(25);

console.log(sensor.fahrenheit);

sensor.fahrenheit = 68;
console.log(sensor.celsius);

try {
  sensor.fahrenheit = "panas";
} catch (error) {
  console.log(error.name + ": " + error.message);
}
text Keluaran
77
20
TypeError: Nilai suhu harus berupa angka

Pembacaan sensor.fahrenheit menjalankan getter dan menghasilkan 77 dari 25 derajat Celsius, tanpa tanda kurung di mana pun. Penugasan sensor.fahrenheit = 68 menjalankan setter, yang mengonversi nilainya ke Celsius dan menyimpannya, sehingga sensor.celsius menjadi 20. Penugasan dengan teks ditolak setter melalui TypeError, dan karena pemeriksaan itu berada di dalam setter, tidak ada jalur penugasan yang lolos dari aturannya.

static untuk utilitas kelas#

Anggota static melekat pada kelasnya, bukan pada setiap object hasil new, dan dipanggil lewat nama kelas. Bentuk ini cocok untuk konstanta bersama dan utilitas yang tidak membutuhkan data instansi tertentu. Di dalam metode static, this menunjuk kelasnya sendiri, sehingga this.bungaTahunan membaca field static milik AkunTabungan.

js utilitas-static.js
// Anggota static melekat pada kelas, bukan pada setiap instansi.

class AkunTabungan {
  static bungaTahunan = 3;

  static hitungBunga(saldo, bulan) {
    const bungaBulanan = this.bungaTahunan / 12;
    return Math.round(saldo * (bungaBulanan / 100) * bulan);
  }
}

const akun = new AkunTabungan();

console.log(AkunTabungan.bungaTahunan);
console.log(AkunTabungan.hitungBunga(1000000, 12));
console.log(akun.hitungBunga === undefined);
text Keluaran
3
30000
true

Perhitungan bunga dipanggil lewat AkunTabungan.hitungBunga dan menghasilkan 30000, yaitu 3 persen dari 1000000 untuk satu tahun. Baris terakhir menegaskan pembedanya, hitungBunga tidak dimiliki object akun karena anggota static tidak berada di prototipe instansi. Bila sebuah utilitas tidak membaca data instansi mana pun, tempatnya di static, bukan di metode instansi.

Field privat dengan tanda pagar#

Field privat ditulis dengan awalan # dan hanya dapat diakses dari dalam tubuh kelasnya. Dari luar, field itu tidak terlihat sama sekali, sehingga perubahan data harus melewati metode yang kelas sediakan. Bentuk penyembunyian seperti ini mendukung enkapsulasi, yaitu penyatuan data dan aturan perubahannya dalam satu batas, sehingga nilai yang tidak sah tidak dapat masuk diam-diam. Kelas AkunTabungan berikut menaruh saldonya di field privat dan mengatur seluruh perubahannya lewat metode.

js field-privat.js
// Field privat # hanya dapat diakses dari dalam tubuh kelasnya.

class AkunTabungan {
  #saldo;

  constructor(saldoAwal) {
    this.#saldo = saldoAwal;
  }

  get saldo() {
    return this.#saldo;
  }

  setor(jumlah) {
    if (jumlah <= 0) {
      throw new RangeError("Jumlah setoran harus positif");
    }
    this.#saldo += jumlah;
  }

  tarik(jumlah) {
    if (jumlah > this.#saldo) {
      throw new RangeError("Saldo tidak mencukupi");
    }
    this.#saldo -= jumlah;
  }
}

const akun = new AkunTabungan(500000);
akun.setor(200000);
console.log(akun.saldo);

try {
  akun.tarik(1000000);
} catch (error) {
  console.log(error.name + ": " + error.message);
}

console.log(akun["#saldo"]);
console.log(akun.saldo);
text Keluaran
700000
RangeError: Saldo tidak mencukupi
undefined
700000

Penarikan sebesar 1000000 ditolak lewat RangeError karena melebihi saldo, dan saldo tetap 700000. Pembacaan lewat kurung siku, akun["#saldo"], menghasilkan undefined karena nama field privat bukan nama properti biasa, sedangkan penulisan akun.#saldo di luar kelas ditolak sebagai error sintaks sejak kode dibaca. Dengan demikian saldo hanya bisa naik lewat setor dan turun lewat tarik, dua jalur yang sama-sama memeriksa keabsahannya. Getter saldo bersifat baca saja karena tidak ada setter pasangannya.

Garis bawah versus tanda pagar#

Sebelum field privat ada, kebiasaan yang beredar menandai properti internal dengan garis bawah, seperti _catatan. Penanda itu hanyalah kesepakatan antaranggota tim karena bahasa tidak melarang apa pun, sehingga properti bertanda garis bawah tetap bisa dibaca dan diubah dari luar. Field dengan tanda pagar ditegaskan oleh bahasanya. Akses dari luar tubuh kelas menghasilkan error, bukan sekadar pelanggaran gaya penulisan.

js garis-bawah-pagar.js
// Garis bawah hanyalah kesepakatan, tanda pagar ditegaskan bahasanya.

class DenganGarisBawah {
  constructor() {
    this._catatan = "masih terlihat dari luar";
  }
}

const objekA = new DenganGarisBawah();
console.log(objekA._catatan);

class DenganPagar {
  #catatan = "tersembunyi dari luar";

  baca() {
    return this.#catatan;
  }
}

const objekB = new DenganPagar();
console.log(objekB.baca());
console.log(objekB._catatan);
text Keluaran
masih terlihat dari luar
tersembunyi dari luar
undefined

Properti _catatan terbaca langsung dari luar tanpa halangan apa pun, karena garis bawah tidak membentenginya. Field #catatan hanya bisa diakses lewat metode baca, dan objekB._catatan menghasilkan undefined karena properti dengan nama itu memang tidak ada. Pembacaan yang ditujukan ke objekB.#catatan akan ditolak sebagai error sintaks. Kombinasi field privat dengan getter di atasnya, seperti getter saldo pada kelas AkunTabungan, menjadi pola standar untuk menyajikan data yang perubahannya harus dikendalikan.

Ringkasan#

  • get dan set mendefinisikan properti terhitung yang dibaca dan ditulis seperti properti biasa, dengan tempat validasi pada setter.
  • Anggota static melekat pada kelasnya, dipanggil lewat nama kelas, dan tidak dimiliki instansinya.
  • Field privat dengan awalan # hanya dapat diakses dari dalam tubuh kelasnya, sehingga perubahan data terkonsentrasi pada metode yang kelas sediakan.
  • Garis bawah hanyalah kesepakatan tanpa penegakan, sedangkan tanda pagar ditegaskan bahasa, dan kombinasi field privat dengan getter menjadi pola standar penyajian data.

Referensi#