← Kursa Dön
📄 Text · 30 min

ES6+ Temel Özellikler

Neden Bu Konu Önemli?

JavaScript 2015 yılında (ES6/ES2015) büyük bir evrim geçirdi. Bundan sonra her yıl yeni özellikler eklendi. Bu özellikler "lüks" değil — modern JavaScript geliştirmenin temel taşları. Bugün GitHub'da, Stack Overflow'da, iş mülakatlarında gördüğün kodlar bu özellikleri yoğun olarak kullanıyor.

Bu derste günlük kodlamada en sık kullanacağın ES6+ özelliklerini öğreneceğiz: template literals, optional chaining, nullish coalescing, logical assignment ve ileri destructuring. Bu özellikler kodu daha kısa, daha okunabilir ve daha güvenli yapar.

Eskiden 5 satırda yazdığın bir "null kontrolü" artık 5 karakterle yapılabiliyor. Ve bu sadece kısaltma değil — kodun niyetini daha açık ifade etmek demek.


Template Literals (Şablon Dizeler)

ES6 öncesinde string birleştirme, + operatörüyle yapılıyordu ve büyük karmaşıklık yaratıyordu. Template literals bu sorunu kökten çözer.

Temel Kullanım

const ad = "Ahmet";
const yas = 28;

// ❌ Eski yol — string birleştirme
const mesaj1 = "Merhaba, ben " + ad + ". " + yas + " yaşındayım.";

// ✅ Template literal — backtick (`) ile
const mesaj2 = `Merhaba, ben ${ad}. ${yas} yaşındayım.`;

console.log(mesaj2); // "Merhaba, ben Ahmet. 28 yaşındayım."

${} içine herhangi bir JavaScript ifadesi yazabilirsin:

const fiyat = 149.90;
const adet = 3;

// Expression kullanımı
console.log(`Toplam: ${fiyat * adet} TL`);
// "Toplam: 449.7 TL"

// Fonksiyon çağrısı
console.log(`Büyük harf: ${ad.toUpperCase()}`);
// "Büyük harf: AHMET"

// Ternary operatör
const durum = yas >= 18 ? "yetişkin" : "çocuk";
console.log(`${ad} bir ${durum}.`);
// "Ahmet bir yetişkin."

// Daha karmaşık ifadeler
console.log(`${yas >= 18 ? "Oy kullanabilir" : "Oy kullanamaz"}`);

Çok Satırlı String

// ❌ Eski yol — \n ile satır sonu
const html1 = "<div>\n  <h1>Başlık</h1>\n  <p>İçerik</p>\n</div>";

// ✅ Template literal — doğal çok satır
const html2 = `
<div>
  <h1>Başlık</h1>
  <p>İçerik</p>
</div>
`;

// SQL sorguları
const sorgu = `
  SELECT u.name, u.email, COUNT(o.id) as order_count
  FROM users u
  LEFT JOIN orders o ON u.id = o.user_id
  WHERE u.active = true
  GROUP BY u.id
  ORDER BY order_count DESC
  LIMIT 10
`;

Tagged Template Literals

Template literal'lerin ileri seviye bir özelliği, tag fonksiyonlarıdır. Template'i bir fonksiyonla işleyebilirsin:

// Tag fonksiyonu: string parçalarını ve değerleri ayrı alır
function vurgula(strings, ...values) {
  return strings.reduce((sonuc, str, i) => {
    const deger = values[i] !== undefined ? `<strong>${values[i]}</strong>` : "";
    return sonuc + str + deger;
  }, "");
}

const urun = "JavaScript Kitabı";
const fiyat = 149.90;

const html = vurgula`Ürün: ${urun}, Fiyat: ${fiyat} TL`;
console.log(html);
// "Ürün: <strong>JavaScript Kitabı</strong>, Fiyat: <strong>149.9</strong> TL"
// Pratik kullanım: SQL injection koruması
function sql(strings, ...values) {
  return {
    text: strings.join("$" + strings.map((_, i) => i + 1).join("$")).replace(/\$\d+/g, (m) => m),
    query: strings.reduce((q, str, i) => {
      return q + str + (i < values.length ? `$${i + 1}` : "");
    }, ""),
    values: values,
  };
}

const userId = "1; DROP TABLE users; --"; // Kötü niyetli girdi
const sorgu = sql`SELECT * FROM users WHERE id = ${userId}`;
// sorgu.query = "SELECT * FROM users WHERE id = $1"
// sorgu.values = ["1; DROP TABLE users; --"]  ← güvenli parametre

💡 İpucu: Tagged template literals, CSS-in-JS kütüphanelerinde (styled-components), GraphQL sorgularında (gql...), ve i18n (uluslararasılaştırma) sistemlerinde yaygın olarak kullanılır.


Optional Chaining (?.)

Bir nesnenin derinlerindeki bir özelliğe erişmeye çalışırken, yoldaki herhangi bir seviye null veya undefined olabilir. ES2020 öncesinde bu durumu kontrol etmek çok zahmetliydi.

Problem

const kullanici = {
  ad: "Ahmet",
  adres: {
    sehir: "İstanbul",
    ilce: "Kadıköy",
  },
};

// Bu çalışır
console.log(kullanici.adres.sehir); // "İstanbul"

// Ama ya adres yoksa?
const kullanici2 = { ad: "Ayşe" }; // adres yok!
// console.log(kullanici2.adres.sehir);
// TypeError: Cannot read properties of undefined (reading 'sehir')

Eski Çözüm vs Optional Chaining

// ❌ Eski yol — uzun ve çirkin
const sehir1 = kullanici2.adres && kullanici2.adres.sehir
  ? kullanici2.adres.sehir
  : "Bilinmiyor";

// ❌ Biraz daha iyi ama hâlâ uzun
const sehir2 = kullanici2 && kullanici2.adres && kullanici2.adres.sehir;

// ✅ Optional chaining — tek operatör
const sehir3 = kullanici2?.adres?.sehir;
// undefined — hata VERMEZ!

// Varsayılan değerle birlikte
const sehir4 = kullanici2?.adres?.sehir ?? "Bilinmiyor";
// "Bilinmiyor"

?. operatörü "sol taraf null veya undefined ise, değerlendirmeyi durdur ve undefined döndür" demektir. Hata fırlatmaz, sessizce undefined verir.

Farklı Kullanım Şekilleri

const veri = {
  kullanicilar: [
    { ad: "Ahmet", telefonlar: ["555-111", "555-222"] },
    { ad: "Ayşe" }, // telefonlar yok
  ],
  ayarlar: null,
};

// 1. Nesne özelliği
console.log(veri?.kullanicilar?.[0]?.ad);
// "Ahmet"

// 2. Dizi erişimi
console.log(veri?.kullanicilar?.[0]?.telefonlar?.[0]);
// "555-111"

console.log(veri?.kullanicilar?.[1]?.telefonlar?.[0]);
// undefined — telefonlar yok, hata yok

// 3. Fonksiyon çağrısı
const nesne = {
  selamla: () => "Merhaba!",
};

console.log(nesne.selamla?.()); // "Merhaba!"
console.log(nesne.vedala?.());  // undefined — fonksiyon yok, hata yok

// 4. Dinamik özellik erişimi
const key = "sehir";
console.log(veri?.ayarlar?.[key]);
// undefined — ayarlar null, hata yok

Gerçek Dünya Kullanımı

// API'den gelen veriler genelde iç içe ve eksik olabilir
async function kullaniciProfiliniGoster(userId) {
  const response = await fetch(`/api/users/${userId}`);
  const data = await response.json();
  
  // Bu alanların HERHANGİ BİRİ olmayabilir
  const profilResmi = data?.profile?.avatar?.url ?? "/default-avatar.png";
  const sehir = data?.address?.city ?? "Belirtilmemiş";
  const ilkSiparis = data?.orders?.[0]?.id ?? "Sipariş yok";
  const premiumMi = data?.subscription?.isPremium ?? false;
  
  // Event handler kontrolü
  data?.analytics?.trackPageView?.("profil");
}

⚠️ Dikkat: Optional chaining'i gereğinden fazla kullanma. Eğer bir değerin kesinlikle olması gerekiyorsa, hata almak iyidir — sessiz undefined hataları maskeleyebilir.

// ❌ Aşırı kullanım — hata maskeleme
const ad = kullanici?.ad; // Kullanıcı HER ZAMAN olmalı — ?. gereksiz

// ✅ Sadece gerçekten opsiyonel olanlarda kullan
const ad2 = kullanici.ad; // Kullanıcı yoksa hata alsın — bu bir bug
const sehir = kullanici.adres?.sehir; // Adres opsiyonel — ?. uygun

Nullish Coalescing (??)

?? operatörü, sol taraf null veya undefined ise sağ tarafı döner. Bu, || operatöründen farklıdır çünkü || tüm falsy değerleri (0, "", false) yakalar.

|| vs ?? Farkı

// || operatörü: TÜM falsy değerlerde sağ tarafı döner
console.log(0 || 10);      // 10  — 0 falsy!
console.log("" || "varsayılan"); // "varsayılan" — "" falsy!
console.log(false || true); // true — false falsy!
console.log(null || "var"); // "var"
console.log(undefined || "var"); // "var"

// ?? operatörü: SADECE null/undefined'da sağ tarafı döner
console.log(0 ?? 10);      // 0   — 0 geçerli bir değer!
console.log("" ?? "varsayılan"); // "" — boş string geçerli!
console.log(false ?? true); // false — false geçerli!
console.log(null ?? "var"); // "var" — null → sağ taraf
console.log(undefined ?? "var"); // "var" — undefined → sağ taraf

Bu fark neden önemli? Çünkü 0, "", false geçerli değerler olabilir:

// Kullanıcı ayarları — 0 ve false geçerli değerler!
function ayarlariUygula(config) {
  // ❌ || ile — kullanıcının "ses kapalı" ayarı görmezden geliniyor
  const sesLeveli = config.volume || 50;
  // Kullanıcı 0 yapmış ama 50 olarak kalıyor!
  
  const karanlikMod = config.darkMode || false;
  // Kullanıcı false yapmış, zaten false geliyor — bu şanslı tesadüf
  
  // ✅ ?? ile — 0 ve false korunuyor
  const sesLeveli2 = config.volume ?? 50;
  // Kullanıcı 0 yapmışsa 0 kalır, undefined ise 50 olur
  
  const karanlikMod2 = config.darkMode ?? false;
  // undefined ise false, false ise false, true ise true
}

ayarlariUygula({ volume: 0, darkMode: false });

Optional Chaining ile Birlikte

// ?. ve ?? mükemmel bir ikili!
const config = {
  database: {
    port: 0, // 0 geçerli bir port... aslında değil ama örnek olarak
    host: "",
  },
};

// Sadece ?. → undefined alırsın, varsayılan yok
console.log(config?.database?.port);     // 0
console.log(config?.database?.name);     // undefined
console.log(config?.redis?.port);        // undefined

// ?. + ?? → varsayılan değerli güvenli erişim
console.log(config?.database?.port ?? 5432);  // 0 (0 korunuyor)
console.log(config?.database?.name ?? "mydb"); // "mydb"
console.log(config?.redis?.port ?? 6379);     // 6379

Logical Assignment (Mantıksal Atama)

ES2021'de eklenen mantıksal atama operatörleri, koşullu atamaları kısaltır.

||= (OR Assignment)

// Falsy ise ata
let kullaniciAdi = "";

// Eski yol
if (!kullaniciAdi) {
  kullaniciAdi = "Misafir";
}

// Kısa yol
kullaniciAdi ||= "Misafir";
// kullaniciAdi falsy (boş string) ise "Misafir" ata

console.log(kullaniciAdi); // "Misafir"

??= (Nullish Assignment)

// null/undefined ise ata
let ayar = 0;

// ❌ ||= kullanırsan 0 kaybolur
ayar ||= 100;
console.log(ayar); // 100 — 0 ezildi!

// ✅ ??= ile 0 korunur
let ayar2 = 0;
ayar2 ??= 100;
console.log(ayar2); // 0 — geçerli değer korundu

let ayar3 = null;
ayar3 ??= 100;
console.log(ayar3); // 100 — null idi, varsayılan atandı

&&= (AND Assignment)

// Truthy ise ata (daha az yaygın)
let oturum = { kullanici: "Ahmet", token: "abc123" };

// Oturum varsa son erişimi güncelle
oturum &&= { ...oturum, sonErisim: new Date() };
console.log(oturum);
// { kullanici: "Ahmet", token: "abc123", sonErisim: Date }

// Oturum yoksa hiçbir şey yapma
let bos = null;
bos &&= { sonErisim: new Date() };
console.log(bos); // null — değişmedi

Pratik Kullanım

// Kullanıcı ayarları birleştirme
function ayarlariBaslat(config = {}) {
  // Eksik ayarlara varsayılan değer ata
  config.tema ??= "light";
  config.dil ??= "tr";
  config.bildirimler ??= true;
  config.fontSize ??= 16;
  
  // Boş string'leri de düzelt
  config.kullaniciAdi ||= "Misafir";
  
  return config;
}

console.log(ayarlariBaslat({}));
// { tema: "light", dil: "tr", bildirimler: true, fontSize: 16, kullaniciAdi: "Misafir" }

console.log(ayarlariBaslat({ tema: "dark", fontSize: 0 }));
// { tema: "dark", dil: "tr", bildirimler: true, fontSize: 0, kullaniciAdi: "Misafir" }
// ↑ fontSize: 0 korundu çünkü ??= kullandık!

Destructuring İleri Seviye

Destructuring'i (yapısal parçalama) daha önce temel seviyede gördük. Şimdi ileri kullanımlarını keşfedelim.

İç İçe Destructuring

const siparis = {
  id: 1001,
  musteri: {
    ad: "Ahmet",
    adres: {
      sehir: "İstanbul",
      ilce: "Kadıköy",
      postaKodu: "34710",
    },
  },
  urunler: [
    { ad: "Laptop", fiyat: 25000 },
    { ad: "Mouse", fiyat: 500 },
  ],
};

// İç içe destructuring
const {
  id,
  musteri: {
    ad: musteriAdi, // Yeniden adlandırma
    adres: { sehir, ilce },
  },
  urunler: [ilkUrun, ...digerUrunler], // Dizi + rest
} = siparis;

console.log(musteriAdi); // "Ahmet"
console.log(sehir);      // "İstanbul"
console.log(ilkUrun.ad); // "Laptop"
console.log(digerUrunler.length); // 1

Varsayılan Değerlerle Destructuring

// Varsayılan değerler — özellik yoksa kullanılır
const {
  ad = "İsimsiz",
  yas = 0,
  email = "belirtilmemiş",
  premium = false,
} = { ad: "Ahmet", yas: 28 };

console.log(ad);      // "Ahmet" — var, varsayılan kullanılmadı
console.log(email);   // "belirtilmemiş" — yok, varsayılan kullanıldı
console.log(premium); // false — yok, varsayılan kullanıldı

Fonksiyon Parametrelerinde Destructuring

// Nesne parametresi + varsayılan değerler
function kullaniciOlustur({
  ad,
  email,
  yas = 18,
  rol = "user",
  aktif = true,
} = {}) {
  return { ad, email, yas, rol, aktif };
}

// Sadece gerekli alanları geç — sıra önemli değil
const user1 = kullaniciOlustur({ ad: "Ahmet", email: "ahmet@test.com" });
// { ad: "Ahmet", email: "ahmet@test.com", yas: 18, rol: "user", aktif: true }

const user2 = kullaniciOlustur({ ad: "Ayşe", email: "ayse@test.com", rol: "admin" });
// { ad: "Ayşe", email: "ayse@test.com", yas: 18, rol: "admin", aktif: true }

// Parametresiz çağrı bile çalışır (= {} sayesinde)
const user3 = kullaniciOlustur();
// { ad: undefined, email: undefined, yas: 18, rol: "user", aktif: true }

Swap (Değer Değişimi)

// Destructuring ile değişken değerlerini takas etme
let a = 1;
let b = 2;

// Eski yol
// let temp = a; a = b; b = temp;

// Destructuring yolu — tek satır
[a, b] = [b, a];
console.log(a, b); // 2, 1

Diğer Önemli ES6+ Özellikleri

Object Shorthand (Kısa Yazım)

const ad = "Ahmet";
const yas = 28;
const sehir = "İstanbul";

// ❌ Eski yol — tekrar
const kullanici1 = { ad: ad, yas: yas, sehir: sehir };

// ✅ Kısa yol — değişken adı ve property adı aynıysa
const kullanici2 = { ad, yas, sehir };
// Aynı sonuç: { ad: "Ahmet", yas: 28, sehir: "İstanbul" }

// Metot kısayolu
const hesapMakinesi = {
  // ❌ Eski: topla: function(a, b) { return a + b; }
  // ✅ Yeni:
  topla(a, b) {
    return a + b;
  },
  carp(a, b) {
    return a * b;
  },
};

Computed Property Names (Hesaplanan Özellik Adları)

const alan = "email";
const deger = "ahmet@test.com";

// Dinamik özellik adı
const nesne = {
  [alan]: deger,
  [`${alan}Dogrulandi`]: true,
};
// { email: "ahmet@test.com", emailDogrulandi: true }

// Pratik kullanım: Form state yönetimi
function inputDegisti(event) {
  const { name, value } = event.target;
  
  // Hangi input değiştiyse o alanı güncelle
  setState((prev) => ({
    ...prev,
    [name]: value, // Dinamik key
  }));
}

Numeric Separators (Sayı Ayırıcıları)

// ES2021 — büyük sayıları okunabilir yapma
const milyon = 1_000_000;           // 1000000
const milyar = 1_000_000_000;       // 1000000000
const pi = 3.141_592_653;           // 3.141592653
const hex = 0xFF_EC_D5_9C;          // 4293713308
const binary = 0b1010_0001_1000;    // 2584

// Underscore değeri DEĞİŞTİRMEZ — sadece görsel ayırıcı
console.log(1_000_000 === 1000000); // true

Object.entries, Object.fromEntries

// Object.entries: Nesne → [key, value] çiftleri dizisi
const fiyatlar = { elma: 5, armut: 7, muz: 12 };

const entries = Object.entries(fiyatlar);
// [["elma", 5], ["armut", 7], ["muz", 12]]

// Nesneyi dönüştürmek için çok kullanışlı
const zamliFiyatlar = Object.fromEntries(
  Object.entries(fiyatlar).map(([urun, fiyat]) => [urun, fiyat * 1.18])
);
// { elma: 5.9, armut: 8.26, muz: 14.16 }

// Map ↔ Object dönüşümü
const map = new Map(Object.entries(fiyatlar));
const tekrarNesne = Object.fromEntries(map);

structuredClone — Derin Kopyalama

// ES2022 — derin (deep) kopyalama
const orijinal = {
  ad: "Ahmet",
  adres: { sehir: "İstanbul" },
  tarihler: [new Date("2024-01-01")],
};

// ❌ Sığ kopya — iç nesneler referans paylaşır
const sigKopya = { ...orijinal };
sigKopya.adres.sehir = "Ankara";
console.log(orijinal.adres.sehir); // "Ankara" — orijinal de değişti!

// ❌ JSON trick — Date, Map, Set, undefined kayboluyor
const jsonKopya = JSON.parse(JSON.stringify(orijinal));
// tarihler string'e dönüşür, fonksiyonlar kaybolur

// ✅ structuredClone — gerçek derin kopya
const derinKopya = structuredClone(orijinal);
derinKopya.adres.sehir = "Ankara";
console.log(orijinal.adres.sehir); // "İstanbul" — orijinal KORUNDU
console.log(derinKopya.tarihler[0] instanceof Date); // true — Date korundu

⚠️ Dikkat: structuredClone fonksiyonları, DOM elementlerini ve Symbol'leri kopyalayamaz. Bu tip değerler için özel çözümler gerekir.


Yaygın Hatalar ve Tuzaklar

Hata 1: ?? ile || Karıştırmak

// Senaryo: Sayfalama — sayfa 0 da geçerli
const sayfa = 0;

const aktifSayfa1 = sayfa || 1;  // 1 — YANLIŞ! 0 ezildi
const aktifSayfa2 = sayfa ?? 1;  // 0 — DOĞRU! 0 korundu

// Senaryo: Boş string kontrolü
const isim = "";

const gosterilecek1 = isim || "Anonim"; // "Anonim" — boş string falsy
const gosterilecek2 = isim ?? "Anonim"; // "" — boş string null/undefined değil
// Hangisi doğru? Senaryoya bağlı!

Hata 2: Optional Chaining ile Atama

// ❌ Optional chaining sol tarafta (atama) kullanılamaz!
// kullanici?.adres?.sehir = "Ankara"; // SyntaxError!

// ✅ Önce kontrol et, sonra ata
if (kullanici?.adres) {
  kullanici.adres.sehir = "Ankara";
}

Hata 3: Operator Karıştırma

// ?? ile || veya && aynı ifadede PARANTEZ olmadan kullanılamaz
// const val = a || b ?? c; // SyntaxError!
// const val = a ?? b || c; // SyntaxError!

// ✅ Parantez ile açıkça belirt
const val1 = (a || b) ?? c; // Önce || sonra ??
const val2 = a ?? (b || c); // Önce ?? sonra ||

Gerçek Dünya Örneği: Konfigürasyon Sistemi

Tüm ES6+ özelliklerini birleştiren kapsamlı bir örnek:

// Uygulama konfigürasyon sistemi
class AppConfig {
  #defaults = {
    tema: "light",
    dil: "tr",
    bildirimler: true,
    fontSize: 16,
    animasyonlar: true,
    otomatikKaydet: true,
    kaydetAraligi: 30,
  };
  
  #config;
  
  constructor(userConfig = {}) {
    // structuredClone ile derin kopya — orijinal korunur
    this.#config = structuredClone(this.#defaults);
    this.birlesir(userConfig);
  }
  
  birlesir(yeniAyarlar) {
    // Object.entries + ??= ile varsayılan korumalı birleştirme
    for (const [key, value] of Object.entries(yeniAyarlar)) {
      if (key in this.#config) {
        this.#config[key] = value; // Kullanıcı değeri
      }
    }
  }
  
  al(yol) {
    // Optional chaining benzeri noktalı yol erişimi
    return yol.split(".").reduce(
      (obj, key) => obj?.[key],
      this.#config
    ) ?? undefined;
  }
  
  ayarla(yol, deger) {
    const anahtarlar = yol.split(".");
    const sonAnahtar = anahtarlar.pop();
    const hedef = anahtarlar.reduce((obj, key) => {
      obj[key] ??= {};
      return obj[key];
    }, this.#config);
    
    hedef[sonAnahtar] = deger;
  }
  
  toString() {
    const entries = Object.entries(this.#config);
    return entries
      .map(([k, v]) => `  ${k}: ${v}`)
      .join("\n");
  }
}

// Kullanım
const config = new AppConfig({
  tema: "dark",
  fontSize: 0,         // 0 geçerli — ??= ile korunur
  bildirimler: false,   // false geçerli
});

console.log(`Tema: ${config.al("tema")}`);            // "dark"
console.log(`Font: ${config.al("fontSize")}`);         // 0
console.log(`Bildirimler: ${config.al("bildirimler")}`); // false
console.log(`Dil: ${config.al("dil")}`);               // "tr" (varsayılan)

// Dinamik ayar değiştirme
config.ayarla("tema", "solarized");
console.log(`Yeni tema: ${config.al("tema")}`); // "solarized"

Özet

Bu derste modern JavaScript'in günlük hayatta en çok kullanılan özelliklerini öğrendik:

  • Template literals (` ) string birleştirmeyi, çok satırlı string'leri ve string içi ifadeleri (${}`) kolaylaştırır. Tagged template'ler ile özel string işleme yapılabilir.

  • Optional chaining (?.) null/undefined kontrollerini tek operatöre indirir. Nesne, dizi ve fonksiyon erişimlerinde hata yerine undefined döner.

  • Nullish coalescing (??) sadece null/undefined için varsayılan değer atar — || operatörünün aksine 0, "", false gibi geçerli değerleri korur.

  • Logical assignment (??=, ||=, &&=) koşullu atama işlemlerini tek operatöre sığdırır.

  • structuredClone gerçek derin kopyalama sağlar — JSON.parse(JSON.stringify()) hack'ine artık gerek yok.

Bir sonraki derste JavaScript'in modül sistemini öğreneceğiz.