ES6 Class Syntax
Neden Bu Konu Önemli?
Önceki derste constructor fonksiyonlar ve prototipal kalıtımın ne kadar çetrefilli olduğunu gördük. Object.create, prototype atamak, constructor referansını düzeltmek, call(this) ile üst constructor'ı çağırmak... Tüm bunlar doğru ama yazması, okuması ve bakımı zor.
ES6 (2015) ile gelen class keyword'ü, bu karmaşıklığı şık bir sözdizimi arkasına gizler. Ama — ve bu çok önemli — class sadece sözdizimsel şeker (syntactic sugar) dir. Arkada hâlâ prototipal kalıtım çalışır. class yeni bir kalıtım modeli getirmez; var olanı daha kolay yazmayı sağlar.
Bunu bir araba kullanmaya benzetebiliriz: motor, şanzıman, diferansiyel — hepsi hâlâ aynı. Ama eski arabada her şeyi manuel yapıyordun (prototipler). Yeni arabada ise otomatik vites var (class) — aynı mekanizma, daha kolay kullanım.
Class Tanımlama
// ES6 Class — temiz, okunabilir syntax
class Kullanici {
// Constructor — nesne oluşturulurken çağrılır
constructor(ad, email) {
this.ad = ad;
this.email = email;
this.olusturmaTarihi = new Date();
}
// Metotlar — otomatik olarak prototype'a eklenir
selamla() {
return `Merhaba, ben ${this.ad}`;
}
bilgiGoster() {
return `${this.ad} (${this.email})`;
}
}
// new ile instance oluştur
const ahmet = new Kullanici("Ahmet", "ahmet@test.com");
console.log(ahmet.selamla()); // "Merhaba, ben Ahmet"
console.log(ahmet.bilgiGoster()); // "Ahmet (ahmet@test.com)"
// Arkada hâlâ prototype var!
console.log(typeof Kullanici); // "function" — class aslında bir fonksiyon
console.log(ahmet instanceof Kullanici); // trueClass vs Constructor Fonksiyon Karşılaştırması
// Eski yol — constructor fonksiyon
function EskiKullanici(ad) {
this.ad = ad;
}
EskiKullanici.prototype.selamla = function () {
return `Merhaba, ben ${this.ad}`;
};
// Yeni yol — class
class YeniKullanici {
constructor(ad) {
this.ad = ad;
}
selamla() {
return `Merhaba, ben ${this.ad}`;
}
}
// Her ikisi de aynı şeyi yapar:
const e = new EskiKullanici("Ahmet");
const y = new YeniKullanici("Ahmet");
console.log(e.selamla()); // "Merhaba, ben Ahmet"
console.log(y.selamla()); // "Merhaba, ben Ahmet"
// AMA class'ların farklılıkları var:
// 1. class ZORUNLU olarak new ile çağrılmalı
// YeniKullanici("Ahmet"); // TypeError: Class constructor cannot be invoked without 'new'
// EskiKullanici("Ahmet"); // Hata vermez — ama yanlış çalışır
// 2. class metotları enumerable DEĞİL
console.log(Object.keys(EskiKullanici.prototype)); // ["selamla"]
console.log(Object.keys(YeniKullanici.prototype)); // [] — boş!
// 3. class body'si her zaman strict mode'dadırClass Fields (Sınıf Alanları)
ES2022 ile birlikte, constructor dışında doğrudan sınıf alanları tanımlanabilir:
class Oyun {
// Public class fields — constructor dışında tanımlanır
ad = "Bilinmeyen Oyun";
puan = 0;
seviye = 1;
baslangicTarihi = new Date();
constructor(ad) {
if (ad) this.ad = ad;
}
puanEkle(miktar) {
this.puan += miktar;
if (this.puan >= this.seviye * 100) {
this.seviyeAtla();
}
}
seviyeAtla() {
this.seviye++;
console.log(`🎉 Seviye ${this.seviye}!`);
}
}
const oyun = new Oyun("Uzay Savaşı");
console.log(oyun.ad); // "Uzay Savaşı"
console.log(oyun.puan); // 0
console.log(oyun.seviye); // 1
oyun.puanEkle(150);
// 🎉 Seviye 2!💡 İpucu: Class fields, her instance için ayrı değerler oluşturur (constructor'da
this.x = yyazmak gibi). Prototype'a eklenmezler. Bu, özellikle arrow function class fields için önemlidir.
Getter ve Setter
Getter ve setter'lar, bir özelliğe erişirken veya değer atarken özel mantık çalıştırmanı sağlar. Dışarıdan bakıldığında normal bir özellik gibi görünür ama arkada fonksiyon çalışır.
class Dikdortgen {
constructor(genislik, yukseklik) {
this.genislik = genislik;
this.yukseklik = yukseklik;
}
// Getter — özellik gibi erişilir ama hesaplama yapar
get alan() {
return this.genislik * this.yukseklik;
}
get cevre() {
return 2 * (this.genislik + this.yukseklik);
}
// Setter — atama yapıldığında özel mantık çalıştırır
set boyut(deger) {
if (deger <= 0) {
throw new RangeError("Boyut pozitif olmalıdır");
}
this.genislik = deger;
this.yukseklik = deger;
}
toString() {
return `Dikdörtgen(${this.genislik}x${this.yukseklik})`;
}
}
const d = new Dikdortgen(10, 5);
// Getter — fonksiyon çağrısı gibi DEĞİL, özellik gibi erişilir
console.log(d.alan); // 50 — d.alan() DEĞİL!
console.log(d.cevre); // 30
// Setter — atama operatörü ile çağrılır
d.boyut = 7;
console.log(d.genislik); // 7
console.log(d.yukseklik); // 7
console.log(d.alan); // 49
// d.boyut = -5; // RangeError: Boyut pozitif olmalıdırGetter/Setter ile Veri Doğrulama
class Kullanici {
#_email; // Private backing field
constructor(ad, email) {
this.ad = ad;
this.email = email; // Setter çağrılır — doğrulama yapılır
}
get email() {
return this.#_email;
}
set email(deger) {
if (!deger.includes("@")) {
throw new TypeError("Geçerli bir email adresi giriniz");
}
this.#_email = deger.toLowerCase().trim();
}
get emailDomain() {
return this.#_email.split("@")[1];
}
}
const k = new Kullanici("Ahmet", "Ahmet@Test.COM");
console.log(k.email); // "ahmet@test.com" — küçük harf, trimmed
console.log(k.emailDomain); // "test.com"
// k.email = "geçersiz"; // TypeError!
k.email = "yeni@email.com"; // ✅ Setter ile doğrulanırStatic Metotlar ve Özellikler
static keyword'ü ile tanımlanan metot ve özellikler, sınıfa aittir — instance'lara değil. new ile oluşturulan nesnelerden erişilemez; doğrudan sınıf adı üzerinden çağrılır.
class MathHelper {
// Static özellik
static PI = 3.14159265359;
static E = 2.71828182846;
// Static metot
static dairenAlani(yaricap) {
return MathHelper.PI * yaricap ** 2;
}
static faktoriyel(n) {
if (n <= 1) return 1;
return n * MathHelper.faktoriyel(n - 1);
}
static derece2Radyan(derece) {
return derece * (MathHelper.PI / 180);
}
}
// Static metotlar sınıf adı ile çağrılır
console.log(MathHelper.PI); // 3.14159265359
console.log(MathHelper.dairenAlani(5)); // 78.539...
console.log(MathHelper.faktoriyel(5)); // 120
// Instance'tan erişilemez!
// const m = new MathHelper();
// m.dairenAlani(5); // TypeError: m.dairenAlani is not a functionFactory Pattern ile Static Metot
class Kullanici {
constructor(ad, email, rol) {
this.ad = ad;
this.email = email;
this.rol = rol;
this.olusturmaTarihi = new Date();
}
// Static factory metotları — farklı oluşturma yolları
static misafirOlustur() {
return new Kullanici("Misafir", "guest@temp.com", "guest");
}
static adminOlustur(ad, email) {
const admin = new Kullanici(ad, email, "admin");
admin.yetkiler = ["okuma", "yazma", "silme", "yönetim"];
return admin;
}
static jsondenOlustur(json) {
const data = typeof json === "string" ? JSON.parse(json) : json;
return new Kullanici(data.ad, data.email, data.rol || "user");
}
// Static karşılaştırma metodu
static eskiMi(kullanici, gun) {
const fark = Date.now() - kullanici.olusturmaTarihi.getTime();
return fark > gun * 24 * 60 * 60 * 1000;
}
}
// Factory metotlar ile nesne oluşturma
const misafir = Kullanici.misafirOlustur();
const admin = Kullanici.adminOlustur("Ayşe", "ayse@admin.com");
const user = Kullanici.jsondenOlustur('{"ad": "Mehmet", "email": "m@t.com"}');
console.log(misafir.rol); // "guest"
console.log(admin.rol); // "admin"
console.log(user.rol); // "user"Private Fields (#) — Gerçek Gizlilik
ES2022 ile JavaScript'e gerçek private alanlar geldi. # prefix'i ile tanımlanan alanlar, sınıfın dışından hiçbir şekilde erişilemez.
class BankaHesabi {
// Private fields — # ile tanımlanır
#bakiye;
#hesapNo;
#islemGecmisi = [];
constructor(sahip, baslangicBakiye) {
this.sahip = sahip; // Public — dışarıdan erişilebilir
this.#bakiye = baslangicBakiye; // Private — dışarıdan erişilemez
this.#hesapNo = this.#hesapNoUret();
}
// Private metot
#hesapNoUret() {
return "TR" + Math.random().toString().slice(2, 12);
}
#islemKaydet(tur, miktar) {
this.#islemGecmisi.push({
tur,
miktar,
tarih: new Date(),
bakiye: this.#bakiye,
});
}
// Public metotlar — private alanlara kontrollü erişim sağlar
paraYatir(miktar) {
if (miktar <= 0) {
throw new Error("Miktar pozitif olmalıdır");
}
this.#bakiye += miktar;
this.#islemKaydet("yatırma", miktar);
return this;
}
paraCek(miktar) {
if (miktar <= 0) {
throw new Error("Miktar pozitif olmalıdır");
}
if (miktar > this.#bakiye) {
throw new Error("Yetersiz bakiye");
}
this.#bakiye -= miktar;
this.#islemKaydet("çekme", miktar);
return this;
}
get bakiye() {
return this.#bakiye;
}
get hesapNo() {
// Sadece son 4 haneyi göster
return "****" + this.#hesapNo.slice(-4);
}
get sonIslemler() {
// Kopyasını döndür — orijinal korunsun
return [...this.#islemGecmisi].slice(-5);
}
}
const hesap = new BankaHesabi("Ahmet", 1000);
hesap.paraYatir(500).paraCek(200); // Method chaining
console.log(hesap.bakiye); // 1300 — getter ile erişim
console.log(hesap.hesapNo); // "****7834" — maskelenmiş
console.log(hesap.sahip); // "Ahmet" — public
// Private alanlara dışarıdan erişim İMKÂNSIZ
// console.log(hesap.#bakiye); // SyntaxError!
// console.log(hesap.#hesapNo); // SyntaxError!
// hesap.#islemKaydet(); // SyntaxError!Private vs WeakMap (Eski Yöntem)
// ES2022 öncesinde private simülasyonu — WeakMap ile
const _bakiyeler = new WeakMap();
class EskiBankaHesabi {
constructor(baslangic) {
_bakiyeler.set(this, baslangic); // WeakMap'te sakla
}
get bakiye() {
return _bakiyeler.get(this); // WeakMap'ten oku
}
}
// Dezavantajlar:
// - Daha fazla boilerplate
// - Performans maliyeti
// - IDE desteği zayıf
// - Sınıf dışındaki koddan erişilebilir (aynı scope'taysa)
// ✅ Modern yol — # private fields kullan
class YeniBankaHesabi {
#bakiye;
constructor(baslangic) {
this.#bakiye = baslangic;
}
get bakiye() {
return this.#bakiye;
}
}Static Private
class Config {
// Static private — sınıf seviyesinde gizli veri
static #instance = null;
static #defaults = {
tema: "light",
dil: "tr",
};
#ayarlar;
constructor() {
this.#ayarlar = { ...Config.#defaults };
}
static getInstance() {
if (!Config.#instance) {
Config.#instance = new Config();
}
return Config.#instance;
}
al(key) {
return this.#ayarlar[key];
}
ayarla(key, value) {
this.#ayarlar[key] = value;
}
}
const config = Config.getInstance();
config.ayarla("tema", "dark");
console.log(config.al("tema")); // "dark"
const config2 = Config.getInstance();
console.log(config2.al("tema")); // "dark" — aynı instance!
console.log(config === config2); // truetoString, valueOf ve Symbol.toPrimitive
Sınıfınıza özel tip dönüşüm davranışları ekleyebilirsin:
class Para {
#miktar;
#birim;
constructor(miktar, birim = "TRY") {
this.#miktar = miktar;
this.#birim = birim;
}
// String dönüşümü
toString() {
return `${this.#miktar.toFixed(2)} ${this.#birim}`;
}
// Sayısal dönüşüm
valueOf() {
return this.#miktar;
}
// Gelişmiş: Symbol.toPrimitive — duruma göre dönüşüm
[Symbol.toPrimitive](hint) {
if (hint === "string") {
return this.toString();
}
if (hint === "number") {
return this.#miktar;
}
// "default" — + operatörü gibi
return this.#miktar;
}
}
const fiyat = new Para(149.90, "TRY");
console.log(`Fiyat: ${fiyat}`); // "Fiyat: 149.90 TRY" — string hint
console.log(fiyat + 50); // 199.9 — default/number hint
console.log(fiyat > 100); // true — number hint
console.log(+fiyat); // 149.9 — number hintYaygın Hatalar ve Tuzaklar
Hata 1: Metotlardaki this Kaybı
class Sayac {
#deger = 0;
artir() {
this.#deger++;
console.log(this.#deger);
}
}
const sayac = new Sayac();
sayac.artir(); // ✅ 1
// ❌ Metodu değişkene atayınca this kaybolur
const fn = sayac.artir;
// fn(); // TypeError: Cannot read private member #deger from an object
// // whose class did not declare it
// ✅ Çözüm 1: bind
const fn2 = sayac.artir.bind(sayac);
fn2(); // ✅ 2
// ✅ Çözüm 2: Arrow function class field (en yaygın çözüm)
class Sayac2 {
#deger = 0;
// Arrow function — this otomatik bağlı
artir = () => {
this.#deger++;
console.log(this.#deger);
};
}
const sayac2 = new Sayac2();
const fn3 = sayac2.artir;
fn3(); // ✅ Çalışır — arrow function this'i yakalar⚠️ Dikkat: Arrow function class fields her instance için ayrı fonksiyon oluşturur (prototype'a eklenmez). Az sayıda instance'ta sorun değil ama binlerce instance'ta bellek maliyeti artabilir.
Hata 2: Class Declaration Hoisting Yok
// ❌ Class declaration'lar hoist EDİLMEZ
// const k = new Kullanici("Ahmet"); // ReferenceError!
class Kullanici {
constructor(ad) {
this.ad = ad;
}
}
// ✅ Tanımlamadan sonra kullan
const k = new Kullanici("Ahmet"); // ✅Hata 3: Constructor'dan Nesne Dönmek
class Kisi {
constructor(ad) {
this.ad = ad;
// ⚠️ Constructor'dan nesne dönersen, o nesne döner!
// return { ad: "Sahte", hacked: true };
// ↑ new Kisi("Ahmet") → { ad: "Sahte", hacked: true }
// Primitif dönmek göz ardı edilir
// return 42; ← göz ardı edilir, this döner
}
}Gerçek Dünya Örneği: Todo Manager
class Todo {
static #sayac = 0;
#tamamlandi = false;
#olusturmaTarihi;
constructor(baslik, oncelik = "normal") {
this.id = ++Todo.#sayac;
this.baslik = baslik;
this.oncelik = oncelik;
this.#olusturmaTarihi = new Date();
}
get tamamlandi() {
return this.#tamamlandi;
}
get yasGun() {
const fark = Date.now() - this.#olusturmaTarihi.getTime();
return Math.floor(fark / (1000 * 60 * 60 * 24));
}
tamamla() {
this.#tamamlandi = true;
return this;
}
geriAl() {
this.#tamamlandi = false;
return this;
}
toString() {
const durum = this.#tamamlandi ? "✅" : "⬜";
return `${durum} [${this.oncelik}] ${this.baslik}`;
}
}
class TodoManager {
#todolar = [];
ekle(baslik, oncelik) {
const todo = new Todo(baslik, oncelik);
this.#todolar.push(todo);
return todo;
}
sil(id) {
this.#todolar = this.#todolar.filter((t) => t.id !== id);
}
tamamla(id) {
const todo = this.#todolar.find((t) => t.id === id);
if (todo) todo.tamamla();
return todo;
}
get bekleyenler() {
return this.#todolar.filter((t) => !t.tamamlandi);
}
get tamamlananlar() {
return this.#todolar.filter((t) => t.tamamlandi);
}
get istatistik() {
return {
toplam: this.#todolar.length,
bekleyen: this.bekleyenler.length,
tamamlanan: this.tamamlananlar.length,
tamamlanmaOrani: this.#todolar.length
? ((this.tamamlananlar.length / this.#todolar.length) * 100).toFixed(1) + "%"
: "0%",
};
}
// Iterable protokolü
[Symbol.iterator]() {
return this.#todolar[Symbol.iterator]();
}
listele() {
if (this.#todolar.length === 0) {
console.log("📋 Yapılacaklar listesi boş");
return;
}
console.log("📋 Yapılacaklar:");
for (const todo of this) {
console.log(` ${todo}`);
}
const { bekleyen, tamamlanan } = this.istatistik;
console.log(`\n Bekleyen: ${bekleyen} | Tamamlanan: ${tamamlanan}`);
}
}
// Kullanım
const manager = new TodoManager();
manager.ekle("JavaScript dersini bitir", "yüksek");
manager.ekle("Market alışverişi", "normal");
manager.ekle("Spor yap", "düşük");
manager.tamamla(1);
manager.listele();
// 📋 Yapılacaklar:
// ✅ [yüksek] JavaScript dersini bitir
// ⬜ [normal] Market alışverişi
// ⬜ [düşük] Spor yap
//
// Bekleyen: 2 | Tamamlanan: 1
console.log(manager.istatistik);
// { toplam: 3, bekleyen: 2, tamamlanan: 1, tamamlanmaOrani: "33.3%" }Özet
Bu derste ES6 class syntax'ını derinlemesine öğrendik:
`class` keyword'ü prototipal kalıtımın üzerine sözdizimsel şekerdir. Arkada hâlâ prototype chain çalışır ama syntax çok daha temiz ve okunabilir.
Constructor
newile çağrıldığında instance'ı başlatır. Class fields constructor dışında varsayılan değerler tanımlamayı sağlar.Getter/setter özellik erişimini özelleştirmeyi sağlar — özellik gibi görünür ama arkada fonksiyon çalışır. Veri doğrulama ve hesaplanan özellikler için ideal.
Static metot ve özellikler sınıfa aittir, instance'lara değil. Factory pattern, yardımcı fonksiyonlar için kullanılır.
Private fields (#) gerçek kapsülleme sağlar — dışarıdan hiçbir şekilde erişilemez. Önceki WeakMap tabanlı hack'lerin yerini aldı.
Arrow function class fields this kaybını çözer ama her instance için ayrı fonksiyon oluşturur.
Bir sonraki derste kalıtım, polimorfizm ve composition vs inheritance tartışmasını inceleyeceğiz.
AI Asistan
Sorularını yanıtlamaya hazır