Blok 2 · Ders 10
Fonksiyonlar derinlemesine
Ok fonksiyonları, varsayılan parametreler, kapsam, closure ve fonksiyonu başka fonksiyona vermek (callback).
Bu dersin sonunda
- Aynı fonksiyonu bildirim, fonksiyon ifadesi ve ok fonksiyonu (
=>) olarak yazabilir, örtükreturnile açıkreturn'ü doğru yerde kullanabilirsin - Varsayılan parametreyle, eksik argüman yüzünden çıkan
NaNsorununu önleyebilirsin - Bir değişkenin blok, fonksiyon ve genel kapsamdan hangisinde göründüğünü söyleyebilir; gölgeleme ve hoisting kaynaklı hataları tanıyabilirsin
- Fonksiyonu değişkene, diziye ve obje alanına koyabilir; kendi yazdığın bir fonksiyona ya da
setTimeout'a callback olarak verebilirsin - Closure ile kendi hafızası olan sayaç ve yetenek bekleme süresi (cooldown) fonksiyonları yazabilirsin
- Saf fonksiyonu yan etkili fonksiyondan ayırıp hesapları saf fonksiyonlara taşıyabilirsin
Bu sayfada
Fonksiyonu Ders 3'te bir tarif olarak tanıdın, Ders 6'da return ile sonuç döndürmesini öğrendin. Bu derste fonksiyonların asıl gücünü görüyorsun: kısa yazılmaları, eksik bilgiyle çağrılmaları, kutulara ve listelere konmaları, başka fonksiyonlara verilmeleri ve kendi hafızalarını taşımaları. Bir sonraki dersin map ve filter'ı, web sayfalarındaki tıklama olayları ve React bu dersin üstüne kurulu.
Bu derste C:\kod\ogren klasöründe yeni bir ders10.js dosyasında çalışıyorsun. Her yeni örnekten önce eski kodu sil ya da satırların başına // koy: aynı adları birkaç kez tanımlayacağız ve aynı adı iki kez const ile açmak Ders 1'deki has already been declared hatasını verir.
Aynı fonksiyon, üç yazım
Ders 3'ten beri fonksiyonları hep aynı kalıpla yazdın: function, ad, parametreler, gövde. Bu yazımın adı fonksiyon bildirimi (function declaration). JavaScript'te aynı fonksiyonu yazmanın iki yolu daha var ve ikisini de her gün göreceksin: internetteki örneklerde, iş yerindeki kodda, bir sonraki dersin dizi metotlarında ve React'te. Okuyamazsan karşılaştığın kodun yarısı sana yabancı kalır.
Fonksiyon ifadesi: fonksiyonu kutuya koymak
Önce küçük bir kavram: ifade (expression), çalışınca bir değer üreten kod parçası demek. 40 * 3 bir ifadedir (değeri 120), "Ahri" bir ifadedir, hp > 0 bir ifadedir (değeri true ya da false). if, for ya da let hp = 100 ise ifade değildir; bir değer üretmezler, bir iş yaptırırlar. Bunlara komut (statement) denir.
JavaScript'te fonksiyonun kendisi de bir değerdir. Bu yüzden adı olmayan bir fonksiyonu, tıpkı 100 gibi, = işaretinin sağına yazıp bir kutuya koyabilirsin:
const hasarHesapla = function (guc, carpan) { return guc * carpan} console.log(hasarHesapla(40, 3))120Çağırma biçimi hiç değişmedi: kutunun adı, parantez, argümanlar. Değişen yalnızca tanım: function sözcüğünden sonra ad yok, adı const veriyor. Bu yazıma fonksiyon ifadesi denir, çünkü fonksiyon ='in sağında bir değer olarak duruyor.
Ok fonksiyonu: =>
Fonksiyon ifadesinin kısa yazımına ok fonksiyonu denir: function sözcüğünü sil, parametre parantezinden sonra => koy. => işareti eşittir ile büyüktürün bitişik yazılmışıdır. Türkçe Q klavyede = için Shift+0'a, > için Ders 2'deki gibi sol Shift'in hemen sağındaki tuşa Shift ile basarsın.
const hasarHesapla = (guc, carpan) => { return guc * carpan} console.log(hasarHesapla(40, 3))120(guc, carpan) => kısmını "guc ile carpan'ı al ve…" diye okuyabilirsin.
Tek ifadelik gövde: örtük return
Gövdede yalnızca tek bir return satırı varsa daha da kısaltabilirsin: süslü parantezleri ve return sözcüğünü sil, döndürülecek ifadeyi okun hemen sağına yaz.
const hasarHesapla = (guc, carpan) => guc * carpan console.log(hasarHesapla(40, 3))120Okun sağında süslü parantez yoksa JavaScript oradaki ifadenin değerini kendiliğinden döndürür. Buna örtük return (implicit return) denir. Süslü parantezli yazımda ise return'ü sen yazarsın; ona açık return diyeceğiz.
Parametre sayısına göre ok fonksiyonu şöyle yazılır:
| Parametre | Kural | Örnek |
|---|---|---|
| Hiç yok | Boş parantez şart | () => "Hazır" |
| Bir tane | Parantez isteğe bağlı | (hp) => hp > 0 ya da hp => hp > 0 |
| İki ve fazlası | Parantez şart | (guc, carpan) => guc * carpan |
Tek parametrede parantezsiz yazım da geçerli ve internette çok görürsün. Bu kursta hep parantez kullanıyoruz: sonradan ikinci bir parametre eklediğinde parantezi unutma derdi kalmıyor. Kodu otomatik düzenleyen Prettier (Ders 53) de varsayılan olarak böyle yazıyor.
Hangisini ne zaman?
Üç yazım da aynı işi yapar; bu derste karşına çıkacak tek fark, birazdan göreceğin hoisting. Bu kursta şöyle kullanacağız:
| Ne yazıyorsun? | Yazım | Örnek |
|---|---|---|
| Programın ana parçası, adıyla çağrılan fonksiyon | function bildirimi ya da const + ok fonksiyonu; ikisi de olur | function hasarHesapla(guc) { … } |
| Başka bir fonksiyona verilecek kısa fonksiyon | Ok fonksiyonu, çoğu zaman tek satır | (sampiyon) => sampiyon.hp > 0 |
Gövdesinde if, döngü ya da birden çok satır olan ok fonksiyonu | Süslü parantez + açık return | (x) => { if (…) { … } return … } |
Son satırın nedeni ifade ile komut farkı: okun sağına süslü parantezsiz yalnızca bir ifade yazılabilir. if bir komut olduğu için onu tek satırlık ok fonksiyonuna sığdıramazsın; süslü parantez açman gerekir.
undefined döndürüyorNe zaman çıkar? Tek satırlık bir ok fonksiyonunu sonradan süslü parantezle açıp return yazmayı unutunca. Hata mesajı yok: program çalışır ama sonuç undefined gelir.
const kritikHasar = (hasar) => { hasar * 2} console.log(kritikHasar(50))undefinedNasıl düzelir? Süslü parantez açtıysan return senin işin: return hasar * 2. Ya da süslü parantezleri silip tek satıra dön: (hasar) => hasar * 2. Kural: süslü parantez varsa return yaz, yoksa yazma. Ders 6'da return'ü unutan fonksiyonun undefined döndürdüğünü görmüştün; ok fonksiyonunda bu hata daha sinsi, çünkü tek satırlık hâli return'süz çalışıyordu.
SyntaxError: Unexpected token 'return'Ne zaman çıkar? Ters hata: tek satırlık ok fonksiyonuna return yazınca.
const ikiKat = (x) => return x * 2C:\kod\ogren\ders10.js:1const ikiKat = (x) => return x * 2 ^^^^^^ SyntaxError: Unexpected token 'return' at wrapSafe (node:internal/modules/cjs/loader:1861:18) ... Node.js v24.20.0Nasıl düzelir? return bir komut; okun sağına süslü parantezsiz yalnızca bir ifade yazılabilir. Ya return'ü sil ((x) => x * 2) ya da süslü parantez aç ((x) => { return x * 2 }).
Varsayılan parametre
Bir iksir fonksiyonu yazalım. Çoğu iksir 50 can veriyor, bazıları daha az. Önce sorunu gör:
function iksirIc(hp, iyilesme) { return hp + iyilesme} console.log(iksirIc(100, 30))console.log(iksirIc(100))130NaNİkinci çağrıda iyilesme için argüman vermedin. Ders 3'te gördüğün gibi JavaScript bunu hata saymaz; eksik kalan parametreye undefined koyar. undefined bir sayı olmadığı için 100 + undefined hesabından da Ders 9'dan tanıdığın NaN çıkar. Oyunda bu, canın bir anda "sayı değil" olması demek. Şimdi parametreye bir varsayılan değer ver:
function iksirIc(hp, iyilesme = 50) { return hp + iyilesme} console.log(iksirIc(100, 30))console.log(iksirIc(100))130150iyilesme = 50 "bu argüman gelmezse 50 kullan" demek. Argüman gelirse (30) varsayılan değer devreye girmez. Aynı yazım ok fonksiyonunda da geçerli: const iksirIc = (hp, iyilesme = 50) => hp + iyilesme.
Şimdi sınırları dene. Aynı dosyada, öncekinin altına yaz:
console.log(iksirIc(100, 0))console.log(iksirIc(100, undefined))100150Varsayılan değer yalnızca argüman hiç gelmediğinde ya da undefined geldiğinde kullanılır. 0 gerçek bir değerdir: iksir bu sefer hiç can vermediyse sonuç 100 kalır, 150 olmaz.
Kapsam: kim neyi görür?
Ders 3'te fonksiyonun içinde açılan değişkenin dışarıdan görünmediğini, Ders 5'te de for (let i = 0; …) ile açılan sayacın döngüden sonra kullanılamadığını öğrendin. Kuralın tamamı şu: let ya da const ile açılan bir değişken, açıldığı bloğun içinde yaşar. Kod bloğu oluşturan her süslü parantez çifti kendi kapsamını (scope) açar ve kapsamlar iç içe durur:
- Genel kapsam (global scope): hiçbir bloğun içinde olmayan en dış katman. Burada açılan değişkeni dosyanın her yeri görür.
- Fonksiyon kapsamı: fonksiyonun süslü parantezleri. İçinde açılan değişkenler ve parametreler yalnızca o fonksiyonda yaşar.
- Blok kapsamı:
if,else,for,whilegibi yapıların süslü parantezleri. İçinde açılan değişken yalnızca o blokta yaşar.
Obje yazarken kullandığın süslü parantezler ({ isim: "Ahri" }) blok değildir; kapsam açmazlar.
const takimAdi = "Mavi Takım" function saldir(hedef) { const hasar = 60 if (hedef === "Garen") { const bonus = 20 console.log(`${takimAdi}: ${hedef} hedefine ${hasar + bonus} hasar`) } console.log(`Temel hasar: ${hasar}`)} saldir("Garen")Mavi Takım: Garen hedefine 80 hasarTemel hasar: 60Kodun 7. satırı üç katmanı birden kullanıyor: bonus kendi bloğundan, hasar ile hedef fonksiyondan, takimAdi en dıştan geliyor. İçeriden dışarıya bakmak serbest. Tersi yasak: fonksiyonun dışından hasar'a, if bloğunun dışından bonus'a ulaşamazsın. saldir adının kendisi ise genel kapsamda duruyor; son satırda fonksiyonu dışarıdan çağırabilmenin sebebi bu.
ReferenceError: bonus is not definedNe zaman çıkar? Bir blokta (if, for…) açtığın değişkeni bloğun dışında kullanınca.
const takimAdi = "Mavi Takım" function saldir(hedef) { const hasar = 60 if (hedef === "Garen") { const bonus = 20 console.log(`${takimAdi}: ${hedef} hedefine ${hasar + bonus} hasar`) } console.log(bonus)} saldir("Garen")Mavi Takım: Garen hedefine 80 hasarC:\kod\ogren\ders10.js:9 console.log(bonus) ^ ReferenceError: bonus is not defined at saldir (C:\kod\ogren\ders10.js:9:17) at Object.<anonymous> (C:\kod\ogren\ders10.js:12:1) ... Node.js v24.20.0Nasıl düzelir? bonus, 5. satırda açılan if bloğunun kapanışıyla (8. satır) birlikte yok oldu. Değişkene bloğun dışında da ihtiyacın varsa onu bloğun üstünde aç: let bonus = 0 yaz, blokta yalnızca değerini değiştir (bonus = 20). Aynı kural döngüler için de geçerli: for (let tur = 1; …) ile açılan tur, döngü bitince yok olur.
Gölgeleme: aynı ad, iki kutu
İç kapsamda, dıştakiyle aynı adı taşıyan yeni bir değişken açarsan ne olur?
const isim = "Ahri" function tanit() { const isim = "Lux" console.log(`İçeride: ${isim}`)} tanit()console.log(`Dışarıda: ${isim}`)İçeride: LuxDışarıda: AhriBurada iki ayrı kutu var ve ikisinin de etiketi isim. JavaScript bir adı ararken en içten başlar, dışarı doğru bakar ve ilk bulduğu kutuda durur. Fonksiyonun içinde isim arandığında önce fonksiyonun kendi kutusu bulunur; dıştaki "Ahri"ye hiç bakılmaz. İçteki kutu dıştakini gölgede bırakır, bu yüzden buna gölgeleme (shadowing) denir.
Aynı adı ikinci kez const ile açtığın hâlde has already been declared hatası almadın, çünkü ikinci kutu başka bir kapsamda açıldı. O hata yalnızca aynı kapsamda iki kez açınca çıkar.
Gölgeleme kendi başına hata değildir, ama en sık şu kılıkta hataya dönüşür:
Ne zaman çıkar? Parametreye dışarıdaki değişkenle aynı adı verip fonksiyonun içinde değiştirince, dışarıdakinin de değişeceğini sanınca. Hata mesajı yok; yalnızca sonuç yanlış.
let hp = 100 function hasarAl(hp, hasar) { hp = hp - hasar console.log(`İçeride hp: ${hp}`)} hasarAl(hp, 30)console.log(`Dışarıda hp: ${hp}`)İçeride hp: 70Dışarıda hp: 100Neden? Ders 4'te sayının fonksiyona kopyalanarak gittiğini görmüştün. Burada iş bir de adla karışıyor: parametre hp, fonksiyonun içinde açılan yeni bir kutu ve dıştaki hp'yi gölgeliyor. hasarAl(hp, 30) çağrısı dıştaki kutunun değerini (100) bu yeni kutuya kopyalar. 4. satırdaki iki hp de içteki kutuyu gösterir; dıştakine dokunulmaz. Adlar aynı olduğu için kod okurken bunu fark etmek çok zordur.
Nasıl düzelir? Fonksiyon yeni değeri return etsin, kutuyu güncelleme işini çağıran taraf yapsın:
let hp = 100 function hasarAl(mevcutHp, hasar) { return mevcutHp - hasar} hp = hasarAl(hp, 30)console.log(`Dışarıda hp: ${hp}`)Dışarıda hp: 70Parametreye farklı bir ad (mevcutHp) vermek, okurken hangi kutunun hangisi olduğunu da netleştirir.
Hoisting: tanımdan önce çağırmak
Şu programda fonksiyon, tanımlandığı satırdan önce çağrılıyor. Çalışır mı?
console.log(hasarHesapla(80)) function hasarHesapla(guc) { return guc * 2}160Çalışıyor. Ders 1'de gördün: Node programı başlatmadan önce dosyanın tamamını okur; yazım hatası bulursa programı hiç başlatmaz. Bu okuma sırasında function ile yazılmış bildirimleri de bulur ve tamamen hazır eder. Program ilk satırdan başladığında hasarHesapla çoktan hazırdır; sanki fonksiyon dosyanın en üstüne taşınmış gibi. Bu davranışa hoisting (yukarı taşıma) denir.
Şimdi dosyaya const ile tanımlanmış bir ok fonksiyonu ekle ve onu da tanımından önce çağır:
ReferenceError: Cannot access 'kritikVurus' before initializationNe zaman çıkar? const (ya da let) ile tanımlanmış bir fonksiyonu, tanımlandığı satırdan önce çağırınca.
console.log(hasarHesapla(80))console.log(kritikVurus(80)) function hasarHesapla(guc) { return guc * 2} const kritikVurus = (guc) => guc * 3160C:\kod\ogren\ders10.js:2console.log(kritikVurus(80)) ^ ReferenceError: Cannot access 'kritikVurus' before initialization at Object.<anonymous> (C:\kod\ogren\ders10.js:2:13) ... Node.js v24.20.0İlk satır yine çalıştı ve 160 yazdı; program ikinci satırda durdu.
Ne diyor? "kritikVurus'a ilk değeri verilmeden (initialization) önce erişilemez." Dikkat et, is not defined demiyor: JavaScript bu adı tanıyor ama kutu henüz kilitli.
Nasıl düzelir? const ile tanımladığın fonksiyonları, onları kullanan satırlardan önce yaz. Çoğu ekip bu düzeni zaten izler: önce tanım, sonra kullanım. Fonksiyonu sonda tutmak istiyorsan function bildirimiyle yaz.
Neden böyle? Okuma turunda const ve let ile açılan adlar da kayda geçer, ama kutuları kilitli kalır. Kilit, o const satırı çalıştığında açılır. Kilit açılmadan kutuya dokunursan bu hatayı alırsın. Kutunun kilitli olduğu bu aralığa İngilizcede temporal dead zone denir; mülakatlarda sorulan bir kavramdır.
function bildirimi ilk turda hazır olur; const ile açılan ad kendi satırı çalışana kadar kilitli kalır.Fonksiyon bir değerdir
Ders 3'te parantezsiz yazılan fonksiyon adının fonksiyonu çalıştırmadığını, console.log içine yazınca da [Function: durumYaz] çıktığını görmüştün. Orada bunu bir hata olarak ele aldık. Aslında o çıktı, JavaScript'in en güçlü özelliklerinden birinin ucuydu: parantezsiz ad, fonksiyonun kendisidir, yani bir değerdir. Parantez eklemek "şimdi çalıştır" demektir.
function selamla(isim) { return `Merhaba ${isim}`} console.log(selamla("Lux"))console.log(selamla) const merhabaDe = selamlaconsole.log(merhabaDe("Jinx"))Merhaba Lux[Function: selamla]Merhaba Jinxconsole.log(selamla) satırında selamla parantezsiz; fonksiyon çalışmadı, yalnızca ne olduğu yazıldı: [Function: selamla], yani "selamla adında bir fonksiyon". const merhabaDe = selamla satırı da fonksiyonu çalıştırmadan başka bir kutuya koydu. Artık aynı fonksiyonu iki adla çağırabilirsin.
Bir değerle ne yapabiliyorsan fonksiyonla da yapabilirsin: değişkene koymak, diziye koymak, obje alanına koymak, başka bir fonksiyona vermek, bir fonksiyondan döndürmek. Öteki değerlerle aynı haklara sahip oldukları için JavaScript'teki fonksiyonlara "birinci sınıf fonksiyon" (first-class function) denir. Bu bölümde ilk üçünü, sonraki bölümlerde son ikisini göreceksin.
Diziye koymak: kombo
const qYetenegi = (hedef) => `${hedef} hedefine Q: 60 hasar`const wYetenegi = (hedef) => `${hedef} hedefine W: 40 hasar`const eYetenegi = (hedef) => `${hedef} büyülendi (E)` const kombo = [eYetenegi, qYetenegi, wYetenegi] for (const yetenek of kombo) { console.log(yetenek("Garen"))}Garen büyülendi (E)Garen hedefine Q: 60 hasarGaren hedefine W: 40 hasarkombo dizisinde üç fonksiyon var, çağrılmamış hâlleriyle (parantez yok). Döngü her turda birini yetenek kutusuna koyuyor ve yetenek("Garen") ile çağırıyor. Kombonun sırasını değiştirmek için fonksiyonlara dokunmana gerek yok; diziyi değiştirmen yeter: [qYetenegi, eYetenegi].
Obje alanına koymak: tuş atama
const yetenekler = { q: (hedef) => `${hedef} hedefine Q: 60 hasar`, w: (hedef) => `${hedef} hedefine W: 40 hasar`, e: (hedef) => `${hedef} büyülendi (E)`} console.log(yetenekler.q("Garen")) const basilanTus = "e"console.log(yetenekler[basilanTus]("Yasuo"))Garen hedefine Q: 60 hasarYasuo büyülendi (E)yetenekler.q bir fonksiyon, yetenekler.q("Garen") onu çağırmak. Ders 4'teki köşeli parantezle okuma burada çok işe yarıyor: basılan tuş bir değişkende dursa bile doğru yeteneği bulup çağırabiliyorsun.
Callback: fonksiyonu fonksiyona vermek
Ders 5'ten beri takımı hep aynı kalıpla geziyorsun: for (const sampiyon of takim) { … }. Değişen yalnızca süslü parantezin içi: bir seferinde adları yazdırıyorsun, bir seferinde ölüleri buluyorsun, bir seferinde canları topluyorsun. Döngüyü bir kez yazıp "her şampiyonda ne yapılacağını" dışarıdan alabilir miyiz? Fonksiyon bir değer olduğu için evet.
Kendi döngü yardımcın
function herSampiyonIcin(takim, islem) { for (const sampiyon of takim) { islem(sampiyon) }} const takim = [ { isim: "Ahri", hp: 100 }, { isim: "Garen", hp: 0 }, { isim: "Jinx", hp: 45 }] const isminiYaz = (sampiyon) => { console.log(sampiyon.isim)} herSampiyonIcin(takim, isminiYaz)AhriGarenJinxherSampiyonIcin iki şey alıyor: bir dizi ve bir fonksiyon. Diziyi gezmeyi biliyor ama her elemanla ne yapacağını bilmiyor; bunu ikinci parametresinden, islem'den öğreniyor. 3. satırdaki islem(sampiyon), senin verdiğin fonksiyonu o anki şampiyonla çağırıyor. Başka bir fonksiyona verilen ve onun tarafından uygun anda "geri çağrılan" böyle fonksiyonlara geri çağırma fonksiyonu (callback) denir.
Son satıra dikkat: isminiYaz parantezsiz. Fonksiyonu veriyoruz, çağırmıyoruz. Çağırma işini herSampiyonIcin kendisi yapacak: doğru anda ve doğru şampiyonla.
herSampiyonIcin diziyi gezmeyi bilir; her şampiyonda ne yapılacağını ona verdiğin islem fonksiyonu söyler.Callback'i yerinde yazmak
Callback'e ayrı bir ad vermek zorunda değilsin; ok fonksiyonunu doğrudan argümanın yerine yazabilirsin. Ok fonksiyonlarının en çok kullanıldığı yer burasıdır. Aynı dosyada, öncekinin altına yaz:
herSampiyonIcin(takim, (sampiyon) => { if (sampiyon.hp === 0) { console.log(`${sampiyon.isim} ölü, yeniden doğmayı bekliyor`) }}) let toplamHp = 0herSampiyonIcin(takim, (sampiyon) => { toplamHp = toplamHp + sampiyon.hp})console.log(`Takımın toplam canı: ${toplamHp}`)Garen ölü, yeniden doğmayı bekliyorTakımın toplam canı: 145Döngüyü bir kez yazdın, onu iki farklı iş için kullandın. İkinci callback dışarıdaki toplamHp'yi görebiliyor ve değiştirebiliyor; içeriden dışarıya bakmak serbestti. Bir sonraki dersteki map, filter ve find tam olarak böyle çalışır: döngüyü JavaScript yazar, sen yalnızca callback'i verirsin.
TypeError: islem is not a functionNe zaman çıkar? Callback bekleyen bir fonksiyona fonksiyon vermeyi unutunca (ya da fonksiyon yerine başka bir değer verince).
function herSampiyonIcin(takim, islem) { for (const sampiyon of takim) { islem(sampiyon) }} const takim = [ { isim: "Ahri", hp: 100 }, { isim: "Garen", hp: 0 }] herSampiyonIcin(takim)C:\kod\ogren\ders10.js:3 islem(sampiyon) ^ TypeError: islem is not a function at herSampiyonIcin (C:\kod\ogren\ders10.js:3:9) at Object.<anonymous> (C:\kod\ogren\ders10.js:12:1) ... Node.js v24.20.0Nasıl düzelir? Hata 3. satırı gösteriyor ama asıl yanlış 12. satırda: ikinci argüman verilmediği için islem kutusunda undefined var ve undefined çağrılamaz. at satırları da bunu söylüyor: herSampiyonIcin, ders10.js'in 12. satırından çağrılmış (bu satırları Ders 13'te ayrıntılı okuyacaksın). Çağrıya bir fonksiyon ekle: herSampiyonIcin(takim, isminiYaz).
setTimeout: "şu kadar sonra çağır"
Callback'in ikinci büyük kullanım alanı beklemek. Node'da hazır gelen setTimeout fonksiyonu iki şey alır: bir fonksiyon ve milisaniye cinsinden bir süre (1000 milisaniye = 1 saniye). Süre dolunca verdiğin fonksiyonu çağırır.
console.log("Maç başladı") setTimeout(() => { console.log("Baron Nashor doğdu!")}, 2000) console.log("Minyonlar yürüyor")PS C:\kod\ogren> node ders10.jsMaç başladıMinyonlar yürüyorBaron Nashor doğdu!Son satır iki saniye sonra gelir. Program o sırada durup beklemez; "Minyonlar yürüyor" satırını hemen yazar.
Sıraya bak: setTimeout satırı "Minyonlar yürüyor"dan önce yazılmış ama Baron en son geliyor. setTimeout beklemez: zamanlayıcıyı kurar ve hemen sonraki satıra geçer. İki saniye dolunca Node verdiğin fonksiyonu çağırır. Bunun arka planda nasıl işlediğini (olay döngüsü) Ders 15'te göreceksin. Şimdilik kuralı bil: setTimeout'a verdiğin fonksiyon sonra çalışır, program o arada yoluna devam eder.
setTimeout'a da fonksiyonu verirsin, çağırmazsın:
setTimeout(yaz(), 1000)setTimeout(yaz, 1000)TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefinedNe zaman çıkar? setTimeout'a fonksiyonu vermek yerine fonksiyonu çağırıp sonucunu verince.
const yaz = () => console.log("Baron doğdu!")setTimeout(yaz(), 1000)Baron doğdu!node:internal/errors:543 throw error; ^ TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefined at setTimeout (node:timers:124:3) at Object.<anonymous> (C:\kod\ogren\ders10.js:2:1) ... at node:internal/main/run_main_module:33:47 { code: 'ERR_INVALID_ARG_TYPE'} Node.js v24.20.0Ne oldu? yaz() parantezli olduğu için hemen çalıştı ("Baron doğdu!" hiç beklemeden yazıldı) ve sonucunu setTimeout'a verdi. yaz'ın döndürdüğü şey console.log'un sonucu; console.log ekrana yazar ama değer döndürmez, yani sonuç undefined. Mesaj tam bunu söylüyor: "callback argümanı bir fonksiyon olmalı, undefined geldi." Bu kez hatanın ilk satırı senin dosyanı değil, Node'un kendi iç dosyasını (node:internal/errors) gösteriyor. Kendi satırını bulmak için at satırlarında dosyanın adını ara: ders10.js:2:1, yani 2. satır.
Nasıl düzelir? Parantezi sil: setTimeout(yaz, 1000). Callback'i verirken parantez yok; parantez "şimdi çalıştır" demek.
Closure: hafızası olan fonksiyon
Bir maç boyunca kill saymak istiyorsun. İlk akla gelen, genel kapsamda bir değişken:
let killSayisi = 0 function killAldi() { killSayisi++ return killSayisi}Çalışır ama iki sorunu var. Birincisi, dosyanın her yeri bu kutuyu görür ve değiştirebilir: biri yanlışlıkla killSayisi = 0 yazarsa sayı sessizce sıfırlanır. İkincisi, asist de saymak istersen bütün kodu kopyalaman gerekir. İstediğin şey, kendi sayısını kendisi tutan ve başka kimsenin dokunamadığı bir fonksiyon. Bunun yolu, fonksiyon döndüren bir fonksiyon yazmak.
Fonksiyon döndüren fonksiyon
Fonksiyon bir değer olduğuna göre return ile döndürülebilir:
function hasarciOlustur(carpan) { return (guc) => guc * carpan} const kritik = hasarciOlustur(2)const ulti = hasarciOlustur(3) console.log(kritik(50))console.log(ulti(50))100150hasarciOlustur(2) bir sayı değil, bir fonksiyon döndürüyor: (guc) => guc * carpan. Onu kritik kutusuna koyduk. 8. satırda kritik(50) çağrıldığında hasarciOlustur çoktan işini bitirmişti; kapsam kuralına göre carpan parametresinin de onunla birlikte yok olması gerekirdi. Ama kritik, carpan'ın 2 olduğunu hâlâ hatırlıyor. ulti ise 3 olduğunu hatırlıyor.
Bunun nedeni şu: bir fonksiyon oluşturulduğunda, çevresinde gördüğü ve kullandığı değişkenleri bir sırt çantasına koyup yanında taşır. kritik'in çantasında carpan: 2, ulti'ninkinde carpan: 3 var. Bir fonksiyonun, oluşturulduğu yerdeki değişkenleri böyle yanında taşımasına kapanış (closure) denir.
Sayaç: çantadaki değer değişebilir
Çantadaki değer yalnızca okunmaz, değiştirilebilir de. Kill sayacını şimdi böyle yazalım:
function sayacOlustur() { let sayi = 0 return () => { sayi++ return sayi }} const killSayaci = sayacOlustur()console.log(killSayaci())console.log(killSayaci())console.log(killSayaci()) const asistSayaci = sayacOlustur()console.log(asistSayaci())console.log(killSayaci())12314sayacOlustur'u bir fabrika gibi düşün: her çağrıldığında yeni bir sayaç fonksiyonu üretip verir. Adım adım:
sayacOlustur()çağrılınca içindesayiadlı bir kutu açılır (değeri 0) ve bir ok fonksiyonu oluşturulup döndürülür. Ok fonksiyonusayi'yı kullandığı için kutuyu çantasına koyar.killSayaciartık o ok fonksiyonu. Her çağrıda çantasındakisayi'yı bir artırıp döndürüyor: 1, 2, 3.asistSayaci = sayacOlustur()fabrikayı yeniden çalıştırır: yeni birsayikutusu (0), yeni bir fonksiyon, yeni bir çanta. Bu yüzden asist sayacı 1'den başlıyor, kill sayacı ise 4'ten devam ediyor.
Dışarıdan sayi'ya ulaşmanın yolu yok: console.log(sayi) yazarsan ReferenceError: sayi is not defined alırsın. Sayıyı değiştirmenin tek yolu killSayaci() çağırmak. Genel değişkenli çözümün iki sorunu da çözüldü: sayı korunuyor ve istediğin kadar bağımsız sayaç üretebiliyorsun.
[Function (anonymous)]: fabrikayı çağırmayı unutmakNe zaman çıkar? Fabrika fonksiyonunu parantezsiz atayınca. Hata mesajı yok; sayı beklerken fonksiyon yazdırılır.
function sayacOlustur() { let sayi = 0 return () => { sayi++ return sayi }} const killSayaci = sayacOlusturconsole.log(killSayaci())console.log(killSayaci())[Function (anonymous)][Function (anonymous)]Neden? 9. satırda parantez olmadığı için killSayaci, sayaç değil fabrikanın kendisi oldu. killSayaci() her çağrıda fabrikayı çalıştırıp yeni bir sayaç fonksiyonu üretiyor ve onu döndürüyor; console.log da o adsız fonksiyonu yazıyor.
Nasıl düzelir? Fabrikayı bir kez, parantezle çağır: const killSayaci = sayacOlustur(). Sonra hep aynı killSayaci fonksiyonunu kullan. Her kill'de yeniden sayacOlustur() çağırırsan her seferinde boş bir çanta alırsın ve sayı hep 1'de kalır.
Yetenek bekleme süresi (cooldown)
Closure'ın oyundaki en doğal örneği bekleme süresi. Ahri'nin Q'su kullanıldıktan sonra 7 saniye bekler; bu sürede yeniden basarsan "hazır değil" der. Her yeteneğin kendi bekleme süresi ve "ne zaman hazır olacağı" bilgisi var; bunları yeteneğin kendi çantasına koyalım. Zamanı programa biz söyleyeceğiz: fonksiyona maçın kaçıncı saniyesinde olduğumuzu argüman olarak veriyoruz.
function yetenekOlustur(ad, beklemeSuresi) { let hazirOlacagiAn = 0 return (saniye) => { if (saniye < hazirOlacagiAn) { return `${saniye}. sn: ${ad} bekliyor, ${hazirOlacagiAn - saniye} sn kaldı` } hazirOlacagiAn = saniye + beklemeSuresi return `${saniye}. sn: ${ad} kullanıldı` }} const ahriQ = yetenekOlustur("Ahri Q", 7)const flash = yetenekOlustur("Flash", 300) console.log(ahriQ(0))console.log(flash(2))console.log(ahriQ(3))console.log(ahriQ(7))console.log(flash(10))console.log(ahriQ(9))0. sn: Ahri Q kullanıldı2. sn: Flash kullanıldı3. sn: Ahri Q bekliyor, 4 sn kaldı7. sn: Ahri Q kullanıldı10. sn: Flash bekliyor, 292 sn kaldı9. sn: Ahri Q bekliyor, 5 sn kaldıyetenekOlustur her çağrıldığında yeni bir çanta hazırlıyor. İçinde üç şey var: parametreler ad ve beklemeSuresi (parametreler de çantaya girer) ve hazirOlacagiAn. Yetenek kullanılınca hazirOlacagiAn, "şimdi + bekleme süresi" olarak güncelleniyor; bir sonraki çağrı bu değere bakıp karar veriyor. ahriQ ile flash'ın çantaları ayrı: Q'yu kullanmak Flash'ın bekleme süresini etkilemiyor.
Sınırlara bak: Q, 7. saniyede tam hazır oluyor (7 < 7 yanlış olduğu için kullanılıyor); 3. saniyede ise 4 saniye kaldığını söylüyor. Saniyeyi argüman olarak vermek, programı denemeyi de kolaylaştırıyor: gerçek saati beklemeden her durumu deneyebiliyorsun.
Saf fonksiyon ve yan etki
Şu iki fonksiyon aynı işi yapıyor gibi görünüyor:
let toplamDeneyim = 0 function deneyimKazan(miktar) { toplamDeneyim = toplamDeneyim + miktar console.log(`Deneyim: ${toplamDeneyim}`)} deneyimKazan(50)deneyimKazan(50) const deneyimEkle = (deneyim, miktar) => deneyim + miktar console.log(deneyimEkle(0, 50))console.log(deneyimEkle(0, 50))Deneyim: 50Deneyim: 1005050deneyimKazan(50) iki kez aynı argümanla çağrıldı ama iki farklı şey yazdı: 50 ve 100. Sonucu yalnızca girdisine değil, dışarıdaki bir kutunun o anki hâline de bağlı. Üstelik o kutuyu değiştiriyor ve ekrana yazıyor. deneyimEkle(0, 50) ise kaç kez çağırırsan çağır 50 döndürüyor ve dünyada hiçbir şeyi değiştirmiyor.
İkinci türe saf fonksiyon (pure function) denir. İki şartı var: aynı girdilere her zaman aynı sonucu verir ve dışarıda hiçbir şeyi değiştirmez. Bir fonksiyonun sonuç döndürmek dışında dünyada bıraktığı her ize de yan etki (side effect) denir.
| Fonksiyonun içinde | Yan etki mi? |
|---|---|
return hp - hasar | Hayır, yalnızca hesaplıyor |
toplamDeneyim = … (dıştaki değişkene yeni değer koymak) | Evet, dışarıdaki kutu değişiyor |
sampiyon.hp = 0 (parametre olarak gelen obje) | Evet, dışarıdaki obje değişiyor |
console.log(…) | Evet, ekranda yazı beliriyor |
Math.random() | Dışarıda görünür bir iz bırakmaz, ama fonksiyonu saf olmaktan çıkarır: aynı girdiye farklı sonuç |
Yan etki kötü bir şey değil: ekrana hiç yazmayan, hiçbir şeyi değiştirmeyen bir program kimsenin işine yaramaz. Önemli olan yan etkileri bir yerde toplamak: hesapları saf fonksiyonlara ver, değiştirme ve yazdırma işini az sayıda, belli yerlerde yap. Bunun üç kazancı var:
- Denemek kolay: saf fonksiyona girdiyi ver, çıktıya bak; başka hiçbir şeyi hazırlaman gerekmez. Ders 53'te test yazarken bunu çok seveceksin.
- Hata aramak kolay: sonuç yanlışsa sorun ya girdide ya fonksiyonun içindedir; dosyanın geri kalanında suçlu aramazsın.
- İleride şart: React (Ders 40) bileşenlerinin saf fonksiyon gibi davranmasını bekler.
Görevler
Her görevi ders10.js dosyasında yap; önceki kodu sil ya da yoruma al. Önce kendin dene, takılırsan ipuçlarını sırayla aç; çözüme en son bak. Her çözümden sonra sınır değerlerini de dene.
Ok fonksiyonuna çevir
Aşağıdaki üç fonksiyonu const ile tanımlanan ok fonksiyonlarına çevir. Gövdesi tek bir return olanları tek satıra indir; öbüründe süslü parantez ve return kalsın. Alttaki console.log satırlarına dokunma, çıktı aynı kalmalı.
function manaYeter(mana, maliyet) { return mana >= maliyet} function seviyeMetni(seviye) { return `Seviye ${seviye}`} function koridorAdi(no) { if (no === 1) { return "üst koridor" } else if (no === 2) { return "orta koridor" } return "alt koridor"} console.log(manaYeter(80, 50))console.log(manaYeter(50, 50))console.log(seviyeMetni(6))console.log(koridorAdi(2))console.log(koridorAdi(3))truetrueSeviye 6orta koridoralt koridorİpucu 1
function manaYeter(mana, maliyet) { satırı const manaYeter = (mana, maliyet) => olur. Gövde yalnızca return mana >= maliyet olduğu için süslü parantezleri ve return'ü silip ifadeyi okun sağına yazabilirsin.
İpucu 2
koridorAdi fonksiyonunun gövdesinde if var. if bir ifade değil, bir komut; okun sağına süslü parantezsiz yazılamaz. Bu yüzden onda süslü parantez ve her return kalmalı.
Çözümü göster
const manaYeter = (mana, maliyet) => mana >= maliyet const seviyeMetni = (seviye) => `Seviye ${seviye}` const koridorAdi = (no) => { if (no === 1) { return "üst koridor" } else if (no === 2) { return "orta koridor" } return "alt koridor"} console.log(manaYeter(80, 50))console.log(manaYeter(50, 50))console.log(seviyeMetni(6))console.log(koridorAdi(2))console.log(koridorAdi(3)) // sınır denemesi: bir eksik manaconsole.log(manaYeter(49, 50))truetrueSeviye 6orta koridoralt koridorfalseİlk beş satır görevdeki çıktının aynısı. Sona sınır denemesi için bir satır ekledik: manaYeter(50, 50) tam yettiği için true, manaYeter(49, 50) bir eksik olduğu için false. Fonksiyonları console.log satırlarının üstünde tanımlamak şart: const ile tanımlanan bir fonksiyonu önceden çağırırsan Cannot access … before initialization alırsın.
Hata avı: kule ve ejderha
Aşağıdaki programda üç hata var; üçü de bu dersin konusu. Gözle aramadan, yalnızca hata mesajlarını okuyarak düzelt: çalıştır, ilk mesajı oku, düzelt, yeniden çalıştır.
console.log(kalkanVer(80)) const kalkanVer = (hp, kalkan = 40) => hp + kalkan function kuleyeVur(kuleHp, hasar) { if (hasar > 0) { const kalanHp = kuleHp - hasar } return kalanHp} console.log(kuleyeVur(1000, 150))setTimeout(console.log("Ejderha doğdu"), 1000)C:\kod\ogren\ders10.js:1console.log(kalkanVer(80)) ^ ReferenceError: Cannot access 'kalkanVer' before initialization at Object.<anonymous> (C:\kod\ogren\ders10.js:1:13) ... Node.js v24.20.0Hepsini düzeltince program 120 ve 850 yazmalı, bir saniye sonra da Ejderha doğdu. Sonra sınırları dene: kule hiç hasar almazsa kuleyeVur(1000, 0) sonucu 1000 olmalı, kalkanVer(80, 0) sonucu 80.
İpucu 1
İlk mesaj Cannot access 'kalkanVer' before initialization. const ile tanımlanan bir fonksiyonu, kendi satırı çalışmadan kullanabilir misin? Çağrının yerini değiştir.
İpucu 2
İkinci mesaj ReferenceError: kalanHp is not defined olacak. kalanHp hangi süslü parantezin içinde açıldı, hangi satırda kullanılıyor? Kutuyu bloğun dışında açman gerekiyor; hasar 0 olunca da içinde anlamlı bir değer olsun.
İpucu 3
Üçüncü mesajda ERR_INVALID_ARG_TYPE ve Received undefined göreceksin; Ejderha doğdu ise hiç beklemeden yazılacak. setTimeout'a bir fonksiyon değil, console.log çağrısının sonucu gidiyor. console.log'a argüman vermen gerektiği için onu bir ok fonksiyonunun içine al.
Çözümü göster
const kalkanVer = (hp, kalkan = 40) => hp + kalkan console.log(kalkanVer(80)) function kuleyeVur(kuleHp, hasar) { let kalanHp = kuleHp if (hasar > 0) { kalanHp = kuleHp - hasar } return kalanHp} console.log(kuleyeVur(1000, 150))setTimeout(() => console.log("Ejderha doğdu"), 1000) console.log(kuleyeVur(1000, 0))console.log(kalkanVer(80, 0))120850100080Ejderha doğduSırasıyla gelen mesajlar: Cannot access 'kalkanVer' before initialization (const fonksiyon tanımından önce çağrıldı; kalkanVer'i function bildirimine çevirmek de olurdu), kalanHp is not defined (blokta açılan kutu, blok kapanınca yok oldu), ERR_INVALID_ARG_TYPE (setTimeout'a fonksiyon yerine console.log'un döndürdüğü undefined gitti).
Sınırlara bak: son iki satır setTimeout'tan sonra yazıldığı hâlde Ejderha doğdu'dan önce çıktı, çünkü setTimeout beklemez. kalkanVer(80, 0) sonucu 80: 0 gerçek bir değer, varsayılan 40 devreye girmedi. kalanHp'yi başlangıç değeri vermeden açsaydın (let kalanHp), hasar 0 iken fonksiyon undefined döndürürdü; program çalışırdı ama sonuç yanlış olurdu.
Dükkânı saf yap
Bu kodda iki sorun var: satinAl dışarıdaki altin'ı değiştiriyor (yan etki) ve adet verilmeyince bozuluyor.
let altin = 500 function satinAl(fiyat, adet) { altin = altin - fiyat * adet console.log(`Kalan altın: ${altin}`)} satinAl(150, 2)satinAl(50)Kalan altın: 200Kalan altın: NaNBu kodu sil ve yerine saf bir kalanAltin(altin, fiyat, adet) fonksiyonu yaz:
adetverilmezse 1 sayılsın.- Altın yetiyorsa kalan altını döndürsün. Yetmiyorsa alışveriş olmasın, altını olduğu gibi döndürsün.
- Ekrana yazmasın, dışarıdaki hiçbir değişkeni değiştirmesin. Yazdırma ve
altin'ı güncelleme işini çağıran taraf yapsın.
Fonksiyonunu yazdıktan sonra altına şu satırları ekle:
let altin = 500altin = kalanAltin(altin, 150, 2)console.log(`Kalan altın: ${altin}`)altin = kalanAltin(altin, 50)console.log(`Kalan altın: ${altin}`)altin = kalanAltin(altin, 400)console.log(`Kalan altın: ${altin}`)Beklenen çıktı:
Kalan altın: 200Kalan altın: 150Kalan altın: 150İpucu 1
Adet için varsayılan parametre kullan: (altin, fiyat, adet = 1).
İpucu 2
Önce const toplamFiyat = fiyat * adet hesapla, sonra bir if ile altin'la karşılaştır. Yetmiyorsa return altin.
İpucu 3
Fonksiyonun içinde altin = … yazma; return altin - toplamFiyat yeter. Parametre altin ile dışarıdaki altin iki ayrı kutu (gölgeleme); dışarıdakini güncellemek çağıranın işi: altin = kalanAltin(altin, 150, 2).
Çözümü göster
const kalanAltin = (altin, fiyat, adet = 1) => { const toplamFiyat = fiyat * adet if (toplamFiyat > altin) { return altin } return altin - toplamFiyat} let altin = 500altin = kalanAltin(altin, 150, 2)console.log(`Kalan altın: ${altin}`)altin = kalanAltin(altin, 50)console.log(`Kalan altın: ${altin}`)altin = kalanAltin(altin, 400)console.log(`Kalan altın: ${altin}`) console.log(kalanAltin(150, 150))console.log(kalanAltin(150, 151))console.log(kalanAltin(150, 50, 0))Kalan altın: 200Kalan altın: 150Kalan altın: 1500150150Sınır denemeleri: altın tam yetince (150, 150) sonuç 0, bir altın eksikse (150, 151) alışveriş olmuyor. Adet 0 verilince varsayılan değer devreye girmiyor (0 gerçek bir değer), toplam fiyat 0 olduğu için altın değişmiyor. Parametrenin adı dışarıdaki altin ile aynı ama bu gölgeleme burada zararsız: fonksiyon parametreyi değiştirmiyor, yalnızca okuyup yeni değer döndürüyor.
TFT dükkân komutları
TFT'de dükkânda üç işin var: yenile (2 altın), xp (4 altın) ve kilitle (bedava). Bunlar bir objede fonksiyon olarak duruyor:
const komutlar = { yenile: (altin) => altin - 2, xp: (altin) => altin - 4, kilitle: (altin) => altin}Bu objeyi kullanan komutCalistir(komut, altin) fonksiyonunu yaz. Komutu objede bulup çağırsın ve sonucu metin olarak döndürsün:
- Başında ya da sonunda boşluk olsa, büyük harfle yazılsa da çalışsın (
" XP "). - Objede olmayan bir komut gelirse
Bilinmeyen komut: …döndürsün. - Altın eksiye düşecekse komut çalışmasın,
Yetersiz altındöndürsün.
Fonksiyonunu şu çağrılarla dene:
console.log(komutCalistir("yenile", 10))console.log(komutCalistir(" XP ", 10))console.log(komutCalistir("xp", 4))console.log(komutCalistir("xp", 3))console.log(komutCalistir("kilitle", 0))console.log(komutCalistir("sat", 10))Beklenen çıktı:
Kalan altın: 8Kalan altın: 6Kalan altın: 0Yetersiz altınKalan altın: 0Bilinmeyen komut: satİpucu 1
Ders 9'daki trim ve toLowerCase ile komutu temizle: const ad = komut.trim().toLowerCase().
İpucu 2
komutlar[ad] köşeli parantezle okur. Objede olmayan alan undefined verir (Ders 4); if (islem === undefined) ile yakala.
İpucu 3
Komut fonksiyonları saf: onları çağırmak hiçbir şeyi değiştirmiyor. Bu yüzden önce const yeniAltin = islem(altin) ile sonucu hesaplayıp ardından eksiye düşüp düşmediğine bakabilirsin.
Çözümü göster
const komutlar = { yenile: (altin) => altin - 2, xp: (altin) => altin - 4, kilitle: (altin) => altin} function komutCalistir(komut, altin) { const ad = komut.trim().toLowerCase() const islem = komutlar[ad] if (islem === undefined) { return `Bilinmeyen komut: ${ad}` } const yeniAltin = islem(altin) if (yeniAltin < 0) { return "Yetersiz altın" } return `Kalan altın: ${yeniAltin}`} console.log(komutCalistir("yenile", 10))console.log(komutCalistir(" XP ", 10))console.log(komutCalistir("xp", 4))console.log(komutCalistir("xp", 3))console.log(komutCalistir("kilitle", 0))console.log(komutCalistir("sat", 10))Kalan altın: 8Kalan altın: 6Kalan altın: 0Yetersiz altınKalan altın: 0Bilinmeyen komut: satSınırlar: 4 altınla xp tam yetiyor (0), 3 altınla yetmiyor. 0 altınla kilitle çalışıyor, çünkü bedava. Yeni bir komut eklemek için komutCalistir'a dokunmana gerek yok; objeye bir satır eklemen yeter. Fonksiyonları obje alanına koymanın kazancı bu.
Kendi sayma yardımcın
kacTane(dizi, kosul) adında bir fonksiyon yaz. Dizinin her elemanı için kosul callback'ini çağırsın ve callback'in true döndürdüğü eleman sayısını döndürsün. Sonra onu TFT tahtanla dene:
const tahta = [ { isim: "Jinx", yildiz: 2, maliyet: 4 }, { isim: "Vi", yildiz: 1, maliyet: 1 }, { isim: "Ekko", yildiz: 3, maliyet: 2 }, { isim: "Lux", yildiz: 2, maliyet: 3 }]Şu soruları kendi yazacağın callback'lerle cevapla: kaç birim en az 2 yıldızlı (3), kaç birimin maliyeti 4 (1), kaç birim hem 2 yıldızlı hem maliyeti en az 3 (2). Boş diziyle çağırınca 0 dönmeli.
İpucu 1
Gövde Ders 5'teki "koşula uyanları say" kalıbı: let sayi = 0, for...of, koşul doğruysa sayi++, en sonda return sayi.
İpucu 2
Koşulu kendin yazmıyorsun, callback'e soruyorsun: if (kosul(oge)) { … }. Callback'in kendisi true ya da false döndüren tek satırlık bir ok fonksiyonu olabilir: (birim) => birim.yildiz >= 2.
Çözümü göster
function kacTane(dizi, kosul) { let sayi = 0 for (const oge of dizi) { if (kosul(oge)) { sayi++ } } return sayi} const tahta = [ { isim: "Jinx", yildiz: 2, maliyet: 4 }, { isim: "Vi", yildiz: 1, maliyet: 1 }, { isim: "Ekko", yildiz: 3, maliyet: 2 }, { isim: "Lux", yildiz: 2, maliyet: 3 }] console.log(kacTane(tahta, (birim) => birim.yildiz >= 2))console.log(kacTane(tahta, (birim) => birim.maliyet === 4))console.log(kacTane(tahta, (birim) => birim.yildiz === 2 && birim.maliyet >= 3))console.log(kacTane([], (birim) => birim.yildiz >= 2))console.log(kacTane(tahta, (birim) => birim.maliyet > 5))31200Sınırlar: boş dizide döngü hiç dönmüyor ve 0 dönüyor; hiçbir birimin uymadığı koşul (maliyet > 5) da 0 veriyor. kacTane ne saydığını bilmiyor: aynı fonksiyon kill sayısını, ölü şampiyonları ya da pahalı eşyaları da sayabilir. Ders 11'de JavaScript'in hazır dizi metotlarının da tam bu fikirle çalıştığını göreceksin.
Galibiyet serisi
TFT'de üst üste kazandıkça her tura ek altın gelir. Kurallarımız:
| Seri | Bonus |
|---|---|
| 0–1 | 0 altın |
| 2–3 | 1 altın |
| 4 | 2 altın |
| 5 ve üstü | 3 altın |
seriTakipcisi() adında bir fabrika yaz. Döndürdüğü fonksiyon her tur sonucunu ("G" galibiyet, "M" mağlubiyet) alsın; o anki seriyi ve o ana kadarki en uzun seriyi hatırlasın, bir metin döndürsün. Mağlubiyette seri sıfırlansın, en uzun seri kalsın. Her takipçinin hafızası ayrı olmalı.
const benimSerim = seriTakipcisi()console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("M"))console.log(benimSerim("G")) const rakipSerisi = seriTakipcisi()console.log(rakipSerisi("G"))Beklenen çıktı:
Seri: 1 (en iyi: 1), bonus: 0 altınSeri: 2 (en iyi: 2), bonus: 1 altınSeri: 3 (en iyi: 3), bonus: 1 altınSeri: 4 (en iyi: 4), bonus: 2 altınSeri: 5 (en iyi: 5), bonus: 3 altınSeri bozuldu (en iyi: 5), bonus: 0 altınSeri: 1 (en iyi: 5), bonus: 0 altınSeri: 1 (en iyi: 1), bonus: 0 altınİpucu 1
İskelet sayaç fabrikasıyla aynı: seriTakipcisi içinde iki kutu aç (let seri = 0, let enIyi = 0) ve (sonuc) => { … } biçiminde bir ok fonksiyonu döndür.
İpucu 2
Döndürülen fonksiyonda önce mağlubiyeti ele al: seri = 0 yap ve erken return ile çık. Galibiyette seri++; seri enIyi'yi geçtiyse enIyi'yi güncelle.
İpucu 3
Bonus için let bonus = 0 aç ve else if zincirini büyükten küçüğe kur: önce seri >= 5, sonra seri === 4, sonra seri >= 2. Sıra neden önemli? Ders 3'ü hatırla.
Çözümü göster
function seriTakipcisi() { let seri = 0 let enIyi = 0 return (sonuc) => { if (sonuc === "M") { seri = 0 return `Seri bozuldu (en iyi: ${enIyi}), bonus: 0 altın` } seri++ if (seri > enIyi) { enIyi = seri } let bonus = 0 if (seri >= 5) { bonus = 3 } else if (seri === 4) { bonus = 2 } else if (seri >= 2) { bonus = 1 } return `Seri: ${seri} (en iyi: ${enIyi}), bonus: ${bonus} altın` }} const benimSerim = seriTakipcisi()console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("G"))console.log(benimSerim("M"))console.log(benimSerim("G")) const rakipSerisi = seriTakipcisi()console.log(rakipSerisi("G"))Seri: 1 (en iyi: 1), bonus: 0 altınSeri: 2 (en iyi: 2), bonus: 1 altınSeri: 3 (en iyi: 3), bonus: 1 altınSeri: 4 (en iyi: 4), bonus: 2 altınSeri: 5 (en iyi: 5), bonus: 3 altınSeri bozuldu (en iyi: 5), bonus: 0 altınSeri: 1 (en iyi: 5), bonus: 0 altınSeri: 1 (en iyi: 1), bonus: 0 altınSınırlar: tablonun her eşiği (1→0, 2→1, 4→2, 5→3) çıktıda tek tek görünüyor. Mağlubiyetten sonraki galibiyet seriyi 1'den başlatıyor ama en iyi: 5 kalıyor. rakipSerisi kendi çantasıyla 1'den başlıyor; benimSerim'in sayıları onu etkilemiyor.
Kendini sına
Soru 1
Bu program ne yazar?
const ikiKat = (x) => x * 2const ucKat = (x) => { x * 3}console.log(ikiKat(5))console.log(ucKat(5))Cevabı göster
10 ve undefined. ikiKat tek satırlık ok fonksiyonu; okun sağındaki x * 2 kendiliğinden döndürülür. ucKat'ın süslü parantezi var ama return yok: x * 3 hesaplanır, sonucu hiçbir yere gitmez ve fonksiyon undefined döndürür.
Soru 2
Bu program çalışır mı? Ok fonksiyonu yok, function sözcüğü var.
console.log(selam("Lux")) const selam = function (isim) { return `Merhaba ${isim}`}Cevabı göster
Çalışmaz: ReferenceError: Cannot access 'selam' before initialization. Hoisting'de belirleyici olan function sözcüğü değil, tanımın biçimi. Bu bir fonksiyon ifadesi: fonksiyon const ile açılan selam kutusuna konuyor ve o kutu 3. satır çalışana kadar kilitli. Yalnızca function selam(isim) { … } biçimindeki bildirim tanımından önce çağrılabilir.
Soru 3
Bu program hangi üç sayıyı, hangi sırayla yazar?
const seviye = 1 function seviyeAtla() { const seviye = 2 if (seviye > 1) { const seviye = 3 console.log(seviye) } console.log(seviye)} seviyeAtla()console.log(seviye)Cevabı göster
3, 2, 1. Üç ayrı kutu var: genel kapsamda 1, fonksiyonda 2, if bloğunda 3. Her console.log kendine en yakın kutuyu görür (gölgeleme). 5. satırdaki koşulda henüz bloğun içinde değiliz, orada seviye fonksiyonun kutusu (2) olduğu için koşul doğru.
Soru 4
Son satır ne yazar?
function cantaOlustur() { let esya = 0 return () => { esya = esya + 1 return esya }} const a = cantaOlustur()const b = cantaOlustur()a()a()console.log(a(), b())Cevabı göster
3 1. a ile b iki ayrı fabrika çağrısından geldiği için iki ayrı çanta taşıyor. a toplam üç kez çağrıldı (sonuçları yazdırılmayan iki çağrı da sayılır), b bir kez.
Soru 5
Harfler hangi sırayla yazılır?
console.log("A")setTimeout(() => console.log("B"), 0)console.log("C")Cevabı göster
A, C, B. Süre 0 milisaniye olsa da setTimeout'a verilen fonksiyon, program o anki işini bitirdikten sonra çalışır. Program setTimeout satırında durmaz, C'yi hemen yazar. Bunun nedenini (olay döngüsü) Ders 15'te göreceksin.
Soru 6
Hangileri saf fonksiyon?
const kdaHesapla = (k, d, a) => (k + a) / d const yazdir = (mesaj) => console.log(mesaj) function hasarVer(sampiyon) { sampiyon.hp = sampiyon.hp - 10} const zarAt = () => Math.floor(Math.random() * 6) + 1Cevabı göster
Yalnızca kdaHesapla saf: aynı üç sayıya hep aynı sonucu verir ve hiçbir şeye dokunmaz. yazdir ekrana yazar (yan etki). hasarVer kendisine verilen objeyi, yani dışarıdaki şampiyonu değiştirir (yan etki). zarAt bir şeyi değiştirmez ama her çağrıda farklı sonuç verebilir, bu yüzden o da saf değil.
Soru 7
Bu satır neden hata verir, nasıl düzelir?
setTimeout(uyar(), 3000)Cevabı göster
uyar() parantezli olduğu için hemen çalışır ve setTimeout'a fonksiyon yerine onun sonucu gider. uyar bir şey döndürmüyorsa sonuç undefined olur ve Node TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefined hatası verir. Doğrusu setTimeout(uyar, 3000): fonksiyonu ver, çağırma işini setTimeout'a bırak.
Kayıt noktası
Dersin kodunu Git'e kaydet. VS Code terminalinde, C:\kod\ogren klasöründeyken:
Bir sonraki derste callback'lerin en çok kullanıldığı yere geçiyorsun: map, filter, find ve reduce gibi dizi metotlarıyla, döngü yazmadan veriyi dönüştüreceksin.