Pola constructor function di chapter sebelumnya bisa diandalkan, tetapi tiga kebiasaannya terasa melelahkan: method dibagi lewat penugasan manual ke KartuMember.prototype, tanda new mudah terlewat, dan logika “cetakan” terpencar di dua tempat — function di atas, prototype di bawah. Budi yang membaca ulang kode kafenya tiga bulan kemudian butuh waktu untuk menyusun ulang ceritanya.

Sintaks class — hadir sejak ECMAScript 2015 — merapikan semua itu. Method ditulis langsung di badan class, tautan prototype dirakit otomatis, dan niat “ini cetakan object” terbaca dari satu kata kunci. Penting dipahami sejak awal: class tidak menggantikan mekanisme prototype yang sudah kita bedah; class adalah pembungkus rapi di atasnya. Kita buktikan itu lewat kode, termasuk dua bentuk penulisan class dan satu jebakan hoisting yang khas.

Sintaks class: Method Langsung di Badan #

Aturan penulisannya: sebuah class dideklarasikan dengan kata kunci class diikuti nama, method ditulis tanpa kata kunci function, dan instance dibuat dengan new — sama seperti constructor function. Berbeda dari object literal, badan class hanya berisi method dan pendeklarasian anggota; data per-instance tidak diisikan di sini, melainkan di constructor() yang kita bedah di chapter berikutnya.

Berkas berikut membangun kalkulator kasir untuk kafe Budi dalam dua bentuk: class statement dan class expression:

// kelas-kasir.js
// Class: cetakan object dengan method yang otomatis tersimpan di prototype.

class KalkulatorKasir {
  diskon(total, persen) {
    const potongan = (total * persen) / 100;
    return total - potongan;
  }

  kenaPajak(total) {
    return Math.round(total * 0.11);
  }
}

const kasir = new KalkulatorKasir();

console.log(kasir.diskon(50000, 10));
console.log(kasir.kenaPajak(50000));
console.log(typeof KalkulatorKasir);
console.log(KalkulatorKasir.prototype.diskon === kasir.diskon);

const KonverterPoin = class {
  dariRupiah(total) {
    return Math.floor(total / 10000);
  }
};

const konverter = new KonverterPoin();
console.log(konverter.dariRupiah(25000));

Output yang Dihasilkan:

text
45000
5500
function
true
2

Mari kita bedah:

  1. Baris 4–13 (class KalkulatorKasir): deklarasi class dengan dua method. Perhatikan tidak ada koma antar method — badan class bukan object literal.
  2. Baris 5–8 (diskon): potongan dihitung dari persen, lalu dikembalikan; diskon(50000, 10) menghasilkan 45000.
  3. Baris 10–12 (kenaPajak): pajak 11% dibulatkan; Math.round menutup celah angka desimal seperti 5500.000000000001 menjadi 5500.
  4. Baris 15 (new KalkulatorKasir()): instance dibuat. Tidak ada constructor() di class ini, sehingga engine memakai constructor kosong bawaan.
  5. Baris 19 (typeof KalkulatorKasir): hasilnya "function" — bukti bahwa class adalah jenis khusus function, bukan tipe baru.
  6. Baris 20 (KalkulatorKasir.prototype.diskon === kasir.diskon): true — method yang kita tulis di badan class otomatis mendarat di prototype, pola bersama dari chapter constructor function tanpa penugasan manual.
  7. Baris 22–26 (const KonverterPoin = class {...}): bentuk class expression — class didefinisikan sebagai nilai dan disimpan di konstanta, persis seperti function expression.
  8. Baris 28–29 (konverter.dariRupiah): instance dari class expression bekerja sama baiknya; dariRupiah(25000) menghasilkan 2.

class Adalah function #

Baris typeof KalkulatorKasir memunculkan "function", bukan "class" — karena memang tidak ada tipe data baru bernama class. Di balik layar, class adalah function yang ditandai khusus: ia hanya boleh dipanggil dengan new, badannya selalu berjalan dalam mode ketat, dan properti .prototype-nya terisi method-method dari badan class.

Semua perangkat yang sudah kita kuasai tetap berlaku. KalkulatorKasir.prototype bisa diperiksa dengan Object.getPrototypeOf dari sisi instance, method-methodnya dibagi bersama sebagaimana ubahPoin di chapter lalu, dan instanceof membaca rantai yang sama. Karena itulah kita menyebut class sebagai pembungkus rapi, bukan mesin baru.

Sifat “badan class selalu mode ketat” juga bukan catatan kosong — ia mengubah perilaku kode yang kita tulis di dalam method. Penugasan ke variabel yang tidak pernah dideklarasikan, yang pada berkas biasa berlalu senyap, di dalam badan class langsung ditolak:

js
class Penghitung {
  langkah = 0;
  naik() {
    langkahBaru = this.langkah + 1;
  }
}

const penghitung = new Penghitung();
try {
  penghitung.naik();
} catch (err) {
  console.log(`${err.name}: ${err.message}`);
}

Output yang Dihasilkan:

text
ReferenceError: langkahBaru is not defined

Kesalahan ketik variabel — semacam langkahBaru yang seharusnya this.langkah — terdeteksi seketika alih-alih merusak senyap lewat pembuatan variabel global. Perlindungan kecil seperti ini bekerja di semua method tanpa kita menulis directive apa pun.

Bentuk penulisannya ada dua, dan keduanya sudah kita lihat di berkas:

BentukPenulisanKapan dipakai
class statementclass Nama { ... }Standar; class punya nama sendiri di lingkupnya
class expressionconst Nama = class { ... }Ketika class diperlakukan sebagai nilai: dikembalikan dari function, diteruskan sebagai argumen

Kedua bentuk menghasilkan class dengan perilaku identik. Pilihlah yang membuat niat kode terbaca paling jelas.

Dipanggil Tanpa new: Ditolak Runtime #

Satu perbedaan penting dari constructor function: pemanggilan class tanpa new tidak diizinkan bahasa. Pada constructor function lama, kelalaian ini berlalu senyap dengan this yang salah — kita lihat musibahnya di chapter sebelumnya. Pada class, runtime menolaknya di depan:

js
class KalkulatorKasir {
  diskon(total) {
    return total;
  }
}

try {
  KalkulatorKasir();
} catch (err) {
  console.log(`${err.name}: ${err.message}`);
}

Output yang Dihasilkan:

text
TypeError: Class constructor KalkulatorKasir cannot be invoked without 'new'

Pesan galat itu bahkan menyebut solusinya: tambahkan new. Perlindungan otomatis semacam ini adalah salah satu alasan class direkomendasikan untuk kode baru — kesalahan yang dulu senyap kini berteriak di baris yang tepat.

Perbandingan singkat keduanya merangkum perjalanan dua chapter ini:

AspekConstructor functionClass
Bentuk penulisanfunction Nama() {}class Nama {}
Cara membuat instancenew Nama()new Nama()
Method dibagi lewatPenugasan ke .prototype secara manualOtomatis dari badan class
Pemanggilan tanpa newBerjalan, this menunjuk ke globalThisDitolak dengan TypeError
Badan kodeMode ketat hanya bila ditandaiSelalu mode ketat

Tidak Di-hoisting: Awas Pakai Sebelum Deklarasi #

Function declaration di-hoisting sehingga boleh dipanggil sebelum barisnya, tetapi class statement tidak. Class diperlakukan seperti let: namanya sudah diketahui sejak awal blok, tetapi belum terinisialisasi sampai baris deklarasinya dijalankan. Membaca class sebelum deklarasinya masuk ke temporal dead zone (TDZ).

Berkas berikut memanggil new KalkulatorKasir() sebelum class-nya dideklarasikan. Sintaksnya sah, sehingga parser meloloskannya — yang gagal adalah eksekusi. Jika dijalankan, Node menampilkan transkrip seperti ini (path dipendekkan):

js
// Demonstrasi: class dipakai sebelum dideklarasikan.
const kasir = new KalkulatorKasir();

class KalkulatorKasir {}
text
/home/…/tdz-kelas.js:2
const kasir = new KalkulatorKasir();
              ^

ReferenceError: Cannot access 'KalkulatorKasir' before initialization

Pesan galatnya dapat dibaca sebagai kalimat: nama KalkulatorKasir sudah ada, tetapi nilainya belum diinisialisasi ketika baris 2 mencoba mengaksesnya. Pola galat ini sama dengan let dan const yang kita pelajari di section Dasar Pemrograman — kini kita tahu class ikut aturan yang sama.

Kelas di berkas tadi sengaja tanpa data — dua method menghitung apa pun yang diteruskan pemanggil. Sebagian besar class nyata justru membawa data per-instance, dan tempat resmi memasangnya adalah constructor(). Itulah topik chapter berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

kelas-kasir.js

Unduh berkas
javascript
// kelas-kasir.js
// Class: cetakan object dengan method yang otomatis tersimpan di prototype.

class KalkulatorKasir {
  diskon(total, persen) {
    const potongan = (total * persen) / 100;
    return total - potongan;
  }

  kenaPajak(total) {
    return Math.round(total * 0.11);
  }
}

const kasir = new KalkulatorKasir();

console.log(kasir.diskon(50000, 10));
console.log(kasir.kenaPajak(50000));
console.log(typeof KalkulatorKasir);
console.log(KalkulatorKasir.prototype.diskon === kasir.diskon);

const KonverterPoin = class {
  dariRupiah(total) {
    return Math.floor(total / 10000);
  }
};

const konverter = new KonverterPoin();
console.log(konverter.dariRupiah(25000));