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
classveconstructorile bir kalıp yazıpnewile ondan istediğin kadar obje üretebilirsin- Metotların içinde
thisile objenin kendi bilgisine ulaşabilir,thiskaybolunca ok fonksiyonu ya dabindile düzeltebilirsin extendsvesuperile bir sınıftan yeni sınıf türetip metotlarını geçersiz kılabilirsinget,setve#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
- Aynı yapıda çok şampiyon
- İlk sınıfın: class, constructor ve new
- Metotlar ve alanlar
- this: metodun "kendi" sözcüğü
- this kaybolunca: setTimeout, ok fonksiyonu ve bind
- Kalıtım: extends ve super
- get ve set: hesaplanan ve denetlenen alanlar
- Özel alanlar: # ile kilitli kasa
- static: sınıfın kendisine ait olanlar
- Sınıf mı, düz obje mi, fonksiyon mu?
- Mülakatta sınıflar
- Sık hatalar
- Görevler
- Kendini sına
- 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:
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)Ahri canı: 30Lux canı: NaNLux'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ı:
hPgibi bir hata, ancak sonuç saçmalayınca fark ediliyor. - Veri ile davranış ayrı:
saldirfonksiyonu ş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:
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)PS C:\kod\ogren> node ders16.jsSampiyon { isim: 'Ahri', hp: 100 }Garen 620Ü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 { ... }:Sampiyonadı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 objeninisimalanına, parametreyle gelenisimdeğerini koyar. Soldakithis.isimobjenin alanı, sağdakiisimparametre. 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:
- Boş, yepyeni bir obje açılır.
constructorçalışır. Onun içindekithiso yeni objedir;this.isim = isimgibi satırlar objeyi doldurur.- Dolan obje döndürülür ve
ahrikutusuna konur.constructoriçinereturnyazmana gerek yok.
new her çağrıldığında kalıptan, kendi değerlerini taşıyan yeni bir obje çıkar.TypeError: Class constructor Sampiyon cannot be invoked without 'new'Ne zaman çıkar? Sınıfı bir fonksiyon gibi, başına new yazmadan çağırınca.
class Sampiyon { constructor(isim, hp) { this.isim = isim this.hp = hp }} const ahri = Sampiyon("Ahri", 100)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.0Nası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.
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)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
functionyazılmaz: ad, parantez, süslü parantez.function saldir(hedef)yazarsanSyntaxError: 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.hedefise 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.
ReferenceError: hasar is not definedNe 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.
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)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.0Nası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ı:
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)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:
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()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.
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.
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)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.0setTimeout(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'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çindeahri.yenidenDog()noktayla çağrıldığı içinthisAhri olur. bind: Fonksiyonlarınbindadında bir metodu var.lux.yenidenDog.bind(lux),this'i kalıcı olaraklux'a bağlanmış yeni bir fonksiyon üretir. O fonksiyon nereye giderse gitsin, nasıl çağrılırsa çağrılsınthishep Lux'tur.
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)Ahri: 100 canLux: 90 canTypeError: this.durum is not a functionNe 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:
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()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.0forEach, 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:
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)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,Sampiyonsınıfının her şeyini devralsın."Sampiyonüst sınıf,Nisancialt sınıf.super(isim, hp, hasar): üst sınıfınconstructormetodunu çağırır.isim,hp,hasaralanlarını o kurar; alt sınıf yalnız kendine özgümenzilalanını ekler.jinx.saldir(garen)vejinx.durum():Nisanciiçine ikisini de yazmadın ama çalıştılar, çünkü üst sınıftan devralındılar.console.log(jinx): objeNisancikalıbından çıktı ve dört alanın hepsini taşıyor; üçünü üst sınıf kurdu, birini alt sınıf.
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorNe 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.
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)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.0Nası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:
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)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ınsaldirsü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 birSampiyon. 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:
console.log(jinx instanceof Nisanci, jinx instanceof Sampiyon, garen instanceof Nisanci)true true falseJinx hem Nisanci hem Sampiyon örneğidir, çünkü her nişancı bir şampiyondur. Garen ise bir Nisanci değildir.
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:
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)}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:
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}`)100Garen: %65Garen: %5get 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.
TypeError: garen.canYuzdesi is not a functionNe zaman çıkar? Getter'ı bir metot gibi, parantezle çağırınca.
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())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.0Nası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:
RangeError: Maximum call stack size exceededNe zaman çıkar? Bir setter'ın içinde, setter'la aynı adlı alana atama yapınca. Genel olarak, bir fonksiyon durmadan kendini çağırdığında.
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)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.0Sı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:
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)0100Sampiyon { isim: 'Ahri', maksHp: 100 }#hpsatı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ındakiSyntaxError'ı alırsın.- Setter artık
this.#hpalanına yazıyor.#hpilehpfarklı adlar olduğu için setter artık kendini çağırmıyor. ahri.hp - 130işleminin sonucu-30'du, setter onu0'a çekti. Sonraki atamada0 + 500işlemi500verdi, setter onu100'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.hpgetter'ı onu okuyabiliyor.- Getter'ı yazmayı unutup yalnız
set hpyazarsan hata almazsın, amaahri.hpher okunuştaundefinedverir.ahri.hp = ahri.hp - 10satırı da sessizceNaNhesaplar ve canNaNolur. Bu yüzdengetilesetç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.
get ve set kapılarından geçer.SyntaxError: Private field '#hp' must be declared in an enclosing classNe 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.
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 = 9999C:\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.0Nası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:
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)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.0static toplam = 0: örneklerde değil,Sampiyonsınıfının kendisinde duran tek bir sayaç.constructorher çalıştığındaSampiyon.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
staticalanları ve metotları görmez:takim[0].toplamundefinedverdi,takim[0].enDayanikli(...)deTypeError.
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:
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(){"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.0JSON.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:
| Elindeki | Seç | Örnek |
|---|---|---|
| Yalnız bilgi, davranış yok | Düz obje | Ayarlar { ses: 70 }, gelen JSON |
| Girdi alıp sonuç döndüren iş | Fonksiyon | kdaHesapla(oyuncu) |
| Çok obje, değişen veri, kurallar | Sınıf | Şampiyon, cüzdan, envanter |
| Ortak davranış, farklı ayrıntı | Sınıf ve kalıtım | Niş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.
| Soru | Kı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.
TypeError: ahri.saldir is not a functionNe 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.
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)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.0Nası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.
SyntaxError: Unexpected token ','Ne zaman çıkar? Sınıfın metotları arasına, objedeki gibi virgül koyunca.
class Sampiyon { constructor(isim) { this.isim = isim }, selamVer() { console.log("Ben " + this.isim) }}C:\kod\ogren\ders16.js:4 }, ^ SyntaxError: Unexpected token ',' at wrapSafe (node:internal/modules/cjs/loader:1861:18) ... Node.js v24.20.0Nası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.
ReferenceError: Cannot access 'Sampiyon' before initializationNe 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.
const ahri = new Sampiyon("Ahri", 100) class Sampiyon { constructor(isim, hp) { this.isim = isim this.hp = hp }}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.0Nası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.
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ıpDeniz seviye atladı: 30yazsı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:
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
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()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.
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.
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)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.0Hepsini düzeltince çıktı şu olmalı:
Ü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
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)Üst kulesi Minyon hedefine ateş ettiÜst kulesi Garen hedefine ateş ettiSı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.
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 gelirsethrowileYıldız 1, 2 ya da 3 olmalı, gelen: 4mesajlı 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:
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
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)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 150Sı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ı.
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:
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
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())}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.
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.
YetersizAltin extends Error: constructor eksik miktarı alsın;superile mesajı285 altın eksikyapsın;this.name = "YetersizAltin"vethis.eksikalanlarını koysun.Cuzdan: altını#altinözel alanında tutsun;get altin(),kazan(miktar)veharca(miktar)olsun.harca, altın yetmiyorsaYetersizAltinfırlatsın, yetiyorsa altını düşsün.- Sınıfların dışında bir
satinAl(cuzdan, esyaAdi, fiyat)fonksiyonu:tryiçinde harcasın ve… alındı, kalan altın: …yazsın.catchiçinde hataYetersizAltinise… alınamadı: …yazsın, başka bir hataysathrow hataile 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).
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
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)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 eksikSı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?
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?
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?
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?
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?
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?
- Oyunun ses düzeyi ve dil ayarı.
- İki oyuncunun KDA'sını karşılaştırıp daha iyi olanı döndüren iş.
- Maç boyunca altın kazanıp harcayan, eşya alan ve her biri kendi envanterini tutan 10 oyuncu.
Cevabı göster
- Düz obje: bir kez kurulup okunan, davranışı olmayan bilgi.
- Fonksiyon: girdi alıp sonuç döndürüyor, aklında bir şey tutmuyor.
- Sınıf: aynı yapıda çok obje, zamanla değişen veri ve kurallar (altın eksiye düşmemeli). Altını
#altinile 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.