Class KalkulatorKasir di chapter sebelumnya membawa method saja: setiap new menghasilkan instance kosong tanpa data, dan semua nilai datang dari parameter method. Kebutuhan kafe Budi kini berubah — tiap pesanan membawa nama minuman, suhu, ukuran, dan status gula yang berbeda-beda. Data itu harus terpasang pada saat instance lahir, bukan setelahnya.
Tempat resminya adalah constructor(): method khusus yang dipanggil engine tepat satu kali di setiap pemanggilan new. Chapter ini menyusun class PesananKopi yang menggabungkan tiga alat inisialisasi sekaligus — parameter wajib, object konfigurasi dengan nilai default, dan field bawaan — lalu menutup dengan kasus class yang sama sekali tidak punya constructor().
constructor(): Satu Kali per Instance #
Aturannya begini: bila badan class memuat method bernama constructor, engine memanggilnya otomatis setiap kali new dijalankan. Method ini menerima argumen dari pemanggil, dan di dalamnya this sudah menunjuk ke instance baru yang sedang dibangun — tepat seperti this di constructor function chapter ketiga, kali ini tanpa protokol yang harus dijaga tangan.
Tiga aturan pendamping yang perlu dipegang:
- Nama method harus persis
constructor; tidak ada alias lain. - Satu class hanya boleh punya satu
constructor— JavaScript tidak mengenal overloading. - Tanpa
constructor(), engine memakai constructor kosong bawaan yang tidak menerima data.
Parameter Konfigurasi dan Nilai Default #
Pesanan kopi nyata jarang mengisi semua opsi. Pola yang lazim: satu parameter wajib di depan (namaMinuman), lalu satu object konfigurasi opsional yang propertinya diisi lewat operator ?? — nilai cadangan berjalan otomatis ketika opsi tidak diberikan. Operator ?? sudah kita kuasai di section Operators, dan di sini ia menemukan pemanfaatan yang sangat pas.
Data yang sama untuk semua instance, seperti poin dasar, lebih baik disimpan sebagai field bawaan — properti yang dideklarasikan langsung di badan class dengan nilai awalnya:
// pesanan-kopi.js
// constructor(): satu tempat memasang data awal tiap instance.
class PesananKopi {
poinDasar = 10;
constructor(namaMinuman, konfigurasi = {}) {
this.namaMinuman = namaMinuman;
this.suhu = konfigurasi.suhu ?? "Panas";
this.ukuran = konfigurasi.ukuran ?? "Reguler";
this.pakaiGula = konfigurasi.pakaiGula ?? false;
this.poin = this.poinDasar;
if (this.ukuran === "Grande") {
this.poin += 5;
}
}
struk() {
return `${this.namaMinuman} (${this.suhu}, ${this.ukuran})`;
}
}
class NotaKosong {
nomor = "NT-001";
terbayar = false;
}
const pesananBudi = new PesananKopi("Kopi Susu Gula Aren");
const pesananSiti = new PesananKopi("Americano", {
suhu: "Dingin",
ukuran: "Grande",
pakaiGula: true,
});
console.log(pesananBudi.struk());
console.log(pesananBudi.poin);
console.log(pesananSiti.struk());
console.log(pesananSiti.poin);
console.log(pesananSiti.pakaiGula);
const nota = new NotaKosong();
console.log(nota.nomor);
console.log(nota.terbayar);Output yang Dihasilkan:
Kopi Susu Gula Aren (Panas, Reguler)
10
Americano (Dingin, Grande)
15
true
NT-001
falseMari kita baca baris demi baris:
- Baris 5 (
poinDasar = 10): field bawaan — setiap instance memulai dengan propertipoinDasarbernilai10, tanpa lewat parameter apa pun. - Baris 7 (
constructor(namaMinuman, konfigurasi = {})): parameter kedua memakai nilai default{}sehingga pemanggilan tanpa konfigurasi tetap aman membaca propertinya. - Baris 9–11 (tiga penugasan
??): opsi yang tidak diberikan mengambil nilai cadangan — pesanan Budi tanpa konfigurasi memperolehPanas,Reguler, danfalse. - Baris 12–15 (poin): poin memulai dari field
poinDasar, lalu bertambah5bila ukuranGrande— aturan bisnis hidup di satu tempat, yaitu constructor. - Baris 18–20 (
struk()): method membaca data instance lewatthisdan merakit satu baris struk dengan template literal. - Baris 28 (
new PesananKopi("Kopi Susu Gula Aren")): argumen kedua tidak diberikan;konfigurasimenjadi{}dan semua opsi jatuh ke default. - Baris 29–33 (
new PesananKopi("Americano", {...})): object konfigurasi lengkap; ukuranGrandemengangkat poin Siti menjadi15. - Baris 23–26 (
class NotaKosong): class tanpaconstructor()sama sekali — field bawaannya tetap terpasang, membuktikan constructor bawaan yang kosong.
Field Bawaan Dihitung Ulang di Tiap Instance #
Perilaku field bawaan yang perlu dipahami sejak awal: ekspresi di belakang tanda = dievaluasi setiap kali ada new, bukan sekali di awal program. Konsekuensi pentingnya terlihat pada field yang isinya object — array misalnya. Bila nilai awalnya disalin bersama, dua pesanan akan menulis di satu array yang sama; kenyataannya tidak:
class Pesanan {
catatan = [];
constructor(nama) {
this.catatan.push(`Pesanan ${nama} dibuat`);
}
}
const p1 = new Pesanan("Matcha");
const p2 = new Pesanan("Americano");
console.log(p1.catatan);
console.log(p2.catatan);Output yang Dihasilkan:
[ 'Pesanan Matcha dibuat' ]
[ 'Pesanan Americano dibuat' ]Dua array terpisah, dua isi yang terpisah — p2 tidak ikut mewarisi catatan p1. Ini karena urutan inisialisasi yang konsisten: engine mengeksekusi field bawaan satu per satu untuk instance baru, baru setelah itu badan constructor() dijalankan. Aturan ini juga menjelaskan kenapa poinDasar di berkas utama aman dibaca oleh constructor — fieldnya sudah terisi sebelum constructor mulai bekerja.
Mengapa Object Konfigurasi, Bukan Deretan Parameter #
Pemula sering bertanya kenapa tidak menyusun semua opsi sebagai parameter berurutan — constructor(namaMinuman, suhu, ukuran, pakaiGula). Untuk dua opsi itu terlihat masih jinak, tetapi kebutuhan menambah opsi datang cepat-cepat, dan deretan parameter punya dua kelemahan yang makin terasa:
| Aspek | Deretan parameter | Object konfigurasi |
|---|---|---|
| Melewati opsi di tengah | Harus mengisi dengan undefined: new Pesanan("X", undefined, "Grande") | Cukup lewati kuncinya |
| Menambah opsi baru | Semua pemanggil lama terancam salah urutan | Pemanggil lama tidak terpengaruh |
| Keterbacaan di pemanggil | new Pesanan("Americano", true, "Grande", false) — mana true? | { suhu: "Dingin", ukuran: "Grande" } — tiap nilai berlabel |
Dengan ??, opsi yang tidak disebut pemanggil jatuh ke nilai default yang kita tetapkan di satu tempat. Kombinasi “object konfigurasi + ??” menjadi pola bawaan yang lazim di library JavaScript modern, dan membiasakannya sekarang memangkas biaya belajar saat membaca kode orang lain.
Menolak Data di Pintu Masuk #
Nilai default menjawab “bagaimana bila opsi tidak diisi” — tetapi bukan jawaban untuk “bagaimana bila data wajibnya salah”. Constructor boleh menolak: bila parameter wajib ternyata kosong, lemparkan galat di sana juga, sehingga instance yang cacat tidak pernah sempat ada. Alatnya sudah kita punya dari section Error Handling:
class PesananTervalidasi {
constructor(namaMinuman) {
if (typeof namaMinuman !== "string" || namaMinuman.trim() === "") {
throw new RangeError("Nama minuman wajib diisi");
}
this.namaMinuman = namaMinuman.trim();
}
}
try {
new PesananTervalidasi(" ");
} catch (err) {
console.log(`${err.name}: ${err.message}`);
}Output yang Dihasilkan:
RangeError: Nama minuman wajib diisiConstructor melempar, try...catch menangkap, dan tidak ada instance PesananTervalidasi yang tercipta dari input kosong. Pola “validasi di pintu masuk” ini menjadi semakin bernilai setelah private field dipakai: semua perubahan data berikutnya sudah terjaga, sehingga validasi awal yang benar menjamin seluruh umur instance.
Perhatikan pembagian perannya. Field bawaan cocok untuk nilai yang sama bagi semua instance; parameter konfigurasi cocok untuk nilai yang berbeda-beda. Keduanya bertemu di constructor, tempat this siap diisi.
Data kini terpasang rapi per instance. Pertanyaan berikutnya muncul ketika dua class hampir identik — kartu member biasa dan kartu siswa, misalnya, yang aturan poinya sedikit berbeda. Menyalin seluruh class hanya untuk mengubah satu method bukanlah jalan yang baik; chapter berikutnya memperkenalkan extends dan super.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
pesanan-kopi.js
Unduh berkas// pesanan-kopi.js
// constructor(): satu tempat memasang data awal tiap instance.
class PesananKopi {
poinDasar = 10;
constructor(namaMinuman, konfigurasi = {}) {
this.namaMinuman = namaMinuman;
this.suhu = konfigurasi.suhu ?? "Panas";
this.ukuran = konfigurasi.ukuran ?? "Reguler";
this.pakaiGula = konfigurasi.pakaiGula ?? false;
this.poin = this.poinDasar;
if (this.ukuran === "Grande") {
this.poin += 5;
}
}
struk() {
return `${this.namaMinuman} (${this.suhu}, ${this.ukuran})`;
}
}
class NotaKosong {
nomor = "NT-001";
terbayar = false;
}
const pesananBudi = new PesananKopi("Kopi Susu Gula Aren");
const pesananSiti = new PesananKopi("Americano", {
suhu: "Dingin",
ukuran: "Grande",
pakaiGula: true,
});
console.log(pesananBudi.struk());
console.log(pesananBudi.poin);
console.log(pesananSiti.struk());
console.log(pesananSiti.poin);
console.log(pesananSiti.pakaiGula);
const nota = new NotaKosong();
console.log(nota.nomor);
console.log(nota.terbayar);