Menu

JavaScript Map ve Set: Ne Zaman Kullanılır?

JavaScript'te Map ve Set nasıl çalışır, plain object ve array'den farkları nelerdir, hangi durumda hangisini tercih etmek mantıklı?

Object ve Array Dışında İki Koleksiyon

Düz nesneler (object) ve diziler (array), JavaScript programlarının ihtiyaç duyduğu şeylerin büyük kısmını karşılar; ama her iş için tasarlanmamışlardır. Map ve Set, iki belirli boşluğu dolduran yerleşik koleksiyonlardır: anahtarların string olmadığı durumlarda anahtar bazlı erişim ve tekrarsız üyelik kontrolleri.

Her ikisi de ES2015'ten beri dilin parçası. İkisi de iterable, ikisinde de .size özelliği var ve spread operatörüyle güzel çalışıyorlar. Zihinsel modeli oldukça basit:

  • Map — nesneye benzer, ama anahtarlar her şey olabilir ve ekleme sırası korunur.
  • Set — diziye benzer, ama değerler tekrarsızdır ve erişim hızlıdır.

Map Oluşturma ve Kullanımı

Bir Map, anahtar/değer çiftlerini tutar. new Map() ile oluşturur; .set(), .get(), .has() ve .delete() metotlarıyla kullanırsın:

index.js
Output
Click Run to see the output here.

Başlangıç değerleriyle doldurmak için yapıcıya [anahtar, değer] çiftlerinden oluşan bir dizi de verebilirsin:

index.js
Output
Click Run to see the output here.

That two-element-array shape shows up everywhere Maps are involved — it's how entries are represented when you iterate.

Map vs Object: Why Bother?

Plain objects look like they do the same job. Most of the time they do. But Maps fix a few specific rough edges:

index.js
Output
Click Run to see the output here.

Nesneler Object.prototype'tan miras alır, yani toString, constructor ve hasOwnProperty gibi anahtarlar her nesnede zaten hazır gelir. Map'lerin böyle bir yükü yoktur — sadece senin eklediğin anahtarlar vardır, başka hiçbir şey.

Bilmeye değer diğer farklar:

  • Her türden anahtar. Map'ler anahtar olarak nesne, fonksiyon, sayı, boolean kabul eder. Nesneler ise string olmayan anahtarları sessizce string'e çevirir: obj[1] ile obj["1"] aynı yere yazar.
  • Garantili ekleme sırası. Map, girişleri eklendikleri sırayla dolaşır. Nesneler de çoğunlukla öyle yapar ama sayıya benzeyen string anahtarlar önce sıralanır — sinsi bir tuzak.
  • Hazır size özelliği. map.size O(1) çalışır. Nesnelerde aynı şey için Object.keys(obj).length yazman gerekir ki bu da her seferinde yeni bir dizi oluşturur.
  • Sık değişim için optimize. JavaScript motorları Map'leri sık ekleme/çıkarma senaryolarına göre ayarlar. Nesneler ise yapısı sabit kalan kayıtlar için optimize edilmiştir.

Anahtarları önceden bilinen string'lerden oluşan bir kayıt modelliyorsan ({ name, email, age } gibi) nesne kullan. Anahtarlar dinamikse, string değilse ya da sürekli giriş ekleyip çıkaracaksan Map tercih et.

Map üzerinde iterasyon (dolaşma)

Map'ler iterable'dır; yani doğrudan for...of ile dolaşabilir, her entry'yi destructuring ile rahatça parçalayabilirsin:

index.js
Output
Click Run to see the output here.

Sadece key'leri ya da sadece value'ları istiyorsan .keys() veya .values() metodlarını çağırabilirsin. Tercihin bu yöndeyse .forEach() da kullanılabilir:

index.js
Output
Click Run to see the output here.

Bir Map'i tekrar düz bir objeye ya da diziye çevirmek için spread operatörünü kullanabilirsin:

index.js
Output
Click Run to see the output here.

Set Oluşturma ve Kullanma

Set, yalnızca benzersiz değerleri tutan bir yapıdır. Zaten var olan bir değeri tekrar eklemek hiçbir şey yapmaz:

index.js
Output
Click Run to see the output here.

Benzersizlik kontrolü, === ile aynı eşitlik kuralına göre yapılır; ancak küçük bir istisna var: başka her yerde NaN === NaN ifadesi false dönerken, Set içinde NaN kendisine eşit sayılır.

Bir Set'i baştan doldurmak için constructor'a iterable bir yapı verebilirsiniz — işte javascript diziden tekrar eden elemanları silme hilesi de tam olarak buradan geliyor:

index.js
Output
Click Run to see the output here.

Tek satır, herhangi bir primitive tip. Obje dizilerinde bu yöntem işe yaramaz — aynı alanlara sahip iki farklı obje yine iki farklı değerdir — ama stringler, sayılar ve boolean'lar için en yaygın tekilleştirme yöntemi budur.

Set mi Array mi: Hangi Durumda Hangisi?

Hem diziler hem de Set'ler bir değer koleksiyonu tutar. Peki hangisini ne zaman tercih etmeli?

Şu durumlarda Set kullanın:

  • Değerlerin benzersiz olması gerekiyorsa ve bunu JavaScript'in kendisinin garanti etmesini istiyorsanız.
  • Çok fazla "var mı?" kontrolü yapıyorsanız. set.has(x) O(1) karmaşıklığındadır; array.includes(x) ise O(n). Bir döngünün içinde bu fark hızla katlanır.
  • Sadece ekleme sırasına ihtiyacınız varsa. Set'ler eklendikleri sırayla dolaşılır ama index ile erişime izin vermez.

Şu durumlarda array'de kalın:

  • İndexle erişime ihtiyacınız varsaarr[0], dilimleme, sıralama gibi işlemler.
  • Tekrar eden değerler anlamlıysa — mesela aynı üründen iki tane içeren bir sepet.
  • .map, .filter, .reduce gibi array metodlarını yoğun şekilde kullanacaksanız. Set'lerde bu metodlar yok; önce spread ile bir array'e çevirmeniz gerekir.

Performans farkını gösteren kısa bir örnek:

index.js
Output
Click Run to see the output here.

banned bir dizi olsaydı, her filter çağrısında tüm liste baştan sona taranırdı. Set kullanınca her arama sabit zamanda tamamlanıyor.

Set üzerinde iterasyon

Map'teki mantığın aynısı geçerli: for...of sorunsuz çalışıyor, spread operatörüyle de rahatça diziye çevirebilirsin:

index.js
Output
Click Run to see the output here.

Set'lerde de Map ile simetrik olması için .keys(), .values() ve .entries() metotları bulunur; ne var ki Set söz konusu olduğunda anahtar ile değer zaten aynı şey. Pratikte çoğu zaman doğrudan iterasyonla işini görürsün.

Gerçek Bir Örnek: Sayfa Başına Tekil Ziyaretçi Sayma

Şimdi ikisini birleştirelim — anahtarı sayfa yolu, değeri ise ziyaretçi ID'lerinden oluşan bir Set olan bir Map:

index.js
Output
Click Run to see the output here.

Map, yolu ilgili kovaya eşlemeyi üstleniyor; Set ise her kovanın içindeki tekrarları eleme işini hallediyor. Aynı şeyi düz bir object ve dizilerle de yapabilirdin, ama baştan sona bir sürü indexOf kontrolü ve hasOwnProperty koruması yazmak zorunda kalırdın.

Kısaca WeakMap ve WeakSet

Dar bir kullanım senaryosuna hitap eden iki akraba koleksiyon daha var: WeakMap ve WeakSet. Bunlar referansları "zayıf" tutar; yani bir girdinin anahtarına (WeakMap için) ya da değerine (WeakSet için) başka hiçbir yerden referans kalmadığında, o girdi çöp toplayıcı tarafından otomatik olarak temizlenir.

index.js
Output
Click Run to see the output here.

Anahtar olarak yalnızca nesne kabul ederler, iterasyona açık değildirler ve .size özellikleri yoktur. Bu bilinçli bir tercih — eğer üzerlerinde gezinebilseydiniz, çöp toplayıcının (garbage collector) davranışı gözlemlenebilir hale gelirdi. Sahip olmadığınız nesnelere ait meta verileri önbelleğe almak için işe yararlar, ama günlük kodda pek karşınıza çıkmazlar.

Sırada: JSON

Map ve Set bellekte harika çalışır, ama hiçbiri JSON.stringify'dan sağ çıkamaz — Map'ler {} olur, Set'ler de {} olur. Bir sonraki sayfada JSON'u ele alacağız: veriyi nasıl serileştirip ayrıştıracağınızı ve bu sayfada tanıttığımız koleksiyonların ağ ya da dosya sınırını aşması gerektiğinde kullanılan kalıpları göreceğiz.

Sıkça Sorulan Sorular

JavaScript'te Map ile Object arasındaki fark nedir?

Map her türlü değeri anahtar olarak kabul eder — obje, fonksiyon, sayı, ne olursa. Object ise anahtarları string'e (ya da symbol'e) çevirir. Ayrıca Map, .size ile eleman sayısını doğrudan verir, ekleme sırasını koruyarak iterasyon yapar ve prototipten anahtar miras almaz; yani toString veya constructor gibi isimlerle çakışma derdi yok. Anahtarların string olmadığı ya da sık sık eleman ekleyip çıkardığın senaryolarda Map çok daha rahat.

JavaScript'te Set ne işe yarar?

Set, sadece benzersiz değerleri tutar — tekrar eden elemanları sessizce yutar. Bir diziden tekrar eden elemanları atmanın en kısa yolu [...new Set(arr)]. Ayrıca Set'in .has() metodu O(1) çalışıyor; bir döngünün içinde eleman kontrolü yapacaksan array.includes()'a göre çok daha hızlı.

Bir Map üzerinde nasıl iterasyon yapılır?

for...of doğrudan çalışıyor: for (const [key, value] of myMap) şeklinde her girdiyi destructure edebilirsin. Alternatif olarak myMap.keys(), myMap.values() veya myMap.entries() üzerinde de dönebilirsin. İterasyon sırası her zaman eklenme sırasıyla aynıdır; plain object'lerde sayısal görünümlü anahtarlar için bu garanti yok.

Coddy ile kodlamayı öğren

BAŞLA