Kenapa ini penting
Membuat satu object dengan literal {} itu mudah. Tapi bagaimana kalau kamu butuh 100 object produk, 50 object karyawan, atau ribuan object pesanan yang bentuknya sama dan punya method yang sama? Menulis ulang literal satu per satu itu melelahkan dan rawan salah ketik. Lebih parah lagi, kalau suatu hari method-nya perlu diubah, kamu harus mengubah di banyak tempat.
Programmer menyelesaikan masalah ini dengan pola (pattern) pembuatan object: cetakan yang bisa dipakai berulang. Di modul ini kamu belajar empat pola dasar: factory function, constructor function, module pattern, dan komposisi object. Pola-pola ini adalah fondasi dari class (yang akan kamu pelajari lebih dalam nanti) dan juga dipakai luas di library JavaScript. Memahami pola ini membuatmu bisa memilih struktur yang tepat untuk aplikasi yang terus tumbuh.
Konsep inti
Factory function
Factory function adalah function biasa yang mengembalikan object baru. Analogi: mesin cetak kue. Kamu masukkan bahan (parameter), keluar kue (object) dengan bentuk yang sama.
Kelebihan: sederhana, tidak perlu new, tidak ada urusan this yang membingungkan kalau kamu memakai closure. Kekurangan: kalau method ditulis di dalam factory, setiap object mendapat salinan function sendiri, sehingga boros memori untuk object dalam jumlah sangat banyak.
Closure untuk data privat
Closure adalah kemampuan function "mengingat" variabel dari lingkup tempat ia dibuat, bahkan setelah function luarnya selesai dijalankan. Dengan closure, kamu bisa membuat data yang tidak bisa diakses langsung dari luar, hanya lewat method yang kamu sediakan. Ini disebut enkapsulasi.
Constructor function dan prototype
Constructor function adalah function yang dipanggil dengan new. Konvensinya diawali huruf kapital. Saat new Produk(...) dipanggil, JavaScript:
- membuat object kosong baru,
- menghubungkan object itu ke
Produk.prototype, - menjalankan function dengan
this= object baru, - mengembalikan object tersebut.
Method yang ditaruh di Produk.prototype dibagi bersama oleh semua object yang dibuat, jadi hemat memori. Sintaks class yang modern sebenarnya bekerja dengan mekanisme prototype yang sama, hanya tulisannya lebih rapi.
Module pattern
Module pattern memakai function yang langsung dijalankan (IIFE, Immediately Invoked Function Expression) atau sekadar closure untuk membuat satu object dengan data privat dan API publik. Cocok untuk hal yang cukup ada satu, misalnya keranjang belanja aktif atau logger aplikasi. Di proyek modern, peran ini sering digantikan oleh ES module (file dengan export/import), tapi pola closure-nya tetap penting dipahami.
Komposisi object
Daripada membuat pohon warisan yang panjang ("Kucing turunan Mamalia turunan Hewan"), komposisi menyusun object dari potongan kemampuan kecil: bisaJalan, bisaBerenang, bisaTerbang. Bebek tinggal mengambil ketiganya, ikan mengambil berenang saja. Prinsip ini sering diringkas sebagai "favor composition over inheritance": lebih fleksibel dan tidak terkunci pada hierarki yang kaku.
| Pola | Cara buat | Data privat | Method dibagi bersama |
|---|---|---|---|
| Factory | buatX() |
bisa (closure) | tidak (kecuali sengaja) |
| Constructor | new X() |
tidak secara bawaan | ya (prototype) |
| Module | IIFE/closure sekali jalan | ya | hanya satu instance |
| Komposisi | gabung beberapa factory kecil | bisa | tergantung potongan |
Contoh kode
"use strict";
// 1) Factory function
function buatProduk(nama, harga) {
return {
nama,
harga,
labelHarga() {
return `${nama}: Rp${harga.toLocaleString("id-ID")}`;
},
};
}
const p1 = buatProduk("Sepatu", 350000);
const p2 = buatProduk("Tas", 275000);
console.log(p1.labelHarga()); // → Sepatu: Rp350.000
console.log(p1.labelHarga === p2.labelHarga); // → false (setiap object punya salinan method)
// 2) Factory dengan data privat lewat closure
function buatDompet(saldoAwal) {
let saldo = saldoAwal; // tidak bisa diakses langsung dari luar
return {
bayar(jumlah) {
if (jumlah > saldo) return "Saldo tidak cukup";
saldo -= jumlah;
return `Sisa saldo: ${saldo}`;
},
cekSaldo: () => saldo,
};
}
const dompet = buatDompet(100000);
console.log(dompet.bayar(30000)); // → Sisa saldo: 70000
console.log(dompet.saldo); // → undefined (privat)
console.log(dompet.cekSaldo()); // → 70000
// 3) Constructor function + prototype
function Karyawan(nama, gaji) {
this.nama = nama;
this.gaji = gaji;
}
Karyawan.prototype.naikGaji = function (persen) {
this.gaji += (this.gaji * persen) / 100;
return this.gaji;
};
const k1 = new Karyawan("Tono", 5000000);
const k2 = new Karyawan("Siti", 6000000);
console.log(k1.naikGaji(10)); // → 5500000
console.log(k1.naikGaji === k2.naikGaji); // → true (method dibagi lewat prototype)
console.log(k1 instanceof Karyawan); // → true
// 4) Module pattern (IIFE)
const keranjang = (function () {
const items = []; // privat
return {
tambah(nama, harga) {
items.push({ nama, harga });
return items.length;
},
total() {
return items.reduce((jumlah, item) => jumlah + item.harga, 0);
},
};
})();
keranjang.tambah("Kaos", 80000);
keranjang.tambah("Topi", 45000);
console.log(keranjang.total()); // → 125000
console.log(keranjang.items); // → undefined
// 5) Komposisi: rakit object dari kemampuan kecil
const bisaJalan = (state) => ({
jalan: () => `${state.nama} berjalan`,
});
const bisaBerenang = (state) => ({
berenang: () => `${state.nama} berenang`,
});
const bisaTerbang = (state) => ({
terbang: () => `${state.nama} terbang`,
});
function buatBebek(nama) {
const state = { nama };
return { ...state, ...bisaJalan(state), ...bisaBerenang(state), ...bisaTerbang(state) };
}
function buatIkan(nama) {
const state = { nama };
return { ...state, ...bisaBerenang(state) };
}
const bebek = buatBebek("Donal");
const ikan = buatIkan("Nemo");
console.log(bebek.terbang()); // → Donal terbang
console.log(ikan.berenang()); // → Nemo berenang
console.log("terbang" in ikan); // → false
Kesalahan umum
- Lupa
newsaat memanggil constructor function. Di strict modethismenjadiundefineddan munculTypeError. Biasakan nama constructor berhuruf kapital supaya mudah dikenali, atau pakai factory/class. - Menaruh method di dalam constructor, bukan di prototype.
this.naikGaji = function () {}membuat salinan method per object. Taruh diKaryawan.prototype. - Mengganti
prototypedengan object baru lalu lupaconstructor.Karyawan.prototype = { ... }menghapus properticonstructor. Lebih aman menambah method satu per satu. - Mengira data di closure ikut tersalin saat spread. Spread hanya menyalin properti, bukan variabel privat; method hasil spread tetap merujuk ke closure yang sama.
- Membuat hierarki warisan panjang untuk semua hal. Kalau kemampuan bisa dicampur-campur, komposisi lebih fleksibel daripada pewarisan bertingkat.
Latihan
- Mudah, Buat factory
buatSiswa(nama, kelas)yang mengembalikan object dengan methodperkenalan()berbunyi"Saya <nama> dari kelas <kelas>". - Sedang, Buat constructor
Lingkaran(r)dengan method di prototypeluas()yang mengembalikan luas dibulatkan dua desimal (pakaiMath.PI). Buktikan method-nya dibagi bersama oleh dua object. - Menantang, Buat factory
buatCounter()dengan data privatnilaidan methodnaik(),turun(),reset(), danlihat().turun()tidak boleh membuat nilai di bawah 0.
Lihat solusi
"use strict";
// 1) Mudah
function buatSiswa(nama, kelas) {
return {
nama,
kelas,
perkenalan() {
return `Saya ${nama} dari kelas ${kelas}`;
},
};
}
console.log(buatSiswa("Ari", "7B").perkenalan()); // → Saya Ari dari kelas 7B
// 2) Sedang
function Lingkaran(r) {
this.r = r;
}
Lingkaran.prototype.luas = function () {
return Number((Math.PI * this.r ** 2).toFixed(2));
};
const a = new Lingkaran(7);
const b = new Lingkaran(1);
console.log(a.luas(), b.luas()); // → 153.94 3.14
console.log(a.luas === b.luas); // → true
// 3) Menantang
function buatCounter() {
let nilai = 0;
return {
naik: () => ++nilai,
turun: () => (nilai > 0 ? --nilai : nilai),
reset: () => (nilai = 0),
lihat: () => nilai,
};
}
const c = buatCounter();
c.naik(); c.naik(); c.turun(); c.turun(); c.turun();
console.log(c.lihat()); // → 0
Ringkasan
- Factory function mengembalikan object baru; sederhana dan cocok dipadukan dengan closure untuk data privat.
- Constructor function dipanggil dengan
new; method diprototypedibagi bersama sehingga hemat memori. - Module pattern membuat satu object dengan data privat dan API publik memakai closure atau IIFE.
- Komposisi menyusun object dari potongan kemampuan kecil dan lebih fleksibel daripada hierarki pewarisan panjang.
- Sintaks
classmodern dibangun di atas mekanisme prototype yang sama.