Belajar dengan membaca secara berurutan

Getter dan Setter — Menjalankan Kode Saat Property Dibaca atau Di-assign

Getter dan setter menjalankan kode saat property dibaca atau di-assign. Membahas computed property, validasi value, setter dari constructor, dan property read-only.

Misalnya halaman pendaftaran seminar menampilkan sisa kursi sebagai "kapasitas − jumlah pendaftar". Kalau sisa kursi dihitung dan disimpan saat instance dibuat, tampilannya tidak berubah walaupun pendaftar bertambah belakangan. Kalau kapasitas diisi value yang lebih kecil dari jumlah pendaftar pun, tidak ada yang mencegahnya.

Artikel ini membahas getter dan setter, yang menjalankan kode saat property dibaca atau di-assign.

Dihitung Ulang Setiap Kali Dibaca — get dan Computed Property

Kalau sisa kursi dihitung sebagai "kapasitas − jumlah pendaftar" di constructor lalu disimpan, value-nya tetap seperti saat dibuat walaupun jumlah pendaftar diubah belakangan. Kalau dijadikan method, value-nya memang bisa dihitung ulang, tapi pemanggilnya harus ingat bahwa kapasitas ditulis tanpa tanda kurung, sedangkan sisa kursi pakai tanda kurung.

Getter (method di dalam class yang diawali get dan berjalan saat property-nya dibaca) ditulis tanpa parameter, seperti get remainingSeats() { ... }, dan value yang di-return menjadi hasil pembacaannya.

Property seperti remainingSeats, yang dihitung dari property lain lalu dikembalikan, disebut computed property.

class Seminar {
  reserved = 0;                  // Jumlah pendaftar

  constructor(capacity) {
    this.capacity = capacity;    // Kapasitas
  }

  // Dihitung dari kapasitas dan jumlah pendaftar saat itu, setiap kali dibaca
  get remainingSeats() {
    return this.capacity - this.reserved;
  }
}

const seminar = new Seminar(30);
console.log(seminar.remainingSeats);   // 30 (dibaca tanpa tanda kurung)

// Baca lagi setelah ada 12 pendaftar
seminar.reserved = 12;
console.log(seminar.remainingSeats);   // 18
Menyimpan Sisa Kursi, atau Menghitungnya Setiap Kali Dibaca?
Simpan sisa kursidi constructorMenyimpan 30 - 0saat dibuatAssign 12ke reservedSaat dibacatetap 30Definisikan sisakursi dengan getSaat dibuat,tidak menyimpanAssign 12ke reservedSaat dibaca,30 - 12 = 18
Kedua baris melakukan assignment yang sama sebelum membaca. Getter menghitung dengan reserved pada saat dibaca.

Angka 30 di baris atas dihitung sekali saat new lalu disimpan. Getter di baris bawah tidak menyimpan value apa pun dan menjalankan ekspresi return setiap kali kamu membaca seminar.remainingSeats, jadi berapa kali pun reserved diubah, kamu tidak perlu menambah baris untuk menghitung ulang sisa kursi.

Memanggil Getter dengan Tanda Kurung Melempar TypeError

Getter ditulis seperti method, tapi pemanggilnya tidak memakai tanda kurung. Kalau kamu menulis seminar.remainingSeats(), angka 18 yang dikembalikan justru dipanggil sebagai function, dan kodenya berhenti dengan TypeError: seminar.remainingSeats is not a function.

Daftar menu masih menampilkan harga termasuk pajak yang lama setelah harga naik. Perbaiki tanpa mengubah kode yang membaca harganya. MenuItem dan menu sudah dideklarasikan.

① Ganti priceWithTax, yang dihitung di constructor, dengan property yang dihitung setiap kali dibaca.

② Naikkan harga sebelum pajak semua produk masing-masing 40 yen.

③ Tampilkan "Nama produk: X yen (termasuk pajak)" untuk semua produk.

④ Tampilkan "Pajak: X yen" untuk produk pertama.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Mengecek Value yang Di-assign — set dan Validasi

Menjelang hari seminar, kapasitas kadang dinaikkan dari 30 menjadi 40 karena menyesuaikan tempat. Pengecekan dengan if dan throw di constructor hanya jalan saat new, jadi kalau setelah itu kamu meng-assign seminar.capacity = 5, yang lebih kecil dari 12 pendaftar, tidak ada yang menghentikannya dan sisa kursi tampil sebagai -7.

Setter (method di dalam class yang diawali set dan berjalan saat property-nya di-assign) menerima value yang di-assign lewat parameter value. Value yang sebenarnya disimpan dengan nama lain, seperti _capacity, lalu dikembalikan dari getter dengan nama yang sama, get capacity.

Awalan _ adalah konvensi yang berarti "jangan diubah langsung dari luar".

class Seminar {
  constructor(capacity, reserved) {
    this.reserved = reserved;     // Jumlah pendaftar
    this._capacity = capacity;    // Value kapasitas yang sebenarnya disimpan di _capacity
  }

  get capacity() { return this._capacity; }

  set capacity(value) {           // Value yang di-assign masuk ke value
    if (value < this.reserved) {
      throw new Error(`Kapasitas tidak boleh kurang dari ${this.reserved} pendaftar: ${value}`);
    }
    this._capacity = value;
  }
}

const seminar = new Seminar(30, 12);
seminar.capacity = 40;             // set capacity berjalan dan menyimpan 40
console.log(seminar.capacity);     // 40 (get capacity berjalan)
seminar.capacity = 5;              // Error: Kapasitas tidak boleh kurang dari 12 pendaftar: 5
Method yang Berjalan Saat Assignment dan Saat Dibaca
get capacityset capacity(value)① seminar.capacity= 40② Bacaseminar.capacity③ seminar.capacity= 5set berjalan:40 ≥ 12get berjalan:return _capacityset berjalan:5 < 12_capacitymenjadi 40Mengembalikan 40throw,tetap 40
Kalau property ditulis di kiri =, set yang berjalan; kalau dibaca di tempat lain, get yang berjalan. Value yang tidak lolos pengecekan tidak pernah sampai ke _capacity.

Baris ③ berhenti karena throw, jadi kalau tidak dibungkus try, baris-baris di bawahnya tidak dijalankan. Selama kamu meng-assign ke capacity, value-nya selalu lewat setter, tapi kalau kamu langsung menulis seminar._capacity = 5, value-nya masuk tanpa dicek. Cara mencegahnya dibahas di artikel berikutnya.

Assign ke Nama yang Sama di Dalam Setter Melempar RangeError

Kalau kamu menulis this.capacity = value; di dalam set capacity(value), assignment itu memanggil setter lagi, lalu meng-assign lagi, dan seterusnya sampai berhenti dengan RangeError: Maximum call stack size exceeded. Simpan value-nya dengan nama lain, seperti _capacity.

Kamu menerapkan angka yang diinput di panel kontrol AC sebagai setelan suhu. AirConditioner dan temperatureInputs sudah dideklarasikan.

① Tambahkan temperature untuk membaca dan menulis setelan suhu, dan lempar exception "Suhu harus antara 16 dan 30 derajat: X" kalau value-nya di luar rentang.

② Assign input satu per satu, lalu tampilkan "Suhu diatur ke X derajat" kalau berhasil diterapkan, atau pesan exception-nya kalau gagal.

③ Tampilkan "Nama ruangan: menyala pada X derajat" dengan setelan suhu terakhir.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Tetap Lewat Setter Saat Dibuat — Assignment di constructor

Seminar di atas menyimpan value langsung ke _capacity di constructor, jadi new Seminar(5, 12) dibuat tanpa pengecekan. Kalau kamu juga menulis if yang sama di constructor, setiap kali syaratnya berubah kamu harus memperbaiki dua tempat, dan kalau salah satunya terlupa, new dan assignment memberi hasil yang berbeda.

Kalau di dalam constructor kamu meng-assign ke nama tanpa _, seperti this.capacity = capacity, setter juga berjalan untuk baris itu. Pengecekannya tetap di satu tempat, yaitu setter, dan value yang dioper ke new maupun value yang di-assign belakangan dicek dengan syarat yang sama.

class Seminar {
  constructor(capacity, reserved) {
    this.reserved = reserved;     // Simpan dulu jumlah pendaftar yang dipakai pengecekan
    this.capacity = capacity;     // Namanya tanpa _, jadi set capacity berjalan
  }

  get capacity() { return this._capacity; }
  set capacity(value) {
    if (value < this.reserved) {
      throw new Error(`Kapasitas tidak boleh kurang dari ${this.reserved} pendaftar: ${value}`);
    }
    this._capacity = value;
  }
}

try {
  new Seminar(5, 12);             // Saat new pun, value lewat pengecekan setter
} catch (error) {
  console.log(error.message);     // Kapasitas tidak boleh kurang dari 12 pendaftar: 5
}
Assignment di constructor Memanggil Setter
Kurung kurawal try (new dipanggil di sini)
  • new Seminar(5, 12) — memanggil constructor
  • Exception dari setter kembali sampai ke sini, dan tidak ada instance yang dikembalikan
Body constructor yang dipanggil
  • this.reserved = 12 — menyimpan jumlah pendaftar sebelum pengecekan
  • this.capacity = 5 — namanya tanpa _, jadi memanggil set capacity
Body set capacity(value) yang dipanggil
  • value bernilai 5, this.reserved bernilai 12
  • 5 < 12, jadi throw — tidak ada yang masuk ke _capacity
Baris this.capacity = 5 memanggil set capacity. Exception-nya keluar dari constructor dan sampai ke try.

Kalau this.reserved = reserved dipindah ke bawah this.capacity = capacity, this.reserved masih undefined saat setter berjalan. Dalam perbandingan angka, undefined diperlakukan sebagai NaN, dan 5 < NaN bernilai false, jadi instance-nya tetap dibuat dengan kapasitas 5.

Kamu mendaftarkan nama lengkap karyawan dengan memisahkan nama depan dan nama belakang. staffNames sudah dideklarasikan.

① Definisikan Employee, yang menerima nama lengkap lalu menyimpan nama depan dan nama belakang secara terpisah.

② Tambahkan fullName, yang membaca dan menulis dalam bentuk "Depan Belakang" dan melempar exception "Pisahkan nama depan dan belakang dengan spasi: X" kalau tidak ada spasi.

③ Buat karyawan satu per satu, dan tampilkan pesan exception-nya untuk yang gagal dibuat.

④ Assign "Siti Wulandari" ke fullName karyawan pertama, lalu tampilkan nama depan, nama belakang, dan nama lengkap semua karyawan.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Menolak Assignment — Property yang Hanya Punya Getter

Sisa kursi ditentukan oleh kapasitas dan jumlah pendaftar. Kalau kode di halaman bisa menulis seminar.remainingSeats = 100, halaman akan menampilkan sisa 100 kursi padahal kapasitasnya 30 dan pendaftarnya 12, sehingga ketiga value itu tidak lagi cocok.

Property yang hanya punya getter tanpa setter dengan nama yang sama bersifat read-only (bisa dibaca, tapi tidak bisa di-assign). Di dalam body class, dan di strict mode (mode eksekusi yang mengubah sebagian kesalahan menjadi exception) seperti di runtime ini, baris yang meng-assign ke property itu berhenti dengan TypeError. Di luar itu, assignment-nya diabaikan tanpa pemberitahuan.

class Seminar {
  constructor(capacity, reserved) {
    this.capacity = capacity;
    this.reserved = reserved;
  }

  get remainingSeats() { return this.capacity - this.reserved; }   // Tanpa setter
}

const seminar = new Seminar(30, 12);
try {
  seminar.remainingSeats = 100;          // Assign ke nama yang tidak punya set
} catch (error) {
  console.log(error.name);               // TypeError
}
console.log(seminar.remainingSeats);     // 18 (sama seperti sebelum assignment)

// Untuk mengubah sisa kursi, ubah jumlah pendaftar yang menjadi dasarnya
seminar.reserved = 20;
console.log(seminar.remainingSeats);     // 10
Assignment ke Sisa Kursi: Diterima atau Ditolak?
Simpan sisa kursisebagai valueremainingSeats= 100100 langsungmasukTidak cocok dengan30 - 12Sisa kursi hanyadengan getremainingSeats= 100Tidak ada set,jadi TypeErrorTetap30 - 12 = 18
Di baris atas, assignment-nya diterima begitu saja, sehingga tersisa value yang tidak cocok dengan kapasitas dan jumlah pendaftar. Kalau hanya ada getter, assignment langsung ditolak di tempat.

Baris bawah berhenti karena nama yang hanya punya getter tidak punya tempat untuk menampung value yang di-assign, dan tidak ada key baru yang dibuat di instance. Tabel di bawah merangkum cara menulis yang dipakai di Seminar pada artikel ini, beserta method sebagai pembanding.

Cara menulisCara memakainyaCocok untuk
Property biasa (reserved)seminar.reserved = 20 langsungValue yang tak perlu dicek
Hanya getter (remainingSeats)Dibaca: seminar.remainingSeatsValue hasil hitungan
Getter dan setter (capacity)seminar.capacity = 40 dicek duluValue yang dicek tiap assign
Method (misal reserve(count))Dipanggil pakai () dan argumentHasil tergantung argument
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Dengan getter yang mengembalikan 30 - this.reserved, apa hasilnya kalau reserved diubah menjadi 20 lalu getter-nya dibaca?

Soal 2Kalau set capacity berisi this.capacity = value; lalu kamu meng-assign 40, apa yang terjadi?

Soal 3Apa yang terjadi kalau kamu meng-assign 100 ke seminar.remainingSeats, yang hanya punya getter?