Blok 2 · Ders 12
Obje ve dizilerde ileri teknikler
Parçalama (destructuring), yayma (...), Object.keys/values/entries, kopya ile referans farkı, JSON ve null güvenliği.
Bu dersin sonunda
- Bir objeyi ya da diziyi parçalayarak (destructuring) değerleri tek satırda kendi kutularına alabilirsin
- Yayma (
...) ile dizi ve objeleri kopyalayıp birleştirebilir, geri kalanları (rest) tek adda toplayabilirsin Object.keys,Object.valuesveObject.entriesile bir objenin bütün alanlarını gezebilirsin- Kopya ile referansı ayırt edip asıl veriyi
structuredCloneile koruyabilirsin JSON.stringifyveJSON.parseile objeyi metne, metni objeye çevirebilirsinnull,undefinedve yanlış sayılan değerlerin tuzaklarından?.ve??ile kaçınabilirsin
Bu sayfada
- Parçalama: objeyi kutulara açmak
- Dizi parçalama ve değiş tokuş
- Yayma: dök, kopyala, birleştir
- Geri kalanı toplamak: rest
- Objenin bütün alanlarını gezmek
- Kopya mı, aynı obje mi?
- Sığ kopya tuzağı ve structuredClone
- JSON: objeyi metne, metni objeye çevirmek
- null ve undefined: iki çeşit "yok"
- Doğru sayılanlar ve yanlış sayılanlar
- Güvenli okuma: ?.
- Yedek değer: ??
- Sık hatalar
- Görevler
- Kendini sına
- Kayıt noktası
Ders 4'te objeyi, Ders 5'te diziyi tanıdın, Ders 11'de dizileri metotlarla dönüştürdün. Bu derste ikisini daha rahat ve daha güvenli kullanmayı öğreneceksin: bir objeyi tek satırda kutulara açmak, kopyalayıp birleştirmek, bütün alanlarını gezmek, metne çevirip saklamak ve olmayan bir değer yüzünden çökmemek.
Bu derste göreceğin const { isim } = oyuncu, { ...ayarlar } ya da oyuncu.klan?.ad ?? "Klansız" gibi satırlar gerçek projelerde neredeyse her dosyada karşına çıkar. Bütün örnekleri C:\kod\ogren\ders12.js dosyasına yaz; her yeni örnekten önce eski kodu sil ya da satırların başına // koy.
Parçalama: objeyi kutulara açmak
Bir objenin birkaç alanıyla çalışacaksan her birini ayrı bir değişkene almak istersin. Şimdiye kadar bunu tek tek yaptın:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }const hp = ahri.hpconst gold = ahri.goldconsole.log(hp, gold)100 300Her satırda aynı adı iki kez yazıyorsun: hp = ahri.hp. Alan sayısı arttıkça bu tekrar hem yorar hem de yazım hatasına kapı açar. JavaScript'in bunun için kısa bir yazımı var:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }const { hp, gold } = ahriconsole.log(hp, gold)console.log(ahri)100 300{ isim: 'Ahri', hp: 100, gold: 300 }İkinci satırı şöyle oku: "ahri objesinden hp ve gold alanlarını al, aynı adlı iki kutuya koy." Süslü parantez burada obje kurmuyor; eşittirin solunda durduğu için "bu objeyi aç" anlamına geliyor. Buna parçalama (destructuring) denir.
Son satıra da bak: obje yerinde duruyor. Parçalama değerleri kopyalar, objeden söküp almaz.
İki kural var. Süslü parantezin içindeki adlar objedeki anahtarlarla aynı olmalı; sıra önemli değil, const { gold, hp } = ahri de aynı işi yapar. Objede olmayan bir adı istersen kutuya undefined konur, hata çıkmaz:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }const { mana, gold } = ahriconsole.log(mana, gold)undefined 300Başka bir adla almak
Aynı alanı iki ayrı objeden almak istediğinde sorun çıkar. Ahri'nin de Garen'in de hp alanı var:
const ahri = { isim: "Ahri", hp: 100 }const garen = { isim: "Garen", hp: 620 }const { hp } = ahriconst { hp } = garenC:\kod\ogren\ders12.js:4const { hp } = garen ^ SyntaxError: Identifier 'hp' has already been declared at wrapSafe (node:internal/modules/cjs/loader:1861:18) ... Node.js v24.20.0Bu, Ders 1'den tanıdığın hata: aynı adla iki kutu açılamaz. Çözüm, alanı alırken kutuya yeni bir ad vermek. Yazımı alan: yeniAd:
const ahri = { isim: "Ahri", hp: 100 }const garen = { isim: "Garen", hp: 620 }const { hp: ahriHp } = ahriconst { hp: garenHp } = garenconsole.log(ahriHp, garenHp)100 620Varsayılan değer
Ders 10'da parametrelere varsayılan değer vermiştin. Parçalamada da aynı = ile, alan objede yoksa kullanılacak değeri yazarsın:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }const { seviye = 1, hp = 50 } = ahriconsole.log(seviye, hp)1 100seviye objede yok, varsayılan 1 kullanıldı. hp var; varsayılan 50 görmezden gelindi. Yeni ad ile varsayılanı birlikte de yazabilirsin. Aşağıdaki dört yazımın hepsi const … = ahri kalıbında eşittirin soluna gelir:
| Eşittirin solu | Anlamı |
|---|---|
{ hp } | hp alanını hp kutusuna al |
{ hp: can } | hp alanını can adlı kutuya al |
{ hp = 50 } | hp alanını al; yoksa 50 kullan |
{ hp: can = 50 } | hp alanını can kutusuna al; yoksa 50 kullan |
Fonksiyonun parantezinde parçalama
Parçalamayı en çok fonksiyon parametrelerinde göreceksin. Bu fonksiyon bir obje alıyor ama içinden yalnız iki alana ihtiyacı var:
function kartYaz({ isim, hp }) { console.log(`${isim}: ${hp} HP`)} const ahri = { isim: "Ahri", hp: 100, gold: 300 }kartYaz(ahri)kartYaz({ isim: "Lux", hp: 90 })Ahri: 100 HPLux: 90 HPGelen obje parantezin içinde hemen açılır; fonksiyonun içinde sampiyon.isim yerine doğrudan isim yazarsın. Ders 4'te her alanın önüne sampiyon. yazmanın satırları uzattığını görmüştün; kısaltmanın yolu buydu. Blok 6'da React'le arayüz yazarken bu kalıbı neredeyse her dosyada göreceksin.
TypeError: Cannot destructure property 'isim' of 'undefined' as it is undefined.Ne zaman çıkar? Parçalanacak şey undefined olduğunda. En sık hâli: objeyi parçalayan bir fonksiyonu argümansız çağırmak.
function kartYaz({ isim, hp }) { console.log(`${isim}: ${hp} HP`)} kartYaz()C:\kod\ogren\ders12.js:1function kartYaz({ isim, hp }) { ^ TypeError: Cannot destructure property 'isim' of 'undefined' as it is undefined. at kartYaz (C:\kod\ogren\ders12.js:1:20) at Object.<anonymous> (C:\kod\ogren\ders12.js:5:1) ... Node.js v24.20.0Mesaj "undefined'ın isim alanını (property) parçalayamam, çünkü o undefined" diyor. Dikkat: en üstteki satır numarası (1) fonksiyonun tanımını gösteriyor. Hatayı tetikleyen çağrıyı ikinci at satırında bulursun: ders12.js:5. Bu satırları okumayı Ders 13'te ayrıntılı göreceksin.
Nasıl düzelir? Fonksiyonu bir objeyle çağır. Argüman gelmeyebilecekse parametreye = {} varsayılanı ver, alanlara da kendi varsayılanlarını yaz:
function kartYaz({ isim = "Bilinmeyen", hp = 0 } = {}) { console.log(`${isim}: ${hp} HP`)} kartYaz({ isim: "Lux", hp: 90 })kartYaz({ isim: "Teemo" })kartYaz()Lux: 90 HPTeemo: 0 HPBilinmeyen: 0 HPSon çağrıda argüman gelmedi, parametre = {} sayesinde boş obje oldu; boş objede olmayan isim ve hp de kendi varsayılanlarını aldı.
Aynı hata bir değişkeni parçalarken de çıkar; o zaman mesajda değişkenin adı yazar: Cannot destructure property 'isim' of 'oyuncu' as it is undefined.
Dizi parçalama ve değiş tokuş
Dizileri de parçalayabilirsin. Dizide ad yok, sıra var; bu yüzden köşeli parantez kullanılır ve kutular sırayla dolar:
const siralama = ["Jinx", "Lux", "Garen", "Teemo"]const [birinci, ikinci] = siralamaconsole.log(birinci, ikinci)Jinx Luxİlk kutu 0. elemanı, ikinci kutu 1. elemanı alır; adları sen seçersin. Atlamak istediğin elemanın yerine yalnız virgülünü yazarsın. Dizide olmayan bir sıraya undefined düşer:
const siralama = ["Jinx", "Lux", "Garen"]const [, , ucuncu, dorduncu] = siralamaconsole.log(ucuncu, dorduncu)Garen undefinedDeğiş tokuş
İki değişkenin değerini takas etmek sık gerekir: iki oyuncunun koridorunu ya da iki eşyanın yerini değiştirmek gibi. Eskiden üçüncü, geçici bir kutu gerekirdi:
let mavi = "Ahri"let kirmizi = "Zed"let gecici = mavimavi = kirmizikirmizi = geciciconsole.log(mavi, kirmizi)Zed AhriDizi parçalamayla tek satır yeter:
let mavi = "Ahri"let kirmizi = "Zed";[mavi, kirmizi] = [kirmizi, mavi]console.log(mavi, kirmizi)Zed AhriÜçüncü satır sağdan sola çalışır: önce [kirmizi, mavi] ile ["Zed", "Ahri"] dizisi kurulur, sonra soldaki parçalama bu diziyi sırayla mavi ve kirmizi kutularına dağıtır. Satırda let yok, çünkü kutular zaten var; yalnız içlerini değiştiriyorsun. Bu yüzden kutular let ile açılmış olmalı, const olsaydı Assignment to constant variable. hatası alırdın.
Satırın başındaki ; bir süs değil. Ders 7'de noktalı virgül koymamanın serbest olduğunu gördün, ama bunun bir istisnası var:
[ ile başlayınca önceki satıra yapışırNe zaman çıkar? Noktalı virgül kullanmadığın kodda bir satır [ ile başladığında. JavaScript satır sonunu her zaman "komut bitti" saymaz; sonraki satır [ ile başlıyorsa onu önceki satırın devamı sanar.
let mavi = "Ahri"let kirmizi = "Zed"[mavi, kirmizi] = [kirmizi, mavi]console.log(mavi, kirmizi)C:\kod\ogren\ders12.js:3[mavi, kirmizi] = [kirmizi, mavi] ^ ReferenceError: Cannot access 'kirmizi' before initialization at Object.<anonymous> (C:\kod\ogren\ders12.js:3:8) ... Node.js v24.20.0JavaScript 2. ve 3. satırı tek komut olarak okudu: let kirmizi = "Zed"[mavi, kirmizi] = …. Yani kirmizi kutusu daha açılırken içinde kirmizi'yi kullanmaya kalktı; mesaj "kirmizi'ye ilk değeri verilmeden erişilemez" diyor.
Önceki satır başka bir şey olsaydı mesaj da değişirdi: console.log(...) satırından sonra TypeError: Cannot set properties of undefined (setting 'Zed') çıkar. Mesajlar kafa karıştırır, çünkü JavaScript senin yazmadığın bir komutu çalıştırmaya çalışıyor.
Nasıl düzelir? [ ile başlayan satırın başına ; koy: ;[mavi, kirmizi] = [kirmizi, mavi]. Noktalı virgülsüz yazanların akılda tuttuğu kural budur: satır [, ( ya da ters tırnak (`) ile başlıyorsa başına ; konur.
Yayma: dök, kopyala, birleştir
Diziye eleman eklemek için Ders 5'te push kullandın. push asıl diziyi değiştirir. Çoğu zaman eski hâli de saklamak istersin: maç başındaki kadroyu ve son kadroyu ayrı ayrı göstermek gibi. Yeni bir dizi kurup eskisinin elemanlarını içine dökersen asıl diziye dokunmamış olursun:
const takim = ["Ahri", "Garen"]const yeniTakim = [...takim, "Lux"]console.log(takim)console.log(yeniTakim)[ 'Ahri', 'Garen' ][ 'Ahri', 'Garen', 'Lux' ]...takim şu demek: "takim'ın elemanlarını buraya tek tek dök." Köşeli parantez yeni bir dizi kurar; içine önce takim'ın elemanları, sonra "Lux" girer. takim olduğu gibi kaldı. Bu üç noktaya yayma (spread) denir.
Farkı kendin gör:
const mavi = ["Ahri", "Garen"]const kirmizi = ["Zed", "Jinx"]console.log([mavi, kirmizi])console.log([...mavi, ...kirmizi])[ [ 'Ahri', 'Garen' ], [ 'Zed', 'Jinx' ] ][ 'Ahri', 'Garen', 'Zed', 'Jinx' ]İlki iki dizi içeren bir dizi (iç içe köşeli parantezlere bak), ikincisi dört isimden oluşan düz bir dizi.
Fonksiyon çağrısında yayma
Math.max (Ders 9) sayıları virgülle ayrılmış hâlde ister; ona dizi verirsen anlamaz:
const hasarlar = [120, 340, 90]console.log(Math.max(hasarlar))console.log(Math.max(...hasarlar))NaN340İlk satırda Math.max tek bir şey aldı: bir dizi. Onu sayıya çeviremedi ve NaN verdi. Yayma diziyi açıp Math.max(120, 340, 90) hâline getirdi. Boş bir diziyi yayarsan Math.max hiç sayı almaz ve -Infinity döndürür; liste boş olabilecekse önce length'e bak.
Objelerde yayma
Üç nokta objelerde de çalışır:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }const guncel = { ...ahri, hp: 80 }console.log(ahri)console.log(guncel){ isim: 'Ahri', hp: 100, gold: 300 }{ isim: 'Ahri', hp: 80, gold: 300 }{ ...ahri, hp: 80 }: yeni bir obje kur, içine önce ahri'nin bütün alanlarını dök, sonra hp'yi 80 yap. Aynı anahtar iki kez gelince sonraki kazanır: önce hp: 100 geldi, sonra hp: 80 onun üstüne yazıldı. Asıl ahri yine değişmedi.
İki objeyi de aynı yolla birleştirirsin. Oyun profilini düşün: her yeni hesap varsayılan bir unvan, çerçeve ve simgeyle başlar; oyuncu bunlardan istediğini değiştirir. Varsayılanları önce, oyuncunun seçtiklerini sonra dökersin:
const varsayilanProfil = { unvan: "Çaylak", cerceve: "gri", simge: 1 }const secilenler = { unvan: "Efsane", simge: 29 }const profil = { ...varsayilanProfil, ...secilenler }console.log(profil){ unvan: 'Efsane', cerceve: 'gri', simge: 29 }Geri kalanı toplamak: rest
Aynı üç nokta ters yönde de çalışır: dökmek yerine toplar. Hangisi olduğunu, üç noktanın nerede durduğundan anlarsın.
Kaç vuruş geleceğini bilmediğin bir toplam hasar fonksiyonu düşün: bazen iki vuruş gelir, bazen dört, bazen hiç.
function toplamHasar(...vuruslar) { return vuruslar.reduce((toplam, vurus) => toplam + vurus, 0)} console.log(toplamHasar(120, 80))console.log(toplamHasar(50, 50, 50, 50))console.log(toplamHasar())2002000...vuruslar "bana gelen bütün argümanları bir diziye topla" demek. İçeride vuruslar gerçek bir dizidir, Ders 11'deki reduce doğrudan çalışır. Hiç argüman gelmezse boş dizi olur; reduce'un başlangıç değeri 0 sayesinde sonuç da 0 çıkar.
Normal parametrelerle birlikte de kullanılır. Önce tek tek parametreler gelir, en sona geri kalan:
function saldiri(saldiran, ...hedefler) { console.log(saldiran, "vurdu:", hedefler)} saldiri("Lux", "Zed", "Jinx", "Teemo")saldiri("Ahri")Lux vurdu: [ 'Zed', 'Jinx', 'Teemo' ]Ahri vurdu: []İlk argüman saldiran'a gider, kalanların hepsi hedefler dizisine. Tek argümanla çağrılınca hedefler boş bir dizidir, undefined değil.
SyntaxError: Rest parameter must be last formal parameterNe zaman çıkar? Geri kalanı toplayan parametreden sonra başka bir parametre yazınca.
function saldiri(...hedefler, saldiran) { console.log(saldiran, hedefler)}C:\kod\ogren\ders12.js:1function saldiri(...hedefler, saldiran) { ^ SyntaxError: Rest parameter must be last formal parameter at wrapSafe (node:internal/modules/cjs/loader:1861:18) ... Node.js v24.20.0Nasıl düzelir? Mesaj "geri kalan parametresi en sonda olmalı" diyor. Bütün argümanları toplayan bir parametreden sonra alacak argüman kalmaz; onu en sona taşı: function saldiri(saldiran, ...hedefler). Parçalamada da kural aynı: const [...digerleri, son] = takim yazarsan SyntaxError: Rest element must be last element alırsın.
Parçalarken de geri kalanı toplayabilirsin. Dizide alınmayan elemanlar bir diziye, objede alınmayan alanlar bir objeye gider:
const takim = ["Garen", "Ahri", "Lux", "Jinx"]const [lider, ...digerleri] = takimconsole.log(lider)console.log(digerleri)Garen[ 'Ahri', 'Lux', 'Jinx' ]const hesap = { kullaniciAdi: "ahri_main", sifre: "gizli123", seviye: 214 }const { sifre, ...herkeseAcik } = hesapconsole.log(herkeseAcik)console.log(hesap){ kullaniciAdi: 'ahri_main', seviye: 214 }{ kullaniciAdi: 'ahri_main', sifre: 'gizli123', seviye: 214 }Bu, gerçek projelerde çok kullanılan bir kalıp: bir kullanıcıyı ekrana basmadan ya da başkasına göndermeden önce şifre alanını ayırmak. Asıl obje bozulmaz. (Gerçek sistemler şifreyi zaten açık metin olarak saklamaz; bunu Ders 31'de göreceksin.)
| Yayma (spread) | Geri kalan (rest) | |
|---|---|---|
| Ne yapar? | Açar, döker | Toplar |
| Nerede durur? | Yeni dizi ya da obje kurarken, fonksiyonu çağırırken | Parçalarken (eşittirin solunda), fonksiyonu tanımlarken |
| Örnek | [...takim, "Lux"], Math.max(...hasarlar) | const [lider, ...digerleri], function toplamHasar(...vuruslar) |
Objenin bütün alanlarını gezmek
Bir dizinin her elemanı için for...of yazabiliyordun. Aynısını bir objede dene:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }for (const alan of ahri) { console.log(alan)}C:\kod\ogren\ders12.js:2for (const alan of ahri) { ^ TypeError: ahri is not iterable at Object.<anonymous> (C:\kod\ogren\ders12.js:2:20) ... Node.js v24.20.0Iterable "üzerinde gezilebilir" demek. Dizi gezilebilir, obje gezilemez: objede sıra numarası yok, anahtarlar var. Çözüm, objeyi önce bir diziye çevirmek. JavaScript'in bunun için üç hazır fonksiyonu var:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }console.log(Object.keys(ahri))console.log(Object.values(ahri))console.log(Object.entries(ahri))[ 'isim', 'hp', 'gold' ][ 'Ahri', 100, 300 ][ [ 'isim', 'Ahri' ], [ 'hp', 100 ], [ 'gold', 300 ] ]entries her alanı iki elemanlı bir diziye çevirir.Object.keys(obje): anahtarlar (İngilizce key, anahtar).Object.values(obje): değerler (value).Object.entries(obje):[anahtar, değer]çiftleri (entry, kayıt). En çok bu kullanılır, çünkü ikisini birden verir.
Üçü de dizi döndürdüğü için artık for...of çalışır. Her turda gelen çifti dizi parçalamayla hemen iki kutuya açarsın:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }for (const [anahtar, deger] of Object.entries(ahri)) { console.log(`${anahtar}: ${deger}`)}isim: Ahrihp: 100gold: 300Bu satırı ezberle: for (const [anahtar, deger] of Object.entries(obje)). Bir objenin her alanıyla iş yapmanın en yaygın yolu bu.
Dönen şey dizi olduğu için Ders 11'deki metotların hepsi de kullanılabilir. Dükkândaki fiyatlarla dene:
const fiyatlar = { kilic: 450, kalkan: 300, iksir: 50 }const toplam = Object.values(fiyatlar).reduce((t, fiyat) => t + fiyat, 0)const ucuzlar = Object.entries(fiyatlar).filter(([esya, fiyat]) => fiyat < 400)console.log(Object.keys(fiyatlar).length)console.log(toplam)console.log(ucuzlar)3800[ [ 'kalkan', 300 ], [ 'iksir', 50 ] ]Object.keys(fiyatlar).lengthobjede kaç alan olduğunu söyler. Boş bir obje için0verir; "bu obje boş mu?" sorusunun cevabı budur.filter'a verdiğin fonksiyon her turda bir[anahtar, değer]çifti alıyor. Parantezin içinde onu hemen parçalayabilirsin:([esya, fiyat]) =>. Parçalama her parametrede çalışır.
Kopya mı, aynı obje mi?
Ders 4'te bir objeyi fonksiyona verince fonksiyonun dışarıdaki objeyi değiştirebildiğini gördün; sayı verince ise yalnız kopyası gidiyordu. Bu farkın sebebini şimdi öğreneceksin. Önce bildiğin bir şey: bir sayıyı başka bir kutuya kopyalayıp kopyayı değiştirirsen asıl kutu etkilenmez.
let can = 100let yedekCan = canyedekCan = 0console.log(can, yedekCan)100 0Şimdi aynısını bir objeyle yap:
const ahri = { isim: "Ahri", hp: 100 }const kopya = ahrikopya.hp = 0console.log(ahri.hp)console.log(kopya === ahri)PS C:\kod\ogren> node ders12.js0truekopya'nın canını sıfırladın, ahri'ninki de sıfırlandı. Hata yok, uyarı yok.
Sebebi şu: obje bir değişkenin içine sığmaz. Değişken, objenin bellekte nerede durduğunu tutar; buna referans denir. const kopya = ahri objeyi kopyalamadı, adresini kopyaladı.
Artık aynı objeye giden iki ad var ve birinden yapılan değişikliği öteki de görür. Sayı, metin, true ve false gibi tek parça değerler ise kutunun içinde durur ve atamada kopyalanır.
Bu yüzden === objelerde içeriğe değil adrese bakar:
const ahri = { hp: 100 }const kopya = ahriconsole.log(kopya === ahri)console.log({ hp: 100 } === { hp: 100 })console.log([] === [])truefalsefalseAynı objeye giden iki ad eşittir. İçerikleri aynı olsa da ayrı ayrı kurulmuş iki obje eşit değildir, çünkü her { } ve her [ ] yeni bir obje kurar.
Ders 4'teki fonksiyon davranışının sebebi de bu. Fonksiyona objenin kopyası değil referansı gider; parametre, aynı objeye giden bir ad daha olur. sampiyon.hp = 0 yazan bir fonksiyon dışarıdaki objeyi değiştirir.
Bu, Ders 10'da gördüğün yan etki; program büyüdükçe "bu değeri kim değiştirdi?" sorusu zorlaşır. Ders 11'deki değişmezlik alışkanlığı burada da geçerli: asıl objeyi değiştirme, yaymayla yenisini kurup döndür:
function hasarVer(sampiyon, miktar) { return { ...sampiyon, hp: sampiyon.hp - miktar }} const ahri = { isim: "Ahri", hp: 100 }const yarali = hasarVer(ahri, 30)console.log(ahri)console.log(yarali){ isim: 'Ahri', hp: 100 }{ isim: 'Ahri', hp: 70 }Ders 11'de sort'un asıl diziyi değiştirdiğini, toSorted'ın ise yeni dizi döndürdüğünü görmüştün. Farkın önemi buradan geliyor: sort dizinin kendisine dokunur ve o diziyi gösteren her ad değişikliği görür. Eski kodlardaki [...dizi].sort() yazımı da bu yüzden önce yaymayla bir kopya alır.
Sığ kopya tuzağı ve structuredClone
Yayma yeni bir obje kurar, ama yalnız en dış katı yenidir. Objenin içinde bir dizi ya da başka bir obje varsa ne olduğuna bak:
const ahri = { isim: "Ahri", hp: 100, esyalar: ["Doran Yüzüğü"] }const kopya = { ...ahri }kopya.hp = 50kopya.esyalar.push("Kılıç")console.log(ahri)console.log(kopya){ isim: 'Ahri', hp: 100, esyalar: [ 'Doran Yüzüğü', 'Kılıç' ] }{ isim: 'Ahri', hp: 50, esyalar: [ 'Doran Yüzüğü', 'Kılıç' ] }hp güvende: Ahri'nin canı hâlâ 100. Ama kılıç iki objeye birden girdi.
Sebep: esyalar alanında dizinin kendisi değil, referansı durur. Yayma bu alanı kopyalarken diziyi değil referansı kopyaladı; iki obje aynı eşya dizisini paylaşıyor. Ders 11'de map'in içinde elemanı değiştirince asıl dizideki objenin de değişmesi aynı sebepten: yeni dizi, eski objeler.
Böyle, yalnız dış katı yeni olan kopyaya sığ kopya denir. { ...obje } ve [...dizi] sığ kopya üretir. İç içe bütün katları yeni olan kopya ise derin kopyadır ve JavaScript'te tek komutla yapılır: structuredClone.
const ahri = { isim: "Ahri", hp: 100, esyalar: ["Doran Yüzüğü"] }const kopya = structuredClone(ahri)kopya.esyalar.push("Kılıç")console.log(ahri)console.log(kopya){ isim: 'Ahri', hp: 100, esyalar: [ 'Doran Yüzüğü' ] }{ isim: 'Ahri', hp: 100, esyalar: [ 'Doran Yüzüğü', 'Kılıç' ] }structuredClone objeyi bütün katlarıyla, içindeki dizileri ve objeleri de yeniden kurarak kopyalar. Node'da 17. sürümden beri, bütün yaygın tarayıcılarda 2022'den beri hazır gelir; bir şey kurman gerekmez.
| Yazım | Yeni obje kurulur mu? | İçteki diziler ve objeler |
|---|---|---|
const kopya = asil | Hayır, aynı obje | Paylaşılır |
{ ...asil } ya da [...asil] | Evet, sığ kopya | Paylaşılır |
structuredClone(asil) | Evet, derin kopya | Onlar da yeniden kurulur |
Ne zaman çıkar? const kopya = asil yazınca ya da iç içe veride { ...asil } ile kopya alıp kopyanın içteki dizisini veya objesini değiştirince. Hiçbir mesaj çıkmaz; program çalışır ama yanlış çalışır. Belirtisi: hiç dokunmadığını düşündüğün bir değer değişmiş. Bir TFT oyununda yeni oyuncuları bir şablondan üretiyorsun:
const sablon = { ad: "Yeni oyuncu", altin: 0, birimler: [] }const oyuncu1 = { ...sablon, ad: "Deniz" }const oyuncu2 = { ...sablon, ad: "Ece" }oyuncu1.birimler.push("Garen")console.log(oyuncu2.birimler)console.log(sablon.birimler)console.log(oyuncu1.birimler === oyuncu2.birimler)[ 'Garen' ][ 'Garen' ]trueDeniz bir Garen aldı; Ece'nin tahtasında da, şablonda da Garen belirdi. Son satır sebebi kanıtlıyor: üçü de aynı birimler dizisini gösteriyor.
Nasıl düzelir? İç içe veriyi kopyalarken structuredClone kullan: { ...structuredClone(sablon), ad: "Deniz" }. Bir değerin beklenmedik biçimde değiştiğini görürsen === ile iki adın aynı objeyi gösterip göstermediğini denetle; true çıkıyorsa aradığın hata budur.
JSON: objeyi metne, metni objeye çevirmek
Objeler programın belleğinde yaşar; program kapanınca kaybolurlar. Veriyi dosyaya yazmak, bir sitenin sunucudan veri alması ya da iki programın konuşması için verinin metne dönüşmesi gerekir. Bunun bütün dünyada kullanılan ortak biçimi JSON'dur.
JSON.stringify objeyi JSON metnine çevirir:
const ahri = { isim: "Ahri", hp: 100, gold: 300 }const metin = JSON.stringify(ahri)console.log(metin)console.log(metin.length){"isim":"Ahri","hp":100,"gold":300}35metin artık bir obje değil, 35 karakterlik bir metin; length bunu kanıtlıyor. Anahtarların da çift tırnağa alındığına bak, JSON'da bu zorunlu. Okunaklı olsun diye ikinci ve üçüncü argümanla girintili de yazdırabilirsin:
const ahri = { isim: "Ahri", hp: 100, esyalar: ["Doran Yüzüğü", "Kılıç"] }console.log(JSON.stringify(ahri, null, 2)){ "isim": "Ahri", "hp": 100, "esyalar": [ "Doran Yüzüğü", "Kılıç" ]}Üçüncü argüman (2) her katta kaç boşluk içeri girileceğini söyler. Ortadaki null "boş" anlamına gelen bir değer; onu bu dersin ilerisinde tanıyacaksın. O yer, hangi alanların yazılacağını seçmek gibi ileri bir iş için ayrılmış; şimdilik kalıp olarak null yaz.
Ters yön JSON.parse: JSON metninden yeniden obje kurar.
const kayit = '{"isim":"Lux","seviye":6,"esyalar":["Asa","Kitap"]}'const lux = JSON.parse(kayit)console.log(lux)console.log(lux.esyalar[1]){ isim: 'Lux', seviye: 6, esyalar: [ 'Asa', 'Kitap' ] }KitapMetnin içinde çift tırnaklar olduğu için onu tek tırnakla açıp kapattın; Ders 1'deki kuralı hatırla: hangi tırnakla açtıysan onunla kaparsın. Gerçek programlarda bu metni sen yazmayacaksın; internetten (Ders 15) ya da bir dosyadan (Ders 29) gelecek.
stringify paketler, parse paketi açıp objeyi yeniden kurar.JSON gördüğün her yerde ilk soru şu olmalı: "Bu şu an metin mi, obje mi?" parse etmeyi unutursan hata çıkmaz ama sonuç saçmalar:
const kayit = '{"isim":"Lux","seviye":6}'console.log(kayit.isim)console.log(kayit.seviye + 1)undefinedNaNkayit hâlâ bir metin. Metnin isim diye bir alanı yok, undefined geldi; undefined + 1 de NaN verdi. Doğrusu önce JSON.parse(kayit).
JSON yalnız veriyi taşır: metin, sayı, true/false, null, dizi ve obje. Fonksiyonlar ve değeri undefined olan alanlar yolda sessizce düşer:
const teemo = { isim: "Teemo", selam: () => "Hey!", klan: undefined, hedef: null }console.log(JSON.stringify(teemo)){"isim":"Teemo","hedef":null}selam ve klan metne girmedi, hedef girdi. null "bilerek boş" demek; birazdan ayrıntılı göreceksin. Bir bilginin boş olduğunu JSON'a yazmak istiyorsan undefined değil null kullan.
SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)Ne zaman çıkar? JSON.parse'a JSON kurallarına uymayan bir metin verince. En sık sebepler: anahtarlarda tırnak yok, çift tırnak yerine tek tırnak kullanılmış, sonda fazladan virgül var.
const kayit = '{ isim: "Ahri", hp: 100 }'const ahri = JSON.parse(kayit)<anonymous_script>:1{ isim: "Ahri", hp: 100 } ^ SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3) at JSON.parse (<anonymous>) at Object.<anonymous> (C:\kod\ogren\ders12.js:2:19) ... Node.js v24.20.0Bu mesajın ilk satırında senin dosyan yok. <anonymous_script>:1 "ayrıştırılan metnin 1. satırı" demek; altındaki satır da senin kodun değil, JSON metninin kendisi.
^ metnin 3. karakterini (i) gösteriyor: JSON orada çift tırnaklı bir anahtar adı bekliyordu. Mesajdaki position 2 aynı yerin sıfırdan sayılmış hâli. JSON.parse'ı hangi satırda çağırdığını at Object.<anonymous> (…ders12.js:2:19) satırı söylüyor.
Nasıl düzelir? Metni JSON kurallarına uydur: '{ "isim": "Ahri", "hp": 100 }'. Aynı ailenin başka mesajları:
Expected double-quoted property name in JSON at position 29 (line 1 column 30): sonda fazladan virgül var ("hp": 100, }).Unexpected token 'A', "Ahri" is not valid JSON: metin JSON değil, düz yazı."undefined" is not valid JSON:JSON.parse'a değeriundefinedolan bir değişken verdin.Unexpected end of JSON input: metin boş ya da bir değerin gelmesi gereken yerde bitmiş ('{"seviye":'gibi yarım kalmış bir kayıt). Yarım metin, kesildiği yere göreUnterminated string in JSONgibi başka bir mesaj da verebilir.
Dışarıdan gelen JSON bozuk olabilir ve programını çökertir. Bu hatayı programı durdurmadan yakalamayı Ders 13'te try ve catch ile öğreneceksin.
null ve undefined: iki çeşit "yok"
undefined'ı Ders 3 ve Ders 4'ten tanıyorsun: JavaScript bir yere hiç değer konmadığında onu kendisi koyar.
let hedefconst ahri = { isim: "Ahri", hp: 100 }const takim = ["Ahri", "Garen"]console.log(hedef)console.log(ahri.mana)console.log(takim[5])console.log(takim.find((isim) => isim === "Zed"))undefinedundefinedundefinedundefinedDeğer verilmemiş let, olmayan alan, dizinin dışı, bulamayan find: hepsi undefined. null ise farklıdır. JavaScript onu eksik bir yere kendiliğinden koymaz; bir programcı bilerek yazar (çoğu zaman sen, bazen de kullandığın hazır bir fonksiyonun yazarı) ve "burası bilerek boş" demektir:
let hedef = nullconst oyuncu = { isim: "Teemo", klan: null }console.log(hedef)console.log(oyuncu.klan)console.log(oyuncu.mana)nullnullundefinedoyuncu.klan null: Teemo'nun klan bilgisi tutuluyor ve cevabı "klanı yok". oyuncu.mana undefined: böyle bir bilgi hiç tutulmamış. İkisi de "yok" der ama farklı yoklardır; null === undefined sonucu da false'tur.
| Değer | Kim koyar? | Nerede görürsün? |
|---|---|---|
undefined | JavaScript | değer verilmemiş let, olmayan alan, dizinin dışı, bulamayan find, return'süz fonksiyon (Ders 6), gelmeyen parametre |
null | Programcı (çoğu zaman sen) | bilerek boş bırakılan yer: seçilmemiş hedef, klansız oyuncu, JSON'daki boş bilgi |
Bir farkı daha bil: varsayılan değerler (parametredeki de, parçalamadaki de) yalnız undefined gelince devreye girer. null "bilerek boş" olduğu için olduğu gibi kabul edilir:
function kartYaz(isim = "Bilinmeyen") { console.log(isim)} kartYaz(undefined)kartYaz(null)BilinmeyennullParçalama bölümündeki = {} varsayılanı da bu yüzden yalnız undefined'a karşı korur. function kartYaz({ isim = "Bilinmeyen", hp = 0 } = {}) fonksiyonunu kartYaz(null) diye çağırırsan varsayılan devreye girmez ve program TypeError: Cannot read properties of null (reading 'isim') ile durur.
Doğru sayılanlar ve yanlış sayılanlar
Ders 2'de if'in parantezine hep true ya da false veren bir karşılaştırma yazdın. Ama if (hp = 0) hatasında JavaScript'in koşuldaki 0'ı false gibi saydığını da gördün. Parantez her değeri kabul eder ve onu kendi içinde true ya da false'a çevirir. Hangi değerin neye çevrildiğini Boolean(…) ile görebilirsin; Ders 9'daki Number(…) ve String(…) gibi çalışır:
console.log(Boolean(0), Boolean(""), Boolean(null))console.log(Boolean(undefined), Boolean(NaN), Boolean(false))console.log(Boolean("0"), Boolean("false"), Boolean(" "))console.log(Boolean([]), Boolean({}), Boolean(-5))false false falsefalse false falsetrue true truetrue true trueKural kısa. Yanlış sayılan altı değer var: false, 0, "" (boş metin), null, undefined ve NaN. Geri kalan her şey doğru sayılır: "0" ve "false" metinleri, tek boşluklu " ", boş dizi [], boş obje {}, hatta fonksiyonlar da. Ders 10'daki if (hp => 30) tuzağında koşulun hep doğru çıkması bu yüzdendi.
Eksi sıfır -0 ve bu kursta karşına çıkmayacak iki nadir değer daha yanlış sayılır; günlük kodda yukarıdaki altısını bilmen yeter.
Bunun işe yarayan bir yanı var: if (isim) yazmak "isim boş değilse" demektir.
const isim = ""if (isim) { console.log(`Hoş geldin, ${isim}`)} else { console.log("Önce bir isim yaz")}Önce bir isim yazBir de tuzağı var. Boş dizi doğru sayıldığı için if (takim) takım boşken de çalışır; "takımda biri var mı?" diye soracaksan takim.length > 0 yaz. Daha sinsi olanı sıfır:
function altinYaz(gold) { if (gold) { console.log(`Altının: ${gold}`) } else { console.log("Altın bilgisi yok") }} altinYaz(300)altinYaz(0)altinYaz(undefined)Altının: 300Altın bilgisi yokAltın bilgisi yokSıfır altın da bir bilgidir: "hiç altının yok". Ama 0 yanlış sayıldığı için program onu "bilgi yok" sandı. Çözüm, sorduğun soruyu açıkça yazmak:
function altinYaz(gold) { if (gold === undefined) { console.log("Altın bilgisi yok") } else { console.log(`Altının: ${gold}`) }} altinYaz(300)altinYaz(0)altinYaz(undefined)Altının: 300Altının: 0Altın bilgisi yokGüvenli okuma: ?.
JavaScript'te en sık göreceğin hatayı Ders 4'ten tanıyorsun:
const oyuncu = { isim: "Teemo", seviye: 30 }console.log(oyuncu.klan)console.log(oyuncu.klan.ad)undefinedC:\kod\ogren\ders12.js:3console.log(oyuncu.klan.ad) ^ TypeError: Cannot read properties of undefined (reading 'ad') at Object.<anonymous> (C:\kod\ogren\ders12.js:3:25) ... Node.js v24.20.0(reading 'ad') okunmak istenen alanı söylüyor; sorun her zaman onun solunda. Solunda oyuncu.klan var ve ikinci satırın çıktısı onun undefined olduğunu gösteriyor. undefined'ın içi yoktur, içine bakılamaz.
Ders 4'teki çözüm, ara basamağı önce if (oyuncu.klan === undefined) ile denetlemekti; Ders 11'de find'ın sonucunu da böyle denetledin. İkisinde de bunun daha kısa bir yazımı olduğunu duymuştun.
undefined'a çarpınca normal nokta programı çökertir, ?. ise zinciri durdurur.O kısa yazım ?. işareti. Anlamı: "solundaki şey undefined ya da null ise dur ve sonuç undefined olsun; değilse normal nokta gibi devam et."
const oyuncu = { isim: "Teemo", seviye: 30 }const lux = { isim: "Lux", klan: { ad: "Işık Muhafızları", uye: 12 } }console.log(oyuncu.klan?.ad)console.log(lux.klan?.ad)undefinedIşık MuhafızlarıTeemo'nun klanı yok, program çökmedi; Lux'unki var, normal okundu. Bu işareti zincirde birden çok yerde kullanabilirsin: oyuncu.klan?.lider?.isim. find ile çok iyi anlaşır, çünkü find bulamayınca undefined döndürür:
const takim = [ { isim: "Ahri", hp: 100 }, { isim: "Garen", hp: 620 }]console.log(takim.find((s) => s.isim === "Zed")?.hp)console.log(takim.find((s) => s.isim === "Garen")?.hp)undefined620TypeError: Cannot read properties of undefined (reading 'hp')Ne zaman çıkar? undefined olan bir şeyin alanını okumaya çalışınca. Sebep çoğu zaman bir önceki adımdadır: find aradığını bulamadı (Ders 11'de gördüğün durum), objede o alan yok, dizinin olmayan bir sırası istendi ya da bir fonksiyon return etmeyi unuttu.
const takim = [ { isim: "Ahri", hp: 100 }, { isim: "Garen", hp: 620 }]const zed = takim.find((s) => s.isim === "Zed")console.log(zed.hp)C:\kod\ogren\ders12.js:6console.log(zed.hp) ^ TypeError: Cannot read properties of undefined (reading 'hp') at Object.<anonymous> (C:\kod\ogren\ders12.js:6:17) ... Node.js v24.20.0Nasıl düzelir? Parantezdeki ad ('hp') okunmak istenen alan; asıl soru solundakinin neden undefined olduğu. console.log(zed) ile bak ve oradan geriye doğru git: find "Zed"i bulamamış.
Sonra karar ver. Bulunamaması normal bir durumsa zed?.hp yaz ve sonucu ?? ile ele al (birazdan). Normal değilse veriyi ya da aradığın adı düzelt.
Yedek değer: ??
?. çökmeyi önler ama elinde undefined kalır. Ekrana "undefined" yazdırmak istemezsin; yerine anlamlı bir yedek değer koymak istersin. Bunu ?? yapar:
const oyuncu = { isim: "Teemo", seviye: 30 }const lux = { isim: "Lux", klan: { ad: "Işık Muhafızları" } }console.log(oyuncu.klan?.ad ?? "Klansız")console.log(lux.klan?.ad ?? "Klansız")KlansızIşık Muhafızları?? işaretini şöyle oku: "soldaki değer null ya da undefined ise sağdakini kullan, değilse soldakini." ?. ile ?? çoğu zaman birlikte gelir: biri çökmeyi önler, öteki boşluğa anlamlı bir değer koyar.
Eski kodlarda aynı iş için || (Ders 3'teki "veya") kullanıldığını görürsün. || aslında yalnız true ya da false vermez; iki yanındaki değerlerden birini olduğu gibi verir: soldaki doğru sayılıyorsa soldakini, değilse sağdakini. ?? ile farkı buradan çıkar: || solundaki yanlış sayılan her değerde sağdakine geçer ve bunların arasında 0 ile "" de var:
console.log(0 || 100)console.log(0 ?? 100)console.log("" || "İsimsiz")console.log("" ?? "İsimsiz")console.log(null || "Yok")console.log(null ?? "Yok")1000İsimsiz YokYokDördüncü satırın çıktısı boş bir satır: "" korundu.
İki işaretin farkı tablo hâlinde:
| Soldaki değer | soldaki || "yedek" | soldaki ?? "yedek" |
|---|---|---|
0 | "yedek" | 0 |
"" | "yedek" | "" |
false | "yedek" | false |
null | "yedek" | "yedek" |
undefined | "yedek" | "yedek" |
"Ahri" | "Ahri" | "Ahri" |
|| sıfırı silip yedek değeri koyuyorNe zaman çıkar? Yedek değeri || ile verince, 0, "" ya da false gerçek ve geçerli bir değerken. Hata mesajı çıkmaz; yalnız sonuç yanlıştır.
const garen = { isim: "Garen", hp: 0 }const can = garen.hp || 620console.log(`${garen.isim} canı: ${can}`)Garen canı: 620Can bilgisi gelmezse Garen'in başlangıç canını (620) kullanmak istedin. Ama bilgi gelmişti: Garen ölmüştü ve canı 0'dı. Program ölü bir şampiyonu tam canla diriltti.
Nasıl düzelir? "Yoksa şunu kullan" demek istiyorsan ?? yaz: garen.hp ?? 620 sonucu 0 olur, hp alanı hiç yoksa 620. || işaretini yalnız "boş metin ve sıfır da geçersiz" demek istediğin yerde, bilerek kullan.
Sık hatalar
Bu dersin hatalarının çoğunu bölümlerin içinde gördün. Burada kalanlar var. Bütün derslerin hataları hata rehberinde toplanıyor.
TypeError: ahri is not iterableNe zaman çıkar? Bir objeyi for...of ile gezmeye ya da diziymiş gibi yaymaya ([...ahri]) kalkınca. Yukarıda, objenin bütün alanlarını gezmek bölümünde gördün. Dizi parçalamada da çıkar: const [ilk] = oyuncu.
Nasıl düzelir? Objeyi önce diziye çevir: for (const [anahtar, deger] of Object.entries(ahri)). Obje yaymak istiyorsan köşeli değil süslü parantez kullan: { ...ahri }.
TypeError: Cannot read properties of null (reading 'ad')Ne zaman çıkar? null olan bir şeyin alanını okumaya çalışınca. undefined'lı hâlinin kardeşi; fark, boşluğu bu kez birinin bilerek koymuş olması.
const oyuncu = { isim: "Teemo", klan: null }console.log(oyuncu.klan.ad)C:\kod\ogren\ders12.js:2console.log(oyuncu.klan.ad) ^ TypeError: Cannot read properties of null (reading 'ad') at Object.<anonymous> (C:\kod\ogren\ders12.js:2:25) ... Node.js v24.20.0Nasıl düzelir? ?. null'u da durdurur: oyuncu.klan?.ad ?? "Klansız" sonucu Klansız olur. Web sayfalarıyla çalışmaya başlayınca (Blok 3) bu mesajın null'lı hâlini çok göreceksin.
parse etmeden kullanmakNe zaman çıkar? JSON metnini obje sanıp alanlarını okumaya kalkınca. Hata çıkmaz; alanlar undefined, hesaplar NaN gelir. JSON bölümünde kayit.isim örneğiyle gördün.
Nasıl düzelir? Önce const veri = JSON.parse(metin), sonra veri.isim. Emin değilsen console.log(metin) yaz: çıktıda anahtarlar çift tırnaklıysa ({"isim":"Lux"}) elindeki şey metindir; console.log objeleri { isim: 'Lux' } biçiminde yazar.
Görevler
Her görevi ders12.js dosyasında yap; önceki kodu sil ya da satırların başına // koy. Önce kendin dene, takılırsan ipuçlarını sırayla aç. Çözüme en son bak.
Sunucudan gelen profil
Oyunun sunucusundan oyuncu bilgisi İngilizce anahtarlarla geliyor:
const veri = { summonerName: "Deniz", summonerLevel: 214, region: "TR1" }Tek bir parçalama satırıyla summonerName'i isim, summonerLevel'i seviye adlı kutulara al. rank alanını da al; yoksa "Derecesiz" olsun. Sonra şunu yazdır:
Deniz, seviye 214, DerecesizArdından veri'ye rank: "Altın II" ekleyip yeniden çalıştır; son kısım Altın II olmalı.
İpucu 1
Yeni adla almak alan: yeniAd, varsayılan vermek alan = deger biçiminde. Hepsi aynı süslü parantezin içinde, virgülle ayrılır.
İpucu 2
Satırın iskeleti: const { summonerName: isim, … } = veri. Yazdırırken şablon metin kullan: `${isim}, seviye ${seviye}, ${rank}`.
Çözümü göster
const veri = { summonerName: "Deniz", summonerLevel: 214, region: "TR1" }const { summonerName: isim, summonerLevel: seviye, rank = "Derecesiz" } = vericonsole.log(`${isim}, seviye ${seviye}, ${rank}`)Deniz, seviye 214, DerecesizSınır denemesi: rank: "Altın II" eklenince varsayılan devreye girmez ve Altın II yazar. rank: null yazarsan çıktının sonu null olur, çünkü varsayılan değer yalnız undefined'da çalışır.
Koridor değişimi
kadroYaz adında bir fonksiyon yaz. Kaç oyuncu adı verilirse verilsin hepsini oyuncular adlı tek bir dizide toplasın ve sırayla şunları yapsın:
- Kadrodaki kişi sayısını yazdırsın.
- Tek satırda ilk üç adı
ust,orta,altkutularına, kalanlarıyedeklerdizisine alsın. - Maç başında orta ve alt koridordakiler yer değiştirmek istiyor:
ortailealt'ı tek satırda takas etsin. - Koridorları ve yedekleri yazdırsın. Yedekleri dizi olarak göstermek için
console.log("Yedekler:", yedekler)yaz.
kadroYaz("Garen", "Ahri", "Jinx", "Lux", "Teemo")kadroYaz("Garen", "Ahri", "Jinx")Beklenen çıktı:
Kadro: 5 kişiÜst: Garen, orta: Jinx, alt: AhriYedekler: [ 'Lux', 'Teemo' ]Kadro: 3 kişiÜst: Garen, orta: Jinx, alt: AhriYedekler: []İpucu 1
Kaç argüman geleceğini bilmediğin için parametre geri kalan (rest) olmalı: function kadroYaz(...oyuncular). İçeride oyuncular sıradan bir dizidir; length'i vardır.
İpucu 2
Parçalarken de kalanları üç noktayla toplarsın; üç noktalı ad dizi parçalamanın en sonunda durur. Değerleri sonradan değişecek kutuları hangi anahtar sözcükle açman gerekiyor?
İpucu 3
Takas satırı [ ile başlıyor. Önceki satıra yapışmaması için başına ne koyuyordun?
Çözümü göster
function kadroYaz(...oyuncular) { console.log(`Kadro: ${oyuncular.length} kişi`) let [ust, orta, alt, ...yedekler] = oyuncular ;[orta, alt] = [alt, orta] console.log(`Üst: ${ust}, orta: ${orta}, alt: ${alt}`) console.log("Yedekler:", yedekler)} kadroYaz("Garen", "Ahri", "Jinx", "Lux", "Teemo")kadroYaz("Garen", "Ahri", "Jinx")Kadro: 5 kişiÜst: Garen, orta: Jinx, alt: AhriYedekler: [ 'Lux', 'Teemo' ]Kadro: 3 kişiÜst: Garen, orta: Jinx, alt: AhriYedekler: []let yerine const yazarsan takas satırı Assignment to constant variable. verir; başındaki ; işaretini unutursan Cannot access 'orta' before initialization.
Sınır denemesi: kadroYaz("Garen", "Ahri") çağır. alt undefined olur, takastan sonra orta: undefined, alt: Ahri yazar ve yedekler boş dizidir. Program çökmez ama eksik oyuncuyu sessizce kabul eder; gerçek bir oyunda önce oyuncular.length'i denetlerdin.
Envanter raporu
envanterYaz adında bir fonksiyon yaz. Envanteri { esyaAdi: adet } biçiminde bir obje olarak alsın, adedi 0'dan büyük olan her eşyayı iksir x3 biçiminde yazsın, en sona toplam eşya sayısını eklesin. Hiç eşya yoksa (boş obje ya da bütün adetler 0) yalnız Envanter boş yazsın.
envanterYaz({ iksir: 3, totem: 2, kilic: 0 })envanterYaz({})envanterYaz({ kalkan: 0 })Beklenen çıktı:
iksir x3totem x2Toplam 5 eşyaEnvanter boşEnvanter boşİpucu 1
Toplam için adetler yeter: Object.values(envanter) sana [3, 2, 0] verir, gerisi Ders 11'deki reduce.
İpucu 2
Eşya adlarına da adetlere de ihtiyacın olan yerde for (const [esya, adet] of Object.entries(envanter)) kullan.
İpucu 3
Önce toplamı hesapla. Toplam 0 ise Envanter boş yazıp return ile fonksiyondan erken çık (Ders 6); değilse eşyaları ve toplamı yaz.
Çözümü göster
function envanterYaz(envanter) { const toplam = Object.values(envanter).reduce((t, adet) => t + adet, 0) if (toplam === 0) { console.log("Envanter boş") return } for (const [esya, adet] of Object.entries(envanter)) { if (adet > 0) { console.log(`${esya} x${adet}`) } } console.log(`Toplam ${toplam} eşya`)} envanterYaz({ iksir: 3, totem: 2, kilic: 0 })envanterYaz({})envanterYaz({ kalkan: 0 })iksir x3totem x2Toplam 5 eşyaEnvanter boşEnvanter boşSınır denemeleri çağrıların içinde: kilic: 0 listede görünmüyor, boş obje ve "bütün adetler sıfır" durumu aynı mesajı veriyor. reduce'a başlangıç değeri (0) vermeseydin boş objede TypeError: Reduce of empty array with no initial value alırdın.
Simülasyon kopyası
"Takım 100 hasar yerse ne olur?" diye bir simülasyon yapmak istiyorsun. Aşağıdaki kod takımın kopyasını alıp kopyadaki herkese 100 hasar veriyor. Ama çıktıda iki sorun var:
const takim = [ { isim: "Garen", hp: 620 }, { isim: "Lux", hp: 90 }] const deneme = [...takim]for (const sampiyon of deneme) { sampiyon.hp = sampiyon.hp - 100} console.log(deneme)console.log(takim)[ { isim: 'Garen', hp: 520 }, { isim: 'Lux', hp: -10 } ][ { isim: 'Garen', hp: 520 }, { isim: 'Lux', hp: -10 } ]Kodu düzelt: asıl takim hiç değişmesin ve hiçbir şampiyonun canı 0'ın altına inmesin. Beklenen çıktı:
[ { isim: 'Garen', hp: 520 }, { isim: 'Lux', hp: 0 } ][ { isim: 'Garen', hp: 620 }, { isim: 'Lux', hp: 90 } ]İpucu 1
[...takim] yeni bir dizi kurdu ama içindeki objeler aynı objeler. Bu kopyanın adı neydi?
İpucu 2
Her katı yeni bir kopya için structuredClone(takim). Canın eksiye düşmemesi için Ders 9'dan Math.max(0, …).
Çözümü göster
const takim = [ { isim: "Garen", hp: 620 }, { isim: "Lux", hp: 90 }] const deneme = structuredClone(takim)for (const sampiyon of deneme) { sampiyon.hp = Math.max(0, sampiyon.hp - 100)} console.log(deneme)console.log(takim)[ { isim: 'Garen', hp: 520 }, { isim: 'Lux', hp: 0 } ][ { isim: 'Garen', hp: 620 }, { isim: 'Lux', hp: 90 } ]İkinci bir yol: objeleri hiç değiştirmeden map ile yenilerini kurmak. Her şampiyon için yayma ile yeni bir obje döndürürsün:
const takim = [ { isim: "Garen", hp: 620 }, { isim: "Lux", hp: 90 }] const deneme = takim.map((sampiyon) => { return { ...sampiyon, hp: Math.max(0, sampiyon.hp - 100) }}) console.log(deneme)console.log(takim)[ { isim: 'Garen', hp: 520 }, { isim: 'Lux', hp: 0 } ][ { isim: 'Garen', hp: 620 }, { isim: 'Lux', hp: 90 } ]Sınır denemesi: canları 100 ve 101 yap; 0 ve 1 çıkmalı.
Maç özeti hata avı
Bu fonksiyon bir maçın özetini yazıyor. Bilgisi eksik maçlarda ? ya da "bilinmiyor" yazması, süre yoksa 30 dakika varsayması gerekiyor. Ama bir maçta yanlış sonuç veriyor:
function macOzeti(mac) { const sure = mac.sure || 30 const oldurme = mac.oldurme || "?" if (mac.olum) { console.log(`${mac.sampiyon}: ${oldurme} öldürme, ${mac.olum} ölüm, ${sure} dk`) } else { console.log(`${mac.sampiyon}: ${oldurme} öldürme, ölüm bilinmiyor, ${sure} dk`) }} macOzeti({ sampiyon: "Annie", oldurme: 0, olum: 0, sure: 25 })macOzeti({ sampiyon: "Zed", oldurme: 9, olum: 2 })macOzeti({ sampiyon: "Lux" })Annie: ? öldürme, ölüm bilinmiyor, 25 dkZed: 9 öldürme, 2 ölüm, 30 dkLux: ? öldürme, ölüm bilinmiyor, 30 dkAnnie'nin maçında hiç öldürme ve ölüm yok, ama bu bilgi biliniyor. Hataları bul ve düzelt. Beklenen çıktı:
Annie: 0 öldürme, 0 ölüm, 25 dkZed: 9 öldürme, 2 ölüm, 30 dkLux: ? öldürme, ölüm bilinmiyor, 30 dkİpucu 1
Annie'nin değerlerinden hangileri yanlış sayılan değerler listesinde?
İpucu 2
İki tür hata var: || ile verilen yedek değerler ve if (mac.olum) koşulu. Birincisi için ??, ikincisi için açık bir soru (=== undefined).
Çözümü göster
function macOzeti(mac) { const sure = mac.sure ?? 30 const oldurme = mac.oldurme ?? "?" if (mac.olum === undefined) { console.log(`${mac.sampiyon}: ${oldurme} öldürme, ölüm bilinmiyor, ${sure} dk`) } else { console.log(`${mac.sampiyon}: ${oldurme} öldürme, ${mac.olum} ölüm, ${sure} dk`) }} macOzeti({ sampiyon: "Annie", oldurme: 0, olum: 0, sure: 25 })macOzeti({ sampiyon: "Zed", oldurme: 9, olum: 2 })macOzeti({ sampiyon: "Lux" })Annie: 0 öldürme, 0 ölüm, 25 dkZed: 9 öldürme, 2 ölüm, 30 dkLux: ? öldürme, ölüm bilinmiyor, 30 dkAynı düzeltmeyi parametrede parçalamayla da yapabilirsin, çünkü varsayılan değerler de yalnız undefined'da çalışır: function macOzeti({ sampiyon, oldurme = "?", olum, sure = 30 }). Gövdede mac. önekleri kalkar, koşul olum === undefined olur ve üç çağrının çıktısı aynı kalır.
Kayıt dosyası okuyucu
Oyunun kayıtları JSON metni olarak geliyor; bazılarında alanlar eksik:
const varsayilanAyarlar = { ses: 50, dil: "tr" } const kayitlar = [ '{"oyuncu":"Deniz","ayarlar":{"ses":0,"dil":"en"},"sonMac":null}', '{"oyuncu":"Ece"}', '{"oyuncu":"Can","ayarlar":{"ses":35},"sonMac":{"sampiyon":"Lux","sonuc":"zafer"}}']kayitOku(metin) adında bir fonksiyon yaz. Metni objeye çevirsin ve şu biçimde yeni bir obje döndürsün:
oyuncu: kayıttaki ad.ayarlar:varsayilanAyarlarile kayıttaki ayarların birleşimi; kayıtta olan ayar kazanır.sonSampiyon: son maçta oynanan şampiyon; son maç yoksa ya danullise"Henüz maç yok".
Sonra her kaydı kayitOku ile oku ve sonucu JSON.stringify ile tek satır yazdır. varsayilanAyarlar hiç değişmemeli. Beklenen çıktı:
{"oyuncu":"Deniz","ayarlar":{"ses":0,"dil":"en"},"sonSampiyon":"Henüz maç yok"}{"oyuncu":"Ece","ayarlar":{"ses":50,"dil":"tr"},"sonSampiyon":"Henüz maç yok"}{"oyuncu":"Can","ayarlar":{"ses":35,"dil":"tr"},"sonSampiyon":"Lux"}İpucu 1
Fonksiyonun ilk satırı const kayit = JSON.parse(metin). Sınır değerlerine dikkat: Deniz'in sesi 0 ve öyle kalmalı, Ece'nin hiç ayarlar alanı yok, Deniz'in son maçı null.
İpucu 2
Ayarlar için yayma: önce varsayılanlar, sonra kayıttakiler, çünkü sonraki kazanır. Objeye undefined yaymak hata vermez, hiçbir alan eklemez; bu yüzden Ece'nin kaydında da çalışır.
İpucu 3
Son şampiyon için kayit.sonMac?.sampiyon ?? "Henüz maç yok". Neden . değil de ?., neden || değil de ??, ikisini de cevaplayabiliyor olmalısın.
Çözümü göster
const varsayilanAyarlar = { ses: 50, dil: "tr" } function kayitOku(metin) { const kayit = JSON.parse(metin) return { oyuncu: kayit.oyuncu, ayarlar: { ...varsayilanAyarlar, ...kayit.ayarlar }, sonSampiyon: kayit.sonMac?.sampiyon ?? "Henüz maç yok" }} const kayitlar = [ '{"oyuncu":"Deniz","ayarlar":{"ses":0,"dil":"en"},"sonMac":null}', '{"oyuncu":"Ece"}', '{"oyuncu":"Can","ayarlar":{"ses":35},"sonMac":{"sampiyon":"Lux","sonuc":"zafer"}}'] for (const metin of kayitlar) { console.log(JSON.stringify(kayitOku(metin)))}console.log(varsayilanAyarlar){"oyuncu":"Deniz","ayarlar":{"ses":0,"dil":"en"},"sonSampiyon":"Henüz maç yok"}{"oyuncu":"Ece","ayarlar":{"ses":50,"dil":"tr"},"sonSampiyon":"Henüz maç yok"}{"oyuncu":"Can","ayarlar":{"ses":35,"dil":"tr"},"sonSampiyon":"Lux"}{ ses: 50, dil: 'tr' }Son satır varsayilanAyarlar'ın bozulmadığını kanıtlıyor: yayma her seferinde yeni bir obje kurdu. Ayarları kayit.ayarlar.ses || varsayilanAyarlar.ses gibi tek tek yazmaya kalksaydın iki hata alırdın: Deniz'in sesi 50 olurdu, Ece'nin kaydında da Cannot read properties of undefined (reading 'ses') çıkardı.
?. yerine düz nokta yazsaydın program daha ilk kayıtta TypeError: Cannot read properties of null (reading 'sampiyon') ile dururdu, çünkü Deniz'in son maçı null; Ece'nin kaydı da aynı mesajın undefined'lı hâlini verirdi. || yazsaydın burada sonuç değişmezdi, çünkü şampiyon adı hiçbir zaman 0 ya da boş metin olmaz; yine de "yoksa şunu kullan" demenin doğru yolu ??.
Son deneme: listeye bozuk bir kayıt ekle ('{oyuncu:"Zed"}'). Önceki kayıtlar yazılır, sonra SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2) ile program durur. Bozuk kaydı atlayıp devam etmeyi Ders 13'te öğreneceksin.
Kendini sına
Soru 1
Bu program ne yazar?
const lux = { isim: "Lux", mana: 480 }const { mana: m, seviye = 1 } = luxconsole.log(m, seviye)console.log(mana)Cevabı göster
Önce 480 1 yazar: mana alanı m adlı kutuya alındı, seviye objede olmadığı için varsayılan 1 kullanıldı. Sonra ReferenceError: mana is not defined ile durur. mana: m yazınca mana diye bir kutu açılmaz; yalnız m açılır.
Soru 2
nisanci.menzil ve yanlis.menzil kaçtır?
const temel = { hiz: 340, menzil: 125 }const nisanci = { ...temel, menzil: 550 }const yanlis = { menzil: 550, ...temel }console.log(nisanci.menzil, yanlis.menzil)Cevabı göster
550 125. Yaymada aynı anahtar iki kez gelince sonraki kazanır. nisanci'da menzil: 550 sonra geldi; yanlis'ta ise temel'in menzil: 125'i sonra geldi ve 550'nin üstüne yazdı.
Soru 3
Bu program ne yazar?
const takim = ["Ahri"]const yedek = takimconst kopya = [...takim]yedek.push("Garen")console.log(takim.length, kopya.length)Cevabı göster
2 1. yedek yeni bir dizi değil, takim'ın kendisine giden ikinci bir ad; push aynı diziyi büyüttü. kopya ise yayma ile kurulmuş ayrı bir dizi, ona dokunulmadı.
Soru 4
Şu değerlerden hangileri if içinde doğru sayılır? 0, "0", [], null, " ", NaN
Cevabı göster
"0", [] ve " ". Yanlış sayılan altı değer şunlardı: false, 0, "", null, undefined, NaN. "0" sayı değil, içinde bir karakter olan metin; " " boş değil, içinde boşluk olan metin; [] boş da olsa bir dizi.
Soru 5
JSON.stringify ne üretir?
const annie = { isim: "Annie", tibbers: () => "Ayı!", seviye: undefined, klan: null }console.log(JSON.stringify(annie))Cevabı göster
{"isim":"Annie","klan":null}. Fonksiyon olan tibbers ve değeri undefined olan seviye JSON'a girmez. null geçerli bir JSON değeridir ve kalır. Anahtarların çift tırnaklı yazıldığına da dikkat.
Soru 6
Mesajı oku: hata nerede, sebebi ne?
const takim = [ { isim: "Ahri" }, { isim: "Garen" }, { isim: "Lux" }]const son = takim[3]console.log(son.isim)C:\kod\ogren\ders12.js:7console.log(son.isim) ^ TypeError: Cannot read properties of undefined (reading 'isim')Cevabı göster
Hata 7. satırda, .isim okunurken. .isim'in solundaki son undefined. Sebep bir önceki satırda: üç elemanlı dizinin sıra numaraları 0, 1 ve 2'dir; takim[3] diye bir eleman yok (Ders 5'teki "bir fazla" hatası). Son eleman takim[takim.length - 1].
Soru 7
Bu satır ne yazar ve hangisi doğru?
const kill = 0console.log(kill || "yok", kill ?? "yok")Cevabı göster
yok 0. || sıfırı yanlış sayıp "yok"a geçti. ?? yalnız null ve undefined'da yedeğe geçer, 0'ı korudu. Sıfır öldürme gerçek bir bilgi olduğu için doğru olan ??.
Kayıt noktası
Bu dersin çalışmasını Git'e kaydet. VS Code terminalinde, C:\kod\ogren klasöründeyken:
Bir sonraki derste hatalarla barışacaksın: bozuk bir JSON'un ya da beklenmedik bir undefined'ın programını çökertmesini try ve catch ile engellemeyi, throw ile kendi hata mesajlarını üretmeyi öğreneceksin.