Kamu sudah tahu cara membuat objek dengan tangan — sekumpulan pasangan key-value yang dibungkus kurung kurawal. Untuk satu objek, cara itu sudah pas. Tapi gimana kalau kamu butuh lima puluh objek yang bentuknya sama dan perilakunya sama? Daftar pengguna, sekumpulan produk, atau pasukan musuh dalam sebuah game — masing-masing punya field yang sama dan deretan kemampuan yang sama persis. Menulis satu per satu sambil menyalin-tempel method-nya tiap kali jelas melelahkan dan gampang salah.
Di sinilah class masuk sebagai solusinya. Anggap saja class itu sebuah cetak biru: kamu menjelaskan bentuk dan perilaku objek sekali saja, lalu kamu bisa mencetak objek dari cetak biru itu sebanyak yang kamu mau. Tiap objek yang kamu buat dari sebuah class disebut instance. Class adalah perkakas utama JavaScript untuk pemrograman berorientasi objek, dan begitu kamu terbiasa, ada satu kategori kode yang jadi jauh lebih bersih saat ditulis.
Class itu sebenarnya apa
Ini contoh class paling sederhana yang masih berguna — sebuah class yang memodelkan seorang manusia:
class Person {
constructor(nama, umur) {
this.nama = nama;
this.umur = umur;
}
greet() {
return `Hai, aku ${this.nama}, umurku ${this.umur}.`;
}
}
Itu seluruh polanya dalam satu blok. Mari kita bedah bagian demi bagian, karena tiap potongnya punya peran.
Kata kunci class mengawali deklarasi, lalu diikuti namanya. Berdasarkan konvensi yang sudah kuat, nama class memakai PascalCase — huruf pertama kapital, seperti Person, ShoppingCart, atau HttpClient. Ini memang tidak dipaksakan oleh bahasanya, tapi semua orang menaatinya, dan ini langsung memberi sinyal “ini class, bukan variabel atau fungsi biasa.”
Di dalam kurung kurawal kamu mendefinisikan isi dari class: constructor dan method-method-nya. Perhatikan, di antara mereka tidak ada tanda koma — beda dengan object literal, isi sebuah class dipisahkan cukup dengan spasi atau baris baru saja.
Constructor
constructor adalah method khusus yang berjalan otomatis setiap kali kamu membuat instance baru. Tugasnya menyiapkan kondisi awal — memberi objek yang baru lahir itu nilai-nilai permulaannya.
Kamu membuat sebuah instance dengan kata kunci new:
const orang = new Person("John Doe", 28);
console.log(orang.nama); // "John Doe"
console.log(orang.greet()); // "Hai, aku John Doe, umurku 28."
Saat kamu menulis new Person("John Doe", 28), JavaScript membuat sebuah objek kosong yang benar-benar baru, menjalankan constructor dengan argumen yang kamu kirim, lalu menyerahkan objek yang sudah jadi itu kembali ke kamu. Di dalam constructor, this mengacu ke objek baru tersebut. Jadi this.nama = nama artinya “simpan nama yang masuk ini ke instance yang spesifik ini.”
Property yang kamu pasang ke this — nama dan umur di sini — kadang disebut instance field. Tiap instance punya salinannya sendiri, jadi dua orang bisa punya nama yang berbeda tanpa saling tabrakan:
const a = new Person("John Doe", 28);
const b = new Person("Jane Doe", 34);
console.log(a.nama); // "John Doe"
console.log(b.nama); // "Jane Doe"
Lupa new adalah bug klasik
Kalau kamu memanggil class tanpa new — Person("John Doe", 28) alih-alih new Person("John Doe", 28) — JavaScript melempar TypeError: Class constructor Person cannot be invoked without 'new'. Pesan error itu sebenarnya sedang menolongmu; constructor function model lama (sebelum class ada) justru diam-diam berperilaku ngawur ketimbang memberi peringatan. Jadi selalu pasangkan class dengan new.
Method
Semua yang ada di dalam isi class selain constructor disebut method — yaitu fungsi yang bisa dijalankan oleh instance. Di contoh tadi, greet() adalah sebuah method. Kamu memanggilnya pada sebuah instance dengan sintaks titik yang sudah akrab: orang.greet().
Method hampir selalu bekerja sama dengan this untuk menjangkau data milik instance-nya. Memang itulah inti gunanya — sebuah method adalah perilaku yang mengolah objek spesifik tempat ia dipanggil:
class Rectangle {
constructor(lebar, tinggi) {
this.lebar = lebar;
this.tinggi = tinggi;
}
area() {
return this.lebar * this.tinggi;
}
describe() {
return `${this.lebar}×${this.tinggi} = ${this.area()}`;
}
}
const r = new Rectangle(4, 5);
console.log(r.area()); // 20
console.log(r.describe()); // "4×5 = 20"
Perhatikan describe() memanggil this.area() — satu method bisa memanggil method lain pada instance yang sama. Awalan this. itu wajib; menulis area() tanpa awalan justru akan dicari sebagai fungsi lepas di sekitarnya dan akhirnya gagal.
Di balik layar, method-method itu menempel pada prototype milik class, bukan pada tiap instance satu per satu. Ini hemat memori: seribu instance Rectangle sama-sama memakai satu salinan area(), bukan masing-masing membawa salinannya sendiri. Kamu tidak perlu mengurus apa pun soal itu — sudah otomatis — tapi ada baiknya tahu kenapa method ditaruh di dalam isi class dan bukan di-assign di dalam constructor.
Nilai this tergantung cara method dipanggil
Nilai this di dalam sebuah method tidak terpaku ke instance-nya — ia bergantung pada bagaimana method itu dipanggil. Memanggil obj.method() membuat this menjadi obj, tapi kalau method-nya kamu cabut (const m = obj.method; m()), koneksi itu putus dan this menjadi undefined di dalam isi class (class otomatis berjalan dalam strict mode). Hal ini sering bikin orang kepleset saat mengoper method sebagai callback, misalnya ke setTimeout atau ke sebuah event listener. Kalau kamu sudah membaca artikel tentang keyword this, ini sebenarnya aturan yang sama yang diterapkan di dalam class. Solusi yang biasa dipakai adalah membungkusnya dengan arrow function (() => obj.method()) atau mem-bind method tersebut.
Getter dan setter
Kadang kamu ingin sebuah property yang dari luar terlihat seperti field biasa, padahal sebenarnya menjalankan kode saat kamu membaca atau menulisnya. Untuk itulah getter dan setter ada. Kamu mendefinisikannya dengan kata kunci get dan set:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get fahrenheit() {
return this.celsius * 9 / 5 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) * 5 / 9;
}
}
const t = new Temperature(25);
console.log(t.fahrenheit); // 77 — dibaca seperti property, tanpa ()
t.fahrenheit = 212; // ditulis seperti property
console.log(t.celsius); // 100
Keajaibannya di sini, t.fahrenheit itu tanpa tanda kurung — kamu mengaksesnya persis seperti t.celsius, padahal di baliknya ada fungsi yang berjalan. Getter menghitung nilai saat dibutuhkan, sedangkan setter membiarkanmu meng-assign lewatnya lalu menerjemahkan nilai itu menjadi data yang disimpan. Pola ini bagus untuk nilai turunan atau nilai yang divalidasi: dunia luar melihat property sederhana, sementara kamu tetap memegang kendali atas apa yang sesungguhnya terjadi.
Static member
Semua yang dibahas sejauh ini hidup di instance. Tapi kadang ada sepotong data atau perilaku yang menjadi milik class itu sendiri, bukan milik instance mana pun. Itulah static member — ditandai dengan kata kunci static, dan dipanggil pada nama class-nya, bukan pada instance:
class MathHelper {
static PI = 3.14159;
static double(n) {
return n * 2;
}
}
console.log(MathHelper.PI); // 3.14159
console.log(MathHelper.double(8)); // 16
Perhatikan, di sini kamu tidak pernah menulis new MathHelper() — kamu memanggil MathHelper.double(8) langsung pada class-nya. Static method tidak bisa memakai this untuk menjangkau instance field (memang tidak ada instance-nya), jadi static cocok untuk fungsi utilitas dan helper pembuat objek yang berkaitan dengan class tapi tidak bergantung pada objek tertentu. Objek bawaan Math bekerja persis seperti ini: Math.max, Math.round, dan kawan-kawannya semuanya static.
Pewarisan dengan extends dan super
Di sinilah class benar-benar mulai menunjukkan kelasnya. Pewarisan (inheritance) memungkinkan satu class dibangun di atas class lain, memakai ulang field dan method-nya sambil menambah atau mengubah yang ia butuhkan. Kamu pakai kata kunci extends untuk bilang “class ini adalah versi yang lebih spesifik dari class itu”:
class Animal {
constructor(nama) {
this.nama = nama;
}
speak() {
return `${this.nama} mengeluarkan suara.`;
}
}
class Dog extends Animal {
constructor(nama, ras) {
super(nama); // panggil constructor induk
this.ras = ras;
}
speak() {
return `${this.nama} menggonggong!`;
}
}
const d = new Dog("Rex", "Beagle");
console.log(d.nama); // "Rex" — diwarisi dari Animal
console.log(d.ras); // "Beagle" — ditambahkan oleh Dog
console.log(d.speak()); // "Rex menggonggong!" — versi milik Dog
Banyak hal terjadi di sini, jadi mari kita sebut satu per satu. Dog extends Animal berarti sebuah Dog adalah sejenis Animal dan otomatis mendapatkan semua yang dimiliki Animal. Pemanggilan super(nama) di dalam constructor Dog menjalankan constructor induknya lebih dulu — itulah yang menyiapkan this.nama. Lalu Dog mendefinisikan speak()-nya sendiri, yang menimpa (override) versi warisan: saat kamu memanggil d.speak(), JavaScript menemukan versi milik Dog dan memakainya, bukan versi milik Animal.
Kamu juga bisa memanggil versi milik induk secara eksplisit dari dalam sebuah override memakai super.namaMethod():
class Cat extends Animal {
speak() {
return super.speak() + " Ia mengeong.";
}
}
const c = new Cat("Milo");
console.log(c.speak()); // "Milo mengeluarkan suara. Ia mengeong."
Di sini super.speak() menjangkau method milik Animal, lalu Cat menambahkan sesuatu di atasnya. Pendekatan “perluas, jangan ganti total” ini adalah cara yang umum dan rapi untuk membangun di atas perilaku yang sudah ada.
Di subclass, panggil super() sebelum menyentuh this
Saat sebuah subclass punya constructor-nya sendiri, kamu wajib memanggil super() sebelum memakai this. Coba membaca atau menulis this.apapun lebih dulu, dan JavaScript melempar ReferenceError: Must call super constructor ... before accessing 'this'. Alasannya konkret: constructor induklah yang sesungguhnya membuat objek yang ditunjuk oleh this, jadi sebelum super() berjalan, belum ada this yang bisa diutak-atik. Solusinya gampang — jadikan super(...) sebagai baris pertama di constructor subclass-mu.
Private field
Secara default, setiap property pada sebuah instance bersifat publik — siapa pun yang memegang objeknya bisa membaca atau mengubahnya. Sering kali itu tidak masalah. Tapi kadang kamu ingin punya kondisi internal yang dunia luar tidak bisa utak-atik langsung. JavaScript modern memberimu private field, yang ditandai dengan # di awal namanya:
class BankAccount {
#saldo = 0; // privat — tidak bisa dijangkau dari luar
constructor(awal) {
this.#saldo = awal;
}
deposit(jumlah) {
if (jumlah <= 0) return;
this.#saldo += jumlah;
}
get saldo() {
return this.#saldo;
}
}
const akun = new BankAccount(100);
akun.deposit(50);
console.log(akun.saldo); // 150 — dibaca lewat getter
console.log(akun.#saldo); // SyntaxError — tidak bisa disentuh dari luar
Field #saldo hanya bisa diakses dari dalam isi class. Kode di luar tidak bisa membacanya, tidak bisa menulisnya, bahkan tidak bisa mengecek apakah ia ada tanpa memicu error. Ini membuatmu bisa melindungi data: satu-satunya cara mengubah saldo adalah lewat deposit(), yang bisa menegakkan aturan (tidak boleh jumlah negatif). Jaminan semacam itu — “satu-satunya pintu masuk adalah pintu yang kurancang sendiri” — adalah inti dari enkapsulasi.
Tanda # adalah bagian dari nama, selamanya
Tanda # pada private field bukan pengubah yang bisa kamu lepas belakangan — ia benar-benar bagian dari identitas field tersebut. Kamu selalu menulis this.#saldo, tidak pernah this.saldo, untuk menjangkau yang privat. Keduanya juga merupakan slot yang sungguh-sungguh berbeda meski namanya mirip: secara teknis sebuah class bisa punya #saldo dan saldo sekaligus, dan keduanya tidak bertabrakan. Private field sudah didukung luas di semua browser modern dan Node, jadi kamu bebas memakainya sekarang.
Class expression
Seperti fungsi, class punya bentuk deklarasi (semua contoh di atas) dan bentuk expression, di mana kamu meng-assign class itu ke sebuah variabel:
const Circle = class {
constructor(radius) {
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
};
const lingkaran = new Circle(10);
console.log(lingkaran.area().toFixed(2)); // "314.16"
Dalam kode sehari-hari kamu kebanyakan akan memakai bentuk deklarasi, tapi class expression berguna saat kamu perlu membuat class secara kondisional, mengopernya ke dalam sebuah fungsi, atau mengembalikannya dari sebuah factory. Cukup kenali bentuknya, meski kamu tidak sering menulisnya.
Kapan sebaiknya pakai class
Class itu sebuah perkakas, bukan keharusan — JavaScript juga senang-senang saja bekerja dengan objek biasa dan fungsi. Ambil class ketika:
- Kamu butuh banyak instance dengan bentuk dan perilaku yang sama (pengguna, produk, komponen, entitas game).
- Kamu punya data beserta perilaku yang mengolahnya yang memang secara alami menyatu.
- Kamu mau pewarisan — sebuah hierarki tipe-tipe yang berkaitan dan berbagi satu basis.
- Kamu mau enkapsulasi — kondisi privat yang terlindungi di balik antarmuka yang terkendali.
Untuk sekadar wadah data sekali pakai tanpa perilaku, object literal biasa lebih sederhana dan sudah lebih dari cukup. Jangan membungkus sesuatu dalam class cuma karena bisa; lakukan saat model cetak-biru-dan-instance memang benar-benar pas dengan apa yang sedang kamu bangun.
Class itu sintaks di atas prototype
Ada satu kebenaran di balik layar yang baik untuk kamu ketahui: class JavaScript itu gula sintaks (syntactic sugar) di atas sistem prototype bahasa ini yang lebih tua. Sebuah class tidak menghadirkan model objek yang sama sekali baru seperti di beberapa bahasa lain — ia hanyalah cara yang lebih bersih dan lebih mudah dibaca untuk menyiapkan prototype dan constructor function yang sebenarnya sudah ada sejak dulu. Kamu tidak perlu menguasai prototype untuk memakai class secara produktif, tapi memahami bahwa class adalah prototype yang berpakaian lebih rapi akan menjelaskan banyak keanehannya (seperti method yang dipakai bersama, dan perilaku this yang seperti itu). Ini topik yang asyik untuk ditengok lagi begitu dasar-dasarnya terasa nyaman.
Penutup
Class memberimu cara yang terstruktur dan bisa dipakai ulang untuk mendefinisikan objek beserta perilakunya:
- Sebuah class adalah cetak biru; tiap objek yang dibuat darinya dengan
newadalah sebuah instance. constructorberjalan otomatis saatnewdan menyiapkan kondisi awal instance lewatthis.- Method adalah perilaku bersama yang mengolah instance lewat
this. Getter/setter (get/set) terlihat seperti property tapi menjalankan kode. - Member
staticmenjadi milik class itu sendiri dan dipanggil pada nama class-nya, bukan pada instance. extendsmembuat subclass yang mewarisi dari sebuah induk;super()memanggil constructor induk (selalu sebelumthis), dansuper.method()memanggil method induk. Sebuah subclass bisa menimpa (override) method warisan.- Private field (
#nama) menyembunyikan kondisi internal, memberimu enkapsulasi yang sungguhan. - Di balik layar, class adalah sintaks yang ramah di atas prototype milik JavaScript.
Dengan class di kotak perkakasmu, kamu bisa memodelkan hal-hal di dunia nyata dengan rapi dan naik skala dari satu objek menjadi ribuan tanpa mengulang-ulang dirimu sendiri. Dari sini, langkah berikutnya yang alami adalah merangkum semuanya lewat praktik terbaik — kebiasaan-kebiasaan yang menjaga kode JavaScript-mu tetap mudah dibaca, mudah ditebak, dan gampang dirawat seiring proyekmu membesar.