Mert Özen Her Satırda Daha İleri
Frontend JavaScript Orta — #05

JavaScript Map ve Set: Key-Value Koleksiyonları ve Yeni Set Method'ları

Mert Özen 3 Kas 2026 8 dk 4 görüntülenme
JavaScript Map ve Set: Key-Value Koleksiyonları ve Yeni Set Method'ları

Map'in object'ten farkını, Set ile benzersiz değerleri, iterasyonu, ES2025 ile gelen union ve intersection gibi küme method'larını ve ES2026'nın getOrInsert method'unu örneklerle öğreniyoruz; WeakMap'e de kısaca bakıyoruz.

Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.

Önce Tahmin Et

İki meyveye fiyat veren bir object ve tekrar eden değerlerle dolu bir Set:

const prices = {};
const apple = { id: 1 };
const pear = { id: 2 };

prices[apple] = 10;
prices[pear] = 20;

console.log(prices[apple]);
console.log(new Set([1, "1", 1, NaN, NaN]).size);

Bu kod konsola ne yazdırır? Okumaya devam etmeden önce iki satır için de bir tahmin yap. İlk cevabı hemen bir sonraki bölümde, ikincisini Set'i anlatırken vereceğiz.

Object'i Sözlük Olarak Kullanmanın Sınırları

Daha önce object'leri hem kayıt (bir kullanıcının adı, e-postası) hem de sözlük (ürün koduna göre stok) olarak kullandık. İlki için harikadır. Sözlük olarak kullandığında ise bazı köşeler keskinleşir.

Baştaki ilk satırın cevabı 20. Object key'leri string'e (ya da symbol'e) dönüştürülür. prices[apple] yazdığında engine apple object'ini string'e çevirir ve sonuç "[object Object]" olur. pear da aynı string'e dönüşür. Yani iki satır aynı key'e yazdı ve ikincisi birincisini ezdi. Object.keys(prices) dersen tek bir key görürsün: ["[object Object]"].

Bunun dışında da küçük dertler var. Bir object'te kaç key olduğunu öğrenmek için önce Object.keys ile bir array üretmen gerekir. Object, prototype'tan gelen property'lerle karışabilir. Sık ekleme ve silme yapılan büyük sözlükler için de optimize edilmemiştir. ES2015 ile gelen Map ve Set tam bu boşluğu doldurur.

Map: Her Şeyi Key Yapabilen Sözlük

Bir vestiyer düşün. Ceketini verirsin, karşılığında bir fiş alırsın; o fişi gösteren ceketini geri alır. Map'te bu fiş her şey olabilir: bir sayı, bir string, bir object, hatta bir DOM elementi. Key neyse odur; string'e çevrilmez.

const ada = { name: "Ada" };
const alan = { name: "Alan" };

const lastSeen = new Map();
lastSeen.set(ada, "09:30").set(alan, "11:15");

console.log(lastSeen.get(ada));  // "09:30"
console.log(lastSeen.has(alan)); // true
console.log(lastSeen.size);      // 2

lastSeen.delete(alan);
console.log(lastSeen.size);      // 1

const mixed = new Map();
mixed.set(1, "number").set("1", "string");
console.log(mixed.get(1), mixed.get("1")); // "number" "string"

Temel method'lar beş tane: set, get, has, delete ve her şeyi silen clear. Element sayısı her an size property'sinde hazır. set Map'in kendisini döndürdüğü için çağrıları zincirleyebilirsin. Sayı olan 1 ile string olan "1" da artık iki ayrı key.

Map'i başlangıç verisiyle de oluşturabilirsin. Constructor, [key, value] çiftlerinden oluşan bir array bekler. Bu da daha önce gördüğümüz Object.entries'in çıktısıyla birebir aynı biçim, yani object'ten Map'e ve geri dönmek tek satır:

const settings = { theme: "dark", lang: "en" };
const settingsMap = new Map(Object.entries(settings));
console.log(settingsMap.get("theme")); // "dark"

const backToObject = Object.fromEntries(settingsMap);
console.log(backToObject); // { theme: "dark", lang: "en" }

Map Üzerinde Dolaşmak

Map, element'leri eklendiği sırayla tutar ve doğrudan for...of ile gezilebilir. Her adımda bir [key, value] çifti gelir; destructuring ile ikisini ayrı değişkenlere açmak en okunaklı yol:

const stock = new Map([
  ["pen", 120],
  ["notebook", 0],
  ["thermos", 4]
]);

for (const [item, qty] of stock) {
  console.log(`${item}: ${qty}`);
}

console.log([...stock.keys()]);   // ["pen", "notebook", "thermos"]
console.log([...stock.values()]); // [120, 0, 4]

stock.forEach((qty, item) => console.log(item, qty));

keys(), values() ve entries() array değil iterator döndürür. Bir array method'u kullanmak istiyorsan spread ya da Array.from ile array'e çevirirsin. İterator'ları İleri seride ayrıntılı göreceğiz. forEach'te parametre sırasına dikkat: önce değer, sonra key gelir.

getOrInsert: Varsa Ver, Yoksa Ekle

Map ile çalışırken en sık yazılan kalıp şudur: "Bu key var mı? Yoksa bir başlangıç değeri koy. Sonra o değeri al ve kullan." ES2026 ile gelen getOrInsert bu üç adımı tek çağrıya indiriyor. Kelimeleri baş harflerine göre gruplayalım:

const words = ["apple", "banana", "apricot", "blueberry"];

// Klasik yol
const groupsOld = new Map();
for (const word of words) {
  if (!groupsOld.has(word[0])) groupsOld.set(word[0], []);
  groupsOld.get(word[0]).push(word);
}

// ES2026
const groups = new Map();
for (const word of words) {
  groups.getOrInsert(word[0], []).push(word);
}
console.log(groups); // Map(2) { "a" => ["apple", "apricot"], "b" => ["banana", "blueberry"] }

getOrInsert(key, value) key varsa mevcut değeri döndürür; yoksa verdiğin değeri ekler ve onu döndürür. Başlangıç değerini üretmek pahalıysa kardeşi getOrInsertComputed'ı kullan. Değer yerine bir fonksiyon alır ve o fonksiyonu yalnızca key yoksa, key'in kendisiyle çağırır. Basit bir önbellek için tam aradığın şey:

const slugCache = new Map();

function toSlug(title) {
  return slugCache.getOrInsertComputed(title, (t) => {
    console.log("computing:", t);
    return t.toLowerCase().replaceAll(" ", "-");
  });
}

console.log(toSlug("Map and Set")); // "computing: Map and Set", ardından "map-and-set"
console.log(toSlug("Map and Set")); // "map-and-set", cache'ten

Aynı iki method WeakMap'te de var. Baseline'a 2026'nın başında "yeni" olarak girdiler; Chrome ve Edge 145, Firefox 144 ve Safari 26.2'den itibaren çalışıyorlar. Biraz daha eski tarayıcıları da desteklemen gerekiyorsa yukarıdaki klasik has/set/get kalıbı her yerde çalışır.

Set: Benzersiz Değerler Kümesi

Set, her değerin en fazla bir kez bulunabildiği bir koleksiyondur. Bir kapıdaki misafir listesi gibi: aynı kişiyi ikinci kez yazmaya çalışırsan liste değişmez. Key'i ve değeri ayrı ayrı yoktur; sadece değerler vardır.

const tags = new Set();
tags.add("js").add("css").add("js");
console.log(tags.size);       // 2
console.log(tags.has("css")); // true
tags.delete("css");

const unique = [...new Set([3, 1, 3, 2, 1])];
console.log(unique); // [3, 1, 2]

Bir array'deki tekrarları atmanın en kısa yolu, son satırdaki gibi array'i bir Set'ten geçirip spread ile geri array'e çevirmek. Sıra korunur; her değerin ilk görüldüğü yer geçerli olur.

Baştaki ikinci sorunun cevabı da burada: 3. Set iki değerin aynı olup olmadığına SameValueZero adlı bir kuralla karar verir. Bu kural neredeyse === gibidir, tek farkı NaN'ı kendisine eşit saymasıdır. Bu yüzden 1 ile "1" iki ayrı değer, iki NaN ise tek bir değer sayıldı: 1, "1" ve NaN. Aynı kural Map key'leri için de geçerli. Object'lerde ise içerik değil referans karşılaştırılır; new Set([{ id: 1 }, { id: 1 }]).size 2'dir, çünkü bunlar görünüşte aynı olan iki ayrı object'tir.

Yeni Set Method'ları: Küme İşlemleri

Okulda gördüğün Venn şemalarını hatırla: iki daire, kesişim, birleşim, fark. Set uzun süre bu işlemleri yapamıyordu; hepsini filter ve has ile elle yazıyorduk. ES2025 ile yedi yeni method geldi:

const frontend = new Set(["html", "css", "js", "git"]);
const backend = new Set(["sql", "js", "git", "docker"]);

console.log(frontend.union(backend));               // Set(6) {"html", "css", "js", "git", "sql", "docker"}
console.log(frontend.intersection(backend));        // Set(2) {"js", "git"}
console.log(frontend.difference(backend));          // Set(2) {"html", "css"}
console.log(frontend.symmetricDifference(backend)); // Set(4) {"html", "css", "sql", "docker"}

console.log(new Set(["js", "git"]).isSubsetOf(frontend)); // true
console.log(frontend.isSupersetOf(new Set(["css"])));     // true
console.log(frontend.isDisjointFrom(new Set(["php"])));   // true

İlk dördü yeni bir Set döndürür ve orijinallere dokunmaz. union ikisinde olan her şeyi, intersection sadece ortakları, difference ilkinde olup ikincisinde olmayanları, symmetricDifference ise sadece birinde olanları verir. Son üçü bir boolean döndürür: alt küme mi, üst küme mi, hiç ortak element'leri yok mu?

Bu method'lar Baseline'a 2024'te "yeni" olarak girdi; Chrome ve Edge 122, Firefox 127 ve Safari 17'den beri çalışıyorlar, yani bütün güncel tarayıcılarda gönül rahatlığıyla kullanabilirsin. Argüman olarak bir Set (ya da Map gibi Set'e benzeyen bir object) beklerler; array verirsen TypeError alırsın. Elinde array varsa önce new Set(array) ile sar.

Map mi Object mi, Set mi Array mi?

Map ve Set, object ve array'i emekliye ayırmıyor; her birinin kendi işi var. Şekli baştan belli olan bir kayıt için, örneğin adı, e-postası ve rolü olan bir kullanıcı için object kullan. JSON ile sunucuya gidip gelecek veride de object doğal seçimdir. Key'leri çalışma anında belirlenen, sık eklenip silinen, string olmayan key'ler taşıyan ya da boyutunu sık sorduğun bir sözlük için Map daha uygun.

Array ve Set için de benzer bir ayrım var. Sıra, index ve tekrar eden değerler önemliyse array; benzersizlik ve "bu değer içeride var mı?" sorusu önemliyse Set. Büyük koleksiyonlarda set.has(), array.includes()'tan genellikle çok daha hızlıdır, çünkü bütün element'leri tek tek gezmez.

Bir de ismini duyacağın iki kuzen var: WeakMap ve WeakSet. Key olarak string ya da sayı gibi değerleri değil, object'leri kabul ederler; üzerlerinde dolaşılamaz ve size'ları yoktur. Karşılığında, key olan object'i bellekte tutmazlar; object'e başka bir yerden erişim kalmadıysa çöp toplayıcı onu temizleyebilir. Bu konuyu İleri serideki bellek yönetimi yazısında ayrıntılı ele alacağız.

Sık Düşülen Tuzaklar

Map'e köşeli parantezle yazmak. myMap["key"] = 1 çalışıyor gibi görünür ama Map'e bir kayıt eklemez; Map object'inin üzerine sıradan bir property yazar. myMap.size 0 kalır, myMap.get("key") undefined döner. Map ile her zaman set ve get kullan.

Map'i doğrudan JSON'a çevirmek. JSON.stringify(new Map([["a", 1]])) sonucu "{}"'dir; veri sessizce kaybolur. Önce Object.fromEntries(map) ile object'e çevir. Set için de aynı sorun var; [...set] ile array'e çevirerek gönder.

Object key'ini yeniden oluşturmak. lastSeen.get({ name: "Ada" }) undefined döner, çünkü yeni yazdığın object, key olarak kullandığın object'le aynı referans değil. Object key'li Map'lerde key'i bir değişkende tut.

Set method'larına array vermek. frontend.union(["php"]) TypeError verir. Array Set'e benzemez; size ve has gibi gereken üyeleri yoktur.

Küçük Bir Deneme

★☆☆ Çalıştırmadan önce tahmin et: const s = new Set("mississippi"); console.log(s.size, [...s].join("")); String'in de üzerinde dolaşılabildiğini hatırla.

★★☆ Bir cümledeki kelimelerin kaç kez geçtiğini bir Map ile say. Önce get ve ?? ile, sonra getOrInsert ile yaz. En sık geçen kelimeyi bulup konsola yazdır.

★★☆ Pazartesi derse gelen öğrencilerin numaraları [3, 7, 12, 15], salı gelenlerinki [7, 9, 12, 20]. Set method'larıyla iki gün de gelenleri, sadece pazartesi gelenleri ve en az bir gün gelenleri bul.

★★★ memoize(fn) adında bir fonksiyon yaz: aldığı tek argümanlı fonksiyonun sonuçlarını bir Map'te saklasın ve aynı argümanla ikinci kez çağrıldığında hesaplamayı tekrar yapmasın. getOrInsertComputed kullan ve fonksiyonun içine koyduğun bir console.log ile kaç kez gerçekten çalıştığını göster.

Bu yazıda birkaç kez TypeError ile karşılaştık ve hep "hata verir" deyip geçtik. Bir sonraki yazıda bu hataların kendisine odaklanacağız: try/catch/finally ile yakalamak, throw ile fırlatmak, kendi hata türlerini tanımlamak ve cause ile hataları birbirine zincirlemek.