JavaScript: Dinamik ve Etkileşimli Web'in Dili

Modern web geliştirmenin üç temel taşından biri olan JavaScript (JS), web sayfalarına hayat veren, onları statik bilgi yığınları olmaktan çıkarıp dinamik ve etkileşimli deneyimlere dönüştüren güçlü bir betik dilidir. HTML içeriği yapılandırır, CSS görünümü stilize ederken, JavaScript kullanıcının eylemlerine tepki vermeyi, içeriği anında güncellemeyi, animasyonlar oluşturmayı, sunucuyla iletişim kurmayı ve çok daha fazlasını mümkün kılar. Başlangıçta sadece tarayıcılar içinde basit görevler için tasarlanmış olsa da, günümüzde Node.js gibi platformlar sayesinde sunucu tarafında, mobil uygulamalarda, masaüstü uygulamalarında ve hatta IoT cihazlarında bile kullanılabilen çok yönlü bir dil haline gelmiştir.

JavaScript'in en belirgin özelliği, istemci tarafında (kullanıcının tarayıcısında) çalışabilmesidir. Bu, sayfanın yeniden yüklenmesine gerek kalmadan anlık geri bildirimler vermeyi, formları doğrulamayı, kullanıcı arayüzünü dinamik olarak değiştirmeyi sağlar. Örneğin, bir butona tıkladığınızda bir menünün açılması, bir form gönderildiğinde hata mesajlarının gösterilmesi, sayfa kaydırıldıkça yeni içeriğin yüklenmesi gibi işlemler JavaScript sayesinde gerçekleşir. HTML'in Document Object Model (DOM) yapısına erişerek HTML elemanlarını manipüle etme yeteneği, JavaScript'in web sayfaları üzerindeki kontrolünün temelini oluşturur.

1995 yılında Netscape tarafından "LiveScript" adıyla geliştirilen ve kısa süre sonra popüler Java dilinden esinlenerek "JavaScript" adını alan bu dil, ECMA International tarafından standartlaştırılmıştır. ECMAScript adı verilen bu standart, dilin temel özelliklerini ve sözdizimini belirler. Tarayıcılar ve Node.js gibi ortamlar, ECMAScript standardını kendi motorları (V8, SpiderMonkey, JavaScriptCore vb.) ile uygularlar. Özellikle 2015'te yayınlanan ECMAScript 6 (ES6) sürümüyle birlikte dile eklenen let/const, arrow functions, sınıflar, modüller, Promise'ler gibi modern özellikler, JavaScript'i daha güçlü, okunabilir ve yönetilebilir hale getirmiştir. Bu rehber, JavaScript'in temellerinden başlayarak DOM manipülasyonu, olay yönetimi, modern ES6+ özellikleri ve asenkron programlama gibi kritik konuları kapsayarak size sağlam bir başlangıç sunmayı ve web'e dinamizm katma yolculuğunuzda rehberlik etmeyi amaçlamaktadır.

JavaScript Temelleri: Sözdizimi, Değişkenler ve Operatörler

JavaScript kodunu anlamak ve yazmak için öncelikle dilin temel kurallarını, verileri saklama yöntemlerini (değişkenler) ve bu verilerle işlem yapma yollarını (operatörler) öğrenmek gerekir.

JavaScript Kodunu HTML'e Ekleme

JavaScript kodunu HTML sayfalarına dahil etmenin birkaç yolu vardır:

  • Dahili (Internal) Script: Kodlar, HTML dosyasının içinde, genellikle <head> veya <body> bölümünün sonunda yer alan <script> etiketleri arasına yazılır. Küçük kod parçaları veya sadece o sayfaya özgü scriptler için kullanılabilir.
    
    <!DOCTYPE html>
    <html>
    <head>
      <title>Dahili JavaScript</title>
    </head>
    <body>
      <h1 id="baslik">Merhaba!</h1>
      <button onclick="mesajGoster()">Tıkla</button>
    
      <script>
        function mesajGoster() {
          alert('Butona tıklandı!');
          document.getElementById('baslik').textContent = 'JavaScript Çalıştı!';
        }
        console.log('Sayfa yüklendi.');
      </script>
    </body>
    </html>
                                
  • Harici (External) Script: Kodlar ayrı bir .js uzantılı dosyaya yazılır ve <script> etiketinin src niteliği kullanılarak HTML dosyasına bağlanır. Bu, en yaygın ve tavsiye edilen yöntemdir. Kodun tekrar kullanılabilirliğini artırır, HTML dosyasını temiz tutar ve tarayıcıların script dosyasını önbelleğe almasına olanak tanır.
    
    <!DOCTYPE html>
    <html>
    <head>
      <title>Harici JavaScript</title>
    </head>
    <body>
      <!-- Sayfa içeriği -->
    
      <!-- Script dosyasını genellikle body kapanmadan önce eklemek önerilir -->
      <script src="scriptim.js"></script>
    </body>
    </html>
                                

    scriptim.js dosyası:

    
    console.log('Harici script çalıştı!');
    // Diğer JavaScript kodları...
                                

    Not: <script> etiketi src niteliği ile kullanıldığında, etiketler arasına kod yazılmaz.

    async ve defer Nitelikleri: Harici scriptleri eklerken performansı optimize etmek için kullanılırlar.

    • Normalde (niteliksiz), tarayıcı bir <script> etiketine rastladığında HTML işlemeyi durdurur, scripti indirir ve çalıştırır, sonra HTML işlemeye devam eder. Bu, sayfa yüklenmesini bloke edebilir.
    • <script async src="...">: Script, HTML işlenirken asenkron olarak indirilir ve indirildiği anda çalıştırılır (HTML işlemesi yine duraklatılır). Çalışma sırası garanti değildir. DOM manipülasyonu yapmayan veya sırası önemli olmayan scriptler için uygundur.
    • <script defer src="...">: Script, HTML işlenirken asenkron olarak indirilir ancak HTML işlemesi bittikten sonra, DOMContentLoaded olayından hemen önce, eklendikleri sırayla çalıştırılır. Genellikle DOM ile etkileşime giren scriptler için en iyi seçenektir.
  • Satır İçi (Inline) Olay İşleyiciler: JavaScript kodu doğrudan HTML elemanlarının olay niteliklerine (onclick, onmouseover vb.) yazılır. Çok kısa ve basit işlemler dışında genellikle önerilmez, çünkü HTML ve JavaScript kodunu karıştırır ve bakımı zorlaştırır.
    
    <button onclick="alert('Merhaba Dünya!'); console.log('Buton tıklandı');">Bana Tıkla</button>
                                

En iyi pratik, JavaScript kodunu harici .js dosyalarında tutmak ve defer niteliği ile <body> etiketinin kapanışından hemen önce eklemektir.

Temel Sözdizimi, Yorumlar ve Konsol

  • İfadeler (Statements): JavaScript kodu ifadelerden oluşur. Her ifade genellikle bir eylemi gerçekleştirir ve noktalı virgül (;) ile sonlandırılır. Noktalı virgül çoğu durumda isteğe bağlıdır (ASI - Automatic Semicolon Insertion nedeniyle), ancak hataları önlemek ve kod okunabilirliği için kullanılması şiddetle tavsiye edilir.
  • Büyük/Küçük Harf Duyarlılığı: JavaScript büyük/küçük harfe duyarlı bir dildir. Yani degisken ile Degisken farklıdır.
  • Boşluklar (Whitespace): JavaScript, kod okunabilirliğini etkilemeyen ekstra boşlukları, tabları ve yeni satırları genellikle göz ardı eder.
  • Yorumlar:
    • Tek satır yorumları: // ile başlar ve satır sonuna kadar devam eder.
    • Çok satır yorumları: /* ile başlar ve */ ile biter.
  • Konsol (Console): Tarayıcı geliştirici araçlarının (genellikle F12 ile açılır) önemli bir parçasıdır. console.log() fonksiyonu, hata ayıklama veya bilgi gösterme amacıyla konsola mesajlar yazdırmak için kullanılır. Diğer yararlı console metodları: console.warn(), console.error(), console.table() (dizi/nesneleri tablo olarak gösterir), console.time() / console.timeEnd() (işlem süresini ölçer).

// Bu tek satırlık bir yorumdur.
let mesaj = "Merhaba Dünya!"; // Bir ifade: değişkene değer atama
console.log(mesaj);          // Başka bir ifade: konsola yazdırma

/*
 Bu çok satırlı
 bir yorum örneğidir.
*/
let sayi1 = 10;
let sayi2 = 20;
let toplam = sayi1 + sayi2; // Operatör kullanımı

console.warn("Bu bir uyarı mesajıdır.");
console.error("Bu bir hata mesajıdır.");

let kullanicilar = [
  { ad: "Ahmet", yas: 30 },
  { ad: "Ayşe", yas: 25 }
];
console.table(kullanicilar);
                    

Değişkenler (var, let, const)

Değişkenler, verileri (sayılar, metinler vb.) saklamak için kullanılan isimlendirilmiş depolama alanlarıdır. JavaScript'te değişken tanımlamak için üç anahtar kelime kullanılır:

  • var (Eski Yöntem): ES6 öncesinde değişken tanımlamanın tek yoluydu. Fonksiyon kapsamlıdır (function scope) veya global kapsamlıdır. Aynı kapsam içinde tekrar tanımlanabilir ve değeri değiştirilebilir. "Hoisting" adı verilen bir davranış sergiler (tanımlama yukarı taşınır, atama değil). Modern JavaScript'te genellikle let ve const kullanımı tercih edilir.
  • let (ES6+): Blok kapsamlıdır (block scope - { } içinde tanımlandığında sadece orada geçerlidir). Değeri sonradan değiştirilebilir ancak aynı kapsam içinde tekrar tanımlanamaz. Hoisting davranışı var'dan farklıdır (Temporal Dead Zone - TDZ). Modern JavaScript'te değeri değişebilecek değişkenler için kullanılır.
  • const (ES6+): Blok kapsamlıdır. Değeri tanımlandığı anda atanmalıdır ve sonradan tekrar atanamaz (sabit). Ancak, eğer const ile tanımlanan bir nesne (object) veya dizi (array) ise, nesnenin/dizinin içeriği (elemanları veya özellikleri) değiştirilebilir, sadece değişkenin kendisi başka bir nesneye/diziye atanamaz. Hoisting davranışı let gibidir (TDZ). Değeri değişmeyecek değişkenler için (sabitler) varsayılan olarak tercih edilmelidir.

// var kullanımı (Eski - Genellikle Kaçınılır)
var eskiMesaj = "Eski yöntem";
var eskiMesaj = "Tekrar tanımlandı"; // Hata vermez
eskiMesaj = "Değeri değişti";
console.log(eskiMesaj);

if (true) {
  var fonksiyonKapsamli = "Dışarıdan erişilebilir";
}
console.log(fonksiyonKapsamli); // Erişilebilir

// let kullanımı (Değeri Değişebilir, Blok Kapsamlı)
let yeniMesaj = "Merhaba";
// let yeniMesaj = "Hata!"; // Tekrar tanımlanamaz
yeniMesaj = "Merhaba Dünya"; // Değeri değiştirilebilir
console.log(yeniMesaj);

if (true) {
  let blokKapsamli = "Sadece blok içinde";
  console.log(blokKapsamli);
}
// console.log(blokKapsamli); // Hata! Dışarıdan erişilemez

// const kullanımı (Sabit Değer, Blok Kapsamlı)
const PI = 3.14159;
// PI = 3.14; // Hata! Tekrar atanamaz
// const PI = 3; // Hata! Tekrar tanımlanamaz
console.log(PI);

const kullanici = { ad: "Ali", yas: 28 };
kullanici.yas = 29; // Geçerli! Nesnenin içeriği değişebilir.
// kullanici = { ad: "Veli", yas: 40 }; // Hata! Başka bir nesne atanamaz.
console.log(kullanici);

const renkler = ["kırmızı", "yeşil"];
renkler.push("mavi"); // Geçerli! Dizinin içeriği değişebilir.
// renkler = ["sarı", "turuncu"]; // Hata! Başka bir dizi atanamaz.
console.log(renkler);

// Hoisting örneği
console.log(a); // undefined (var ile tanımlandığı için hata vermez ama değeri yoktur)
var a = 5;

// console.log(b); // Hata! Temporal Dead Zone (let/const TDZ'dedir)
let b = 10;
                    

Modern JavaScript'te, değişkenler için varsayılan olarak const kullanın. Değişkenin değerinin daha sonra değişmesi gerekiyorsa let kullanın. var kullanımından mümkün olduğunca kaçının.

Operatörler

Değerler üzerinde işlemler yapmak için kullanılırlar:

  • Atama Operatörleri: Değişkenlere değer atamak için kullanılır (=, +=, -=, *=, /=, %=).
    let x = 10; x += 5; // x = x + 5 anlamına gelir, x şimdi 15
  • Aritmetik Operatörler: Matematiksel işlemler yapar (+ (toplama/string birleştirme), -, *, /, % (modülüs - kalan), ** (üs alma - ES6+), ++ (arttırma), -- (azaltma)).
    let yas = 30; let gelecekYas = yas + 1; let kalan = 10 % 3; // kalan = 1
  • Karşılaştırma Operatörleri: İki değeri karşılaştırır ve bir boolean (true veya false) sonuç döndürür.
    • Eşitlik: == (değer eşitliği, tip dönüşümü yapabilir - genellikle önerilmez), === (katı eşitlik - değer VE tip eşitliği - önerilir).
    • Eşitsizlik: != (değer eşitsizliği), !== (katı eşitsizlik - önerilir).
    • Büyüklük/Küçüklük: >, <, >=, <=.
    
    console.log(5 == "5");  // true (tip dönüşümü yapar)
    console.log(5 === "5"); // false (tipler farklı)
    console.log(10 > 5);    // true
    console.log("a" < "b"); // true (alfabetik karşılaştırma)
                                
  • Mantıksal Operatörler: Boolean değerler üzerinde mantıksal işlemler yapar.
    • && (VE - AND): Her iki koşul da doğruysa true döner.
    • || (VEYA - OR): Koşullardan en az biri doğruysa true döner.
    • ! (DEĞİL - NOT): Bir boolean değeri tersine çevirir.
    
    let sicaklik = 25;
    let yagisVar = false;
    if (sicaklik > 20 &amp;&amp; !yagisVar) {
      console.log("Hava dışarı çıkmak için güzel!");
    }
                                
  • Koşul (Ternary) Operatörü: if/else ifadesinin kısa yoludur. koşul ? deger_dogruysa : deger_yanlissa
    let yas = 18; let durum = (yas >= 18) ? "Yetişkin" : "Çocuk"; console.log(durum); // "Yetişkin"
  • typeof Operatörü: Bir değişkenin veya değerin veri tipini string olarak döndürür (örn: "number", "string", "boolean", "object", "undefined", "function"). null için "object" döndürmesi bilinen bir garipliktir.

DOM Manipülasyonu: Web Sayfasını Değiştirmek

Document Object Model (DOM), bir HTML veya XML belgesinin tarayıcı tarafından oluşturulan programlama arayüzüdür. Belgenin mantıksal yapısını bir ağaç (tree) gibi temsil eder ve her HTML elemanı, niteliği ve metin parçası bu ağaçta bir düğüm (node) olur. JavaScript, DOM API'si aracılığıyla bu ağaca erişebilir, elemanları bulabilir, içeriklerini ve stillerini değiştirebilir, yeni elemanlar ekleyebilir veya mevcutları silebilir.

DOM Nedir?

DOM, statik bir HTML belgesini canlı, değiştirilebilir bir nesneye dönüştürür. Tarayıcı HTML kodunu okuduğunda, bu kodu temel alarak bir DOM ağacı oluşturur. Bu ağaç, belgenin yapısını hiyerarşik olarak temsil eder:

  • En üstte `document` nesnesi bulunur.
  • Onun altında <html> elemanı vardır.
  • <html>'in altında <head> ve <body> elemanları bulunur.
  • Bu elemanların altında da diğer etiketler (<h1>, <p>, <div> vb.), nitelikler ve metin düğümleri yer alır.

JavaScript, bu `document` nesnesi aracılığıyla DOM'a erişir ve onu manipüle eder. Yapılan değişiklikler anında tarayıcıda görüntülenen sayfaya yansır.

HTML Elemanlarını Seçme

DOM'daki belirli HTML elemanlarına erişmek için çeşitli metodlar kullanılır:

  • document.getElementById('id_adi'): Belirtilen id niteliğine sahip tek bir elemanı döndürür. ID'ler benzersiz olduğu için bu en hızlı yöntemlerden biridir. Bulamazsa null döner.
  • document.getElementsByTagName('etiket_adi'): Belirtilen etiket adına (örn: 'p', 'div', 'li') sahip tüm elemanları canlı bir HTMLCollection (dizi benzeri bir yapı) olarak döndürür.
  • document.getElementsByClassName('sinif_adi'): Belirtilen sınıf adına sahip tüm elemanları canlı bir HTMLCollection olarak döndürür.
  • document.querySelector('css_secici'): Belirtilen CSS seçicisine uyan ilk elemanı döndürür. CSS seçicilerinin gücünü kullanır (örn: '#menu li.aktif a'). Bulamazsa null döner. Çok yönlü ve yaygın kullanılır.
  • document.querySelectorAll('css_secici'): Belirtilen CSS seçicisine uyan tüm elemanları statik bir NodeList (dizi benzeri bir yapı) olarak döndürür.

// ID ile seçme
const anaBaslik = document.getElementById('ana-baslik');
console.log(anaBaslik);

// Etiket adıyla seçme (HTMLCollection)
const paragraflar = document.getElementsByTagName('p');
console.log(paragraflar); // Tüm p elemanları
console.log(paragraflar[0]); // İlk paragraf

// Sınıf adıyla seçme (HTMLCollection)
const uyariKutulari = document.getElementsByClassName('uyari');
console.log(uyariKutulari); // 'uyari' sınıfına sahip elemanlar

// CSS seçici ile ilk elemanı seçme
const ilkAktifLink = document.querySelector('#menu li.aktif a');
console.log(ilkAktifLink);

// CSS seçici ile tüm elemanları seçme (NodeList)
const tumLinkler = document.querySelectorAll('a');
console.log(tumLinkler); // Sayfadaki tüm a elemanları

// NodeList üzerinde döngü ile gezinme (forEach kullanılabilir)
tumLinkler.forEach(function(link, index) {
  console.log(`Link ${index + 1}: ${link.href}`);
});

// HTMLCollection üzerinde döngü (forEach doğrudan desteklemez, Array.from ile çevrilebilir)
Array.from(paragraflar).forEach(p => {
    console.log(p.textContent.substring(0, 20) + "...");
});
                    

Canlı HTMLCollection ile statik NodeList arasındaki fark: DOM'a yeni elemanlar eklendiğinde veya silindiğinde, HTMLCollection otomatik olarak güncellenir, ancak querySelectorAll ile elde edilen NodeList güncellenmez (elde edildiği andaki durumu yansıtır).

Eleman İçeriğini ve Niteliklerini Değiştirme

Seçilen elemanların içeriğini, HTML yapısını ve niteliklerini değiştirebiliriz:

  • element.innerHTML: Bir elemanın içindeki HTML kodunu alır veya ayarlar. HTML etiketlerini yorumlar. Güvenilmeyen kaynaklardan gelen verilerle kullanırken dikkatli olunmalıdır (XSS saldırılarına açık olabilir).
  • element.textContent: Bir elemanın içindeki sadece metin içeriğini alır veya ayarlar. HTML etiketlerini metin olarak işler (yorumlamaz). Genellikle innerHTML'den daha güvenli ve performanslıdır.
  • element.innerText: textContent'a benzer, ancak CSS ile gizlenmiş metinleri dikkate alır ve metin formatlamasını (örn: satır atlamaları) korumaya çalışır. Genellikle textContent tercih edilir.
  • element.style.özellikAdı: Elemanın satır içi (inline) stillerini değiştirmek için kullanılır. CSS özellik adları camelCase olarak yazılır (örn: backgroundColor, fontSize).
    
    const baslik = document.getElementById('ana-baslik');
    baslik.style.color = 'red';
    baslik.style.backgroundColor = 'yellow';
    baslik.style.fontSize = '2em';
                                
  • element.setAttribute('nitellik_adi', 'yeni_deger'): Bir elemanın niteliğinin değerini ayarlar veya yeni bir nitelik ekler.
  • element.getAttribute('nitellik_adi'): Bir elemanın belirtilen niteliğinin değerini alır.
  • element.removeAttribute('nitellik_adi'): Bir elemandan belirtilen niteliği kaldırır.
  • element.classList: Elemanın sınıf listesini yönetmek için kullanılır:
    • element.classList.add('yeni-sinif'): Sınıf ekler.
    • element.classList.remove('eski-sinif'): Sınıf kaldırır.
    • element.classList.toggle('aktif-sinif'): Sınıf varsa kaldırır, yoksa ekler.
    • element.classList.contains('sinif-adi'): Elemanın belirtilen sınıfa sahip olup olmadığını kontrol eder (true/false döner).
  • Form Elemanları Değerleri: <input>, <textarea>, <select> gibi form elemanlarının değerlerini almak veya ayarlamak için genellikle element.value özelliği kullanılır. Checkbox ve radio butonları için element.checked (boolean) kullanılır.

// İçerik değiştirme
const aciklama = document.querySelector('.aciklama');
if (aciklama) {
    aciklama.innerHTML = '<strong>Yeni</strong> HTML içerik.'; // HTML yorumlanır
    // aciklama.textContent = 'Sadece metin içerik.'; // HTML yorumlanmaz
}

// Nitelik değiştirme
const logo = document.getElementById('logo');
if (logo) {
    logo.setAttribute('src', 'yeni-logo.png');
    logo.setAttribute('alt', 'Yeni Logo Açıklaması');
    console.log(logo.getAttribute('width')); // Genişlik niteliğini oku
}

// Sınıf yönetimi
const menu = document.getElementById('ana-menu');
if (menu) {
    menu.classList.add('aktif');
    menu.classList.remove('gizli');
    menu.classList.toggle('mobil-görünüm');
    if (menu.classList.contains('aktif')) {
        console.log("Menü aktif!");
    }
}

// Form değeri alma
const kullaniciAdiInput = document.getElementById('kullanici-adi');
if (kullaniciAdiInput) {
    let kullaniciAdi = kullaniciAdiInput.value;
    console.log('Girilen Kullanıcı Adı:', kullaniciAdi);
    kullaniciAdiInput.value = "Varsayılan Değer"; // Değeri değiştirme
}
                    

Yeni Eleman Oluşturma ve Ekleme/Silme

JavaScript ile dinamik olarak yeni HTML elemanları oluşturabilir ve bunları DOM'a ekleyebilir veya mevcut elemanları kaldırabiliriz.

  • document.createElement('etiket_adi'): Belirtilen etiket adına sahip yeni bir HTML elemanı (DOM düğümü) oluşturur (ancak henüz DOM'a eklenmez).
  • document.createTextNode('metin'): Yeni bir metin düğümü oluşturur.
  • parentElement.appendChild(childElement): Oluşturulan bir elemanı (childElement), belirtilen üst elemanın (parentElement) son çocuğu olarak DOM'a ekler.
  • parentElement.insertBefore(newElement, referenceElement): newElement'ı, parentElement içinde referenceElement'dan hemen önce ekler.
  • element.remove(): Elemanı DOM'dan kaldırır.
  • parentElement.removeChild(childElement) (Eski): Belirtilen çocuk elemanı üst elemandan kaldırır.

// Yeni bir liste öğesi oluşturup listeye ekleme
const liste = document.querySelector('#urun-listesi');

if (liste) {
    // Yeni li elemanı oluştur
    const yeniOge = document.createElement('li');

    // İçine metin ekle (metin düğümü oluşturup ekleyerek)
    const metin = document.createTextNode('Yeni Ürün');
    yeniOge.appendChild(metin);

    // Alternatif olarak textContent veya innerHTML kullanılabilir
    // yeniOge.textContent = 'Yeni Ürün';

    // li elemanına sınıf ekle
    yeniOge.classList.add('urun');

    // Oluşturulan li elemanını listenin sonuna ekle
    liste.appendChild(yeniOge);

    // Yeni bir öğeyi listenin başına ekleme
    const ilkOge = liste.querySelector('li'); // İlk li'yi referans al
    const basaEklenecekOge = document.createElement('li');
    basaEklenecekOge.textContent = "En Yeni Ürün";
    if (ilkOge) {
        liste.insertBefore(basaEklenecekOge, ilkOge);
    } else { // Liste boşsa sona ekle
        liste.appendChild(basaEklenecekOge);
    }
}

// Belirli bir elemanı silme
const silinecek = document.getElementById('eski-reklam');
if (silinecek) {
    silinecek.remove(); // Modern ve basit yöntem
    // Veya eski yöntem: silinecek.parentNode.removeChild(silinecek);
}
                    

Olaylar (Events): Kullanıcı Etkileşimlerine Tepki Verme

JavaScript'in en güçlü yanlarından biri, kullanıcı eylemlerine (tıklama, fare hareketi, klavye girişi vb.) veya tarayıcı olaylarına (sayfa yüklenmesi, boyut değişimi vb.) tepki verebilmesidir. Bu, "olay dinleyicileri" (event listeners) aracılığıyla yapılır.

Olay Dinleyici Ekleme (addEventListener)

Bir HTML elemanına olay dinleyici eklemenin modern ve en esnek yolu addEventListener() metodunu kullanmaktır. Bu metod, aynı elemana aynı olay türü için birden fazla dinleyici eklemeye olanak tanır.

Sözdizimi: element.addEventListener('olayTürü', fonksiyonAdıVeyaIfadesi, [seçenekler]);

  • element: Olayı dinleyecek olan HTML elemanı (document, window veya querySelector ile seçilmiş bir eleman olabilir).
  • olayTürü: Dinlenecek olayın adı (string olarak, 'on' öneki olmadan - örn: 'click', 'mouseover', 'keydown').
  • fonksiyonAdıVeyaIfadesi: Olay gerçekleştiğinde çalıştırılacak olan JavaScript fonksiyonudur. Bu, isimlendirilmiş bir fonksiyon, anonim bir fonksiyon veya bir arrow function olabilir. Bu fonksiyona otomatik olarak bir olay nesnesi (event object) parametre olarak geçer.
  • seçenekler (İsteğe bağlı): Olay dinleyicinin davranışını ayarlayan bir nesne veya boolean değer.
    • capture (boolean): Olayın yakalama (capturing) aşamasında mı yoksa kabarcıklanma (bubbling - varsayılan) aşamasında mı çalışacağını belirler.
    • once (boolean): Eğer true ise, dinleyici olay ilk kez tetiklendikten sonra otomatik olarak kaldırılır.
    • passive (boolean): Eğer true ise, dinleyicinin event.preventDefault() metodunu çağırmayacağını tarayıcıya bildirir. Bu, özellikle dokunmatik olaylarda (touchstart, touchmove) kaydırma performansını iyileştirebilir.

Olay dinleyicisini kaldırmak için removeEventListener('olayTürü', fonksiyonAdı, [seçenekler]) metodu kullanılır. Kaldırma işleminin çalışması için eklerken kullanılan fonksiyon referansının aynı olması gerekir (anonim fonksiyonlar doğrudan kaldırılamaz).


const buton = document.getElementById('hesaplaButonu');
const sonucDiv = document.getElementById('sonucAlani');

// İsimlendirilmiş fonksiyon kullanarak
function hesaplamaYap() {
  console.log('Hesaplama yapılıyor...');
  sonucDiv.textContent = 'Sonuç: ' + Math.random();
  // Dinleyiciyi kaldır (sadece bir kez çalışsın)
  buton.removeEventListener('click', hesaplamaYap);
}

if (buton) {
  buton.addEventListener('click', hesaplamaYap);
}

// Anonim fonksiyon kullanarak
const temizleButonu = document.getElementById('temizleButonu');
if (temizleButonu) {
    temizleButonu.addEventListener('mouseover', function() {
        console.log('Fare temizle butonunun üzerinde!');
        // Bu anonim fonksiyonu removeEventListener ile doğrudan kaldıramazsınız.
    });

    // Arrow function kullanarak
    temizleButonu.addEventListener('mouseout', () => {
        console.log('Fare temizle butonundan ayrıldı!');
    });
}

// Olay dinleyiciyi kaldırma örneği (isimlendirilmiş fonksiyonla)
// buton.removeEventListener('click', hesaplamaYap);

// Seçenekler kullanımı
const link = document.getElementById('pasifLink');
if(link) {
    link.addEventListener('click', (event) => {
        // event.preventDefault(); // Passive: true olduğu için bu hataya neden olabilir veya yok sayılır
        console.log("Pasif link tıklandı, varsayılan engellenmiyor.");
    }, { passive: true });
}
                    

Yaygın Olay Türleri

JavaScript'te dinlenebilecek çok sayıda olay türü vardır. En yaygın olanlardan bazıları:

  • Fare Olayları:
    • click: Elemana tıklandığında.
    • dblclick: Elemana çift tıklandığında.
    • mousedown / mouseup: Fare tuşuna basıldığında / bırakıldığında.
    • mouseover / mouseout: Fare imleci elemanın üzerine geldiğinde / elemandan ayrıldığında.
    • mouseenter / mouseleave: mouseover/mouseout gibidir ancak olay kabarcıklanması (bubbling) yapmaz, genellikle daha kullanışlıdır.
    • mousemove: Fare imleci eleman üzerinde hareket ettiğinde.
  • Klavye Olayları:
    • keydown: Bir tuşa basıldığında (tuş basılı tutulursa tekrarlanır).
    • keyup: Basılı bir tuş bırakıldığında.
    • keypress: Karakter üreten bir tuşa basıldığında (genellikle keydown tercih edilir, keypress bazı tarayıcılarda farklı çalışabilir veya deprecated olabilir).
  • Form Olayları:
    • submit: Bir form gönderilmeye çalışıldığında (genellikle <form> elemanında dinlenir). Formun varsayılan gönderimini engellemek için event.preventDefault() sıkça kullanılır.
    • change: Bir form elemanının değeri değiştiğinde ve eleman odaktan çıktığında (<input>, <select>, <textarea>).
    • input: Bir form elemanının değeri her değiştiğinde anında tetiklenir (<input>, <textarea>).
    • focus: Bir eleman odaklandığında.
    • blur: Bir eleman odaktan çıktığında.
  • Pencere/Belge Olayları:
    • load: Sayfanın tüm kaynakları (resimler, scriptler, stiller vb.) tamamen yüklendiğinde (genellikle window nesnesinde dinlenir).
    • DOMContentLoaded: HTML belgesi tamamen yüklenip işlendiğinde (DOM hazır), ancak stil şablonları, resimler gibi dış kaynakların yüklenmesi beklenmeden tetiklenir (genellikle document nesnesinde dinlenir). Scriptlerin çalışması için genellikle load'dan daha erken ve daha uygundur.
    • resize: Tarayıcı penceresi yeniden boyutlandırıldığında (window nesnesinde dinlenir).
    • scroll: Sayfa kaydırıldığında (window veya kaydırılabilir bir elemanda dinlenir).
  • Dokunmatik Olaylar (Mobil): touchstart, touchmove, touchend, touchcancel.

Olay Nesnesi (Event Object)

Bir olay tetiklendiğinde, olay dinleyici fonksiyonuna otomatik olarak bir olay nesnesi (genellikle event, evt veya e olarak adlandırılır) parametre olarak geçirilir. Bu nesne, olay hakkında birçok yararlı bilgi ve metot içerir:

  • event.type: Tetiklenen olayın türünü (örn: 'click', 'keydown') string olarak verir.
  • event.target: Olayın başlangıçta tetiklendiği elemanı (olayın kaynağını) referans eder.
  • event.currentTarget: Olay dinleyicisinin eklendiği elemanı referans eder (olay kabarcıklanması durumunda target'tan farklı olabilir).
  • event.preventDefault(): Tarayıcının olayla ilişkili varsayılan eylemini engeller (örn: bir linke tıklandığında sayfaya gitmeyi, bir form gönderildiğinde sayfayı yenilemeyi engellemek için).
  • event.stopPropagation(): Olayın DOM ağacında daha yukarıdaki elemanlara doğru kabarcıklanmasını (bubbling) durdurur.
  • Fare Olayları İçin Ek Bilgiler: event.clientX, event.clientY (pencereye göre koordinatlar), event.pageX, event.pageY (sayfaya göre koordinatlar), event.button (hangi fare tuşuna basıldığı).
  • Klavye Olayları İçin Ek Bilgiler: event.key (basılan tuşun değeri, örn: 'a', 'Enter', 'Shift'), event.code (basılan fiziksel tuşun kodu, örn: 'KeyA', 'Enter', 'ShiftLeft'), event.altKey, event.ctrlKey, event.shiftKey, event.metaKey (yardımcı tuşlara basılıp basılmadığı - boolean).

const form = document.getElementById('iletisimFormu');
const link = document.getElementById('engellenecekLink');
const kutu = document.getElementById('fareKoordinatKutusu');

if (form) {
  form.addEventListener('submit', function(event) {
    console.log('Form gönderilmeye çalışıldı!');
    event.preventDefault(); // Formun gerçekten gönderilmesini engelle
    console.log('Varsayılan gönderme engellendi.');
    // Burada form verilerini alıp AJAX ile gönderme vb. yapılabilir.
    const isimInput = document.getElementById('isim');
    if (isimInput.value.trim() === '') {
        alert('İsim alanı boş bırakılamaz!');
    } else {
        console.log('Form gönderiliyor (AJAX vb.)...');
        // form.submit(); // Eğer engellemeyi kaldırıp göndermek isterseniz
    }
  });
}

if (link) {
  link.addEventListener('click', function(e) {
    e.preventDefault(); // Linkin varsayılan davranışını (sayfaya gitme) engelle
    alert('Linke gitme engellendi!');
    console.log('Olay Türü:', e.type); // "click"
    console.log('Hedef Eleman:', e.target); // Linkin kendisi (<a>)
  });
}

if (kutu) {
    kutu.addEventListener('mousemove', (e) => {
        kutu.textContent = `X: ${e.clientX}, Y: ${e.clientY}`;
    });
}

// Klavye olayı örneği
const inputAlani = document.getElementById('metinGirisi');
if (inputAlani) {
    inputAlani.addEventListener('keydown', (event) => {
        console.log(`Basılan Tuş: ${event.key} (Kod: ${event.code})`);
        if (event.key === 'Enter') {
            console.log('Enter tuşuna basıldı!');
            // Belki bir işlem yap...
        }
    });
}
                    

Olay Kabarcıklanması (Bubbling) ve Yakalama (Capturing)

Bir eleman üzerinde bir olay tetiklendiğinde (örn: bir butona tıklama), olay aslında DOM ağacında iki aşamada yayılır:

  1. Yakalama (Capturing) Aşaması: Olay, pencereden (window) başlayarak hedef elemana doğru iner. Bu aşamada dinleyiciler varsayılan olarak çalışmaz, ancak addEventListener'da capture: true seçeneği ile etkinleştirilebilir.
  2. Hedef Aşaması: Olay doğrudan hedef elemana ulaşır.
  3. Kabarcıklanma (Bubbling) Aşaması: Olay, hedef elemandan başlayarak DOM ağacında yukarı doğru, yani üst elemanlarına doğru yayılır (pencereye kadar). Varsayılan olarak olay dinleyicileri bu aşamada çalışır.

Bu mekanizma sayesinde, bir olayı doğrudan hedef elemana eklemek yerine, üst elemanlardan birine ekleyerek (event delegation) yönetmek mümkün olur. Örneğin, bir listedeki tüm <li> elemanlarına ayrı ayrı tıklama dinleyicisi eklemek yerine, listenin kendisine (<ul>) tek bir dinleyici ekleyip, tıklanan elemanın event.target ile bir <li> olup olmadığını kontrol edebilirsiniz. Bu, özellikle dinamik olarak eklenen elemanlar için performansı artırır ve kod tekrarını azaltır.

event.stopPropagation() metodu, olayın bu yakalama veya kabarcıklanma aşamasında daha fazla yayılmasını engeller.


const liste = document.getElementById('dinamikListe');

if (liste) {
  // Üst elemana (ul) tek bir dinleyici ekle (Event Delegation)
  liste.addEventListener('click', function(event) {
    // Tıklanan elemanın bir LI olup olmadığını kontrol et
    if (event.target &amp;&amp; event.target.nodeName === 'LI') {
      console.log('Liste öğesi tıklandı:', event.target.textContent);
      event.target.classList.toggle('secili');
    } else if (event.target.nodeName === 'BUTTON') {
        // Eğer LI içindeki bir butona tıklandıysa
        console.log("Buton tıklandı, olay yayılması durduruluyor.");
        event.stopPropagation(); // Olayın LI veya UL'e gitmesini engelle
        event.target.closest('li').remove(); // Butonun ait olduğu LI'yi sil
    }
  });

  // Dinamik olarak yeni öğe ekleme butonu
  const ekleButon = document.getElementById('ogeEkleButon');
  if(ekleButon) {
      ekleButon.addEventListener('click', () => {
          const yeniLi = document.createElement('li');
          yeniLi.textContent = `Yeni Öğe ${liste.children.length + 1}`;
          const silButon = document.createElement('button');
          silButon.textContent = 'Sil';
          yeniLi.appendChild(silButon);
          liste.appendChild(yeniLi);
          // Yeni eklenen öğe için ayrıca listener eklemeye gerek yok!
      });
  }
}
                    

JavaScript Veri Tipleri ve Yapıları

JavaScript, farklı türde verileri temsil etmek için çeşitli yerleşik veri tiplerine sahiptir. Bu tipler temel olarak iki kategoriye ayrılır: İlkel (Primitive) Tipler ve Nesne (Object) Tipi.

İlkel (Primitive) Veri Tipleri

İlkel tipler, değiştirilemez (immutable) olan tek bir değeri temsil ederler. Değişkene ilkel bir değer atandığında, değişken doğrudan o değeri tutar.

  • string: Metin verilerini temsil eder. Tek tırnak (' '), çift tırnak (" ") veya backtick (` ` - template literals için) içine alınır.
    let ad = "Ahmet"; let mesaj = 'Merhaba!'; let aciklama = `Kullanıcı adı: ${ad}`;
  • number: Hem tam sayıları (integers) hem de ondalıklı sayıları (floating-point numbers) temsil eder. Özel sayısal değerler de vardır: Infinity, -Infinity ve NaN (Not a Number - geçerli olmayan bir matematiksel işlemin sonucu).
    let yas = 30; let pi = 3.14; let sonuc = 10 / 0; // Infinity; let hatali = "abc" * 2; // NaN
  • boolean: Sadece iki değeri olabilen mantıksal tipi temsil eder: true (doğru) ve false (yanlış). Genellikle koşullu ifadelerde kullanılır.
    let aktifMi = true; let yetkili = false;
  • null: "Boş değer" veya "hiçbir nesne değeri" anlamına gelen özel bir değerdir. Bir değişkenin bilinçli olarak boş bırakıldığını belirtmek için kullanılır. typeof null'ın "object" döndürmesi tarihsel bir hatadır.
    let seciliKullanici = null;
  • undefined: Bir değişkene henüz bir değer atanmadığını belirtir. Bir fonksiyon değer döndürmezse (return kullanmazsa) varsayılan olarak undefined döner.
    let deger; console.log(deger); // undefined
  • symbol (ES6+): Benzersiz ve değiştirilemez tanımlayıcılar oluşturmak için kullanılır. Genellikle nesne özellik anahtarları olarak çakışmaları önlemek amacıyla kullanılır.
    const id1 = Symbol('id'); const id2 = Symbol('id'); console.log(id1 === id2); // false
  • bigint (ES2020+): Normal number tipinin güvenli tamsayı sınırından (Number.MAX_SAFE_INTEGER) daha büyük tamsayıları temsil etmek için kullanılır. Sayının sonuna n eklenerek oluşturulur.
    const buyukSayi = 1234567890123456789012345678901234567890n;

Nesne (Object) Tipi

İlkel tiplerin aksine, nesneler daha karmaşık veri yapılarıdır ve birden fazla değeri (özellikleri) bir arada tutabilirler. Nesneler değiştirilebilir (mutable) yapıdadır. JavaScript'te ilkel tipler dışındaki hemen her şey (diziler, fonksiyonlar, tarihler vb.) aslında birer nesnedir.

  • Nesne Literali (Object Literal): Nesne oluşturmanın en yaygın yoludur. Süslü parantezler { } içine anahtar: deger çiftleri yazılarak tanımlanır. Anahtarlar genellikle string'dir (tırnaksız yazılabilir), değerler ise herhangi bir veri tipi olabilir (başka bir nesne veya dizi dahil).
    
    let araba = {
      marka: "Toyota",
      model: "Corolla",
      yil: 2022,
      renkler: ["beyaz", "gri"],
      calistir: function() { // Nesne içinde fonksiyon (metot)
        console.log("Motor çalıştı!");
      },
      "yakıt tipi": "Benzin" // Anahtar boşluk içeriyorsa tırnak zorunlu
    };
    
    // Özelliklere erişim
    console.log(araba.marka); // "Toyota" (Nokta notasyonu)
    console.log(araba["model"]); // "Corolla" (Köşeli parantez notasyonu)
    console.log(araba["yakıt tipi"]); // Boşluklu anahtar için köşeli parantez zorunlu
    
    // Metot çağırma
    araba.calistir(); // "Motor çalıştı!"
    
    // Özellik değeri değiştirme
    araba.yil = 2023;
    
    // Yeni özellik ekleme
    araba.hasarliMi = false;
                                
  • Yapıcı Fonksiyonlar (Constructor Functions): new anahtar kelimesi ile birlikte özel fonksiyonlar kullanarak nesneler oluşturulabilir (örn: new Date(), new Object(), veya kendi tanımladığınız yapıcılar).
  • Sınıflar (Classes - ES6+): Nesne oluşturmak için daha modern ve yapılandırılmış bir yol sunar (arka planda prototipleri kullanır).

Diziler (Arrays)

Diziler, sıralı bir veri koleksiyonunu tutan özel bir nesne türüdür. Elemanlar herhangi bir veri tipinde olabilir ve index numaraları (0'dan başlayarak) ile erişilir.

  • Dizi Literali (Array Literal): Dizi oluşturmanın en yaygın yoludur. Köşeli parantezler [ ] içine elemanlar virgülle ayrılarak yazılır.
  • Elemanlara Erişim: diziAdi[index] şeklinde erişilir.
  • length Özelliği: Dizideki eleman sayısını verir.
  • Yaygın Dizi Metotları:
    • push(eleman): Dizinin sonuna eleman ekler.
    • pop(): Dizinin sonundaki elemanı çıkarır ve döndürür.
    • shift(): Dizinin başındaki elemanı çıkarır ve döndürür.
    • unshift(eleman): Dizinin başına eleman ekler.
    • indexOf(eleman): Elemanın ilk bulunduğu index'i döndürür (bulamazsa -1).
    • includes(eleman) (ES7+): Elemanın dizide olup olmadığını kontrol eder (true/false).
    • slice(baslangic, bitis): Dizinin belirli bir bölümünün kopyasını yeni bir dizi olarak döndürür (orijinal diziyi değiştirmez).
    • splice(baslangic, silinecekSayi, eklenecekElemanlar...): Diziden eleman siler ve/veya yerine yenilerini ekler (orijinal diziyi değiştirir).
    • concat(dizi2, dizi3...): Dizileri birleştirerek yeni bir dizi oluşturur.
    • join(ayirici): Dizi elemanlarını belirtilen ayırıcı ile birleştirerek bir string oluşturur.
    • Döngü Metotları (Fonksiyonel): forEach(), map(), filter(), reduce(), find(), findIndex(), some(), every() (detayları fonksiyonlar bölümünde veya ayrıca ele alınabilir).

let meyveler = ["elma", "armut", "muz"];
console.log(meyveler[0]); // "elma"
console.log(meyveler.length); // 3

meyveler.push("çilek"); // ["elma", "armut", "muz", "çilek"]
meyveler.shift(); // "elma"yı çıkarır, dizi ["armut", "muz", "çilek"] olur

console.log(meyveler.indexOf("muz")); // 1
console.log(meyveler.includes("portakal")); // false

let kesit = meyveler.slice(0, 2); // ["armut", "muz"] (orijinal değişmez)
console.log(kesit);
console.log(meyveler); // ["armut", "muz", "çilek"]

meyveler.splice(1, 1, "kavun", "karpuz"); // 1. indexten başla 1 eleman sil, yerine kavun ve karpuz ekle
// meyveler şimdi: ["armut", "kavun", "karpuz", "çilek"]
console.log(meyveler);

let meyveString = meyveler.join(" - "); // "armut - kavun - karpuz - çilek"
console.log(meyveString);
                    

JSON (JavaScript Object Notation)

JSON, veri alışverişi için kullanılan hafif, metin tabanlı bir formattır. JavaScript nesne literali sözdizimine çok benzer, ancak bazı farkları vardır:

  • Anahtarlar (keys) her zaman çift tırnak (" ") içinde olmalıdır.
  • Değerler sadece string, number, boolean, array, null veya başka bir JSON nesnesi olabilir. Fonksiyonlar veya undefined kullanılamaz.
  • Yorum satırı içermez.

JavaScript, JSON verilerini işlemek için yerleşik JSON nesnesi sunar:

  • JSON.stringify(javascriptNesnesi): Bir JavaScript nesnesini veya değerini JSON formatında bir string'e dönüştürür.
  • JSON.parse(jsonString): JSON formatındaki bir string'i ayrıştırarak bir JavaScript nesnesine veya değerine dönüştürür.

// JavaScript Nesnesi
let kisi = {
  ad: "Ayşe",
  yas: 25,
  sehir: "İzmir",
  aktif: true,
  hobiler: ["kitap", "müzik"]
};

// JavaScript nesnesini JSON string'ine çevirme
let jsonString = JSON.stringify(kisi, null, 2); // null, 2 okunabilirlik için girinti ekler
console.log(jsonString);
/* Çıktı:
{
  "ad": "Ayşe",
  "yas": 25,
  "sehir": "İzmir",
  "aktif": true,
  "hobiler": [
    "kitap",
    "müzik"
  ]
}
*/

// JSON string'ini JavaScript nesnesine çevirme
let jsonVerisi = '{ "urunAdi": "Laptop", "fiyat": 15000, "stoktaVar": true }';
let urunNesnesi = JSON.parse(jsonVerisi);
console.log(urunNesnesi.urunAdi); // "Laptop"
console.log(urunNesnesi.fiyat); // 15000
                    

JSON, özellikle web API'leri ile veri alışverişi yaparken (sunucudan veri alma/gönderme) yaygın olarak kullanılır.

Fonksiyonlar: Tekrar Kullanılabilir Kod Blokları

Fonksiyonlar, belirli bir görevi yerine getiren, tekrar tekrar çağrılabilen kod bloklarıdır. JavaScript'in temel yapı taşlarından biridirler. Kod tekrarını azaltır, programı modüler hale getirir ve okunabilirliği artırırlar.

Fonksiyon Tanımlama Yöntemleri

JavaScript'te fonksiyon tanımlamanın birkaç yolu vardır:

  1. Fonksiyon Bildirimi (Function Declaration): En yaygın yöntemdir. function anahtar kelimesi, fonksiyon adı, parantez içinde parametre listesi ve süslü parantez içinde fonksiyon gövdesi ile tanımlanır. Bu şekilde tanımlanan fonksiyonlar "hoist" edilir, yani kod içinde tanımlandıkları yerden önce çağrılabilirler.
    
    selamVer("Ali"); // Hoisting nedeniyle hata vermez
    
    function selamVer(isim) {
      console.log("Merhaba, " + isim + "!");
    }
    
    function topla(a, b) {
      return a + b; // return ifadesi fonksiyonun sonucunu döndürür
    }
    
    let sonuc = topla(5, 3); // 8
    console.log(sonuc);
                                
  2. Fonksiyon İfadesi (Function Expression): Bir fonksiyon oluşturulur ve bir değişkene atanır. Fonksiyon anonim olabilir (adı olmaz) veya bir adı olabilir (genellikle hata ayıklama için faydalıdır). Fonksiyon ifadeleri hoist edilmez, yani tanımlandıkları satırdan önce çağrılamazlar.
    
    // vedaEt("Ayşe"); // Hata! vedaEt henüz tanımlanmadı (hoist edilmez)
    
    const vedaEt = function(isim) {
      console.log("Hoşça kal, " + isim + "!");
    };
    
    vedaEt("Ayşe"); // Şimdi çağrılabilir
    
    const carp = function carpmaIslemi(x, y) { // İsimli fonksiyon ifadesi
      return x * y;
    };
    
    console.log(carp(4, 6)); // 24
    // console.log(carpmaIslemi(2, 3)); // Hata! Fonksiyon adı dışarıdan erişilemez
                                
  3. Arrow Functions (Ok Fonksiyonları - ES6+): Fonksiyon ifadelerine göre daha kısa bir sözdizimi sunar. Özellikle anonim fonksiyonlar ve callback'ler için kullanışlıdır. Kendi this, arguments, super, veya new.target bağlamlarını oluşturmazlar (içinde bulundukları kapsamın bağlamını kullanırlar).
    
    // Tek parametreli, tek ifadeli (return otomatik)
    const ikiKatiniAl = sayi => sayi * 2;
    console.log(ikiKatiniAl(7)); // 14
    
    // Çok parametreli
    const fark = (a, b) => {
      console.log(`Çıkarma işlemi: ${a} - ${b}`);
      return a - b;
    };
    console.log(fark(10, 4)); // 6
    
    // Parametresiz
    const mesajYaz = () => console.log("Bu bir ok fonksiyonu!");
    mesajYaz();
    
    // Nesne döndüren (parantez kullanımı önemli)
    const kisiOlustur = (ad, yas) => ({ ad: ad, yas: yas });
    console.log(kisiOlustur("Zeynep", 22));
                                

Parametreler ve Argümanlar

  • Parametreler: Fonksiyon tanımında parantez içinde belirtilen değişken isimleridir. Fonksiyona dışarıdan değer almak için kullanılırlar.
  • Argümanlar: Fonksiyon çağrılırken parantez içinde fonksiyona gönderilen gerçek değerlerdir.
  • Varsayılan Parametreler (Default Parameters - ES6+): Fonksiyon çağrılırken bir argüman gönderilmezse, parametrenin alacağı varsayılan değeri tanımlamayı sağlar.
  • Rest Parametreleri (Rest Parameters - ES6+): Fonksiyonun belirsiz sayıda argümanı bir dizi olarak almasını sağlar. Parametre listesindeki son parametre olmalıdır ve üç nokta (...) ile başlar.
  • arguments Nesnesi (Eski): Arrow functions hariç tüm fonksiyonlarda kullanılabilen, fonksiyona geçirilen tüm argümanları içeren dizi benzeri bir nesnedir. Modern JavaScript'te genellikle rest parametreleri tercih edilir.

function tanistir(ad, soyad = "Bilgi Yok") { // soyad varsayılan parametre
  console.log(`Ad: ${ad}, Soyad: ${soyad}`);
}

tanistir("Mehmet", "Yılmaz"); // Ad: Mehmet, Soyad: Yılmaz
tanistir("Fatma");          // Ad: Fatma, Soyad: Bilgi Yok

function sayilariTopla(...sayilar) { // Rest parametresi (sayilar bir dizi olur)
  let toplam = 0;
  for (let sayi of sayilar) {
    toplam += sayi;
  }
  return toplam;
}

console.log(sayilariTopla(1, 2, 3));       // 6
console.log(sayilariTopla(10, 20, 30, 40)); // 100

function eskiArgumanlar() {
  console.log(arguments); // Fonksiyona geçirilen tüm argümanları gösterir
  console.log(arguments[0]);
  // arguments.forEach(...); // Hata! arguments gerçek bir dizi değildir
  // Array.from(arguments).forEach(...); // Diziye çevirerek kullanılabilir
}

eskiArgumanlar("a", "b", 10);
                    

Return İfadesi

return ifadesi, bir fonksiyonun çalışmasını sonlandırır ve isteğe bağlı olarak bir değeri fonksiyonun çağrıldığı yere geri döndürür.

  • Bir fonksiyon içinde return'e ulaşıldığında, fonksiyonun geri kalan kodları çalıştırılmaz.
  • Eğer return ifadesinden sonra bir değer belirtilmezse veya fonksiyon içinde hiç return kullanılmazsa, fonksiyon varsayılan olarak undefined döndürür.
  • Bir fonksiyon sadece bir değer döndürebilir. Birden fazla değer döndürmek gerekiyorsa, bu değerler bir dizi veya nesne içinde gruplanarak döndürülebilir.

function karesiniAl(sayi) {
  if (typeof sayi !== 'number') {
    console.error("Lütfen bir sayı girin.");
    return; // Fonksiyonu sonlandırır, undefined döner
  }
  return sayi * sayi; // Hesaplanan değeri döndürür
}

let kare = karesiniAl(5); // kare = 25
console.log(kare);

let gecersiz = karesiniAl("metin"); // Konsola hata yazar, gecersiz = undefined
console.log(gecersiz);

function kullaniciBilgileri(id) {
    // ... id'ye göre kullanıcı bulunur varsayalım ...
    let kullanici = { ad: "Selin", email: "selin@example.com" };
    if (kullanici) {
        return kullanici; // Nesne döndürme
    } else {
        return null; // Kullanıcı bulunamadıysa null döndür
    }
}

let aktifKullanici = kullaniciBilgileri(1);
if(aktifKullanici) {
    console.log(aktifKullanici.ad); // Selin
}
                    

Kapsam (Scope) ve Closures

  • Kapsam (Scope): Bir değişkenin veya fonksiyonun kodun hangi bölümünden erişilebilir olduğunu belirler. JavaScript'te temel olarak iki tür kapsam vardır:
    • Global Kapsam (Global Scope): Herhangi bir fonksiyonun veya bloğun dışında tanımlanan değişkenler global kapsama sahiptir ve kodun her yerinden erişilebilir. Global değişkenler genellikle önerilmez çünkü isim çakışmalarına ve beklenmedik yan etkilere yol açabilirler.
    • Yerel Kapsam (Local Scope):
      • Fonksiyon Kapsamı (Function Scope): var ile tanımlanan değişkenler, içinde bulundukları fonksiyonun kapsamında geçerlidir.
      • Blok Kapsamı (Block Scope - ES6+): let ve const ile tanımlanan değişkenler, içinde bulundukları süslü parantez bloğu ({ } - if, for, while veya tek başına blok) kapsamında geçerlidir.
    İç içe geçmiş kapsamlarda, içteki kapsam dıştaki kapsamdaki değişkenlere erişebilir, ancak dıştaki kapsam içteki kapsamdaki değişkenlere (genellikle) erişemez.
  • Closure (Kapanış): Bir fonksiyonun, kendi kapsamı dışındaki (tanımlandığı çevreleyen kapsamdaki) değişkenlere erişme ve onları "hatırlama" yeteneğidir. Bir fonksiyon başka bir fonksiyon tarafından döndürüldüğünde bile, döndürülen fonksiyon hala tanımlandığı kapsamdaki değişkenlere erişebilir. Closures, özel değişkenler oluşturma, callback'ler ve modül deseni gibi birçok ileri JavaScript tekniğinin temelini oluşturur.

let globalDegisken = "Ben Globalim"; // Global Kapsam

function disFonksiyon() {
  let disDegisken = "Ben Dış Fonksiyondayım"; // Fonksiyon Kapsamı (disFonksiyon için yerel)
  var varDis = "Dış Var";

  console.log(globalDegisken); // Erişilebilir

  if (true) {
    let blokDegisken = "Ben Blok Kapsamındayım"; // Blok Kapsamı
    const blokSabit = "Sabit Blok";
    var varIc = "İç Var"; // Fonksiyon kapsamlı olduğu için dışarıdan erişilebilir
    console.log(disDegisken); // Erişilebilir
    console.log(blokDegisken); // Erişilebilir
  }

  // console.log(blokDegisken); // Hata! Blok dışından erişilemez
  console.log(varIc); // Erişilebilir (var fonksiyon kapsamlı)

  function icFonksiyon() {
    let icDegisken = "Ben İç Fonksiyondayım";
    console.log(globalDegisken); // Erişilebilir
    console.log(disDegisken);    // Erişilebilir (Closure!)
    console.log(varDis);         // Erişilebilir (Closure!)
    // console.log(blokDegisken); // Hata! Bu kapsama ait değil
  }

  icFonksiyon();
  // console.log(icDegisken); // Hata! Dış kapsamdan iç kapsama erişilemez
}

disFonksiyon();
// console.log(disDegisken); // Hata! Fonksiyon dışından erişilemez
// console.log(varDis); // Hata! Fonksiyon dışından erişilemez
// console.log(varIc); // Hata! Fonksiyon dışından erişilemez

// --- Closure Örneği ---
function sayacOlustur() {
  let sayac = 0; // Bu değişken dışarıdan erişilemez ama iç fonksiyonlar hatırlar

  return { // Nesne içinde fonksiyonlar döndürülüyor
    arttir: function() {
      sayac++;
      console.log(sayac);
    },
    azalt: function() {
      sayac--;
      console.log(sayac);
    },
    deger: function() {
      return sayac;
    }
  };
}

const benimSayacim = sayacOlustur();
benimSayacim.arttir(); // 1
benimSayacim.arttir(); // 2
benimSayacim.azalt(); // 1
console.log(benimSayacim.deger()); // 1
// console.log(sayac); // Hata! 'sayac' dışarıdan erişilemez

const baskaSayac = sayacOlustur(); // Her çağrı kendi bağımsız sayacını oluşturur
baskaSayac.arttir(); // 1
                    

Modern JavaScript: ES6+ Özellikleri

ECMAScript 2015 (ES6) ve sonraki sürümler, JavaScript diline birçok önemli yenilik ve sözdizimsel kolaylık getirmiştir. Bu özellikler kodu daha okunabilir, yazması daha kolay ve daha güçlü hale getirir.

let ve const

Daha önce bahsedildiği gibi, ES6 ile gelen let ve const, var'ın yerine blok kapsamlı değişken tanımlama imkanı sunar. Bu, kapsam hatalarını azaltır ve kodu daha öngörülebilir kılar. Değeri değişmeyecek değişkenler için const, değişebilecekler için let kullanmak modern bir standarttır.

Arrow Functions (Ok Fonksiyonları)

Fonksiyon ifadeleri için daha kısa bir sözdizimi sunarlar ve en önemlisi, kendi this bağlamını oluşturmazlar; içinde bulundukları (lexical) kapsamın this değerini miras alırlar. Bu, özellikle nesne metotları içindeki callback'lerde veya olay dinleyicilerde this ile ilgili kafa karışıklığını önler.


// ES5 fonksiyon ifadesi ve 'this' sorunu
function SayacES5() {
  this.saniye = 0;
  setInterval(function() {
    // Buradaki 'this', setInterval'ın çağrıldığı global scope'u (veya strict modda undefined) gösterir,
    // SayacES5 nesnesini göstermez!
    // console.log(this.saniye++); // NaN veya Hata verir
    // Çözüm genellikle bind, call, apply veya that = this gibi yöntemlerdi.
  }, 1000);
}
// const s1 = new SayacES5();

// ES6 Arrow Function ile 'this' çözümü
function SayacES6() {
  this.saniye = 0;
  setInterval(() => {
    // Arrow function kendi 'this'ini oluşturmaz, dış kapsamın (SayacES6'nın) 'this'ini kullanır.
    console.log(this.saniye++); // Doğru şekilde çalışır
  }, 1000);
}
// const s2 = new SayacES6(); // Konsolda saniyeler artarak görünür
                    

Template Literals (Şablon Dizgeleri)

Backtick (` `) karakterleri ile tanımlanan string'lerdir. Şu avantajları sunarlar:

  • String İçinde Değişken Kullanımı (Interpolation): ${degiskenAdi} sözdizimi ile string içine kolayca değişken veya ifade yerleştirmeyi sağlar (artık + ile birleştirme yapmaya gerek kalmaz).
  • Çok Satırlı String'ler: Özel karakterlere (\n) gerek kalmadan doğrudan çok satırlı string'ler oluşturmayı sağlar.

let kullaniciAdi = "Elif";
let puan = 85;

// Eski yöntem
let mesajEski = "Merhaba " + kullaniciAdi + ",\npuanınız: " + puan + ".";
console.log(mesajEski);

// Template Literals ile
let mesajYeni = `Merhaba ${kullaniciAdi},
Puanınız: ${puan}.
Başarı durumunuz: ${puan >= 50 ? 'Geçti' : 'Kaldı'}`; // İçinde ifade de kullanılabilir
console.log(mesajYeni);
                    

Destructuring (Yapı Bozma)

Dizilerin veya nesnelerin içindeki değerleri kolayca ayrı değişkenlere atamayı sağlayan kısa bir sözdizimidir.

  • Dizi Yapı Bozma:
    
    const renkler = ["kırmızı", "yeşil", "mavi"];
    const [ilkRenk, ikinciRenk, ucuncuRenk] = renkler;
    console.log(ilkRenk); // "kırmızı"
    console.log(ikinciRenk); // "yeşil"
    
    const [ , , maviRenk] = renkler; // İstenmeyen elemanları atlama
    console.log(maviRenk); // "mavi"
    
    let a = 1, b = 3;
    [a, b] = [b, a]; // Değişken değerlerini takas etme
    console.log(a, b); // 3 1
                                
  • Nesne Yapı Bozma: Özellik adları ile aynı isimde değişkenler oluşturur. Farklı isim vermek için : kullanılır. Varsayılan değer atanabilir.
    
    const kisi = {
      isim: "Can",
      yas: 35,
      sehir: "Ankara",
      meslek: "Mühendis"
    };
    
    const { isim, yas, ulke = "Türkiye" } = kisi; // ulke özelliği yoksa varsayılan değer atanır
    console.log(isim); // "Can"
    console.log(yas); // 35
    console.log(ulke); // "Türkiye"
    
    const { sehir: yasadigiSehir, meslek } = kisi; // 'sehir' özelliğini 'yasadigiSehir' değişkenine ata
    console.log(yasadigiSehir); // "Ankara"
    
    function ayarlarıYazdir({ tema = "açık", fontSize = 16 }) { // Fonksiyon parametrelerinde yapı bozma
        console.log(`Tema: ${tema}, Font Boyutu: ${fontSize}px`);
    }
    ayarlarıYazdir({ tema: "koyu" }); // Tema: koyu, Font Boyutu: 16px
    ayarlarıYazdir({}); // Tema: açık, Font Boyutu: 16px
                                

Default, Rest ve Spread Operatörleri

  • Default Parameters (Varsayılan Parametreler): Fonksiyon tanımında parametrelere varsayılan değer atamayı sağlar (Fonksiyonlar bölümünde değinilmişti).
  • Rest Parameters (Kalan Parametreler - ...): Fonksiyon tanımında kullanıldığında, belirsiz sayıda argümanı tek bir dizi içinde toplar (Fonksiyonlar bölümünde değinilmişti).
  • Spread Operator (Yayma Operatörü - ...): Rest parametresinin tersi gibi çalışır. Bir diziyi veya nesneyi (veya yinelenebilir başka bir şeyi) tek tek elemanlarına/özelliklerine ayırarak başka bir diziye, nesneye veya fonksiyon çağrısına yaymak için kullanılır.
    
    // Dizilerde Spread
    const sayilar1 = [1, 2, 3];
    const sayilar2 = [4, 5, 6];
    const birlesikSayilar = [...sayilar1, 0, ...sayilar2, 7]; // [1, 2, 3, 0, 4, 5, 6, 7]
    console.log(birlesikSayilar);
    
    const kopyaSayilar = [...sayilar1]; // Diziyi kopyalama
    kopyaSayilar.push(4);
    console.log(sayilar1); // [1, 2, 3] (Orijinal değişmez)
    console.log(kopyaSayilar); // [1, 2, 3, 4]
    
    // Fonksiyon Çağrılarında Spread
    function topla(x, y, z) {
      return x + y + z;
    }
    const degerler = [10, 20, 30];
    const toplam = topla(...degerler); // topla(10, 20, 30) gibi çağırır
    console.log(toplam); // 60
    
    // Nesnelerde Spread (ES2018+)
    const varsayilanAyarlar = { tema: "açık", bildirimler: true };
    const kullaniciAyarlari = { bildirimler: false, dil: "tr" };
    const sonAyarlar = { ...varsayilanAyarlar, ...kullaniciAyarlari, kullaniciAdi: "admin" };
    // { tema: "açık", bildirimler: false, dil: "tr", kullaniciAdi: "admin" }
    // (kullaniciAyarlari'ndaki bildirimler varsayılanı ezer)
    console.log(sonAyarlar);
    
    const nesneKopya = { ...sonAyarlar }; // Nesne kopyalama (sığ kopya)
    console.log(nesneKopya);
                                 

Sınıflar (Classes)

ES6 sınıfları, nesne yönelimli programlama (OOP) konseptlerini JavaScript'te uygulamak için daha temiz ve tanıdık bir sözdizimi sunar. Arka planda hala prototip tabanlı kalıtımı kullanırlar, ancak yazımı kolaylaştırırlar.

  • class Anahtar Kelimesi: Sınıf tanımını başlatır.
  • constructor Metodu: Sınıftan new ile bir nesne oluşturulduğunda otomatik olarak çalışan özel bir metottur. Genellikle nesnenin başlangıç özelliklerini ayarlamak için kullanılır. Bir sınıfta en fazla bir constructor olabilir.
  • Metotlar: Sınıf içinde tanımlanan fonksiyonlardır. function anahtar kelimesi kullanılmaz.
  • Kalıtım (Inheritance): extends anahtar kelimesi ile bir sınıf başka bir sınıftan özellik ve metotları miras alabilir. Alt sınıfın constructor'ı içinde üst sınıfın constructor'ını çağırmak için super() kullanılır.
  • Getter ve Setter: Özelliklere erişimi ve atamayı kontrol etmek için özel metotlar tanımlanabilir (get özellikAdi() { ... }, set özellikAdi(deger) { ... }).
  • Static Metotlar: static anahtar kelimesi ile tanımlanan metotlardır. Sınıfın örneği (instance) üzerinden değil, doğrudan sınıfın kendisi üzerinden çağrılırlar (örn: SinifAdi.staticMetot()).

class Hayvan {
  constructor(ad) {
    this.ad = ad;
    console.log(`${ad} oluşturuldu.`);
  }

  konus() {
    console.log(`${this.ad} bir ses çıkarır.`);
  }

  // Static metot
  static genelBilgi() {
      console.log("Bu bir hayvan sınıfıdır.");
  }
}

class Kopek extends Hayvan { // Hayvan sınıfından kalıtım al
  constructor(ad, cins) {
    super(ad); // Üst sınıfın constructor'ını çağır
    this.cins = cins;
  }

  // Üst sınıftaki metodu override etme
  konus() {
    console.log(`${this.ad} havlar!`);
  }

  // Yeni metot
  getir() {
    console.log(`${this.ad} (${this.cins}) topu getiriyor.`);
  }

  // Getter
  get tamAd() {
      return `${this.ad} (${this.cins})`;
  }
}

Hayvan.genelBilgi(); // "Bu bir hayvan sınıfıdır."

const hayvan1 = new Hayvan("Canavar"); // "Canavar oluşturuldu."
hayvan1.konus(); // "Canavar bir ses çıkarır."

const kopek1 = new Kopek("Karabaş", "Sivas Kangalı"); // "Karabaş oluşturuldu."
kopek1.konus(); // "Karabaş havlar!"
kopek1.getir(); // "Karabaş (Sivas Kangalı) topu getiriyor."
console.log(kopek1.tamAd); // Getter kullanımı: "Karabaş (Sivas Kangalı)"
                    

Modüller (Modules - import/export)

ES6 Modülleri, JavaScript kodunu farklı dosyalara ayırarak organize etmeyi ve tekrar kullanılabilir bileşenler oluşturmayı sağlar. Kodun belirli bölümlerini (değişkenler, fonksiyonlar, sınıflar) bir dosyadan dışa aktarmak (export) ve başka bir dosyada içe aktarmak (import) için standart bir yol sunar.

Modülleri kullanmak için genellikle HTML'deki <script> etiketine type="module" niteliği eklenir.

Dışa Aktarma (export):

  • Named Exports (İsimlendirilmiş): Birden fazla değeri ismiyle dışa aktarır.
    // matematik.js
    export const PI = 3.14;
    export function topla(a, b) { return a + b; }
    export class HesapMakinesi { /* ... */ }
                                
  • Default Export (Varsayılan): Bir dosyadan sadece tek bir ana değeri varsayılan olarak dışa aktarır. Her dosyada en fazla bir tane olabilir.
    // kullanici.js
    export default class Kullanici {
        constructor(ad) { this.ad = ad; }
        // ...
    }
    // VEYA
    // class Kullanici { ... }
    // export default Kullanici;
                                

İçe Aktarma (import):

  • Named Imports: İsimlendirilmiş değerleri süslü parantez içinde alır. İsim değişikliği için as kullanılabilir.
    // ana.js
    import { PI, topla as addition } from './matematik.js';
    console.log(PI);
    console.log(addition(2, 3)); // 5
                                
  • Default Import: Varsayılan değeri alır. Süslü parantez kullanılmaz ve herhangi bir isim verilebilir.
    // ana.js
    import BenimKullaniciSinifim from './kullanici.js';
    const user = new BenimKullaniciSinifim("Ayşe");
                                
  • Namespace Import: Bir modüldeki tüm named export'ları tek bir nesne altında toplar.
    // ana.js
    import * as MathLib from './matematik.js';
    console.log(MathLib.PI);
    console.log(MathLib.topla(5, 5));
                                
  • Hem default hem de named import'ları birleştirmek mümkündür:
    import DefaultDeger, { namedDeger1, namedDeger2 } from './modul.js';

Modüller, kodun kapsamını korur (modül içindeki değişkenler varsayılan olarak global olmaz) ve bağımlılıkları açıkça belirtmeyi sağlar.

<!-- HTML içinde modül kullanımı -->
<script type="module" src="ana.js"></script>
                     

Asenkron JavaScript: Beklemeden İşlem Yapma

JavaScript temelde tek iş parçacıklı (single-threaded) bir dildir, yani aynı anda sadece tek bir işlem yapabilir. Ancak web'de sıkça karşılaşılan ağ istekleri (veri çekme), zamanlayıcılar (setTimeout) veya kullanıcı olayları gibi işlemler zaman alabilir. Eğer bu işlemler senkron (eş zamanlı) olarak yapılsaydı, işlem bitene kadar tüm sayfa donardı (bloke olurdu). Asenkron programlama, bu tür zaman alan işlemlerin arka planda çalışmasına izin verirken, JavaScript motorunun diğer işlemlere devam etmesini sağlar. İşlem tamamlandığında ise bir geri bildirim mekanizması ile sonuçlar işlenir.

Callback Fonksiyonları

Asenkron işlemlerin sonuçlarını işlemenin en temel yollarından biri callback fonksiyonlarıdır. Bir fonksiyon, başka bir fonksiyona parametre olarak geçilir ve asenkron işlem tamamlandığında bu callback fonksiyonu çağrılır.


console.log("İşlem 1 Başladı");

// setTimeout asenkron bir fonksiyondur.
// Belirtilen süre (milisaniye) sonra verilen fonksiyonu çalıştırır.
setTimeout(function() { // Bu bir callback fonksiyonu
  console.log("İşlem 2 (Asenkron) Tamamlandı");
}, 2000); // 2 saniye bekle

console.log("İşlem 3 Başladı");

// Beklenen Konsol Çıktısı:
// İşlem 1 Başladı
// İşlem 3 Başladı
// (2 saniye sonra)
// İşlem 2 (Asenkron) Tamamlandı
                    

Birden fazla iç içe asenkron işlem gerektiğinde, callback'ler "Callback Hell" veya "Pyramid of Doom" adı verilen karmaşık ve okunması zor bir yapıya yol açabilir:


// Callback Hell Örneği (Sadece Konsept)
veriCek('/api/kullanici/1', function(kullanici) {
  console.log("Kullanıcı:", kullanici.ad);
  siparisleriCek(kullanici.id, function(siparisler) {
    console.log("Sipariş Sayısı:", siparisler.length);
    ilkSiparisDetayiniCek(siparisler[0].id, function(detay) {
      console.log("İlk Sipariş Detayı:", detay.urunAdi);
      // ... ve bu böyle devam edebilir ...
    }, function(hataDetay) {
        console.error("Detay çekilemedi:", hataDetay);
    });
  }, function(hataSiparis) {
      console.error("Siparişler çekilemedi:", hataSiparis);
  });
}, function(hataKullanici) {
    console.error("Kullanıcı çekilemedi:", hataKullanici);
});
                    

Bu tür durumlar için Promise'ler ve async/await daha modern ve okunabilir çözümler sunar.

Promise'ler (Sözler)

Promise'ler, asenkron bir işlemin nihai sonucunu (başarı veya başarısızlık) temsil eden nesnelerdir. Callback'lere göre daha yönetilebilir ve zincirlenebilir bir yapı sunarlar.

Bir Promise üç durumda olabilir:

  1. Pending (Beklemede): İşlem henüz tamamlanmadı.
  2. Fulfilled (Başarılı): İşlem başarıyla tamamlandı.
  3. Rejected (Reddedildi): İşlem bir hatayla sonuçlandı.

Promise'ler genellikle .then() ve .catch() metotları ile kullanılır:

  • .then(onFulfilled, onRejected): Promise başarılı olduğunda (fulfilled) onFulfilled fonksiyonunu, reddedildiğinde (rejected) ise isteğe bağlı onRejected fonksiyonunu çalıştırır. .then() metodu kendisi de yeni bir Promise döndürdüğü için işlemler zincirlenebilir (chaining).
  • .catch(onRejected): Sadece Promise reddedildiğinde çalışacak fonksiyonu belirtmenin kısa yoludur (.then(null, onRejected) ile aynıdır). Zincirdeki herhangi bir yerde oluşan hatayı yakalamak için genellikle zincirin sonuna eklenir.
  • .finally(onFinally): Promise başarılı da olsa, reddedilse de her zaman çalışacak bir fonksiyon belirtir (örn: yükleniyor göstergesini kaldırmak için).

// Promise oluşturan basit bir fonksiyon (örnek)
function veriCekPromise(url) {
  return new Promise((resolve, reject) => {
    // Asenkron işlemi simüle etme (örneğin setTimeout veya fetch)
    console.log(`${url} adresinden veri çekiliyor...`);
    setTimeout(() => {
      const basarili = Math.random() > 0.2; // %80 başarı ihtimali
      if (basarili) {
        resolve({ data: `Veri ${url} adresinden geldi!` }); // Başarılı sonuç
      } else {
        reject(new Error(`${url} için veri çekme başarısız!`)); // Hata durumu
      }
    }, 1500);
  });
}

// Promise kullanımı ve zincirleme
veriCekPromise('/api/veri1')
  .then(sonuc1 => {
    console.log("1. İstek Başarılı:", sonuc1.data);
    // İlk istek başarılı olursa ikinci isteği yap
    return veriCekPromise('/api/veri2'); // Yeni bir Promise döndür
  })
  .then(sonuc2 => {
    console.log("2. İstek Başarılı:", sonuc2.data);
    // Başka bir işlem...
    return "Tüm işlemler tamamlandı.";
  })
  .then(sonMesaj => {
      console.log(sonMesaj);
  })
  .catch(hata => {
    // Zincirdeki herhangi bir Promise reddedilirse burası çalışır
    console.error("Bir hata oluştu:", hata.message);
  })
  .finally(() => {
      console.log("Promise zinciri tamamlandı (başarılı veya hatalı).");
  });

console.log("Promise başlatıldı, sonuç bekleniyor...");
                    

Promise.all() (tüm promise'ler başarılı olunca çalışır), Promise.race() (ilk tamamlanan promise'in sonucunu alır), Promise.allSettled() (tüm promise'lerin sonucunu (başarı/hata) bekler) gibi yardımcı metotlar da bulunur.

async/await (ES2017+)

async ve await anahtar kelimeleri, Promise tabanlı asenkron kodları daha senkron gibi görünen, okunması ve yazması daha kolay bir şekilde yazmayı sağlar. Aslında Promise'lerin üzerine kurulmuş sözdizimsel bir kolaylıktır.

  • async: Bir fonksiyon tanımının önüne eklenir (async function fonksiyonAdi() { ... } veya const f = async () => { ... }). Bu, fonksiyonun her zaman bir Promise döndüreceğini belirtir. Fonksiyon içinde return deger; kullanılırsa, Promise bu değerle resolve olur. Hata fırlatılırsa Promise reject olur.
  • await: Sadece async fonksiyonların içinde kullanılabilir. Bir Promise'in önüne eklenir. JavaScript motorunun, Promise tamamlanana (resolve veya reject olana) kadar fonksiyonun çalışmasını duraklatmasını sağlar. Promise resolve olduğunda, await ifadesi Promise'in çözümlenmiş değerini döndürür. Promise reject olursa, await bir hata fırlatır (bu hata try...catch bloğu ile yakalanabilir).

// Önceki Promise örneğini async/await ile yazalım

// Promise oluşturan fonksiyon aynı kalır
function veriCekPromise(url) {
  return new Promise((resolve, reject) => {
    console.log(`${url} adresinden veri çekiliyor...`);
    setTimeout(() => {
      const basarili = Math.random() > 0.2;
      if (basarili) {
        resolve({ data: `Veri ${url} adresinden geldi!` });
      } else {
        reject(new Error(`${url} için veri çekme başarısız!`));
      }
    }, 1500);
  });
}

// async fonksiyon tanımı
async function verileriIsle() {
  console.log("async fonksiyon başladı...");
  try {
    // await ile Promise'in sonucunu bekle
    const sonuc1 = await veriCekPromise('/api/veri1');
    console.log("1. İstek Başarılı (async):", sonuc1.data);

    // İlk işlem bitince ikinciyi başlat ve bekle
    const sonuc2 = await veriCekPromise('/api/veri2');
    console.log("2. İstek Başarılı (async):", sonuc2.data);

    console.log("Tüm işlemler async/await ile tamamlandı.");
    return "Başarılı Sonuç"; // Bu değer Promise olarak dönecek

  } catch (hata) {
    // await ile beklenen Promise reject olursa burası çalışır
    console.error("async/await içinde hata yakalandı:", hata.message);
    return "Hatalı Sonuç"; // Bu değer de Promise olarak dönecek

  } finally {
      console.log("async fonksiyon (try veya catch sonrası) tamamlandı.");
  }
}

// async fonksiyonu çağırma (bir Promise döndürür)
verileriIsle()
  .then(nihaiSonuc => {
    console.log("verileriIsle fonksiyonunun nihai sonucu:", nihaiSonuc);
  })
  .catch(hata => {
      // Genellikle async fonksiyon içindeki catch bloğu hataları yakalar,
      // ama yine de bir güvenlik ağı olarak eklenebilir.
      console.error("async fonksiyon çağrısında beklenmedik hata:", hata);
  });

console.log("async fonksiyon çağrıldı, işlemler bekleniyor...");

                    

async/await, asenkron kodu daha okunabilir hale getirerek Callback Hell ve karmaşık .then() zincirlerinin önüne geçer.

Fetch API

Fetch API, ağ istekleri yapmak (örneğin bir sunucudan veri çekmek veya sunucuya veri göndermek) için modern, Promise tabanlı bir arayüzdür. Eski XMLHttpRequest nesnesine göre daha güçlü ve esnektir.

Temel kullanımı fetch(url, [options]) şeklindedir:

  • url: İstek yapılacak kaynak (API endpoint).
  • options (İsteğe bağlı): İstek ayarlarını içeren bir nesne (method: 'GET', 'POST', 'PUT', 'DELETE' vb., headers: HTTP başlıkları, body: gönderilecek veri (POST/PUT için, genellikle JSON.stringify ile)).

fetch() bir Promise döndürür. Bu Promise, HTTP yanıtının başlıkları geldiğinde resolve olur. Yanıtın gövdesini (body) okumak için (örn: JSON, text) yanıt nesnesinin (Response object) .json(), .text(), .blob() gibi metotları kullanılır ve bu metotlar da yeni bir Promise döndürür.


const apiUrl = 'https://jsonplaceholder.typicode.com/posts/1'; // Örnek API

// GET isteği (veri çekme) - Promise ile
fetch(apiUrl)
  .then(response => {
    // HTTP yanıt durumunu kontrol et (fetch 404 gibi hatalarda reject olmaz)
    if (!response.ok) { // ok özelliği 200-299 arası durum kodları için true olur
      throw new Error(`HTTP Hatası! Durum: ${response.status}`);
    }
    // Yanıt gövdesini JSON olarak oku (bu da bir Promise döndürür)
    return response.json();
  })
  .then(veri => {
    console.log("Promise ile Gelen Veri:", veri);
    // Gelen veri ile DOM'u güncelle vb.
    document.getElementById('veriAlani').textContent = `Başlık: ${veri.title}`;
  })
  .catch(hata => {
    console.error("Fetch hatası (Promise):", hata);
    document.getElementById('veriAlani').textContent = `Hata: ${hata.message}`;
  });

// GET isteği - async/await ile (daha okunabilir)
async function veriCekAsync(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP Hatası! Durum: ${response.status}`);
    }
    const veri = await response.json(); // JSON okuma işlemini de await ile bekle
    console.log("Async/Await ile Gelen Veri:", veri);
    document.getElementById('veriAlaniAsync').textContent = `İçerik: ${veri.body}`;
  } catch (hata) {
    console.error("Fetch hatası (async/await):", hata);
    document.getElementById('veriAlaniAsync').textContent = `Hata: ${hata.message}`;
  }
}

veriCekAsync(apiUrl);

// POST isteği (veri gönderme) örneği
async function veriGonder(url, gonderilecekVeri) {
    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json' // Gönderilen verinin tipini belirt
            },
            body: JSON.stringify(gonderilecekVeri) // JavaScript nesnesini JSON string'e çevir
        });

        if (!response.ok) {
            throw new Error(`HTTP Hatası! Durum: ${response.status}`);
        }

        const donenVeri = await response.json(); // Sunucudan dönen yanıtı işle
        console.log("Sunucudan Dönen Yanıt:", donenVeri);
        return donenVeri;

    } catch (hata) {
        console.error("POST isteği hatası:", hata);
    }
}

const yeniGonderi = { title: 'foo', body: 'bar', userId: 1 };
// veriGonder('https://jsonplaceholder.typicode.com/posts', yeniGonderi); // Örnek çağırma
                    

İleri Konular ve En İyi Uygulamalar

JavaScript'in temellerini ve modern özelliklerini öğrendikten sonra, kod kalitesini artırmak, hataları yönetmek ve daha büyük projeler geliştirmek için bazı ileri konulara ve en iyi uygulamalara odaklanmak önemlidir.

Hata Yönetimi (try...catch...finally)

Program çalışırken oluşabilecek hataları (exceptions) yakalamak ve programın çökmesini önlemek için try...catch...finally bloğu kullanılır.

  • try Bloğu: Hata oluşturma potansiyeli olan kodlar bu bloğa yazılır.
  • catch (hata) Bloğu: try bloğu içinde bir hata oluşursa, programın akışı bu bloğa geçer. Hata nesnesi (genellikle hata veya error olarak adlandırılır) hata hakkında bilgi içerir (örn: hata.message, hata.name, hata.stack).
  • finally Bloğu (İsteğe bağlı): Hata oluşsa da oluşmasa da her zaman çalıştırılacak kodları içerir (örn: açılan bir kaynağı kapatmak için).

throw ifadesi ile özel hatalar da fırlatılabilir.


function bolmeYap(a, b) {
  try {
    if (b === 0) {
      // Özel bir hata fırlat
      throw new Error("Sıfıra bölme hatası!");
    }
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new TypeError("Parametreler sayı olmalıdır!");
    }
    let sonuc = a / b;
    console.log("Bölme işlemi başarılı.");
    return sonuc;

  } catch (hata) {
    // Fırlatılan hatayı yakala
    console.error("Bir hata oluştu!");
    console.error("Hata Adı:", hata.name); // Örn: "Error", "TypeError"
    console.error("Hata Mesajı:", hata.message);
    // console.error("Hata Stack:", hata.stack); // Hatanın oluştuğu yerin izi
    return null; // Hata durumunda null döndür

  } finally {
    // Bu blok her zaman çalışır
    console.log("Hata kontrolü tamamlandı.");
  }
}

console.log(bolmeYap(10, 2)); // 5 (Başarılı)
console.log("---");
console.log(bolmeYap(10, 0)); // null (Hata yakalandı)
console.log("---");
console.log(bolmeYap("abc", 2)); // null (Hata yakalandı)
                    

Strict Mode ('use strict';)

"Strict Mode" (Katı Mod), JavaScript kodunda daha güvenli ve daha az hataya açık bir çalışma şeklini etkinleştiren özel bir moddur. Normalde sessizce görmezden gelinecek bazı hataları belirgin hatalara dönüştürür ve bazı güvensiz kabul edilen özelliklerin kullanımını kısıtlar.

Strict modu etkinleştirmek için, bir script dosyasının veya bir fonksiyonun başına 'use strict'; (veya "use strict";) ifadesi eklenir.

Sağladığı bazı faydalar:

  • Tanımlanmamış değişkenlere değer atamaya çalışmayı hataya dönüştürür (normalde global değişken oluşturur).
  • Silinemeyen özellikleri (örn: Object.prototype) silmeye çalışmayı hataya dönüştürür.
  • Fonksiyon parametre isimlerinin benzersiz olmasını gerektirir.
  • Oktal (sekizlik taban) sayı literallerini (010 gibi) yasaklar.
  • this değerinin global nesneye (window) dönüşmesini engeller (fonksiyon normal şekilde çağrıldığında this undefined olur).
  • eval ve arguments üzerinde bazı kısıtlamalar getirir.

'use strict'; // Script'in tamamı için strict modu etkinleştir

// x = 10; // Hata! x tanımlanmadı (strict mod olmasaydı global x oluşturulurdu)
let y = 20;

function katıFonksiyon(a, a) { // Hata! Parametre isimleri tekrar ediyor
    // ...
}

// delete Object.prototype; // Hata! Silinemeyen özellik

function testThis() {
    console.log(this); // undefined (strict modda)
}
testThis();

console.log("Strict mod aktif.");
                    

Modern JavaScript projelerinde (özellikle modüller kullanılıyorsa, çünkü modüller varsayılan olarak strict moddadır) strict modu kullanmak genellikle iyi bir pratiktir.

Kodlama Standartları ve Linting

Büyük projelerde veya takım çalışmalarında tutarlı, okunabilir ve hatasız kod yazmak çok önemlidir. Kodlama standartları, kodun nasıl biçimlendirileceği (girintileme, boşluklar, noktalı virgül kullanımı vb.) ve hangi dil özelliklerinin nasıl kullanılacağı konusunda kurallar belirler.

Linting Araçları (Linters): ESLint, JSHint, Prettier gibi araçlar, yazılan JavaScript kodunu otomatik olarak analiz ederek potansiyel hataları, stil tutarsızlıklarını ve kötü pratikleri tespit eder. Bu araçlar, belirlenen kodlama standartlarına (Airbnb, Google, StandardJS gibi popüler standartlar veya özel kurallar) uyulmasını sağlamaya yardımcı olur.

  • ESLint: En popüler ve yapılandırılabilir linting aracıdır. Hem kod kalitesini hem de stilini kontrol edebilir.
  • Prettier: Daha çok kod biçimlendirmeye odaklanır. Kodu otomatik olarak belirli bir stile göre yeniden biçimlendirir. Genellikle ESLint ile birlikte kullanılır.

Linting araçlarını kullanmak:

  • Hataları erken aşamada yakalamayı sağlar.
  • Kodun okunabilirliğini ve tutarlılığını artırır.
  • Takım içindeki farklı kodlama stillerinden kaynaklanan sorunları azaltır.
  • En iyi uygulamaların takip edilmesine yardımcı olur.

Bu araçlar genellikle kod editörlerine entegre edilebilir ve geliştirme sürecinin bir parçası haline getirilebilir.

Tarayıcı API'ları ve Web API'ları

JavaScript dilinin kendisi çekirdek özellikler sunarken (veri tipleri, operatörler, kontrol yapıları, fonksiyonlar vb.), tarayıcılar ve Node.js gibi ortamlar, çevreleriyle etkileşim kurmak için ek API'lar (Application Programming Interfaces) sağlarlar. Tarayıcı ortamında en sık kullanılan Web API'larından bazıları şunlardır:

  • DOM API: HTML ve XML belgelerini işlemek için (daha önce detaylandırıldı).
  • Fetch API / XMLHttpRequest: Ağ istekleri yapmak için (daha önce detaylandırıldı).
  • Console API: Tarayıcı konsoluna mesaj yazdırmak için (console.log vb.).
  • Timers: Belirli bir süre sonra veya düzenli aralıklarla kod çalıştırmak için (setTimeout(), setInterval(), clearTimeout(), clearInterval()).
  • Web Storage API: Tarayıcıda veri depolamak için (localStorage ve sessionStorage).
  • Geolocation API: Kullanıcının coğrafi konumunu almak için.
  • History API: Tarayıcı geçmişini yönetmek ve tek sayfa uygulamalarında (SPA) gezinmeyi sağlamak için (history.pushState(), history.back(), popstate olayı).
  • Canvas API: 2D grafik çizmek için.
  • Web Audio API: Ses işlemek ve sentezlemek için gelişmiş yetenekler sunar.
  • Web Workers API: Arka planda script çalıştırmak için.

Bu API'lar, JavaScript'in web sayfalarıyla, tarayıcı özellikleriyle ve dış dünyayla etkileşim kurmasını sağlar.

JavaScript Ekosistemi: Kütüphaneler, Framework'ler ve Araçlar

JavaScript'in popülaritesi, etrafında devasa bir ekosistemin oluşmasını sağlamıştır. Bu ekosistem, geliştirmeyi hızlandıran ve kolaylaştıran sayısız kütüphane, framework ve araç içerir:

  • Kütüphaneler (Libraries): Belirli görevleri kolaylaştıran tekrar kullanılabilir kod koleksiyonlarıdır (örn: jQuery (DOM manipülasyonu - eskisi kadar popüler olmasa da), Lodash/Underscore (yardımcı fonksiyonlar), Moment.js/Day.js (tarih/saat işlemleri), Axios (Promise tabanlı HTTP istemcisi)).
  • Framework'ler (Çatılar): Uygulama geliştirmek için daha kapsamlı bir yapı ve kurallar bütünü sunarlar. Genellikle belirli bir mimariyi (örn: MVC, MVVM, Component-based) takip ederler. Popüler frontend framework'leri:
    • React: Facebook tarafından geliştirilen, bileşen tabanlı kullanıcı arayüzleri oluşturmaya odaklanan popüler bir kütüphane (genellikle framework olarak anılır).
    • Angular: Google tarafından geliştirilen, kapsamlı bir frontend framework'üdür. TypeScript kullanır.
    • Vue.js: Öğrenmesi kolay ve esnek yapısıyla bilinen ilerleyici (progressive) bir framework'tür.
    • Svelte: Geleneksel framework'lerden farklı olarak, sanal DOM kullanmak yerine derleme aşamasında optimize edilmiş vanilla JavaScript kodu üreten bir yaklaşımdır.
  • Paket Yöneticileri: Proje bağımlılıklarını (kütüphaneler, framework'ler) yönetmek için kullanılır (npm - Node Package Manager, yarn).
  • Build Araçları (Module Bundlers): Modern JavaScript kodunu (ES6+, modüller, Sass/Less vb.) tarayıcıların anlayabileceği formata dönüştüren, dosyaları birleştiren, küçülten ve optimize eden araçlardır (Webpack, Parcel, Rollup, Vite).
  • Transpiler'lar: Yeni JavaScript özelliklerini (ES6+) eski tarayıcıların da anlayabileceği ES5 gibi sürümlere çeviren araçlardır (Babel en popüler olanıdır).
  • Test Araçları: Kodun doğru çalıştığını doğrulamak için birim (unit), entegrasyon (integration) ve uçtan uca (end-to-end) testler yazmayı sağlayan araçlardır (Jest, Mocha, Jasmine, Cypress, Playwright).

Bu ekosistemi anlamak ve uygun araçları seçmek, modern JavaScript projeleri geliştirmek için önemlidir.

Sonuç: JavaScript Öğrenme Yolculuğu

JavaScript, web geliştirmenin vazgeçilmez bir parçasıdır ve sürekli gelişen, dinamik bir dildir. Temellerini sağlam bir şekilde öğrenmek, DOM manipülasyonu ve olay yönetimi gibi tarayıcı etkileşimlerini kavramak, ES6+ ile gelen modern özellikleri benimsemek ve asenkron programlamanın mantığını anlamak, etkili web uygulamaları geliştirmek için kritik adımlardır.

Bu rehber, JavaScript dünyasına bir giriş niteliğindedir. Pratik yapmak, küçük projeler geliştirmek, başkalarının kodlarını okumak ve MDN Web Docs gibi güvenilir kaynaklardan yararlanmak öğrenme sürecinizi hızlandıracaktır. JavaScript ekosistemindeki kütüphaneleri, framework'leri ve araçları keşfetmek de ufkunuzu genişletecektir.

Sabır, merak ve sürekli pratikle, JavaScript'in sunduğu sınırsız olanakları keşfedebilir ve web'e kendi dinamizminizi katabilirsiniz.