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

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ük return ile açık return'ü doğru yerde kullanabilirsin
  • Varsayılan parametreyle, eksik argüman yüzünden çıkan NaN sorununu ö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
  1. Aynı fonksiyon, üç yazım
  2. Varsayılan parametre
  3. Kapsam: kim neyi görür?
  4. Hoisting: tanımdan önce çağırmak
  5. Fonksiyon bir değerdir
  6. Callback: fonksiyonu fonksiyona vermek
  7. Closure: hafızası olan fonksiyon
  8. Saf fonksiyon ve yan etki
  9. Görevler
  10. Kendini sına
  11. Kayıt noktası

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:

ders10.js
const hasarHesapla = function (guc, carpan) {    return guc * carpan} console.log(hasarHesapla(40, 3))
Çıktı
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.

ders10.js
const hasarHesapla = (guc, carpan) => {    return guc * carpan} console.log(hasarHesapla(40, 3))
Çıktı
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.

ders10.js
const hasarHesapla = (guc, carpan) => guc * carpan console.log(hasarHesapla(40, 3))
Çıktı
120

Okun 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.

function ikiKat(x) { return x * 2 } bildirim (Ders 3) adını sil, fonksiyonu bir kutuya koy const ikiKat = function (x) { return x * 2 } fonksiyon ifadesi function sözcüğünü sil, parantezden sonra => koy const ikiKat = (x) => { return x * 2 } ok fonksiyonu gövde tek ifadeyse süslü parantezi ve return'ü sil const ikiKat = (x) => x * 2 kısa ok: örtük return
Şekil 10.1 Aynı fonksiyonun dört yazımı. Her adımda yazım kısalıyor, yaptığı iş aynı kalıyor.

Parametre sayısına göre ok fonksiyonu şöyle yazılır:

ParametreKuralÖrnek
Hiç yokBoş parantez şart() => "Hazır"
Bir taneParantez 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 fonksiyonfunction bildirimi ya da const + ok fonksiyonu; ikisi de olurfunction hasarHesapla(guc) { … }
Başka bir fonksiyona verilecek kısa fonksiyonOk fonksiyonu, çoğu zaman tek satır(sampiyon) => sampiyon.hp > 0
Gövdesinde if, döngü ya da birden çok satır olan ok fonksiyonuSü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.

Sık hataSüslü parantezli ok fonksiyonu undefined döndürüyor

Ne 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.

ders10.js
const kritikHasar = (hasar) => {    hasar * 2} console.log(kritikHasar(50))
Çıktı
undefined

Nası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.

Sık hataSyntaxError: Unexpected token 'return'

Ne zaman çıkar? Ters hata: tek satırlık ok fonksiyonuna return yazınca.

ders10.js
const ikiKat = (x) => return x * 2
Hata çıktısı
C:\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.0

Nası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:

ders10.js
function iksirIc(hp, iyilesme) {    return hp + iyilesme} console.log(iksirIc(100, 30))console.log(iksirIc(100))
Çıktı
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:

ders10.js
function iksirIc(hp, iyilesme = 50) {    return hp + iyilesme} console.log(iksirIc(100, 30))console.log(iksirIc(100))
Çıktı
130150

iyilesme = 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:

ders10.js
console.log(iksirIc(100, 0))console.log(iksirIc(100, undefined))
Çıktı
100150

Varsayı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, while gibi 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.

ders10.js
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")
Çıktı
Mavi Takım: Garen hedefine 80 hasarTemel hasar: 60

Kodun 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.

Genel kapsam const takimAdi = "Mavi Takım" saldir(hedef) fonksiyonunun kapsamı const hasar = 60 if bloğu const bonus = 20 buradan görünen: bonus, hasar, hedef, takimAdi, saldir buradan görünen: hasar, hedef, takimAdi, saldir buradan görünen: yalnızca takimAdi ve saldir içeriden dışarısı görünür
Şekil 10.2 Kapsamlar iç içe kutulardır. İçteki kutudan dıştakiler görünür, dıştakinden içerisi görünmez.
Sık hataReferenceError: bonus is not defined

Ne zaman çıkar? Bir blokta (if, for…) açtığın değişkeni bloğun dışında kullanınca.

ders10.js
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")
Hata çıktısı
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.0

Nası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?

ders10.js
const isim = "Ahri" function tanit() {    const isim = "Lux"    console.log(`İçeride: ${isim}`)} tanit()console.log(`Dışarıda: ${isim}`)
Çıktı
İçeride: LuxDışarıda: Ahri

Burada 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:

Sık hataParametre dışarıdaki değişkeni gölgeliyor, değer değişmiyor

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ış.

ders10.js
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}`)
Çıktı
İçeride hp: 70Dışarıda hp: 100

Neden? 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:

ders10.js
let hp = 100 function hasarAl(mevcutHp, hasar) {    return mevcutHp - hasar} hp = hasarAl(hp, 30)console.log(`Dışarıda hp: ${hp}`)
Çıktı
Dışarıda hp: 70

Parametreye 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ı?

ders10.js
console.log(hasarHesapla(80)) function hasarHesapla(guc) {    return guc * 2}
Çıktı
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:

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

Ne zaman çıkar? const (ya da let) ile tanımlanmış bir fonksiyonu, tanımlandığı satırdan önce çağırınca.

ders10.js
console.log(hasarHesapla(80))console.log(kritikVurus(80)) function hasarHesapla(guc) {    return guc * 2} const kritikVurus = (guc) => guc * 3
Hata çıktısı
160C:\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.

1. tur: dosyayı oku hasarHesapla function: tamamen hazır ✓ kritikVurus const: adı var, kutu kilitli 2. tur: satır satır çalıştır 1: hasarHesapla(80) 160 döner ✓ 2: kritikVurus(80) kutu kilitli: ReferenceError ✗ 8: const kritikVurus = … kilit ancak burada açılırdı
Şekil 10.3 Node dosyayı iki turda işler. 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.

ders10.js
function selamla(isim) {    return `Merhaba ${isim}`} console.log(selamla("Lux"))console.log(selamla) const merhabaDe = selamlaconsole.log(merhabaDe("Jinx"))
Çıktı
Merhaba Lux[Function: selamla]Merhaba Jinx

console.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

ders10.js
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"))}
Çıktı
Garen büyülendi (E)Garen hedefine Q: 60 hasarGaren hedefine W: 40 hasar

kombo 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

ders10.js
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"))
Çıktı
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

ders10.js
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)
Çıktı
AhriGarenJinx

herSampiyonIcin 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.

takim Ahri Garen Jinx herSampiyonIcin diziyi gezer, her şampiyon için çağırır: islem(sampiyon) islem = isminiYaz senin verdiğin fonksiyon islem(takim[0]) islem(takim[1]) islem(takim[2]) her çağrı bir ad yazar: Ahri, Garen, Jinx
Şekil 10.4 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:

ders10.js
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}`)
Çıktı
Garen ölü, yeniden doğmayı bekliyorTakımın toplam canı: 145

Dö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.

Sık hataTypeError: islem is not a function

Ne zaman çıkar? Callback bekleyen bir fonksiyona fonksiyon vermeyi unutunca (ya da fonksiyon yerine başka bir değer verince).

ders10.js
function herSampiyonIcin(takim, islem) {    for (const sampiyon of takim) {        islem(sampiyon)    }} const takim = [    { isim: "Ahri", hp: 100 },    { isim: "Garen", hp: 0 }] herSampiyonIcin(takim)
Hata çıktısı
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.0

Nası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.

ders10.js — ogren — Visual Studio Code
ders10.js
console.log("Maç başladı") setTimeout(() => {    console.log("Baron Nashor doğdu!")}, 2000) console.log("Minyonlar yürüyor")
PROBLEMSOUTPUTDEBUG CONSOLETERMINAL
PS C:\kod\ogren> node ders10.jsMaç başladıMinyonlar yürüyorBaron Nashor doğdu!
Ln 1, Col 1Spaces: 4UTF-8JavaScript

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:

JavaScript✗ Yanlış
setTimeout(yaz(), 1000)
JavaScript✓ Doğru
setTimeout(yaz, 1000)
Sık hataTypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefined

Ne zaman çıkar? setTimeout'a fonksiyonu vermek yerine fonksiyonu çağırıp sonucunu verince.

ders10.js
const yaz = () => console.log("Baron doğdu!")setTimeout(yaz(), 1000)
Hata çıktısı
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.0

Ne 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:

JavaScript
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:

ders10.js
function hasarciOlustur(carpan) {    return (guc) => guc * carpan} const kritik = hasarciOlustur(2)const ulti = hasarciOlustur(3) console.log(kritik(50))console.log(ulti(50))
Çıktı
100150

hasarciOlustur(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:

ders10.js
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())
Çıktı
12314

sayacOlustur'u bir fabrika gibi düşün: her çağrıldığında yeni bir sayaç fonksiyonu üretip verir. Adım adım:

  1. sayacOlustur() çağrılınca içinde sayi adlı bir kutu açılır (değeri 0) ve bir ok fonksiyonu oluşturulup döndürülür. Ok fonksiyonu sayi'yı kullandığı için kutuyu çantasına koyar.
  2. killSayaci artık o ok fonksiyonu. Her çağrıda çantasındaki sayi'yı bir artırıp döndürüyor: 1, 2, 3.
  3. asistSayaci = sayacOlustur() fabrikayı yeniden çalıştırır: yeni bir sayi kutusu (0), yeni bir fonksiyon, yeni bir çanta. Bu yüzden asist sayacı 1'den başlıyor, kill sayacı ise 4'ten devam ediyor.
sayacOlustur() let sayi = 0 return () => {…} her çağrı: yeni çanta 3 kez çağrıldı killSayaci sayi++, return sayi sırt çantası sayi: 3 1 kez çağrıldı asistSayaci sayi++, return sayi sırt çantası sayi: 1 dışarıdan: console.log(sayi) → ReferenceError her sayaç yalnızca kendi çantasını açar
Şekil 10.5 Closure bir sırt çantasıdır. Fabrika her çağrıldığında yeni bir çanta hazırlar; her fonksiyon yalnızca kendi çantasını açabilir.

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.

Sık hataÇıktıda [Function (anonymous)]: fabrikayı çağırmayı unutmak

Ne zaman çıkar? Fabrika fonksiyonunu parantezsiz atayınca. Hata mesajı yok; sayı beklerken fonksiyon yazdırılır.

ders10.js
function sayacOlustur() {    let sayi = 0    return () => {        sayi++        return sayi    }} const killSayaci = sayacOlusturconsole.log(killSayaci())console.log(killSayaci())
Çıktı
[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.

ders10.js
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))
Çıktı
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:

ders10.js
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))
Çıktı
Deneyim: 50Deneyim: 1005050

deneyimKazan(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çindeYan etki mi?
return hp - hasarHayı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ç
Saf fonksiyon 0, 50 deneyimEkle yalnızca hesaplar 50 aynı girdi → hep aynı sonuç dışarıda hiçbir şey değişmez Yan etkili fonksiyon 50 deneyimKazan hesaplar, iz bırakır toplamDeneyim değişti console.log ekrana yazdı
Şekil 10.6 Saf fonksiyon yalnızca hesaplar. Yan etkili fonksiyon hesaplarken dışarıda iz bırakır.

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.

Görev 1

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ı.

ders10.js
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))
Çıktı
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
ders10.js
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))
Çıktı
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.

Görev 2

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.

ders10.js✗ Yanlış
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)
Hata çıktısı
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.0

Hepsini 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
ders10.js
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))
Çıktı
120850100080Ejderha doğdu

Sı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.

Görev 3

Dükkânı saf yap

Bu kodda iki sorun var: satinAl dışarıdaki altin'ı değiştiriyor (yan etki) ve adet verilmeyince bozuluyor.

ders10.js
let altin = 500 function satinAl(fiyat, adet) {    altin = altin - fiyat * adet    console.log(`Kalan altın: ${altin}`)} satinAl(150, 2)satinAl(50)
Çıktı
Kalan altın: 200Kalan altın: NaN

Bu kodu sil ve yerine saf bir kalanAltin(altin, fiyat, adet) fonksiyonu yaz:

  • adet verilmezse 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:

JavaScript
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ı:

Metin
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
ders10.js
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))
Çıktı
Kalan altın: 200Kalan altın: 150Kalan altın: 1500150150

Sı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.

Görev 4

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:

JavaScript
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ın döndürsün.

Fonksiyonunu şu çağrılarla dene:

JavaScript
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ı:

Metin
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
ders10.js
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))
Çıktı
Kalan altın: 8Kalan altın: 6Kalan altın: 0Yetersiz altınKalan altın: 0Bilinmeyen komut: sat

Sı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.

Görev 5

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:

JavaScript
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
ders10.js
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))
Çıktı
31200

Sı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.

Görev 6

Galibiyet serisi

TFT'de üst üste kazandıkça her tura ek altın gelir. Kurallarımız:

SeriBonus
0–10 altın
2–31 altın
42 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ı.

JavaScript
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ı:

Metin
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
ders10.js
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"))
Çı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

Sı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?

JavaScript
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.

JavaScript
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?

JavaScript
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?

JavaScript
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?

JavaScript
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?

JavaScript
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) + 1
Cevabı 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?

JavaScript
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.

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