JavaScript'te class, aynı yapıda nesneler üretmek için kullanılan bir şablondur: constructor yeni nesnenin alanlarını doldurur, sınıf gövdesine yazılan metotlar tüm örnekler arasında paylaşılır, static üyeler sınıfın kendisine ait olur, # ile başlayan alanlar dışarıdan erişilemeyen özel alanlardır ve extends ile super bir sınıfı başka bir sınıftan türetir.

Bu yazıda her özelliği Node.js 24 ile çalıştırılmış kısa örneklerle anlatıyoruz. Sınıfların arka planda prototip zinciri üzerine kurulduğunu bilmek hata ayıklamada çok işe yarar; o tarafı ayrıca prototip ve kalıtım yazısında ele aldık.

Sınıf tanımlama ve constructor

Sınıf tanımlama ve constructor

Bir sınıf class Ad { ... } biçiminde bildirilir ve new ile örneği oluşturulur. constructor metodu her new çağrısında bir kez çalışır; içindeki this, o anda üretilen yeni nesneyi gösterir. Constructor'dan bir değer döndürmeniz gerekmez, new ifadesinin sonucu otomatik olarak this olur.

javascript
class Urun {
  constructor(ad, fiyat) {
    this.ad = ad;
    this.fiyat = fiyat;
  }
  etiket() {
    return `${this.ad}: ${this.fiyat} TL`;
  }
}

const kalem = new Urun("Kalem", 25);
console.log(kalem.etiket()); // Kalem: 25 TL
console.log(typeof Urun); // function
console.log(new Urun("A", 1) === new Urun("A", 1)); // false

Son satır, her new çağrısının ayrı bir nesne ürettiğini gösteriyor. typeof Urun sonucunun "function" olması da tesadüf değil: sınıf, çalışma zamanında özel bir constructor fonksiyonudur.

Constructor içinden nesne türünde bir değer döndürürseniz new ifadesinin sonucu o değer olur ve this üzerinde hazırladığınız her şey kaybolur. MDN bu yüzden constructor'dan değer döndürmemeyi önerir.

new olmadan çağırma ve hoisting

Klasik constructor fonksiyonları new olmadan da çağrılabilir; sınıflar çağrılamaz. Ayrıca sınıf bildirimleri fonksiyon bildirimleri gibi önceden kullanılamaz: bildirimden önce erişim, let ve const ile aynı şekilde geçici ölü bölgeye (TDZ) takılır. Ayrıntısı için hoisting ve TDZ yazısına bakabilirsiniz.

javascript
try {
  new Sepet();
} catch (e) {
  console.log(e.constructor.name, e.message);
}
// ReferenceError Cannot access 'Sepet' before initialization

class Sepet {}

try {
  Sepet();
} catch (e) {
  console.log(e.constructor.name);
  // TypeError
  console.log(e.message);
  // Class constructor Sepet cannot be invoked without 'new'
}

Metotlar: neden constructor içine fonksiyon yazmamalı

Sınıf gövdesine yazdığınız metotlar Sinif.prototype üzerinde tek kopya olarak durur ve tüm örnekler bu kopyayı paylaşır. Aynı fonksiyonu constructor içinde this.metot = function () {...} biçiminde atarsanız her örnek için yeni bir fonksiyon oluşur. Sonuç aynı görünse de bellekte gereksiz kopyalar birikir.

javascript
class Sayac {
  constructor() {
    this.deger = 0;
  }
  artir() {
    this.deger++;
    return this;
  }
}

const a = new Sayac();
const b = new Sayac();
console.log(a.artir === b.artir); // true
console.log(Object.hasOwn(a, "artir")); // false
console.log(a.artir().artir().deger); // 2

artir() metodunun this döndürmesi, çağrıların zincirlenmesini sağlıyor. Sınıf gövdesinin her zaman strict mode ile çalıştığını da unutmayın; "use strict" yazmasanız bile geçerlidir. Bunun pratik sonucu, aşağıdaki "yaygın hatalar" bölümünde göreceğiniz this kaybıdır.

Getter ve setter (erişimci alanlar)

get ve set önekli metotlar, dışarıdan bakınca sıradan bir özellik gibi kullanılır ama okuma ve yazma sırasında sizin kodunuz çalışır. Yalnızca getter tanımlanmış bir özellik fiilen salt okunur olur: strict mode dışında atama sessizce yok sayılır, strict mode içinde (örneğin bir ES modülünde ya da sınıf gövdesinde) TypeError fırlatılır.

javascript
class Sicaklik {
  #celsius = 0;
  get celsius() {
    return this.#celsius;
  }
  set celsius(deger) {
    if (typeof deger !== "number") throw new TypeError("Sayı bekleniyordu");
    this.#celsius = deger;
  }
  get fahrenheit() {
    return (this.#celsius * 9) / 5 + 32;
  }
}

const s = new Sicaklik();
s.celsius = 25;
console.log(s.celsius); // 25
console.log(s.fahrenheit); // 77
s.fahrenheit = 100; // yalnız getter var: strict olmayan betikte yok sayılır
console.log(s.fahrenheit); // 77

Özel alanlar (#) ve static üyeler

# ile başlayan alan ve metotlar sınıfın gövdesi dışında hiçbir yerden okunamaz. Bu bir isim kuralı değil, dilin kendi kuralıdır: nesne.#alan ifadesini sınıf dışında yazmak kod çalışmadan önce SyntaxError üretir. Özel alanları kullanmadan önce gövdede bildirmeniz gerekir; çalışma anında yeni bir özel alan eklenemez, delete ile silinemez. Bir nesnede belirli bir özel alanın olup olmadığını #alan in nesne ile güvenle sorabilirsiniz.

static ile işaretlenen metot ve alanlar örneklere değil sınıfın kendisine aittir. Örneğin Date.now() bu türden bir metottur. Static üyelerin de özel sürümü olabilir.

javascript
class Hesap {
  #bakiye = 0;
  static #olusturulan = 0;

  constructor(sahip) {
    this.sahip = sahip;
    Hesap.#olusturulan++;
  }
  yatir(tutar) {
    if (tutar <= 0) throw new RangeError("Tutar pozitif olmalı");
    this.#bakiye += tutar;
    return this.#bakiye;
  }
  static kac() {
    return Hesap.#olusturulan;
  }
  static hesapMi(nesne) {
    return #bakiye in nesne;
  }
}

const h = new Hesap("Ayşe");
console.log(h.yatir(500)); // 500
console.log(Object.keys(h)); // [ 'sahip' ]
console.log(Hesap.hesapMi(h), Hesap.hesapMi({})); // true false
console.log(Hesap.kac()); // 1
console.log(h.kac); // undefined

Object.keys(h) çıktısında #bakiye görünmüyor; özel alanlar sıradan özellik değildir. h.kac ifadesinin undefined vermesi de static metotların örneklerden erişilemediğini gösteriyor. Sınıf yüklenirken bir kez çalışacak kurulum kodu için static { ... } bloğu da kullanılabilir.

extends ve super ile kalıtım

extends ve super ile kalıtım: constructor içinde this.x, Sınıf alanı (x = 1), Metot, #ozel alan veya metot

class Alt extends Ust yazdığınızda Alt, Ust sınıfının metotlarını devralır. Türetilmiş sınıfın constructor'ında this kullanmadan önce super(...) çağırmak zorunludur; bu çağrı üst sınıfın constructor'ını çalıştırır. Metot içinde super.metot() ise üst sınıftaki sürümü çağırır, böylece metodu tamamen yeniden yazmak yerine genişletebilirsiniz.

javascript
class Hayvan {
  constructor(ad) {
    this.ad = ad;
  }
  sesCikar() {
    return `${this.ad} bir ses çıkarıyor`;
  }
  toString() {
    return `Hayvan(${this.ad})`;
  }
}

class Kopek extends Hayvan {
  constructor(ad, cins) {
    super(ad);
    this.cins = cins;
  }
  sesCikar() {
    return `${super.sesCikar()}: hav hav`;
  }
}

const k = new Kopek("Karabaş", "Kangal");
console.log(k.sesCikar()); // Karabaş bir ses çıkarıyor: hav hav
console.log(String(k)); // Hayvan(Karabaş)
console.log(k instanceof Kopek, k instanceof Hayvan); // true true

Birkaç kuralı aklınızda tutun: bir sınıf yalnızca tek bir sınıftan türetilebilir; static metotlar da kalıtımla geçer ve super.staticMetot() ile çağrılabilir; üst sınıfın # ile başlayan özel alanlarına alt sınıf erişemez. Alt sınıfın üst sınıfın iç verisine ihtiyacı varsa üst sınıf bunu bir metot ya da getter ile açmalıdır.

ÖzellikNerede dururÖrnekten erişim
constructor içinde this.xHer örneğin kendi özelliğiEvet
Sınıf alanı (x = 1)Her örneğin kendi özelliğiEvet
MetotSinif.prototype (paylaşılır)Evet
#ozel alan veya metotÖrnek içinde, gizliYalnızca sınıf gövdesinden
static metot veya alanSınıfın kendisiHayır (undefined)

Yaygın hatalar

super çağırmadan this kullanmak

javascript
class A {}
class B extends A {
  constructor() {
    this.x = 1;
  }
}

try {
  new B();
} catch (e) {
  console.log(e.constructor.name);
}
// ReferenceError

Türetilmiş sınıfta this ancak super() döndükten sonra hazır olur. super() çağrısından önce this ile ilgisi olmayan kod yazabilirsiniz ama this'e dokunamazsınız.

Metodu nesneden koparınca this kaybı

javascript
class Selam {
  constructor(ad) {
    this.ad = ad;
  }
  merhaba() {
    return `Merhaba ${this.ad}`;
  }
}

const s = new Selam("Ali");
const f = s.merhaba;
try {
  f();
} catch (e) {
  console.log(e.message); // Cannot read properties of undefined (reading 'ad')
}
console.log(s.merhaba.bind(s)()); // Merhaba Ali

Metodu bir değişkene atayıp tek başına çağırdığınızda this değeri undefined olur, çünkü sınıf gövdesi strict mode'dadır. Metodu geri çağırma fonksiyonu olarak verirken bind kullanın ya da bir arrow function ile sarın. Konunun genel kuralları this anahtar kelimesi yazısında anlatılıyor.

Sık Sorulan Sorular

JavaScript sınıfları gerçek sınıf mı, yoksa sadece sözdizimi mi?

MDN'e göre sınıflar yeni bir kalıtım modeli getirmez; constructor fonksiyonları ve prototip zinciri üzerine kurulmuş bir sözdizimidir. Yine de özel alanlar gibi prototiplerle kolayca taklit edilemeyen özellikler sunar.

Özel alan (#) ile _ önekli alan arasında fark var mı?

Evet. _bakiye yalnızca bir adlandırma alışkanlığıdır ve herkes okuyup değiştirebilir. #bakiye ise dil tarafından korunur; sınıf dışından erişim sözdizimi hatasıdır.

Bir sınıf birden fazla sınıftan türetilebilir mi?

Hayır, extends tek bir üst sınıf alır. Birden çok kaynaktan davranış almak için MDN, sınıf kompozisyonu ve mixin desenlerini önerir.

Sınıf mı, fabrika fonksiyonu mu kullanmalıyım?

Sadece veri taşıyan basit nesneler için düz nesne veya fabrika fonksiyonu yeterlidir. Paylaşılan metotlar, kapsülleme ve kalıtım gerektiğinde sınıf daha düzenli bir yapı sunar. Kapalı veri için fonksiyon tabanlı bir alternatif closure yazısında var.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: JavaScript öğrenme rehberi · Prototip zinciri ve kalıtım · this anahtar kelimesi · ES modülleri: import ve export · Hoisting ve TDZ · TypeScript ileri seviye