Kenapa ini penting
Setelah paham prototype chain, pertanyaan berikutnya: bagaimana membangun hierarki yang rapi? Misalnya di aplikasi toko online kamu punya Pengguna, lalu Pembeli dan Penjual yang mewarisi kemampuan dasar Pengguna tetapi punya perilaku tambahan masing-masing. Inilah pewarisan (inheritance).
Namun pewarisan bukan satu-satunya alat, dan sering kali bukan yang terbaik. Programmer berpengalaman punya prinsip terkenal: "favor composition over inheritance", utamakan komposisi daripada pewarisan. Di modul ini kamu belajar tiga teknik: pewarisan via prototype (cara kerja asli di balik class extends), method override (anak mengganti perilaku orang tua), dan mixin serta komposisi (merakit kemampuan seperti memasang modul LEGO). Dengan begitu kamu bisa memilih alat yang tepat, bukan sekadar memaksa semuanya menjadi pohon keluarga.
Konsep inti
Pewarisan via prototype (cara manual)
Agar Pembeli mewarisi Pengguna, ada dua langkah:
- Warisi properti instance: panggil konstruktor induk dengan
thismilik anak,Pengguna.call(this, nama). - Warisi method: hubungkan prototype,
Pembeli.prototype = Object.create(Pengguna.prototype), lalu kembalikanconstructoryang benar.
Rantainya menjadi: instancePembeli → Pembeli.prototype → Pengguna.prototype → Object.prototype → null.
Kamu jarang menulis ini secara manual sekarang karena ada class extends (modul berikutnya). Tetapi sintaks class hanyalah "kemasan rapi" untuk mekanisme yang persis sama. Memahaminya membuat kamu tidak bingung saat membaca kode lama atau error yang menyebut prototype.
Method override
Jika anak punya method dengan nama yang sama dengan induk, method anak yang dipakai karena ditemukan lebih dulu di rantai. Kalau masih ingin memakai versi induk, panggil eksplisit: Pengguna.prototype.info.call(this) (di class cukup super.info()).
Mixin
Mixin adalah kumpulan method yang "ditempelkan" ke object atau prototype dengan Object.assign. Berguna saat beberapa kelas yang tidak berkerabat butuh kemampuan yang sama, misalnya kemampuan "bisa disimpan ke JSON" atau "bisa memancarkan event".
Object.assign(Produk.prototype, bisaDiserialisasi);
Komposisi vs pewarisan
| Pewarisan ("adalah") | Komposisi ("punya" / "bisa") |
|---|---|
Pembeli adalah Pengguna |
Robot punya kemampuan jalan dan bicara |
| Hierarki tetap, ditentukan di awal | Kemampuan dirakit fleksibel |
| Perubahan di induk merembet ke semua anak | Kemampuan independen, mudah diganti |
| Risiko hierarki terlalu dalam ("gorilla-banana problem") | Bisa jadi banyak potongan kecil |
Masalah klasik pewarisan: kamu membuat Burung dengan method terbang(), lalu muncul Penguin. Penguin adalah burung tetapi tidak bisa terbang. Dengan komposisi, kamu cukup merakit Penguin dari kemampuan berenang dan berjalan tanpa terbang.
Aturan praktis: pakai pewarisan jika hubungannya benar-benar "X adalah jenis khusus dari Y" dan hierarkinya dangkal (1-2 tingkat). Untuk berbagi kemampuan lintas jenis, pakai komposisi atau mixin.
Contoh kode
// ===== 1. Pewarisan via prototype (manual) =====
function Pengguna(nama, email) {
this.nama = nama;
this.email = email;
}
Pengguna.prototype.info = function () {
return `${this.nama} <${this.email}>`;
};
Pengguna.prototype.peran = function () {
return "pengguna";
};
function Penjual(nama, email, namaToko) {
Pengguna.call(this, nama, email); // warisi properti instance
this.namaToko = namaToko;
}
// warisi method
Penjual.prototype = Object.create(Pengguna.prototype);
Penjual.prototype.constructor = Penjual;
// override method peran
Penjual.prototype.peran = function () {
return "penjual";
};
// override sambil memanggil versi induk
Penjual.prototype.info = function () {
const dasar = Pengguna.prototype.info.call(this);
return `${dasar} | Toko: ${this.namaToko}`;
};
const s = new Penjual("Rudi", "rudi@toko.id", "Rudi Elektronik");
console.log(s.info()); // → Rudi <rudi@toko.id> | Toko: Rudi Elektronik
console.log(s.peran()); // → penjual
console.log(s instanceof Penjual, s instanceof Pengguna); // → true true
// ===== 2. Mixin =====
const bisaDiserialisasi = {
keJSON() { return JSON.stringify(this); },
};
const punyaTimestamp = {
tandaiDibuat() { this.dibuatPada = "2026-10-23"; return this; },
};
Object.assign(Penjual.prototype, bisaDiserialisasi, punyaTimestamp);
console.log(s.tandaiDibuat().keJSON());
// → {"nama":"Rudi","email":"rudi@toko.id","namaToko":"Rudi Elektronik","dibuatPada":"2026-10-23"}
// ===== 3. Komposisi: merakit kemampuan =====
const bisaJalan = (state) => ({
jalan: () => `${state.nama} berjalan dengan ${state.kaki} kaki`,
});
const bisaTerbang = (state) => ({
terbang: () => `${state.nama} terbang di ketinggian ${state.ketinggian} m`,
});
const bisaBerenang = (state) => ({
berenang: () => `${state.nama} berenang`,
});
function buatElang(nama) {
const state = { nama, kaki: 2, ketinggian: 300 };
return { ...bisaJalan(state), ...bisaTerbang(state) };
}
function buatPenguin(nama) {
const state = { nama, kaki: 2 };
return { ...bisaJalan(state), ...bisaBerenang(state) }; // tanpa terbang
}
const elang = buatElang("Garuda");
const penguin = buatPenguin("Pingu");
console.log(elang.terbang()); // → Garuda terbang di ketinggian 300 m
console.log(penguin.berenang()); // → Pingu berenang
console.log(typeof penguin.terbang); // → undefined (memang tidak punya)
// ===== 4. Komposisi object di dalam object (has-a) =====
function buatMesin(tenaga) {
return { nyalakan: () => `Mesin ${tenaga} HP menyala` };
}
function buatMobil(merk) {
const mesin = buatMesin(150); // mobil PUNYA mesin
return {
merk,
mulai() { return `${merk}: ${mesin.nyalakan()}`; },
};
}
console.log(buatMobil("Avanza").mulai()); // → Avanza: Mesin 150 HP menyala
Perhatikan pada bagian komposisi, state disimpan di closure. Tidak ada this, tidak ada prototype, dan setiap kemampuan bisa diuji sendiri-sendiri. Konsep closure yang sudah kamu pelajari menjadi sangat berguna di sini.
Kesalahan umum
- Lupa
Pengguna.call(this, ...). Method induk terwarisi, tetapi properti sepertinamatidak pernah diisi sehingga bernilaiundefined. - Menulis
Penjual.prototype = Pengguna.prototype. Ini bukan pewarisan, tapi berbagi object yang sama; menambah method diPenjualikut mengubahPengguna. PakaiObject.create. - Lupa mengembalikan
constructor. SetelahObject.create,Penjual.prototype.constructormenunjuk kePengguna. Set ulang agar tidak membingungkan. - Hierarki terlalu dalam. Lima tingkat pewarisan membuat perubahan kecil di atas merusak banyak hal di bawah. Pertimbangkan komposisi.
- Mixin yang bentrok nama. Dua mixin dengan method bernama sama saling menimpa tanpa peringatan (yang terakhir menang). Beri nama yang spesifik.
Latihan
- Mudah, Buat konstruktor
Bentuk(nama)dengan methoddeskripsi(). BuatPersegi(sisi)yang mewarisiBentukdan menambah methodluas(). - Sedang, Override
deskripsi()diPersegiagar memanggil versi induk lalu menambahkan" dengan luas X". - Menantang, Dengan komposisi, buat
buatKaryawan(nama)danbuatFreelancer(nama). Keduanya bisahitungGaji, tetapi hanya karyawan yangambilCuti. Buat kemampuan sebagai function terpisah.
Lihat solusi
// 1 & 2
function Bentuk(nama) { this.nama = nama; }
Bentuk.prototype.deskripsi = function () { return `Ini adalah ${this.nama}`; };
function Persegi(sisi) {
Bentuk.call(this, "persegi");
this.sisi = sisi;
}
Persegi.prototype = Object.create(Bentuk.prototype);
Persegi.prototype.constructor = Persegi;
Persegi.prototype.luas = function () { return this.sisi ** 2; };
Persegi.prototype.deskripsi = function () {
return `${Bentuk.prototype.deskripsi.call(this)} dengan luas ${this.luas()}`;
};
console.log(new Persegi(4).deskripsi()); // → Ini adalah persegi dengan luas 16
// 3
const bisaDigaji = (s) => ({ hitungGaji: () => s.tarif * s.jam });
const bisaCuti = (s) => ({
ambilCuti(hari) { s.sisaCuti -= hari; return `Sisa cuti ${s.nama}: ${s.sisaCuti}`; },
});
function buatKaryawan(nama) {
const s = { nama, tarif: 50000, jam: 160, sisaCuti: 12 };
return { nama, ...bisaDigaji(s), ...bisaCuti(s) };
}
function buatFreelancer(nama) {
const s = { nama, tarif: 120000, jam: 40 };
return { nama, ...bisaDigaji(s) };
}
const k = buatKaryawan("Tono"), f = buatFreelancer("Lia");
console.log(k.hitungGaji(), k.ambilCuti(2)); // → 8000000 Sisa cuti Tono: 10
console.log(f.hitungGaji(), typeof f.ambilCuti); // → 4800000 undefined
Ringkasan
- Pewarisan manual:
Induk.call(this, ...)untuk properti +Object.create(Induk.prototype)untuk method. - Method override terjadi otomatis karena method anak ditemukan lebih dulu di rantai.
- Mixin menempelkan kumpulan method ke banyak prototype dengan
Object.assign. - Komposisi merakit kemampuan dari function kecil, lebih fleksibel dari hierarki.
- Pakai pewarisan untuk hubungan "adalah" yang dangkal; komposisi untuk "punya"/"bisa".