İçeriğe geç
XDD.baby Yazılım Öğrenme
Sözlük Hata rehberi 0/56

Blok 2 · Ders 16

Sınıflar ve this

class, constructor, metot, this ve kalıtım: mülakatlarda sorulan nesne yönelimli temeller.

Bu dersin sonunda

  • class ve constructor ile bir kalıp yazıp new ile ondan istediğin kadar obje üretebilirsin
  • Metotların içinde this ile objenin kendi bilgisine ulaşabilir, this kaybolunca ok fonksiyonu ya da bind ile düzeltebilirsin
  • extends ve super ile bir sınıftan yeni sınıf türetip metotlarını geçersiz kılabilirsin
  • get, set ve # ile bir değeri hesaplanan, denetlenen ve dışarıdan korunan bir alana çevirebilirsin
  • Bir iş için sınıf mı, düz obje mi, fonksiyon mu gerektiğine karar verebilirsin
  • Mülakatta this, kalıtım ve kapsüllemeyi kısa ve doğru anlatabilirsin
Bu sayfada
  1. Aynı yapıda çok şampiyon
  2. İlk sınıfın: class, constructor ve new
  3. Metotlar ve alanlar
  4. this: metodun "kendi" sözcüğü
  5. this kaybolunca: setTimeout, ok fonksiyonu ve bind
  6. Kalıtım: extends ve super
  7. get ve set: hesaplanan ve denetlenen alanlar
  8. Özel alanlar: # ile kilitli kasa
  9. static: sınıfın kendisine ait olanlar
  10. Sınıf mı, düz obje mi, fonksiyon mu?
  11. Mülakatta sınıflar
  12. Sık hatalar
  13. Görevler
  14. Kendini sına
  15. Kayıt noktası

Ders 4'te bir şampiyonun bütün bilgisini tek objede topladın, Ders 5'te bu objeleri bir diziye dizip takım kurdun. Bu derste aynı yapıda istediğin kadar obje üreten bir kalıp yazmayı öğreneceksin: sınıf (class). Kalıba hem bilgiyi (can, hasar) hem de o bilgiyle çalışan davranışları (saldır, yeniden doğ) koyacaksın.

Sonra this sözcüğünün kimi gösterdiğini, bir sınıftan yeni sınıf türetmeyi ve bir değeri dışarıdan korumayı göreceksin. Bunlar iş görüşmelerinde en sık sorulan konulardandır; dersin sonunda bu soruları kısa ve doğru cevaplayabileceksin. Bütün örnekleri C:\kod\ogren\ders16.js dosyasına yaz; her yeni örnekte eski kodu sil ya da satırların başına // koy.

Aynı yapıda çok şampiyon

Önce sınıfsız yolu dene. Üç şampiyonu Ders 4'teki gibi elle yaz ve bir fonksiyonla birbirine saldırt:

ders16.js
const ahri = { isim: "Ahri", hp: 100, hasar: 60 }const garen = { isim: "Garen", hp: 620, hasar: 70 }const lux = { isim: "Lux", hP: 90, hasar: 55 } function saldir(saldiran, hedef) {    hedef.hp = hedef.hp - saldiran.hasar    console.log(`${hedef.isim} canı: ${hedef.hp}`)} saldir(garen, ahri)saldir(ahri, lux)
Çıktı
Ahri canı: 30Lux canı: NaN

Lux'un canı neden NaN? Üçüncü satırda hp yerine hP yazılmış. lux.hp diye bir alan yok ve undefined - 60 işleminin sonucu NaN (sayı değil).

Program hata vermedi, yanlış sonucu sessizce yazdı. Bu yolun üç sorunu var:

  • Tekrar: Her şampiyon için aynı alanları baştan yazıyorsun. Oyunda 170 şampiyon varsa 170 kez.
  • Sessiz yazım hatası: hP gibi bir hata, ancak sonuç saçmalayınca fark ediliyor.
  • Veri ile davranış ayrı: saldir fonksiyonu şampiyonlardan kopuk duruyor. saldir(garen, ahri) yazarken hangisinin vuran, hangisinin vurulan olduğunu her seferinde hatırlaman gerekiyor.

JavaScript'in bu iş için bir aracı var: sınıf. Sınıf, objelerin hangi alanlara sahip olacağını ve neler yapabileceğini bir kez tarif ettiğin bir kalıptır. Kalıptan ürettiğin her obje aynı alanlarla ve aynı metotlarla doğar.

İlk sınıfın: class, constructor ve new

ders16.js dosyasının içini sil ve şunu yaz:

ders16.js — ogren — Visual Studio Code
ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp    }} const ahri = new Sampiyon("Ahri", 100)const garen = new Sampiyon("Garen", 620)console.log(ahri)console.log(garen.isim, garen.hp)
PROBLEMSOUTPUTDEBUG CONSOLETERMINAL
PS C:\kod\ogren> node ders16.jsSampiyon { isim: 'Ahri', hp: 100 }Garen 620
Ln 1, Col 1Spaces: 4UTF-8JavaScript

Üstte kalıp (sınıf), altında kalıptan üretilen iki obje. Çıktıdaki Sampiyon { ... } ön eki, objenin hangi kalıptan çıktığını gösteriyor.

Satırları tek tek oku:

  • class Sampiyon { ... }: Sampiyon adında bir kalıp tanımlar. Sınıf adları büyük harfle başlar ve her sözcüğün ilk harfi büyük yazılır: Sampiyon, CanIksiri. Bu yazıma PascalCase denir. JavaScript küçük harfe de izin verir ama yazılımcıların neredeyse hepsi bu alışkanlığa uyar: büyük harfle başlayan bir ad gören, onun bir kalıp olduğunu anlar.
  • constructor(isim, hp) { ... }: yapıcı. Kalıptan yeni bir obje üretilirken kendiliğinden çalışan özel bir metot. Parametreleri, new Sampiyon(...) parantezine yazdığın değerlerdir.
  • this.isim = isim: üretilmekte olan yeni objenin isim alanına, parametreyle gelen isim değerini koyar. Soldaki this.isim objenin alanı, sağdaki isim parametre. Aynı adı taşımaları zorunlu değil ama yaygın bir alışkanlık.
  • new Sampiyon("Ahri", 100): kalıptan yeni bir obje üretir.

Alan adlarını artık yalnız constructor içinde, bir kez yazıyorsun. Her şampiyon aynı satırlardan geçerek kurulduğu için Lux'taki gibi tek bir objede gizli kalan bir hP hatası olamaz; bir yazım hatası yaparsan bütün şampiyonlarda birden görürsün.

new yazdığın anda perde arkasında üç şey olur:

  1. Boş, yepyeni bir obje açılır.
  2. constructor çalışır. Onun içindeki this o yeni objedir; this.isim = isim gibi satırlar objeyi doldurur.
  3. Dolan obje döndürülür ve ahri kutusuna konur. constructor içine return yazmana gerek yok.
class Sampiyon isim: ? hp: ? değerler henüz yok Sınıf (kalıp) new new Sampiyon("Ahri", 100) ahriisim: "Ahri", hp: 100 new Sampiyon("Garen", 620) garenisim: "Garen", hp: 620 new Sampiyon("Lux", 90) luxisim: "Lux", hp: 90
Şekil 16.1 Sınıf bir kalıptır. new her çağrıldığında kalıptan, kendi değerlerini taşıyan yeni bir obje çıkar.
Sık hataTypeError: Class constructor Sampiyon cannot be invoked without 'new'

Ne zaman çıkar? Sınıfı bir fonksiyon gibi, başına new yazmadan çağırınca.

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp    }} const ahri = Sampiyon("Ahri", 100)
Hata çıktısı
C:\kod\ogren\ders16.js:8const ahri = Sampiyon("Ahri", 100)             ^ TypeError: Class constructor Sampiyon cannot be invoked without 'new'    at Object.<anonymous> (C:\kod\ogren\ders16.js:8:14)    ... Node.js v24.20.0

Nasıl düzelir? Mesaj çözümü kendisi söylüyor: "Sampiyon sınıfının yapıcısı new olmadan çağrılamaz." Başına new ekle: const ahri = new Sampiyon("Ahri", 100). Kendi yazdığın bir sınıftan obje üretirken önüne hep new yaz.

Metotlar ve alanlar

Şimdi şampiyona bir şey yaptıralım. Ders 5'ten beri takim.push(...), isim.toUpperCase(), takim.map(...) gibi metotlar kullanıyorsun: bir değere bağlı fonksiyonlar. Sınıfın içine yazdığın her fonksiyon, o sınıftan üretilen bütün objelerin metodu olur.

ders16.js
class Sampiyon {    constructor(isim, hp, hasar) {        this.isim = isim        this.hp = hp        this.hasar = hasar    }     saldir(hedef) {        hedef.hp = hedef.hp - this.hasar        console.log(`${this.isim} saldırdı, ${hedef.isim} ${this.hasar} hasar aldı (kalan can: ${hedef.hp})`)    }} const ahri = new Sampiyon("Ahri", 100, 60)const garen = new Sampiyon("Garen", 620, 70)ahri.saldir(garen)garen.saldir(ahri)
Çıktı
Ahri saldırdı, Garen 60 hasar aldı (kalan can: 560)Garen saldırdı, Ahri 70 hasar aldı (kalan can: 30)

Metot yazmanın üç kuralı var:

  • Başına function yazılmaz: ad, parantez, süslü parantez. function saldir(hedef) yazarsan SyntaxError: Unexpected identifier 'saldir' alırsın.
  • Metotların arasına virgül konmaz. Obje yazarken alanları virgülle ayırıyordun; sınıfın içi farklı.
  • Metodun içinde objenin kendi alanına this. ile ulaşırsın: this.hasar, this.isim. hedef ise parametre, yani saldırılan başka bir obje.

Artık vuran ile vurulan karışmıyor. ahri.saldir(garen) bir cümle gibi okunuyor: vuran noktanın solunda, vurulan parantezin içinde.

Bir şeye daha bak: console.log(ahri.saldir === garen.saldir) yazarsan true çıkar. Metot her objeye ayrı ayrı kopyalanmaz, kalıpta bir kez durur ve bütün örnekler aynı metodu kullanır. Peki aynı metot Ahri'nin hasarını mı, Garen'inkini mi kullanacağını nereden biliyor? Cevabı biraz aşağıda, this bölümünde.

Sık hataReferenceError: hasar is not defined

Ne zaman çıkar? Metodun içinde objenin alanını this. olmadan yazınca. Metodun içinde hasar adında bir değişken yok; hasar objenin alanı ve ona yalnız this.hasar diye ulaşılır.

ders16.js
class Sampiyon {    constructor(isim, hp, hasar) {        this.isim = isim        this.hp = hp        this.hasar = hasar    }     saldir(hedef) {        hedef.hp = hedef.hp - hasar    }} const ahri = new Sampiyon("Ahri", 100, 60)const garen = new Sampiyon("Garen", 620, 70)ahri.saldir(garen)
Hata çıktısı
C:\kod\ogren\ders16.js:9        hedef.hp = hedef.hp - hasar                              ^ ReferenceError: hasar is not defined    at Sampiyon.saldir (C:\kod\ogren\ders16.js:9:31)    at Object.<anonymous> (C:\kod\ogren\ders16.js:15:6)    ... Node.js v24.20.0

Nasıl düzelir? this.hasar yaz. Yığın izini de oku: hata Sampiyon.saldir metodunda çıkmış, o metodu da 15. satırdaki çağrı çalıştırmış. constructor içindeki this.hasar = hasar satırı değeri objeye koyar; metot da onu oradan, this.hasar diye okur.

Sınıf alanları: herkesin aynı başladığı bilgiler

Bazı bilgiler new ile verilmez, her şampiyon aynı değerle başlar: seviye 1'dir, envanter boştur. Bunları sınıfın en üstüne, constructor dışına sınıf alanı (class field) olarak yazabilirsin. Sonuçta onlar da objenin sıradan alanları olur; fark yalnızca nerede kuruldukları:

ders16.js
class Sampiyon {    seviye = 1    esyalar = []     constructor(isim, hp) {        this.isim = isim        this.hp = hp    }} const ahri = new Sampiyon("Ahri", 100)const lux = new Sampiyon("Lux", 90)ahri.esyalar.push("Büyü Kitabı")ahri.seviye = 2console.log(ahri)console.log(lux)
Çıktı
Sampiyon {  seviye: 2,  esyalar: [ 'Büyü Kitabı' ],  isim: 'Ahri',  hp: 100}Sampiyon { seviye: 1, esyalar: [], isim: 'Lux', hp: 90 }

Ahri'nin eşyası Lux'un envanterine geçmedi: her örnek kendi esyalar dizisini aldı. Ders 12'deki kopya ve referans konusunu hatırla; iki ad aynı diziyi gösterseydi biri ekleyince öteki de görürdü. Sınıf alanları her new çağrısında baştan oluşturulduğu için böyle bir karışıklık olmaz.

Çıktıda iki ayrıntı var. Sınıf alanları constructor'dan önce kurulduğu için seviye ve esyalar başta görünüyor. Ahri'nin satırı uzadığı için Node onu birden çok satıra böldü; anlamı aynı.

Sınıf alanının başına let, const ya da this. yazılmaz; yalnız ad, = ve değer. let seviye = 1 yazarsan SyntaxError: Unexpected identifier 'seviye', this.seviye = 1 yazarsan SyntaxError: Unexpected token '.' alırsın. Metotların içinden ise ona yine this.seviye diye ulaşırsın.

this: metodun "kendi" sözcüğü

saldir metodu tek, ama ahri.saldir(garen) Ahri'nin hasarını, garen.saldir(ahri) Garen'in hasarını kullandı. Metot bunu this sayesinde biliyor.

Kural tek cümle: this, metodu çağırdığın anda noktanın solunda yazan objedir. Metodun nerede yazıldığına değil, nasıl çağrıldığına bakılır.

Bunu deneyerek gör. Canı yazan bir durum metodu ekle; sonra metodu objeden koparıp tek başına çağır:

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp    }     durum() {        console.log(`${this.isim}: ${this.hp} can`)    }} const ahri = new Sampiyon("Ahri", 100)const garen = new Sampiyon("Garen", 620)ahri.durum()garen.durum() const durumYaz = ahri.durumdurumYaz()
Hata çıktısı
Ahri: 100 canGaren: 620 canC:\kod\ogren\ders16.js:8        console.log(`${this.isim}: ${this.hp} can`)                            ^ TypeError: Cannot read properties of undefined (reading 'isim')    at durum (C:\kod\ogren\ders16.js:8:29)    at Object.<anonymous> (C:\kod\ogren\ders16.js:18:1)    ... Node.js v24.20.0

İlk iki çağrıda noktanın solunda ahri ve garen vardı. 17. satırda parantez yok: metodu çağırmadın, fonksiyonun kendisini başka bir kutuya koydun (Ders 10: fonksiyon bir değerdir). 18. satırda durumYaz() diye çağırınca noktanın solunda kimse yok, this de undefined oluyor. Mesaj tam bunu söylüyor: "undefined'ın alanları okunamaz (isim okunurken)." Ders 4'te undefined'ın içine bakmaya çalışınca gördüğün mesajın aynısı; bu kez undefined olan şey this.

çağrı this sonuç ahri.durum() this = ahri Ahri: 100 can garen.durum() this = garen Garen: 620 can durumYaz() noktanın solunda obje yok this = undefined TypeError
Şekil 16.2 this, metodu çağırdığın anda noktanın solunda yazan objedir. Solda obje yoksa this de yoktur.

this kaybolunca: setTimeout, ok fonksiyonu ve bind

Metodu kimse bilerek objeden koparmaz. Ama bir metodu Ders 15'te öğrendiğin setTimeout'a vermek tam bunu yapar. Senaryo: Ahri ölüyor ve kısa bir süre sonra yeniden doğmalı. Oyunda bu süre saniyelerce, burada 100 milisaniye.

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp        this.maksHp = hp    }     durum() {        console.log(`${this.isim}: ${this.hp} can`)    }     yenidenDog() {        this.hp = this.maksHp        this.durum()    }} const ahri = new Sampiyon("Ahri", 100)ahri.hp = 0console.log("Ahri öldü, birazdan yeniden doğacak")setTimeout(ahri.yenidenDog, 100)
Hata çıktısı
Ahri öldü, birazdan yeniden doğacakC:\kod\ogren\ders16.js:14        this.durum()             ^ TypeError: this.durum is not a function    at Timeout.yenidenDog [as _onTimeout] (C:\kod\ogren\ders16.js:14:14)    ... Node.js v24.20.0

setTimeout(ahri.yenidenDog, 100) satırında yenidenDog adının sonunda parantez yok. Bu doğru, çünkü metodu çağırmıyorsun; setTimeout'a "100 ms sonra bunu çalıştır" diye veriyorsun (geri çağırma fonksiyonu, callback). Sorun şu: setTimeout'a giden şey yalnızca fonksiyon, yanında ahri gitmiyor. 100 ms sonra fonksiyon çağrılıyor ama noktanın solunda artık ahri yok, this de Ahri olmuyor.

Peki solunda ne var? Yığın izindeki at Timeout.yenidenDog satırı ipucu veriyor. Node, zamanlayıcıya verilen fonksiyonu kendi Timeout (zamanlayıcı) objesinin metodu gibi çağırır; noktanın solunda o obje durduğu için this de o olur. Zamanlayıcının durum adında bir metodu olmadığı için mesaj "this.durum bir fonksiyon değil" diyor. Kural yine tuttu: this, noktanın solundaki obje. Yalnız o obje artık Ahri değil.

setTimeout(ahri.yenidenDog, 100) this kaybolur 1. Saklanan yalnız yenidenDog ahri yanında değil 2. 100 ms sonra yenidenDog() solunda ahri yok 3. this ahri değil this.durum bulunamaz TypeError setTimeout(() => ahri.yenidenDog(), 100) this korunur 1. Saklanan küçük ok fonksiyonu ahri'yi içinde taşır 2. 100 ms sonra ahri.yenidenDog() solunda ahri var 3. this = ahri can yenilenir Ahri: 100 can
Şekil 16.3 Metodu setTimeout'a parantezsiz verince metot objesinden kopar. Küçük bir ok fonksiyonuyla sararsan nokta korunur.

İki yaygın çözüm var:

  • Ok fonksiyonuyla sarmak: setTimeout'a metodu değil, metodu noktayla çağıran küçük bir ok fonksiyonu ver: () => ahri.yenidenDog(). 100 ms sonra çalışan şey bu ok fonksiyonu; onun içinde ahri.yenidenDog() noktayla çağrıldığı için this Ahri olur.
  • bind: Fonksiyonların bind adında bir metodu var. lux.yenidenDog.bind(lux), this'i kalıcı olarak lux'a bağlanmış yeni bir fonksiyon üretir. O fonksiyon nereye giderse gitsin, nasıl çağrılırsa çağrılsın this hep Lux'tur.
ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp        this.maksHp = hp    }     durum() {        console.log(`${this.isim}: ${this.hp} can`)    }     yenidenDog() {        this.hp = this.maksHp        this.durum()    }} const ahri = new Sampiyon("Ahri", 100)const lux = new Sampiyon("Lux", 90)ahri.hp = 0lux.hp = 0setTimeout(() => ahri.yenidenDog(), 100)setTimeout(lux.yenidenDog.bind(lux), 200)
Çıktı
Ahri: 100 canLux: 90 can
Sık hataTypeError: this.durum is not a function

Ne zaman çıkar? Bir metodu parantezsiz olarak başka bir fonksiyona verince: setTimeout(ahri.yenidenDog, 100) gibi. Metot objesinden kopar ve this artık Ahri olmaz. Mesajdaki ad, metodun içinde neye ulaşılmaya çalışıldığına göre değişir. Metodu rakipler.forEach(ahri.saldir) gibi bir dizi metoduna verirsen this değeri undefined olur ve mesaj başka görünür: TypeError: Cannot read properties of undefined (reading 'hasar'). İkisinin sebebi aynı: metot objesinden koptu.

Nasıl düzelir? Metodu objesiyle birlikte gönder: setTimeout(() => ahri.yenidenDog(), 100) ya da setTimeout(ahri.yenidenDog.bind(ahri), 100). İkisini de yukarıdaki kodda gördün. Dizide de aynı yol: rakipler.forEach((rakip) => ahri.saldir(rakip)).

Ok fonksiyonunun kendi this'i yoktur

Sarmalayıcının ok fonksiyonu olması şart değildi: setTimeout(function () { ahri.yenidenDog() }, 100) da çalışır. İşi gören, içerideki noktalı çağrı. Ok fonksiyonu ile function arasındaki asıl fark, fonksiyonun gövdesinde this yazınca ortaya çıkar. Ders 10'da sonraya bıraktığımız fark tam olarak bu: ok fonksiyonlarının kendi this'i yoktur, yazıldıkları yerin this'ini kullanırlar. Bu özellik en çok bir metodun içinde geri çağırma yazarken işine yarar:

ders16.js
class Sampiyon {    constructor(isim) {        this.isim = isim        this.esyalar = ["Büyü Kitabı", "Mana Taşı"]    }     envanter() {        this.esyalar.forEach((esya) => {            console.log(`${this.isim} taşıyor: ${esya}`)        })    }     envanterBozuk() {        this.esyalar.forEach(function (esya) {            console.log(`${this.isim} taşıyor: ${esya}`)        })    }} const ahri = new Sampiyon("Ahri")ahri.envanter()ahri.envanterBozuk()
Hata çıktısı
Ahri taşıyor: Büyü KitabıAhri taşıyor: Mana TaşıC:\kod\ogren\ders16.js:15            console.log(`${this.isim} taşıyor: ${esya}`)                                ^ TypeError: Cannot read properties of undefined (reading 'isim')    at C:\kod\ogren\ders16.js:15:33    at Array.forEach (<anonymous>)    at Sampiyon.envanterBozuk (C:\kod\ogren\ders16.js:14:22)    ... Node.js v24.20.0

forEach, ona verdiğin fonksiyonu her eleman için noktasız çağırır. function ile yazılan fonksiyonun kendi this'i vardır; noktasız çağrıldığı için o da undefined olur. Ok fonksiyonunun ise kendi this'i yoktur, yazıldığı yerdekini kullanır: envanter metodunun this'ini, yani ahri'yi.

Bir metodu sık sık geri çağırma olarak vereceksen üçüncü bir yol daha var: metodu, değeri ok fonksiyonu olan bir sınıf alanı olarak yazmak (yenidenDog = () => { ... }). Sınıf alanı her new çağrısında yeni obje için kurulur; ok fonksiyonu da yazıldığı yerin this'ini, yani o yeni objeyi kullanır. Bu yüzden this her zaman o objedir ve setTimeout(ahri.yenidenDog, 100) bile çalışır. Bedeli şu: fonksiyon kalıpta tek kopya durmaz, her örneğe ayrı ayrı kopyalanır; console.log(ahri) çıktısında yenidenDog: [Function: yenidenDog] diye görünür. Yeni başlarken ilk iki çözüm yeter.

Kalıtım: extends ve super

Nisanci sınıfını sıfırdan yazarsan Sampiyon sınıfındaki her satırı kopyalarsın; Ders 3'teki "kendini tekrar etme" kuralı bozulur. Bunun yerine Nisanci sınıfını Sampiyon sınıfından türetirsin:

ders16.js
class Sampiyon {    constructor(isim, hp, hasar) {        this.isim = isim        this.hp = hp        this.hasar = hasar    }     saldir(hedef) {        hedef.hp = hedef.hp - this.hasar        console.log(`${this.isim} saldırdı, ${hedef.isim} ${this.hasar} hasar aldı (kalan can: ${hedef.hp})`)    }     durum() {        console.log(`${this.isim}: ${this.hp} can`)    }} class Nisanci extends Sampiyon {    constructor(isim, hp, hasar, menzil) {        super(isim, hp, hasar)        this.menzil = menzil    }} const jinx = new Nisanci("Jinx", 80, 75, 600)const garen = new Sampiyon("Garen", 620, 70)jinx.saldir(garen)jinx.durum()console.log(jinx)
Çıktı
Jinx saldırdı, Garen 75 hasar aldı (kalan can: 545)Jinx: 80 canNisanci { isim: 'Jinx', hp: 80, hasar: 75, menzil: 600 }

Yeni olan parçalar:

  • class Nisanci extends Sampiyon: "Nisanci, Sampiyon sınıfının her şeyini devralsın." Sampiyon üst sınıf, Nisanci alt sınıf.
  • super(isim, hp, hasar): üst sınıfın constructor metodunu çağırır. isim, hp, hasar alanlarını o kurar; alt sınıf yalnız kendine özgü menzil alanını ekler.
  • jinx.saldir(garen) ve jinx.durum(): Nisanci içine ikisini de yazmadın ama çalıştılar, çünkü üst sınıftan devralındılar.
  • console.log(jinx): obje Nisanci kalıbından çıktı ve dört alanın hepsini taşıyor; üçünü üst sınıf kurdu, birini alt sınıf.
Sık hataReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

Ne zaman çıkar? Alt sınıfın constructor metodunda, super(...) çağrısından önce this kullanınca ya da super çağrısını hiç yazmayınca.

ders16.js
class Sampiyon {    constructor(isim) {        this.isim = isim    }} class Nisanci extends Sampiyon {    constructor(isim, menzil) {        this.menzil = menzil        super(isim)    }} const jinx = new Nisanci("Jinx", 600)
Hata çıktısı
C:\kod\ogren\ders16.js:9        this.menzil = menzil        ^ ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor    at new Nisanci (C:\kod\ogren\ders16.js:9:9)    at Object.<anonymous> (C:\kod\ogren\ders16.js:14:14)    ... Node.js v24.20.0

Nasıl düzelir? Mesaj: "Türetilmiş sınıfta this'e dokunmadan ya da constructor'dan çıkmadan önce üst sınıfın yapıcısını çağırmalısın." Alt sınıfın objesi önce üst sınıfın yapıcısıyla kurulur; super(...) çalışmadan this henüz yoktur. super(isim) satırını constructor içinde en üste taşı.

Metodu geçersiz kılmak

Şu an Jinx, Garen'le aynı biçimde saldırıyor. Nişancıya kendine özgü bir saldırı vermek için Nisanci sınıfına, üst sınıftakiyle aynı adlı bir saldir metodu ekle. Dosyanın son satırlarını da değiştir:

ders16.js
class Sampiyon {    constructor(isim, hp, hasar) {        this.isim = isim        this.hp = hp        this.hasar = hasar    }     saldir(hedef) {        hedef.hp = hedef.hp - this.hasar        console.log(`${this.isim} saldırdı, ${hedef.isim} ${this.hasar} hasar aldı (kalan can: ${hedef.hp})`)    }     durum() {        console.log(`${this.isim}: ${this.hp} can`)    }} class Nisanci extends Sampiyon {    constructor(isim, hp, hasar, menzil) {        super(isim, hp, hasar)        this.menzil = menzil    }     saldir(hedef) {        console.log(`${this.isim} ${this.menzil} birim uzaktan ateş ediyor`)        super.saldir(hedef)    }} const jinx = new Nisanci("Jinx", 80, 75, 600)const garen = new Sampiyon("Garen", 620, 70)jinx.saldir(garen)garen.saldir(jinx)
Çıktı
Jinx 600 birim uzaktan ateş ediyorJinx saldırdı, Garen 75 hasar aldı (kalan can: 545)Garen saldırdı, Jinx 70 hasar aldı (kalan can: 10)
  • saldir(hedef): üst sınıfta da aynı adlı bir metot var. Alt sınıfta yeniden yazınca nişancılar için bu sürüm çalışır. Buna metodu geçersiz kılmak (override) denir.
  • super.saldir(hedef): üst sınıfın saldir sürümünü çağırır. Nişancı önce kendi cümlesini söylüyor, hasar hesabını üst sınıfa bırakıyor; hesap kodu tek yerde kalıyor.
  • garen.saldir(jinx): Garen bir nişancı değil, sıradan bir Sampiyon. Onun için üst sınıftaki sürüm çalıştı; "ateş ediyor" cümlesi yazılmadı.

Bir objenin hangi kalıptan çıktığını instanceof ile sorabilirsin. Adı İngilizce instance of (örneği) sözlerinden gelir: jinx instanceof Nisanci, "jinx bir Nisanci örneği mi?" demektir ve true ya da false verir. Dosyanın sonuna şu satırı ekle:

ders16.js
console.log(jinx instanceof Nisanci, jinx instanceof Sampiyon, garen instanceof Nisanci)
Çıktının son satırı
true true false

Jinx hem Nisanci hem Sampiyon örneğidir, çünkü her nişancı bir şampiyondur. Garen ise bir Nisanci değildir.

Sampiyon üst sınıf isim, hp, hasar saldir(hedef) durum() Nisanci alt sınıf menzil saldir(hedef) üsttekini geçersiz kılar extends ile devralır jinx.durum() Nisanci içinde yok, üst sınıftaki çalışır jinx.saldir(garen) Nisanci içinde var, kendi sürümü çalışır
Şekil 16.4 Alt sınıf üst sınıfın her şeyini devralır. Bir metot çağrılınca önce objenin kendi sınıfına, orada yoksa üst sınıfa bakılır.

Aynı çağrı, farklı davranış

Kalıtımın asıl gücü, farklı türdeki şampiyonları aynı biçimde kullanabilmek. Aynı dosyanın sonuna, instanceof satırının altına bir büyücü sınıfı ve bir takım ekle. Hedef, antrenman modundaki gibi bir eğitim kuklası. Çalıştırınca önceki satırların çıktısının altına dört satır eklenir:

ders16.js
class Buyucu extends Sampiyon {    saldir(hedef) {        const buyuHasari = this.hasar * 2        hedef.hp = hedef.hp - buyuHasari        console.log(`${this.isim} büyü yaptı, ${hedef.isim} ${buyuHasari} hasar aldı (kalan can: ${hedef.hp})`)    }} const kukla = new Sampiyon("Kukla", 5000, 0)const takim = [garen, jinx, new Buyucu("Lux", 90, 55)] for (const sampiyon of takim) {    sampiyon.saldir(kukla)}
Çıktının son dört satırı
Garen saldırdı, Kukla 70 hasar aldı (kalan can: 4930)Jinx 600 birim uzaktan ateş ediyorJinx saldırdı, Kukla 75 hasar aldı (kalan can: 4855)Lux büyü yaptı, Kukla 110 hasar aldı (kalan can: 4745)

Döngü her şampiyona aynı şeyi söylüyor: saldir(kukla). Her biri kendi sınıfının sürümünü çalıştırıyor. Döngüyü yazan kodun hangi şampiyonun hangi türden olduğunu bilmesine gerek yok. Buyucu sınıfına constructor yazmadığına da dikkat et: kendine özgü bir alanı olmadığı için üst sınıfın yapıcısı yetti.

get ve set: hesaplanan ve denetlenen alanlar

Arayüzde can barının yanına yüzde yazacaksın. Yüzdeyi ayrı bir canYuzdesi alanında tutarsan her hasarda onu da güncellemen gerekir; unuttuğun an yüzde ile can birbirini tutmaz. Doğrusu, yüzdeyi her sorulduğunda o anki candan hesaplamak.

Bunun için getter yazarsın:

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp        this.maksHp = hp    }     get canYuzdesi() {        return Math.round((this.hp / this.maksHp) * 100)    }} const garen = new Sampiyon("Garen", 620)console.log(garen.canYuzdesi)garen.hp = 400console.log(`Garen: %${garen.canYuzdesi}`)garen.hp = 31console.log(`Garen: %${garen.canYuzdesi}`)
Çıktı
100Garen: %65Garen: %5

get canYuzdesi() { ... } bir metot gibi yazılır ama bir alan gibi okunur: garen.canYuzdesi, parantez yok. Her okunuşta içindeki kod yeniden çalışır ve o anki candan yeni bir değer döndürür. %65 yazmamızın sebebi, Türkçede yüzde işaretinin sayının önüne gelmesi.

Sık hataTypeError: garen.canYuzdesi is not a function

Ne zaman çıkar? Getter'ı bir metot gibi, parantezle çağırınca.

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp        this.maksHp = hp    }     get canYuzdesi() {        return Math.round((this.hp / this.maksHp) * 100)    }} const garen = new Sampiyon("Garen", 620)console.log(garen.canYuzdesi())
Hata çıktısı
C:\kod\ogren\ders16.js:14console.log(garen.canYuzdesi())                  ^ TypeError: garen.canYuzdesi is not a function    at Object.<anonymous> (C:\kod\ogren\ders16.js:14:19)    ... Node.js v24.20.0

Nasıl düzelir? Parantezi sil: garen.canYuzdesi. Getter okunur okunmaz çalışıp bir sayı (100) verir; sonuna () koyunca JavaScript o sayıyı fonksiyon gibi çalıştırmaya kalkar.

Şimdi ikinci sorun: can 0'ın altına inmemeli, en yüksek canın üstüne de çıkmamalı. ahri.hp = ... yazan her yerde bunu ayrı ayrı denetlemek yerine denetimi atamanın kendisine koyabilirsin: setter. set hp(deger) yazarsan ahri.hp = ... her çalıştığında bu fonksiyon çağrılır ve atanan değer deger parametresine gelir. İlk deneme çöküyor:

Sık hataRangeError: Maximum call stack size exceeded

Ne zaman çıkar? Bir setter'ın içinde, setter'la aynı adlı alana atama yapınca. Genel olarak, bir fonksiyon durmadan kendini çağırdığında.

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.maksHp = hp        this.hp = hp    }     set hp(deger) {        this.hp = Math.max(0, Math.min(deger, this.maksHp))    }} const ahri = new Sampiyon("Ahri", 100)
Hata çıktısı
C:\kod\ogren\ders16.js:9        this.hp = Math.max(0, Math.min(deger, this.maksHp))                ^ RangeError: Maximum call stack size exceeded    at set hp (C:\kod\ogren\ders16.js:9:17)    at set hp (C:\kod\ogren\ders16.js:9:17)    at set hp (C:\kod\ogren\ders16.js:9:17)    ... Node.js v24.20.0

Sınırlama fikri doğru: Math.min(deger, this.maksHp) değerin en yüksek canı aşmasını, Math.max(0, ...) da sıfırın altına düşmesini engeller (Ders 9). Ama setter'ın içindeki this.hp = ... yine bir hp ataması, yani setter kendini çağırıyor; o da yine kendini... Yığın izinde aynı at set hp satırının üst üste dizilmesi bunu gösteriyor, mesaj da "en fazla çağrı yığını boyutu aşıldı" diyor.

Bu hata her çalıştırmada birebir aynı görünmeyebilir: yığın hangi adımda taştıysa ^ oraya konur. Sende ^ işareti Math'in altında durabilir ya da yığın izinin başında at Math.min (<anonymous>) satırı çıkabilir. Mesaj ve üst üste dizilen at set hp satırları hep aynıdır.

Nasıl düzelir? Değeri setter'dan farklı bir adla sakla. En doğru yer, dışarıdan dokunulamayan bir özel alan; bir sonraki bölümün konusu.

Özel alanlar: # ile kilitli kasa

Adı # ile başlayan alan özeldir: onu yalnızca sınıfın kendi kodu okuyup yazabilir. Can değerini #hp içinde saklar, dışarıya get hp ve set hp kapılarını açarsın:

ders16.js
class Sampiyon {    #hp     constructor(isim, hp) {        this.isim = isim        this.maksHp = hp        this.#hp = hp    }     get hp() {        return this.#hp    }     set hp(deger) {        this.#hp = Math.max(0, Math.min(deger, this.maksHp))    }} const ahri = new Sampiyon("Ahri", 100)ahri.hp = ahri.hp - 130console.log(ahri.hp)ahri.hp = ahri.hp + 500console.log(ahri.hp)console.log(ahri)
Çıktı
0100Sampiyon { isim: 'Ahri', maksHp: 100 }
  • #hp satırı özel alanı tanıtır. Kullanacağın her özel alanı sınıfın gövdesinde mutlaka böyle tanıtırsın; alışkanlık olarak en üste yazılır. Tanıtmayı unutursan aşağıdaki hata kartındaki SyntaxError'ı alırsın.
  • Setter artık this.#hp alanına yazıyor. #hp ile hp farklı adlar olduğu için setter artık kendini çağırmıyor.
  • ahri.hp - 130 işleminin sonucu -30'du, setter onu 0'a çekti. Sonraki atamada 0 + 500 işlemi 500 verdi, setter onu 100'e indirdi. Artık kodun hiçbir yerinde can sınırların dışına çıkamaz.
  • console.log(ahri) özel alanı göstermiyor. Göremediğin için yok sanma; ahri.hp getter'ı onu okuyabiliyor.
  • Getter'ı yazmayı unutup yalnız set hp yazarsan hata almazsın, ama ahri.hp her okunuşta undefined verir. ahri.hp = ahri.hp - 10 satırı da sessizce NaN hesaplar ve can NaN olur. Bu yüzden get ile set çoğunlukla çift yazılır.

İstersen setter'da sınırlamak yerine Ders 13'teki gibi throw ile geçersiz değeri tamamen reddedebilirsin; görevlerde bunu yapacaksın. Metotların adı da # ile başlayabilir (#hasarHesapla()); onları da yalnız sınıfın içi çağırabilir.

ahri (Sampiyon örneği) set hp get hp #hp 0 yalnız sınıfın içi isim ve maksHp: açık alanlar ahri.hp = -30 set onu 0'a çeker ahri.hp get okur: 0 ahri.#hp = 9999 ✗ SyntaxError
Şekil 16.5 Özel alana dışarıdan ulaşılamaz. Okuma ve yazma, sınıfın açtığı get ve set kapılarından geçer.
Sık hataSyntaxError: Private field '#hp' must be declared in an enclosing class

Ne zaman çıkar? Özel alana sınıfın dışından ulaşmaya çalışınca. Sınıfın içinde this.#hp yazıp sınıfın gövdesine #hp satırını koymayı unutunca da aynı mesaj çıkar.

ders16.js
class Sampiyon {    #hp     constructor(isim, hp) {        this.isim = isim        this.#hp = hp    }} const ahri = new Sampiyon("Ahri", 100)console.log("Program başladı")ahri.#hp = 9999
Hata çıktısı
C:\kod\ogren\ders16.js:12ahri.#hp = 9999    ^ SyntaxError: Private field '#hp' must be declared in an enclosing class    at wrapSafe (node:internal/modules/cjs/loader:1861:18)    ... Node.js v24.20.0

Nasıl düzelir? Mesaj: "'#hp' özel alanı, onu çevreleyen bir sınıfta tanımlanmış olmalı." Yani bu satır #hp alanını tanıyan sınıfın içinde değil. Dışarıdan yalnız sınıfın açtığı kapıları kullan: ahri.hp (bu kodda henüz yoksa sınıfa bir get hp() ekle). Hata sınıfın içindeki bir satırı gösteriyorsa sınıfın gövdesine #hp satırını eklemeyi unutmuşsundur. Bir SyntaxError olduğu için "Program başladı" bile yazılmadı; Node dosyayı çalıştırmadan önce okurken yakaladı (Ders 1). Özel alanın amacı tam olarak bu: kuralı çiğneyen kod hiç çalışmasın.

static: sınıfın kendisine ait olanlar

Şimdiye kadar yazdığın her alan ve metot örneklere aitti: her şampiyonun kendi hp değeri var. Bazı bilgiler ise tek tek şampiyonlara değil, kalıbın kendisine aittir: "Şimdiye kadar kaç şampiyon üretildi?" ya da "Bu listedeki en dayanıklı şampiyon hangisi?" Bunları static ile yazarsın ve sınıfın adıyla kullanırsın:

ders16.js
class Sampiyon {    static toplam = 0     constructor(isim, hp) {        this.isim = isim        this.hp = hp        Sampiyon.toplam++    }     static enDayanikli(liste) {        let enIyi = liste[0]        for (const sampiyon of liste) {            if (sampiyon.hp > enIyi.hp) {                enIyi = sampiyon            }        }        return enIyi    }} const takim = [new Sampiyon("Ahri", 100), new Sampiyon("Garen", 620), new Sampiyon("Lux", 90)]console.log(Sampiyon.toplam)console.log(Sampiyon.enDayanikli(takim).isim)console.log(takim[0].toplam)takim[0].enDayanikli(takim)
Hata çıktısı
3GarenundefinedC:\kod\ogren\ders16.js:25takim[0].enDayanikli(takim)         ^ TypeError: takim[0].enDayanikli is not a function    at Object.<anonymous> (C:\kod\ogren\ders16.js:25:10)    ... Node.js v24.20.0
  • static toplam = 0: örneklerde değil, Sampiyon sınıfının kendisinde duran tek bir sayaç. constructor her çalıştığında Sampiyon.toplam++ onu bir artırıyor.
  • static enDayanikli(liste): tek bir şampiyonla değil, bir listeyle ilgili bir iş; o yüzden sınıfın adıyla çağrılıyor: Sampiyon.enDayanikli(takim).
  • Örnekler static alanları ve metotları görmez: takim[0].toplam undefined verdi, takim[0].enDayanikli(...) de TypeError.

Statik metotları aslında çok kullandın: Number.isNaN(...), Object.keys(...), Promise.all(...). Hepsi bir örneğin değil, sınıfın adıyla çağrılır.

Sınıf mı, düz obje mi, fonksiyon mu?

Sınıfları öğrenince her şeyi sınıfa çevirmek isteyeceksin; gerek yok. Karar vermeden önce JSON ile ilgili bir sürprizi gör, çünkü Ders 17'de oyununu dosyaya kaydedeceksin:

ders16.js
class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp    }     durum() {        console.log(`${this.isim}: ${this.hp} can`)    }     static kayittan(veri) {        return new Sampiyon(veri.isim, veri.hp)    }} const ahri = new Sampiyon("Ahri", 75)const metin = JSON.stringify(ahri)console.log(metin) const geriGelen = JSON.parse(metin)console.log(geriGelen) const yeniAhri = Sampiyon.kayittan(geriGelen)yeniAhri.durum()geriGelen.durum()
Hata çıktısı
{"isim":"Ahri","hp":75}{ isim: 'Ahri', hp: 75 }Ahri: 75 canC:\kod\ogren\ders16.js:25geriGelen.durum()          ^ TypeError: geriGelen.durum is not a function    at Object.<anonymous> (C:\kod\ogren\ders16.js:25:11)    ... Node.js v24.20.0

JSON.stringify yalnız objenin kendi alanlarını metne çevirir (Ders 12). Metinden geri dönen { isim: 'Ahri', hp: 75 } artık bir Sampiyon değil, düz bir obje: önünde sınıf adı yok, metotları da yok. Kayıttan sınıf örneğini yeniden kurmak için kayittan gibi bir static metot yazmak yaygın bir çözüm.

Özel alanlar (#hp) ve getter'lar ise metne hiç girmez. Canı #hp içinde tutan sınıfı metne çevirirsen sonuç {"isim":"Ahri","maksHp":100} olur: can kaybolur. Böyle bir objeyi kaydederken saklanacak bilgiyi elle düz bir objeye koy: JSON.stringify({ isim: ahri.isim, hp: ahri.hp }).

Karar verirken şu tabloya bak:

ElindekiSeçÖrnek
Yalnız bilgi, davranış yokDüz objeAyarlar { ses: 70 }, gelen JSON
Girdi alıp sonuç döndüren işFonksiyonkdaHesapla(oyuncu)
Çok obje, değişen veri, kurallarSınıfŞampiyon, cüzdan, envanter
Ortak davranış, farklı ayrıntıSınıf ve kalıtımNişancı, büyücü, hata türlerin

Kalıtım için "X bir Y'dir" sınamasını kullan. "Nişancı bir şampiyondur" doğru, kalıtım uygun. "Şampiyon bir envanterdir" yanlış; şampiyonun bir envanteri vardır, o zaman envanter bir alan olur (this.esyalar). Kalıtım zincirini kısa tut; bir iki basamaktan derin zincirler okunmaz hâle gelir.

İş hayatında iki tarzla da karşılaşacaksın. React bileşenleri (Blok 6) bugün çoğunlukla fonksiyonla yazılıyor; sınıflı React bileşenlerini eski projelerde görürsün. Hata türleri, oyun motorları ve bazı büyük çatılar (framework) ise sınıfları bolca kullanır. Ders 17'deki projede oyuncuyu bir sınıfla tutacak ve o sınıfı ayrı bir dosyaya koyacaksın: Ders 14'teki export function gibi export class Oyuncu { ... } yazıp öteki dosyada import { Oyuncu } from "./oyuncu.js" ile alacaksın.

Mülakatta sınıflar

Bu dersin konusu nesne yönelimli programlamanın (object-oriented programming, kısaca OOP) temeli; nesne, objenin Türkçesi. Junior mülakatlarında şu sorular sık gelir. Cevapları ezberleme; her birini bu dersteki bir örnekle anlatabildiğinde hazırsın.

SoruKısa cevap
this nedir?Metodu çağıran obje: ahri.saldir() içinde ahri. Yazıldığı yere değil, çağrılış biçimine bakılır. Ok fonksiyonunun kendi this'i yoktur, yazıldığı yerinkini kullanır. bind ile sabitlenebilir.
Kalıtım nedir?Bir sınıfın başka bir sınıfın alanlarını ve metotlarını devralması (extends). Alt sınıf üst sınıfın yapıcısını super(...) ile çağırır, istediği metodu geçersiz kılar.
Kapsülleme nedir?İç bilgiyi gizleyip yalnız kurallı kapılardan değiştirmek: #hp özel alanı, get ve set.
Çok biçimlilik nedir?Aynı çağrının sınıfa göre farklı davranması: takımdaki herkese saldir() deyip her birinin kendi saldırısını yapması.
JavaScript'te sınıflar nasıl çalışır?class, dilin prototip (prototype) düzeninin daha okunaklı bir yazımıdır. Prototip, bir objenin kendinde bulamadığı alanı ya da metodu aradığı ortak objedir. Metotlar örneklere kopyalanmaz, sınıfın prototipinde bir kez durur; ahri.saldir === garen.saldir sonucunun true olması bu yüzden.

"OOP'nin dört ilkesi" diye sorulursa dördüncüsü soyutlamadır (abstraction): kullanana yalnız gereken kapıları göstermek. ahri.saldir(garen) diyen kod, hasarın içeride nasıl hesaplandığını bilmek zorunda değil.

Sık hatalar

Bölümlerde gördüklerine ek olarak üç hata daha. Bir hata aldığında bütün derslerin hatalarının toplandığı hata rehberine de bakabilirsin.

Sık hataTypeError: ahri.saldir is not a function

Ne zaman çıkar? Bir alana, bir metotla aynı adı verince. Burada saldırı gücü this.saldir alanına konmuş. Alan objenin kendisinde, metot ise kalıpta durur; JavaScript önce objenin kendisine baktığı için alan metodu örter ve ahri.saldir artık 60 sayısıdır. Ders 10'daki gölgeleme gibi: aynı ad iki yerde varsa yakındaki kazanır.

ders16.js
class Sampiyon {    constructor(isim, saldir) {        this.isim = isim        this.saldir = saldir    }     saldir(hedef) {        console.log(`${this.isim}, ${hedef.isim} hedefine saldırdı`)    }} const ahri = new Sampiyon("Ahri", 60)const garen = new Sampiyon("Garen", 70)ahri.saldir(garen)
Hata çıktısı
C:\kod\ogren\ders16.js:14ahri.saldir(garen)     ^ TypeError: ahri.saldir is not a function    at Object.<anonymous> (C:\kod\ogren\ders16.js:14:6)    ... Node.js v24.20.0

Nasıl düzelir? Alanla metoda farklı adlar ver. Alışkanlık edin: alanlara bir şeyin adını (hasar, saldiriGucu), metotlara bir eylemin adını (saldir, iyiles) koy.

Sık hataSyntaxError: Unexpected token ','

Ne zaman çıkar? Sınıfın metotları arasına, objedeki gibi virgül koyunca.

ders16.js
class Sampiyon {    constructor(isim) {        this.isim = isim    },     selamVer() {        console.log("Ben " + this.isim)    }}
Hata çıktısı
C:\kod\ogren\ders16.js:4    },     ^ SyntaxError: Unexpected token ','    at wrapSafe (node:internal/modules/cjs/loader:1861:18)    ... Node.js v24.20.0

Nasıl düzelir? Virgülü sil. Objede alanlar virgülle ayrılır; sınıfın içinde metotlar arka arkaya, virgülsüz yazılır.

Sık hataReferenceError: Cannot access 'Sampiyon' before initialization

Ne zaman çıkar? Sınıfı, tanımladığın satırdan önce kullanınca. Ders 10'da function ile yazılan fonksiyonların tanımlanmadan önce çağrılabildiğini (hoisting) gördün; sınıflar böyle değildir, let ve const gibi davranırlar.

ders16.js
const ahri = new Sampiyon("Ahri", 100) class Sampiyon {    constructor(isim, hp) {        this.isim = isim        this.hp = hp    }}
Hata çıktısı
C:\kod\ogren\ders16.js:1const ahri = new Sampiyon("Ahri", 100)             ^ ReferenceError: Cannot access 'Sampiyon' before initialization    at Object.<anonymous> (C:\kod\ogren\ders16.js:1:14)    ... Node.js v24.20.0

Nasıl düzelir? Sınıf tanımlarını dosyanın üstüne, kullandığın satırlardan önceye taşı.

Görevler

Her görevi ders16.js dosyasında yap; önceki kodu sil ya da satırların başına // koy. Önce kendin dene, takılırsan ipuçlarını sırayla aç. Çözüme en son bak.

Görev 1

Sihirdar kartı

Oyuncu adında bir sınıf yaz. constructor iki değer alsın: kullaniciAdi ve seviye. Sınıfın iki metodu olsun:

  • tanit(): Deniz (seviye 30) biçiminde yazsın.
  • seviyeAtla(): seviyeyi 1 artırıp Deniz seviye atladı: 30 yazsın. Seviye zaten en yüksek seviyedeyse artırmasın, Deniz zaten en yüksek seviyede (30) yazsın.

En yüksek seviye her oyuncu için aynı: 30. Bu sayıyı metotların içine elle yazma; sınıfa ait static bir enYuksekSeviye alanında tut. Seviyesi 29 olan Deniz'i iki kez seviye atlat, seviyesi 12 olan Ece'ye dokunma; sonra ikisini de tanıt:

Metin
Deniz seviye atladı: 30Deniz zaten en yüksek seviyede (30)Deniz (seviye 30)Ece (seviye 12)
İpucu 1

Kalıp: class Oyuncu { constructor(kullaniciAdi, seviye) { ... } }. constructor içinde iki değeri this. ile objeye koy.

İpucu 2

static enYuksekSeviye = 30 sınıfın en üstüne. Metodun içinde ona sınıfın adıyla ulaşırsın: Oyuncu.enYuksekSeviye. this.enYuksekSeviye işe yaramaz, çünkü örnekler static alanları görmez.

İpucu 3

seviyeAtla içinde önce if (this.seviye >= Oyuncu.enYuksekSeviye) ile sınırı denetle; sınırdaysa mesajı yazıp return ile çık (Ders 6'daki erken çıkış).

Çözümü göster
ders16.js
class Oyuncu {    static enYuksekSeviye = 30     constructor(kullaniciAdi, seviye) {        this.kullaniciAdi = kullaniciAdi        this.seviye = seviye    }     tanit() {        console.log(`${this.kullaniciAdi} (seviye ${this.seviye})`)    }     seviyeAtla() {        if (this.seviye >= Oyuncu.enYuksekSeviye) {            console.log(`${this.kullaniciAdi} zaten en yüksek seviyede (${Oyuncu.enYuksekSeviye})`)            return        }        this.seviye++        console.log(`${this.kullaniciAdi} seviye atladı: ${this.seviye}`)    }} const deniz = new Oyuncu("Deniz", 29)const ece = new Oyuncu("Ece", 12)deniz.seviyeAtla()deniz.seviyeAtla()deniz.tanit()ece.tanit()
Çıktı
Deniz seviye atladı: 30Deniz zaten en yüksek seviyede (30)Deniz (seviye 30)Ece (seviye 12)

Sınır denemesi: 29'dan 30'a geçiş izinli, 30'dan sonrası değil. Koşulu yanlışlıkla > Oyuncu.enYuksekSeviye yazsaydın Deniz 31 olurdu. Ece'nin 12'de kalması da örneklerin birbirinden bağımsız olduğunu gösteriyor. Bir gün en yüksek seviye 50 olursa tek satırı değiştirmen yeter.

Görev 2

Hata avı: kule atışı

Aşağıdaki programda üst koridor kulesi 100 ms sonra listesindeki her hedefe ateş etmeli. İçinde bu dersten üç hata var. Gözle aramadan, yalnızca mesajları okuyarak düzelt: çalıştır, ilk mesajı oku, düzelt, yeniden çalıştır.

ders16.js✗ Yanlış
class Kule {    constructor(koridor) {        this.koridor = koridor        this.hedefler = ["Minyon", "Garen"]    }     atesEt() {        this.hedefler.forEach(function (hedef) {            console.log(`${this.koridor} kulesi ${hedef} hedefine ateş etti`)        })    }} const ustKule = Kule("Üst")setTimeout(ustKule.atesEt, 100)
Hata çıktısı
C:\kod\ogren\ders16.js:14const ustKule = Kule("Üst")                ^ TypeError: Class constructor Kule cannot be invoked without 'new'    at Object.<anonymous> (C:\kod\ogren\ders16.js:14:17)    ... Node.js v24.20.0

Hepsini düzeltince çıktı şu olmalı:

Metin
Üst kulesi Minyon hedefine ateş ettiÜst kulesi Garen hedefine ateş etti
İpucu 1

İlk mesaj, sınıfın nasıl çağrıldığıyla ilgili. Mesaj çözümü kendisi söylüyor.

İpucu 2

İkinci mesaj TypeError: Cannot read properties of undefined (reading 'forEach') olacak ve yığın izinde Timeout geçecek. Demek ki this.hedefler undefined, yani this kule değil. setTimeout'a ne verdiğine bak: metot objesiyle birlikte mi gidiyor?

İpucu 3

Üçüncü mesaj yine Cannot read properties of undefined, ama bu kez (reading 'koridor') ve yığın izinde Array.forEach var. forEach'e verdiğin fonksiyon function ile mi yazılmış, ok fonksiyonuyla mı?

Çözümü göster
ders16.js
class Kule {    constructor(koridor) {        this.koridor = koridor        this.hedefler = ["Minyon", "Garen"]    }     atesEt() {        this.hedefler.forEach((hedef) => {            console.log(`${this.koridor} kulesi ${hedef} hedefine ateş etti`)        })    }} const ustKule = new Kule("Üst")setTimeout(() => ustKule.atesEt(), 100)
Çıktı
Üst kulesi Minyon hedefine ateş ettiÜst kulesi Garen hedefine ateş etti

Sırasıyla gelen mesajlar: TypeError: Class constructor Kule cannot be invoked without 'new' (eksik new), TypeError: Cannot read properties of undefined (reading 'forEach') (setTimeout metodu objesinden kopardı, this kule olmadı), TypeError: Cannot read properties of undefined (reading 'koridor') (function ile yazılan geri çağırmanın kendi this'i var ve o undefined). İkinci hatayı bind ile de düzeltebilirdin: setTimeout(ustKule.atesEt.bind(ustKule), 100).

Sınır denemesi: forEach içindeki fonksiyonu function ile bırakıp hedefler listesini boş ([]) yapsaydın program hiçbir şey yazmaz, hata da vermezdi, çünkü forEach boş listede fonksiyonu hiç çağırmaz. Bu tür hataları ancak dolu bir listeyle denerken görürsün.

Görev 3

TFT birimi: yıldız ve güç

Birim adında bir sınıf yaz; isim ve temelGuc alsın. Yıldızı #yildiz özel alanında tut, başlangıç değeri 1 olsun.

  • get yildiz() yıldızı döndürsün.
  • set yildiz(deger) yalnız 1, 2 ve 3'ü kabul etsin. Başka bir değer gelirse throw ile Yıldız 1, 2 ya da 3 olmalı, gelen: 4 mesajlı bir hata fırlatsın.
  • get guc() gücü hesaplasın: temel güç çarpı yıldız.

Temel gücü 50 olan bir Jinx üret ve console.log(jinx.yildiz, jinx.guc) ile yazdır. Yıldızını önce 2, sonra 3 yap; her seferinde yeniden yazdır. Sonra yıldızı sırayla 4, 0 ve 2.5 yapmayı dene: her denemeyi try/catch içinde yap, hata gelirse hata.message yazdır. En sonda yıldızı ve gücü bir kez daha yazdır:

Metin
1 502 1003 150Yıldız 1, 2 ya da 3 olmalı, gelen: 4Yıldız 1, 2 ya da 3 olmalı, gelen: 0Yıldız 1, 2 ya da 3 olmalı, gelen: 2.53 150
İpucu 1

Özel alanı sınıfın en üstünde, başlangıç değeriyle tanıt: #yildiz = 1. guc bir getter; içinde this.temelGuc * this.#yildiz döndür. Okurken parantez yok: jinx.guc.

İpucu 2

"Yalnız 1, 2 ya da 3" demek, değerin [1, 2, 3] listesinde olması demek. Bunu Ders 5'teki includes sorar: [1, 2, 3].includes(deger). Değer listede değilse (!) Ders 13'teki gibi hata fırlat: throw new Error(`Yıldız 1, 2 ya da 3 olmalı, gelen: ${deger}`).

İpucu 3

Üç denemeyi tek tek yazmak yerine bir döngü kur: for (const deneme of [4, 0, 2.5]) { ... }. Döngünün içine bir try/catch koy; try içinde jinx.yildiz = deneme yaz.

Çözümü göster
ders16.js
class Birim {    #yildiz = 1     constructor(isim, temelGuc) {        this.isim = isim        this.temelGuc = temelGuc    }     get yildiz() {        return this.#yildiz    }     set yildiz(deger) {        if (![1, 2, 3].includes(deger)) {            throw new Error(`Yıldız 1, 2 ya da 3 olmalı, gelen: ${deger}`)        }        this.#yildiz = deger    }     get guc() {        return this.temelGuc * this.#yildiz    }} const jinx = new Birim("Jinx", 50)console.log(jinx.yildiz, jinx.guc)jinx.yildiz = 2console.log(jinx.yildiz, jinx.guc)jinx.yildiz = 3console.log(jinx.yildiz, jinx.guc) for (const deneme of [4, 0, 2.5]) {    try {        jinx.yildiz = deneme    } catch (hata) {        console.log(hata.message)    }}console.log(jinx.yildiz, jinx.guc)
Çıktı
1 502 1003 150Yıldız 1, 2 ya da 3 olmalı, gelen: 4Yıldız 1, 2 ya da 3 olmalı, gelen: 0Yıldız 1, 2 ya da 3 olmalı, gelen: 2.53 150

Sınır denemesi: 3 kabul edildi; hemen üstü (4), altı (0) ve aradaki 2.5 reddedildi, reddedilen atamalardan sonra yıldız 3'te kaldı. Koşulu deger < 1 || deger > 3 diye bir aralıkla yazsaydın 4 ile 0 yine reddedilirdi ama 2.5 geçerdi ve gücü 125 olan, iki buçuk yıldızlı bir Jinx doğardı. jinx.#yildiz = 5 yazmayı da dene: program hiç başlamamalı.

Görev 4

Dükkân eşyaları

Esya adında bir sınıf yaz: isim ve fiyat alsın. bilgi() metodu Uzun Kılıç: 350 altın gibi bir metin döndürsün (console.log değil, return). Fiyat 0 ise Uyarı Totemi: bedava döndürsün.

Sonra Esya sınıfından türeyen bir Iksir sınıfı yaz. Ek olarak iyilestirme alsın ve bilgi() metodunu geçersiz kılıp üst sınıfın metninin sonuna (+120 can) gibi bir ek koysun. Dört eşyayı bir diziye koyup döngüyle yazdır:

Metin
Uzun Kılıç: 350 altınCan İksiri: 50 altın (+120 can)Uyarı Totemi: bedavaBaşlangıç İksiri: bedava (+60 can)
İpucu 1

class Iksir extends Esya. constructor metodunun ilk satırı super(isim, fiyat), sonra this.iyilestirme = iyilestirme.

İpucu 2

Üst sınıfın metnini super.bilgi() verir. Bedava kuralını Iksir içinde yeniden yazma; super.bilgi() onu zaten uyguluyor.

İpucu 3

Döngü: for (const esya of dukkan) { console.log(esya.bilgi()) }. Hangi sınıfın bilgi metodunun çalışacağını JavaScript kendisi seçer.

Çözümü göster
ders16.js
class Esya {    constructor(isim, fiyat) {        this.isim = isim        this.fiyat = fiyat    }     bilgi() {        if (this.fiyat === 0) {            return `${this.isim}: bedava`        }        return `${this.isim}: ${this.fiyat} altın`    }} class Iksir extends Esya {    constructor(isim, fiyat, iyilestirme) {        super(isim, fiyat)        this.iyilestirme = iyilestirme    }     bilgi() {        return `${super.bilgi()} (+${this.iyilestirme} can)`    }} const dukkan = [    new Esya("Uzun Kılıç", 350),    new Iksir("Can İksiri", 50, 120),    new Esya("Uyarı Totemi", 0),    new Iksir("Başlangıç İksiri", 0, 60)] for (const esya of dukkan) {    console.log(esya.bilgi())}
Çıktı
Uzun Kılıç: 350 altınCan İksiri: 50 altın (+120 can)Uyarı Totemi: bedavaBaşlangıç İksiri: bedava (+60 can)

Sınır denemesi: fiyatı 0 olan iksir, bedava kuralını Iksir içinde hiç yazmadığın hâlde doğru çıktı; kural üst sınıfta bir kez duruyor. Iksir içindeki super(isim, fiyat) satırını this.iyilestirme satırının altına alırsan bu derste gördüğün ReferenceError mesajını alırsın.

Görev 5

Cüzdan ve kendi hata türün

Ders 13'te yetersiz altını throw new Error("Yetersiz altın") ile bildirmiş, hata.message metnine bakmanın kırılgan olduğunu görmüştün: mesajı değiştirdiğin an kontrol bozulur. extends ile JavaScript'in hazır sınıflarından da türetebilirsin; şimdi Error sınıfından türeyen kendi hata türünü yazıp ona türüyle bakacaksın.

  1. YetersizAltin extends Error: constructor eksik miktarı alsın; super ile mesajı 285 altın eksik yapsın; this.name = "YetersizAltin" ve this.eksik alanlarını koysun.
  2. Cuzdan: altını #altin özel alanında tutsun; get altin(), kazan(miktar) ve harca(miktar) olsun. harca, altın yetmiyorsa YetersizAltin fırlatsın, yetiyorsa altını düşsün.
  3. Sınıfların dışında bir satinAl(cuzdan, esyaAdi, fiyat) fonksiyonu: try içinde harcasın ve … alındı, kalan altın: … yazsın. catch içinde hata YetersizAltin ise … alınamadı: … yazsın, başka bir hataysa throw hata ile yeniden fırlatsın.

500 altınla başla ve şu sırayı dene: Uzun Kılıç (350), Büyü Kitabı (435), 285 altın kazan, Büyü Kitabı (435), Can İksiri (50).

Metin
Uzun Kılıç alındı, kalan altın: 150Büyü Kitabı alınamadı: 285 altın eksikBüyü Kitabı alındı, kalan altın: 0Can İksiri alınamadı: 50 altın eksik
İpucu 1

Kendi hata sınıfını yazmak, kalıtımın en yaygın kullanımlarından. super(`${eksik} altın eksik`) mesajı Error sınıfının kendi message alanına koyar.

İpucu 2

harca içinde koşul miktar > this.#altin, eksik miktar da miktar - this.#altin. Altın tam yetiyorsa satın alma gerçekleşmeli ve kalan 0'a inmeli.

İpucu 3

Hatanın türünü instanceof ile ayırt et: if (hata instanceof YetersizAltin) { ... } else { throw hata }. Böylece beklemediğin hataları yutmamış olursun (Ders 13: ne zaman yakalamalı).

Çözümü göster
ders16.js
class YetersizAltin extends Error {    constructor(eksik) {        super(`${eksik} altın eksik`)        this.name = "YetersizAltin"        this.eksik = eksik    }} class Cuzdan {    #altin     constructor(altin) {        this.#altin = altin    }     get altin() {        return this.#altin    }     kazan(miktar) {        this.#altin = this.#altin + miktar    }     harca(miktar) {        if (miktar > this.#altin) {            throw new YetersizAltin(miktar - this.#altin)        }        this.#altin = this.#altin - miktar    }} function satinAl(cuzdan, esyaAdi, fiyat) {    try {        cuzdan.harca(fiyat)        console.log(`${esyaAdi} alındı, kalan altın: ${cuzdan.altin}`)    } catch (hata) {        if (hata instanceof YetersizAltin) {            console.log(`${esyaAdi} alınamadı: ${hata.message}`)        } else {            throw hata        }    }} const cuzdan = new Cuzdan(500)satinAl(cuzdan, "Uzun Kılıç", 350)satinAl(cuzdan, "Büyü Kitabı", 435)cuzdan.kazan(285)satinAl(cuzdan, "Büyü Kitabı", 435)satinAl(cuzdan, "Can İksiri", 50)
Çıktı
Uzun Kılıç alındı, kalan altın: 150Büyü Kitabı alınamadı: 285 altın eksikBüyü Kitabı alındı, kalan altın: 0Can İksiri alınamadı: 50 altın eksik

Sınır denemesi: Büyü Kitabı'nın ikinci denemesinde altın tam 435'ti. Eşitlik satın almaya izin verdi ve kalan 0 oldu; koşulu yanlışlıkla >= yazsaydın bu alışveriş reddedilirdi. Altın 0'dayken yapılan son deneme de doğru miktarı (50) eksik gösterdi.

this.name satırını silip dene: çıktı değişmez, ama hata.name artık "Error" verir. Ders 13'teki gibi türe hata.name ile bakan bir kod senin hatanı tanıyamaz. O satır yerindeyken hatayı hiç yakalamazsan terminaldeki hata satırı da YetersizAltin: 50 altın eksik diye başlar; türü bir bakışta görürsün.

Kendini sına

Soru 1

Bu program ne yazar?

JavaScript
class Kule {    constructor(koridor) {        this.koridor = koridor        this.zirh = 40    }} const ust = new Kule("Üst")const alt = new Kule("Alt")ust.zirh = ust.zirh - 15console.log(ust.zirh, alt.zirh)
Cevabı göster

25 40. Her örneğin kendi alanları var; ust kulesinin zırhını değiştirmek alt kulesine dokunmaz.

Soru 2

Bu program ne yazar? this hangi obje?

JavaScript
class Sampiyon {    constructor(isim) {        this.isim = isim    }     selamla(diger) {        console.log(`${this.isim}, ${diger.isim} ile selamlaştı`)    }} const ahri = new Sampiyon("Ahri")const lux = new Sampiyon("Lux")lux.selamla(ahri)
Cevabı göster

Lux, Ahri ile selamlaştı. Noktanın solunda lux var, this odur. ahri ise parametre olarak diger adıyla metoda girer.

Soru 3

new Buyucu("Lux", 300) çalışınca hangi satır, hangi hatayı verir?

JavaScript
class Buyucu extends Sampiyon {    constructor(isim, mana) {        this.mana = mana        super(isim)    }}
Cevabı göster

this.mana = mana satırı ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor verir. Alt sınıfta super(...) çalışmadan this yoktur. super(isim) satırını en üste al.

Soru 4

Bir arkadaşın setTimeout(garen.yenidenDog, 5000) yazmış; program beş saniye sonra çöküyor. Sebebi ne? İki farklı düzeltme yaz.

Cevabı göster

setTimeout'a yalnız fonksiyon gidiyor, garen gitmiyor; beş saniye sonra metot çağrıldığında noktanın solunda garen yok, this de Garen olmuyor. Düzeltmeler: setTimeout(() => garen.yenidenDog(), 5000) ya da setTimeout(garen.yenidenDog.bind(garen), 5000).

Soru 5

Bu program ne yazar?

JavaScript
class Kule {    constructor(maksZirh) {        this.maksZirh = maksZirh        this.zirh = maksZirh    }     get yikildiMi() {        return this.zirh <= 0    }} const kule = new Kule(50)kule.zirh = kule.zirh - 50console.log(kule.yikildiMi)console.log(kule.yikildiMi())
Cevabı göster

Önce true yazar: zırh tam 0'a indi ve 0 <= 0 doğru (sınır değeri). Sonra ikinci satır TypeError: kule.yikildiMi is not a function verir, çünkü getter parantezle çağrılmaz. Koşulu < 0 yazsaydın tam 0'da kule ayakta sayılırdı.

Soru 6

Bu dosya çalışınca ekrana ne yazar?

JavaScript
class Kasa {    #altin = 100} console.log("Başlıyor")const kasa = new Kasa()console.log(kasa.#altin)
Cevabı göster

Hiçbir şey yazmaz, Başlıyor bile. Son satır SyntaxError: Private field '#altin' must be declared in an enclosing class verir. Bu bir yazım hatası olduğu için Node programı hiç başlatmaz. Değeri dışarı açmak istiyorsan sınıfa bir get altin() ekle.

Soru 7

Her biri için sınıf mı, düz obje mi, fonksiyon mu kullanırdın?

  1. Oyunun ses düzeyi ve dil ayarı.
  2. İki oyuncunun KDA'sını karşılaştırıp daha iyi olanı döndüren iş.
  3. Maç boyunca altın kazanıp harcayan, eşya alan ve her biri kendi envanterini tutan 10 oyuncu.
Cevabı göster
  1. Düz obje: bir kez kurulup okunan, davranışı olmayan bilgi.
  2. Fonksiyon: girdi alıp sonuç döndürüyor, aklında bir şey tutmuyor.
  3. Sınıf: aynı yapıda çok obje, zamanla değişen veri ve kurallar (altın eksiye düşmemeli). Altını #altin ile korursun.

Soru 8

Mülakatta "Kalıtım nedir?" diye soruldu. Bu dersteki bir örnekle iki üç cümlede cevapla.

Cevabı göster

Örnek bir cevap: "Kalıtım, bir sınıfın başka bir sınıfın alanlarını ve metotlarını devralmasıdır. Örneğin Nisanci extends Sampiyon yazınca nişancı, şampiyonun canını, hasarını ve durum metodunu kendiliğinden alır; yapıcısında super(...) ile üst sınıfı kurar ve saldir metodunu kendi davranışıyla geçersiz kılar. Aynı kodu kopyalamadan, 'bir nişancı bir şampiyondur' ilişkisini kurmuş olurum."

Kayıt noktası

Bir sonraki ders Blok 2'nin projesi: modülleri, sınıfları, async'i ve kullanıcı girdisini birleştirip terminalde oynanan, kaydını dosyada tutan bir metin tabanlı oyun yazacaksın.

İşaret yalnızca bu tarayıcıda saklanır, kimseye gönderilmez.