Kafe Budi mulai ramai dan daftar membernya bertambah setiap minggu. Dengan cara lama, tiap member ia tulis sebagai object literal baru — nama, poin, dan method cekPoin disalin satu per satu. Setelah member kesepuluh, Budi mulai kesulitan: satu method diubah di kartu A, kartu B sampai kartu J masih memakai versi lama. Menyalin object secara manual tidak bisa diandalkan lagi.
JavaScript memberi jawaban klasik untuk kebutuhan ini: constructor function. Satu function yang dipanggil dengan operator new mampu menghasilkan object sejumlah yang kita mau, lengkap dengan datanya. Chapter ini membongkar apa yang sebenarnya dilakukan new, lalu menunjukkan satu masalah yang selalu mengikutinya — method yang tergandakan — beserta solusinya yang menyatukan kembali semua yang sudah kita pelajari tentang prototype.
Function yang Dipanggil dengan new #
Aturan dasarnya: setiap function biasa — tidak ada kata kunci khusus — bisa dipanggil dengan new di depannya. Saat itu terjadi, engine JavaScript menjalankan empat langkah di balik layar:
- Membuat object kosong yang baru.
- Menautkan [[Prototype]] object baru ke properti
.prototypemilik function. - Memanggil function tersebut dengan
thismenunjuk ke object baru. - Mengembalikan object baru itu ke pemanggil — tanpa perlu
returneksplisit.
Konsekuensi langkah kedua pantau baik: semua object hasil new bertaut ke satu prototype yang sama, yaitu KartuMember.prototype. Tautan inilah yang kelak kita pakai untuk menyelesaikan masalah penggandaan method.
Satu Function, Banyak Kartu #
Berkas berikut mendemonstrasikan kedua sisi pola ini: method yang ditulis di dalam body function (tergandakan per instance) dan method yang ditempel ke prototype (dibagi bersama):
// kartu-member.js
// Pola constructor function: new + this, lalu method yang tidak perlu digandakan.
function KartuMember(nama, poin) {
this.nama = nama;
this.poin = poin;
this.cekPoin = function () {
return `${this.nama} punya ${this.poin} poin`;
};
}
KartuMember.prototype.ubahPoin = function (jumlah) {
this.poin += jumlah;
};
const kartuBudi = new KartuMember("Budi", 120);
const kartuSiti = new KartuMember("Siti", 85);
console.log(kartuBudi.cekPoin());
kartuSiti.ubahPoin(15);
console.log(kartuSiti.poin);
console.log(kartuSiti.cekPoin());
console.log(kartuBudi.cekPoin === kartuSiti.cekPoin);
console.log(kartuBudi.ubahPoin === kartuSiti.ubahPoin);
console.log(kartuBudi instanceof KartuMember);
console.log(Object.getPrototypeOf(kartuBudi) === KartuMember.prototype);Output yang Dihasilkan:
Budi punya 120 poin
100
Siti punya 100 poin
false
true
true
trueMari kita bedah baris pentingnya:
- Baris 4–10 (
function KartuMember): function biasa, tetapi parameternamadanpoinditempel kethis. Saat dipanggil dengannew,thismenunjuk object baru yang sedang dirakit. - Baris 7–9 (
this.cekPoin = function ...): setiap pemanggilannewmembuat functioncekPoinyang benar-benar baru — inilah sumber penggandaan yang segera kita buktikan. - Baris 12–14 (
KartuMember.prototype.ubahPoin = ...): method ditempel sekali saja ke prototype yang dipakai bersama, bukan ke masing-masing instance. - Baris 16–17 (
new KartuMember(...)): dua pemanggilan menghasilkan dua object terpisah, masing-masing dengan data sendiri. - Baris 20 (
kartuSiti.ubahPoin(15)): method tidak ada dikartuSiti, ditemukan di prototype lewat rantai chapter kedua, dan menambah poin Siti menjadi100. - Baris 24 (
kartuBudi.cekPoin === kartuSiti.cekPoin): hasilnyafalse— dua function terpisah, dua salinan yang menempati memori masing-masing. - Baris 25 (
kartuBudi.ubahPoin === kartuSiti.ubahPoin): hasilnyatrue— kedua kartu berbagi satu function dari prototype. - Baris 27–28 (pemeriksaan tautan):
instanceofmenjawabtrue, dangetPrototypeOfmembuktikan [[Prototype]] kartu Budi memang menunjuk keKartuMember.prototype— langkah kedua dari empat langkahnewtadi.
Masalah: Method Tergandakan #
Angka false di baris 24 adalah temuan penting. Setiap pemanggilan new KartuMember(...) mengeksekusi ulang this.cekPoin = function ..., sehingga sepuluh member berarti sepuluh function cekPoin yang isinya identik. Belum ada yang rusak — program tetap benar — tetapi penggunaan memori membesar tanpa manfaat, dan “satu logika, banyak salinan” memicu perbaikan yang tertinggal di sebagian salinan bila penugasan ini diubah langsung per kartu.
Aturan praktis yang bisa kita pegang: data yang berbeda antar instance (nama, poin) memang tempatnya di this, sedangkan perilaku yang sama untuk semua instance lebih baik disimpan di prototype.
Solusi: Satu Method di Prototype #
Method ubahPoin menunjukkan pola yang benar. Ia ditulis sekali di KartuMember.prototype, dan kedua kartu memakai function yang sama — dibuktikan true di baris 25. Ketika kartuSiti.ubahPoin(15) dijalankan, this di dalam method menunjuk ke kartuSiti, sehingga poin yang bertambah hanya milik Siti; data tetap terpisah, perilaku tetap bersama.
Pola constructor function menyisakan satu kebiasaan yang perlu dijaga: tanda new wajib ditulis, dan tautan prototype dirakit dengan tangan lewat penugasan ke .prototype. Sintaks class di chapter berikutnya mengotomatiskan keduanya.
new yang Terlewat: this Menunjuk ke Salah Tempat #
Aturan “selalu awali dengan new” bukan sekadar gaya — tanpa new, semua langkah di awal chapter itu tidak terjadi, dan function berjalan sebagai pemanggilan biasa. this tidak menunjuk ke object baru; dalam berkas .js biasa tanpa mode ketat, this menunjuk ke globalThis:
function KartuMember(nama, poin) {
this.nama = nama;
this.poin = poin;
}
const kartu = KartuMember("Budi", 120);
console.log(kartu);
console.log(globalThis.nama);Output yang Dihasilkan:
undefined
BudiDua baris keluaran itu merangkum musibahnya. Pemanggilan tanpa new mengembalikan undefined — langkah keempat new (mengembalikan object baru) tidak pernah terjadi — dan this.nama = nama justru menempelkan data ke globalThis. Kartu “Budi” tidak pernah tercipta, tetapi variabel nama global tercemar tanpa pesan galat apa pun. Karena tidak ada yang menolak, jebakan ini baru terasa jauh setelahnya; mode ketat yang kita pelajari di section Dasar Pemrograman setidaknya membuat penugasan tanpa deklarasi berhenti dengan ReferenceError, memberi petunjuk lebih cepat.
Batasan: Hanya function Biasa yang Bisa Di-new #
Operator new tidak menerima semua jenis function. Function panah — meski bentuknya serupa — tidak punya mekanisme internal untuk menjadi constructor, sehingga parser meloloskan kodenya tetapi runtime menolak:
const BuatKartu = () => {};
try {
new BuatKartu();
} catch (err) {
console.log(`${err.name}: ${err.message}`);
}Output yang Dihasilkan:
TypeError: BuatKartu is not a constructorPesan BuatKartu is not a constructor menjelaskan dirinya sendiri: function panah tidak dirancang sebagai cetakan object, dan itu memang bukan perannya. Bila kita perlu memanggil dengan new, tulis dengan function biasa — atau lebih baik lagi, dengan class yang menjadi topik chapter berikutnya.
instanceof: Memeriksa Asal Instance #
Operator instanceof menjawab pertanyaan “apakah object ini dibuat dari function itu?” dengan menyusuri rantai prototype — mekanisme chapter kedua bekerja lagi di sini. Karena [[Prototype]] kartuBudi menunjuk ke KartuMember.prototype, ekspresi kartuBudi instanceof KartuMember bernilai true. Operator ini kembali muncul di chapter inheritance dengan hasil yang lebih menarik.
Di samping pemeriksaan, setiap instance juga membawa properti constructor — tautan balik ke function pembuatnya. Nilai kartuBudi.constructor === KartuMember menghasilkan true, dan properti ini berguna ketika kode hanya memegang instance tanpa tahu class asalnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
kartu-member.js
Unduh berkas// kartu-member.js
// Pola constructor function: new + this, lalu method yang tidak perlu digandakan.
function KartuMember(nama, poin) {
this.nama = nama;
this.poin = poin;
this.cekPoin = function () {
return `${this.nama} punya ${this.poin} poin`;
};
}
KartuMember.prototype.ubahPoin = function (jumlah) {
this.poin += jumlah;
};
const kartuBudi = new KartuMember("Budi", 120);
const kartuSiti = new KartuMember("Siti", 85);
console.log(kartuBudi.cekPoin());
kartuSiti.ubahPoin(15);
console.log(kartuSiti.poin);
console.log(kartuSiti.cekPoin());
console.log(kartuBudi.cekPoin === kartuSiti.cekPoin);
console.log(kartuBudi.ubahPoin === kartuSiti.ubahPoin);
console.log(kartuBudi instanceof KartuMember);
console.log(Object.getPrototypeOf(kartuBudi) === KartuMember.prototype);