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:
Başlangıç değerleriyle doldurmak için yapıcıya [anahtar, değer] çiftlerinden oluşan bir dizi de verebilirsin:
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:
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]ileobj["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.sizeO(1) çalışır. Nesnelerde aynı şey içinObject.keys(obj).lengthyazman 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:
Sadece key'leri ya da sadece value'ları istiyorsan .keys() veya .values() metodlarını çağırabilirsin. Tercihin bu yöndeyse .forEach() da kullanılabilir:
Bir Map'i tekrar düz bir objeye ya da diziye çevirmek için spread operatörünü kullanabilirsin:
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:
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:
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 varsa —
arr[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,.reducegibi 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:
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:
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:
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.
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.