Program kafe Budi kini punya class KartuMember yang sudah sehat: constructor mengisi nama dan poin, method poinDari menghitung poin per transaksi, dan method catatTransaksi mencatatnya. Masalah muncul ketika manajemen menambah jenis kartu siswa — identik dengan kartu member, hanya saja membawa properti status dan poinnya digandakan. Menyalin seluruh class lalu mengubah dua baris berarti dua tempat yang harus disinkronkan setiap ada perbaikan.
Untuk hubungan seperti ini — “kartu siswa adalah kartu member, dengan tambahan” — JavaScript menyediakan kata kunci extends. Satu class mewarisi seluruh anggota class lain, lalu mengganti bagian yang berbeda. Chapter ini menyusun keturunan pertama kita, membedah aturan super() yang ketat di constructor, dan menunjukkan cara menimpa method sambil tetap memanggil versi aslinya.
extends: Menautkan Dua Class #
Aturan dasarnya: class KartuSiswa extends KartuMember membuat KartuSiswa mewarisi semua anggota KartuMember. Di balik layar, engine menautkan rantai prototype dari chapter kedua satu tingkat lebih panjang: [[Prototype]] milik KartuSiswa.prototype menunjuk ke KartuMember.prototype.
Buktinya bisa diperiksa langsung:
class KartuMember {}
class KartuSiswa extends KartuMember {}
console.log(Object.getPrototypeOf(KartuSiswa.prototype) === KartuMember.prototype);Output yang Dihasilkan:
trueKarena tautan itu, pencarian properti dari sebuah instance siswa menyusuri dua prototype: KartuSiswa.prototype dulu, lalu KartuMember.prototype. Method yang tidak ditimpa — seperti catatTransaksi — ditemukan di tingkat kedua. Konsekuensi praktisnya: perbaikan yang dilakukan di class induk otomatis dirasakan semua keturunannya, tanpa salinan.
Yang ikut menular bukan hanya method instance. Anggota static dari chapter berikutnya juga diwariskan, karena object class keturunan sendiri bertaut ke object class induknya:
class KartuMember {
static batasPerpanjangan = "2030-01-01";
}
class KartuSiswa extends KartuMember {}
console.log(KartuSiswa.batasPerpanjangan);Output yang Dihasilkan:
2030-01-01KartuSiswa tidak mendeklarasikan apa pun, tetapi KartuSiswa.batasPerpanjangan tetap terbaca — pencarian menyusuri tautan antar object class, cerminan dari rantai yang bekerja pada instance.
super() Wajib Sebelum this #
Di class keturunan, constructor punya satu aturan yang paling sering menjebak pemula: sebelum menyentuh this dalam bentuk apa pun, constructor wajib memanggil super(...) — pemanggilan constructor class induk. Aturan ini bukan gaya, melainkan keharusan bahasa: instance belum sah digunakan sampai induknya selesai menyiapkan bagian yang diwariskan.
Berkas berikut sengaja melanggarnya — this.status ditulis sebelum super(nama). Sintaksnya sah, sehingga galat baru muncul saat eksekusi. Jika dijalankan, Node menampilkan:
class KartuMember {
constructor(nama) {
this.nama = nama;
}
}
class KartuSiswa extends KartuMember {
constructor(nama) {
this.status = "Siswa";
super(nama);
}
}
new KartuSiswa("Siti");ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorPesan panjang itu bermanfaat untuk dibaca pelan: constructor turunan mencoba mengakses this sebelum constructor induk dipanggil. Dalam kode nyata, urutan yang benar selalu sama — super(...) di baris pertama constructor keturunan, penugasan this setelahnya.
Satu Berkas: Induk, Keturunan, dan Pembuktian #
Berkas lengkap berikut merakit kedua class dan membandingkan perilakunya pada transaksi yang sama:
// kartu-siswa.js
// extends: mewarisi dari class lain dan menimpa sebagian perilakunya.
class KartuMember {
constructor(nama) {
this.nama = nama;
this.poin = 0;
}
poinDari(total) {
return Math.floor(total / 10000);
}
catatTransaksi(total) {
this.poin += this.poinDari(total);
return this.poin;
}
}
class KartuSiswa extends KartuMember {
constructor(nama) {
super(nama);
this.status = "Siswa";
}
poinDari(total) {
return super.poinDari(total) * 2;
}
}
const kartuBudi = new KartuMember("Budi");
const kartuSiti = new KartuSiswa("Siti");
console.log(kartuBudi.catatTransaksi(45000));
console.log(kartuSiti.catatTransaksi(45000));
console.log(kartuSiti.status);
console.log(kartuSiti instanceof KartuMember);
console.log(kartuSiti instanceof KartuSiswa);
console.log(kartuBudi instanceof KartuSiswa);Output yang Dihasilkan:
4
8
Siswa
true
true
falseMari kita bedah:
- Baris 4–18 (
class KartuMember): class induk.poinDarimenghitung satu poin per Rp10.000, dancatatTransaksimenambahkan hasilnya kepoin. - Baris 20 (
extends KartuMember): penautan keturunan —KartuSiswasejak awal memiliki semua anggota induknya. - Baris 21–24 (constructor keturunan):
super(nama)dipanggil di baris pertama, sehinggathis.namadanthis.pointerisi oleh constructor induk; setelah ituthis.status = "Siswa"menambah data khusus siswa. - Baris 26–28 (menimpa
poinDari): versi baru memanggilsuper.poinDari(total)— hasil induk — lalu menggandakannya. Kita tidak menyalin rumus induk, kita menyambungnya. - Baris 34 (
kartuBudi.catatTransaksi(45000)): Budi tanpa penimpaan;Math.floor(45000 / 10000)menghasilkan4. - Baris 35 (
kartuSiti.catatTransaksi(45000)): Siti memakaicatatTransaksiwarisan, yang di dalamnya memanggilpoinDariversi siswa — hasilnya8. Method warisan dan method penimpaan bekerja dalam satu alur. - Baris 38–39 (
instanceofdua arah):kartuSitiadalahKartuMembersekaligusKartuSiswa—instanceofmenyusuri rantai dan menemukan kedua prototype. - Baris 40 (
kartuBudi instanceof KartuSiswa): arah sebaliknyafalse— induk bukan keturunan; pewarisan hanya mengalir satu arah.
Keturunan Tanpa constructor #
Apakah setiap class keturunan wajib menulis super()? Tidak — bila constructor tidak ditulis sama sekali, engine menyediakannya: constructor bawaan keturunan berisi tepat satu pemanggilan super(...args) dengan seluruh argumen yang diteruskan apa adanya. Class anak lalu berperilaku seperti salinan tanpa tambahan data:
class KartuMember {
constructor(nama) {
this.nama = nama;
this.poin = 0;
}
}
class KartuAnak extends KartuMember {}
const anak = new KartuAnak("Rina");
console.log(anak.nama);
console.log(anak.poin);Output yang Dihasilkan:
Rina
0new KartuAnak("Rina") tanpa constructor apa pun tetap mengisi nama dan poin — constructor induk yang mengerjakannya, dipanggil oleh constructor bawaan keturunan. Aturan super() sebelum this baru berlaku ketika kita menulis constructor sendiri: pada titik itu kita memegang kendali, dan bersama kendali datang kewajiban memanggil induknya.
Rantai Keturunan Lebih dari Dua Tingkat #
extends boleh dilanjutkan: class yang sudah mewarisi boleh menjadi induk bagi class berikutnya. Rantai prototype tumbuh sepanjang keturunan, dan instanceof menyusurinya sampai ke puncak:
class A {
hallo() {
return "A";
}
}
class B extends A {}
class C extends B {}
const c = new C();
console.log(c.hallo());
console.log(c instanceof A);
console.log(c instanceof B);
console.log(c instanceof C);Output yang Dihasilkan:
A
true
true
trueInstance C menemukan hallo dua tingkat di atasnya, dan instanceof mengakui ketiga class sekaligus. Mekanismenya persis rantai dari chapter kedua — hanya tautannya kini dirakit oleh kata kunci extends, bukan Object.create.
Menimpa Method dan super.method() #
Contoh di atas menampilkan dua gaya penimpaan sekaligus. Ketika method keturunan sepenuhnya berbeda, badannya ditulis baru tanpa menyentuh induk. Ketika method keturunan hanya melengkapi, super.method() dipanggil dulu untuk mengambil perilaku induk, lalu hasilnya dimodifikasi — pola yang membuat rumus di satu tempat saja.
Nilai super.poinDari(total) dihitung persis seperti pemanggilan biasa, hanya pencariannya dimulai dari prototype induk. Tanpa kata super, this.poinDari(total) akan menemukan versi siswa lagi dan memanggil dirinya sendiri tanpa henti. Itulah alasan kata ini wajib hadir ketika kita ingin memanggil versi asli.
Sekali lagi, garis batas pewarisan perlu ditetapkan jelas. Yang diturunkan: method instance lewat prototype dan anggota static lewat tautan object class. Yang tidak ikut diturunkan: data instance — nama dan poin tidak “dimiliki” keturunan; ia terpasang karena constructor induk benar-benar dipanggil lewat super(). Inilah alasan lupa super() berdampak total: tanpa induk yang dijalankan, instance keturunan tidak mendapatkan penyiapan datanya sama sekali.
Kartu member kini bisa diwariskan, tetapi semua datanya masih terbuka: kode mana pun bebas menulis kartuSiti.poin = -999 dan tidak ada yang menghentikannya. Chapter berikutnya memperkenalkan private field — properti yang hanya bisa dijangkau dari dalam class-nya sendiri.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
kartu-siswa.js
Unduh berkas// kartu-siswa.js
// extends: mewarisi dari class lain dan menimpa sebagian perilakunya.
class KartuMember {
constructor(nama) {
this.nama = nama;
this.poin = 0;
}
poinDari(total) {
return Math.floor(total / 10000);
}
catatTransaksi(total) {
this.poin += this.poinDari(total);
return this.poin;
}
}
class KartuSiswa extends KartuMember {
constructor(nama) {
super(nama);
this.status = "Siswa";
}
poinDari(total) {
return super.poinDari(total) * 2;
}
}
const kartuBudi = new KartuMember("Budi");
const kartuSiti = new KartuSiswa("Siti");
console.log(kartuBudi.catatTransaksi(45000));
console.log(kartuSiti.catatTransaksi(45000));
console.log(kartuSiti.status);
console.log(kartuSiti instanceof KartuMember);
console.log(kartuSiti instanceof KartuSiswa);
console.log(kartuBudi instanceof KartuSiswa);