Bab 8: Class
8.1 Sintaks Dasar Class
Kalau constructor function itu kayak resep masakan yang ditulis di kertas, maka class itu kayak buku resep yang rapi — isinya sama, tapi lebih terstruktur, lebih mudah dibaca, dan punya aturan tambahan yang mencegah kesalahan.
Sintaks
class User {
constructor(nama) {
this.nama = nama;
}
sapaan() {
console.log(`Halo, ${this.nama}!`);
}
}
const user = new User("Budi");
user.sapaan(); // "Halo, Budi!"Apa Sebenarnya Class Itu?
Class di JavaScript bukan konsep baru — dia tetap function di balik layar:
console.log(typeof User); // "function"
// Class = constructor function + prototype methods
console.log(User === User.prototype.constructor); // true
console.log(User.prototype.sapaan); // function sapaan() {...}Bukan Sekadar Syntactic Sugar
Meski mirip constructor function, class punya perbedaan penting:
- Harus pakai
new— tidak bisa dipanggil tanpanew - Method non-enumerable — tidak muncul di
for..in - Selalu strict mode — kode di dalam class otomatis strict
Class Expression
// Seperti function expression
const User = class {
sapaan() {
console.log("Halo!");
}
};
// Named class expression (nama hanya terlihat di dalam class)
const User2 = class MyClass {
info() {
console.log(MyClass); // Works di dalam
}
};
// console.log(MyClass); // Error! Tidak terlihat di luarGetter/Setter di Class
class User {
constructor(nama) {
this.nama = nama; // Memanggil setter!
}
get nama() {
return this._nama;
}
set nama(value) {
if (value.length < 3) {
console.log("Nama terlalu pendek!");
return;
}
this._nama = value;
}
}
const user = new User("Budi");
console.log(user.nama); // "Budi"
const user2 = new User("Bu"); // "Nama terlalu pendek!"Class Fields (Properti Langsung)
class Button {
// Class field — disimpan di objek, BUKAN di prototype
label = "Click me";
// Arrow function sebagai class field — this selalu terikat!
click = () => {
console.log(`Clicked: ${this.label}`);
}
}
const btn = new Button();
setTimeout(btn.click, 1000); // "Clicked: Click me" — this benar!Kenapa ini penting? Kalau pakai method biasa dan di-pass ke
setTimeout,thisakan hilang. Class field dengan arrow function menyelesaikan masalah ini.
-
Tidak ada koma antar method! (Beda dengan object literal)
jsclass User { method1() {} // Tidak ada koma! method2() {} } -
Class field ada di objek, bukan prototype:
jsclass User { nama = "Budi"; } const u = new User(); console.log(u.nama); // "Budi" console.log(User.prototype.nama); // undefined!
Buat class Jam yang:
- Constructor menerima
{ template }(misal"h:m:s") - Punya method
render()yang print waktu sekarang sesuai template - Punya method
start()yang mulai print setiap detik - Punya method
stop()yang berhenti
8.2 Class Inheritance (Pewarisan)
Kamu punya class Hewan yang bisa jalan dan berhenti. Sekarang kamu mau bikin Kelinci yang bisa semua yang Hewan bisa, PLUS bisa sembunyi. Daripada copy-paste semua kode, kamu extend (perpanjang) dari Hewan.
Keyword extends
class Hewan {
constructor(nama) {
this.kecepatan = 0;
this.nama = nama;
}
lari(kecepatan) {
this.kecepatan = kecepatan;
console.log(`${this.nama} lari dengan kecepatan ${this.kecepatan}`);
}
berhenti() {
this.kecepatan = 0;
console.log(`${this.nama} berhenti.`);
}
}
class Kelinci extends Hewan {
sembunyi() {
console.log(`${this.nama} sembunyi!`);
}
}
const kelinci = new Kelinci("Kelinci Putih");
kelinci.lari(5); // "Kelinci Putih lari dengan kecepatan 5"
kelinci.sembunyi(); // "Kelinci Putih sembunyi!"Override Method + super
class Kelinci extends Hewan {
sembunyi() {
console.log(`${this.nama} sembunyi!`);
}
berhenti() {
super.berhenti(); // Panggil method parent
this.sembunyi(); // Lalu tambah behavior
}
}
const k = new Kelinci("Putih");
k.lari(5);
k.berhenti();
// "Putih berhenti."
// "Putih sembunyi!"Override Constructor — WAJIB super()
class Kelinci extends Hewan {
constructor(nama, panjangTelinga) {
super(nama); // WAJIB! Panggil constructor parent DULU
this.panjangTelinga = panjangTelinga;
}
}
const k = new Kelinci("Putih", 10);
console.log(k.nama); // "Putih"
console.log(k.panjangTelinga); // 10Kenapa wajib
super()? Di class turunan, objekthisbelum ada sampai parent constructor membuatnya. Kalau kamu aksesthissebelumsuper(), akan error!
-
Lupa
super()di constructor = Error!jsclass Anak extends Ortu { constructor() { // this.x = 1; // ERROR: this belum ada! super(); // Harus ini dulu this.x = 1; // Baru boleh pakai this } } -
Arrow function tidak punya
supersendiri. Dia ambil dari fungsi luar — ini biasanya yang kita mau:jsclass Kelinci extends Hewan { berhenti() { setTimeout(() => super.berhenti(), 1000); // OK! } } -
Class field override punya perilaku aneh. Parent constructor selalu pakai field miliknya sendiri, bukan yang di-override anak. Solusi: pakai method, bukan field.
Buat class Kendaraan dan Mobil:
Kendaraanpunya constructor(nama, kecepatan)dan methodinfo()yang return"[nama] - [kecepatan] km/h"MobilextendsKendaraan, tambah parametermerkdi constructorMobiloverrideinfo()untuk return"[merk] [nama] - [kecepatan] km/h"- Pastikan pakai
superdengan benar
8.3 Static Properties & Methods
Method biasa itu milik setiap objek (setiap karyawan bisa kerja()). Static method itu milik class itu sendiri (HRD bisa rekrut() — bukan karyawan individual yang rekrut).
Sintaks
class Artikel {
constructor(judul, tanggal) {
this.judul = judul;
this.tanggal = tanggal;
}
// Static method — dipanggil di class, bukan di instance
static bandingkan(a, b) {
return a.tanggal - b.tanggal;
}
// Static property
static penerbit = "JavaScript.info";
}
const artikels = [
new Artikel("HTML", new Date(2024, 1, 1)),
new Artikel("CSS", new Date(2024, 0, 1)),
new Artikel("JS", new Date(2024, 11, 1))
];
artikels.sort(Artikel.bandingkan);
console.log(artikels[0].judul); // "CSS" (paling lama)
console.log(Artikel.penerbit); // "JavaScript.info"Factory Method (Pola Umum)
class Artikel {
constructor(judul, tanggal) {
this.judul = judul;
this.tanggal = tanggal;
}
static buatHariIni() {
return new this("Digest Hari Ini", new Date());
}
}
const artikel = Artikel.buatHariIni();
console.log(artikel.judul); // "Digest Hari Ini"Static Diwariskan!
class Hewan {
static planet = "Bumi";
}
class Kelinci extends Hewan {}
console.log(Kelinci.planet); // "Bumi" — diwariskan!Static method tidak bisa dipanggil dari instance!
const a = new Artikel("Test", new Date());
a.bandingkan(...); // ERROR! bandingkan ada di Artikel, bukan di instanceBuat class User dengan:
- Constructor
(nama, umur) - Static property
jumlahUseryang bertambah setiap kali user baru dibuat - Static method
info()yang return"Total user: [jumlahUser]"
8.4 Private & Protected Properties
Mesin kopi: dari luar kamu cuma lihat tombol dan display (public). Di dalam ada boiler, pemanas, dll (private). Kamu tidak perlu (dan tidak boleh) utak-atik bagian dalam — cukup tekan tombol.
Protected (Konvensi _)
class MesinKopi {
_jumlahAir = 0; // Protected: jangan akses langsung!
set jumlahAir(value) {
if (value < 0) value = 0;
this._jumlahAir = value;
}
get jumlahAir() {
return this._jumlahAir;
}
}
const mesin = new MesinKopi();
mesin.jumlahAir = -10;
console.log(mesin.jumlahAir); // 0 (divalidasi!)
_itu konvensi, bukan aturan bahasa. Programmer sepakat: "jangan akses properti_dari luar."
Private (Bahasa: #)
class MesinKopi {
#batasAir = 200; // Private: benar-benar tidak bisa diakses dari luar!
#cekAir(value) {
if (value < 0) return 0;
if (value > this.#batasAir) return this.#batasAir;
return value;
}
setAir(value) {
this.#batasAir = this.#cekAir(value);
}
}
const mesin = new MesinKopi();
// mesin.#batasAir = 1000; // ERROR! Tidak bisa akses dari luar
// mesin.#cekAir(100); // ERROR!Perbedaan Protected vs Private
Protected (_) | Private (#) | |
|---|---|---|
| Enforced oleh bahasa? | ❌ Konvensi saja | ✅ Error kalau diakses dari luar |
| Bisa diakses class turunan? | ✅ Ya | ❌ Tidak |
| Kapan pakai? | Kebanyakan kasus | Kalau benar-benar harus disembunyikan |
-
Private field tidak bisa diakses via
this[name]:jsthis["#field"]; // Tidak works! Ini bukan cara akses private -
Private tidak diwariskan. Class anak tidak bisa akses
#fieldmilik parent — harus lewat getter/setter public.
Buat class BankAccount dengan:
- Private field
#saldo(default 0) - Method
setor(jumlah)— tambah saldo (tolak kalau jumlah negatif) - Method
tarik(jumlah)— kurangi saldo (tolak kalau saldo tidak cukup) - Getter
saldoyang return saldo saat ini - Pastikan
#saldotidak bisa diakses langsung dari luar
8.5 Extending Built-in Classes
Konsep
Kamu bisa extend class bawaan seperti Array:
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
}
const arr = new PowerArray(1, 2, 5, 10, 50);
console.log(arr.isEmpty()); // false
const filtered = arr.filter(item => item >= 10);
console.log(filtered); // [10, 50]
console.log(filtered.isEmpty()); // false — masih PowerArray!Keren: Method bawaan seperti
filter,mapotomatis return instance dari class turunan (bukan Array biasa)! Ini karena mereka pakaiconstructordari objek.
Symbol.species — Kontrol Return Type
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
// Paksa filter/map return Array biasa, bukan PowerArray
static get [Symbol.species]() {
return Array;
}
}
const arr = new PowerArray(1, 2, 5);
const filtered = arr.filter(item => item >= 2);
// filtered sekarang Array biasa, bukan PowerArray
// filtered.isEmpty(); // ERROR!Built-in class tidak mewariskan static method! Array.isArray ada, tapi kalau kamu extend Array, MyArray.isArray tetap works karena diwariskan lewat prototype chain class. Tapi Date dan Object tidak saling mewariskan static.
Buat class SuperString yang extends String dan punya method isPalindrome() yang cek apakah string itu palindrom.
8.6 Pengecekan Class: instanceof
Cara Pakai
class Kelinci {}
const k = new Kelinci();
console.log(k instanceof Kelinci); // true
console.log(k instanceof Object); // true (semua objek inherit Object)Works dengan Constructor Function Juga
function Kucing() {}
const kucing = new Kucing();
console.log(kucing instanceof Kucing); // trueWorks dengan Built-in
console.log([1, 2] instanceof Array); // true
console.log([1, 2] instanceof Object); // true
console.log("hi" instanceof String); // false! (primitif, bukan objek)Cara Kerja di Balik Layar
instanceof cek apakah Class.prototype ada di rantai prototype objek:
obj.__proto__ === Class.prototype?
obj.__proto__.__proto__ === Class.prototype?
// ... dan seterusnya
Object.prototype.toString — "typeof on Steroids"
const toString = Object.prototype.toString;
console.log(toString.call(123)); // [object Number]
console.log(toString.call("hi")); // [object String]
console.log(toString.call(true)); // [object Boolean]
console.log(toString.call(null)); // [object Null]
console.log(toString.call([])); // [object Array]
console.log(toString.call(function(){})); // [object Function]instanceof cek prototype, bukan constructor! Kalau kamu ganti prototype setelah objek dibuat, instanceof bisa return false:
function Kelinci() {}
const k = new Kelinci();
Kelinci.prototype = {}; // Ganti prototype
console.log(k instanceof Kelinci); // false!Buat function cekTipe(value) yang return string deskriptif:
- Array → "array"
- null → "null"
- Object biasa → "object"
- Function → "function"
- Number → "number"
- String → "string"
(Hint: pakai Object.prototype.toString)
8.7 Mixin
JavaScript hanya boleh inherit dari SATU class. Tapi kadang kamu butuh kemampuan dari banyak sumber. Mixin itu kayak "plugin" — kamu copy method dari objek lain ke prototype class-mu.
Contoh Dasar
const sayHiMixin = {
sayHi() {
console.log(`Halo, ${this.nama}!`);
},
sayBye() {
console.log(`Dadah, ${this.nama}!`);
}
};
class User {
constructor(nama) {
this.nama = nama;
}
}
// Copy semua method dari mixin ke prototype User
Object.assign(User.prototype, sayHiMixin);
const user = new User("Budi");
user.sayHi(); // "Halo, Budi!"
user.sayBye(); // "Dadah, Budi!"Mixin + Inheritance (Bisa Dua-duanya!)
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
// User inherit dari Person DAN punya method dari mixinEventMixin (Contoh Nyata)
const eventMixin = {
on(eventName, handler) {
if (!this._handlers) this._handlers = {};
if (!this._handlers[eventName]) this._handlers[eventName] = [];
this._handlers[eventName].push(handler);
},
off(eventName, handler) {
const handlers = this._handlers?.[eventName];
if (!handlers) return;
const index = handlers.indexOf(handler);
if (index !== -1) handlers.splice(index, 1);
},
trigger(eventName, ...args) {
if (!this._handlers?.[eventName]) return;
this._handlers[eventName].forEach(h => h.apply(this, args));
}
};
// Pakai di class mana saja:
class Menu {
pilih(value) {
this.trigger("select", value);
}
}
Object.assign(Menu.prototype, eventMixin);
const menu = new Menu();
menu.on("select", value => console.log(`Dipilih: ${value}`));
menu.pilih("Nasi Goreng"); // "Dipilih: Nasi Goreng"- Nama method bisa bentrok! Kalau dua mixin punya method dengan nama sama, yang terakhir di-assign menang.
- Mixin bukan inheritance.
instanceoftidak akan mendeteksi mixin.
Buat loggerMixin yang punya:
log(pesan)— print"[NamaClass] pesan"warn(pesan)— print"⚠️ [NamaClass] pesan"error(pesan)— print"❌ [NamaClass] pesan"
Lalu terapkan ke class Server dan Database. Pastikan nama class-nya benar di output.
(Hint: this.constructor.name untuk dapat nama class)
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.