Symbol: Her Zaman Benzersiz Bir Değer
JavaScript'in ilkel (primitive) tiplerinden biri olan symbol; string, number, boolean, null, undefined ve bigint ile aynı ailede yer alır. Onu diğerlerinden ayıran özellik çok basit: oluşturduğunuz her sembol, diğer tüm sembollerden farklıdır ve bu sonsuza dek böyle kalır.
İki tane Symbol oluşturun, ikisine de hiçbir argüman vermeyin — yine de birbirine eşit olmazlar. Bu bir yan etki değil, olayın ta kendisi. Bir symbol'ü taklit edemez, yanlışlıkla yeniden üretemez ya da başkasının oluşturduğuyla çakıştıramazsınız.
Hata ayıklamayı kolaylaştırmak için bir açıklama (description) ekleyebilirsiniz. Bu açıklama kimliği etkilemez:
Aynı açıklama, farklı sembol. Açıklama dediğimiz şey sadece logları okuyan insan için bir etiket.
Neden Var: Çakışmayan Anahtarlar
JavaScript'te symbol tipinin var olma sebebi, bir nesneye özellik eklerken anahtarınızın başka birinin anahtarıyla çakışma ihtimalini tamamen ortadan kaldırmaktır. String anahtarlar düz bir isim uzayında yarışır; iki kütüphane de meta verisini obj.meta altında tutmaya karar verirse birbirinin üzerine yazar. Symbol anahtarlarda böyle bir yarış yok, çünkü sizin symbol'ünüz başka kimsede yok.
[ID]: 42 ifadesindeki köşeli parantezler hesaplanmış özellik (computed property) sözdizimidir; yani "anahtar olarak ID'nin değerini kullan" demektir. Bu özelliği yalnızca aynı ID sembolünü elinde tutan kod okuyabilir ya da üzerine yazabilir. Başka bir modül "id" string'ini veya kendi Symbol("id")'sini kullanıyorsa tamamen farklı bir alandan söz ediyor demektir.
Symbol anahtarları (çoğunlukla) gizlidir
Symbol anahtarlı özellikler for...in döngüsünde, Object.keys çıktısında veya JSON.stringify sonucunda görünmez. Gizli sayılmazlar — kararlı bir kod bunlara yine de ulaşabilir — ama normal iterasyon sırasında ayak altında dolaşmazlar.
Object.keys ve JSON.stringify, symbol anahtarlarını tamamen görmezden gelir. Eğer symbol özelliklerine bilinçli olarak erişmek istiyorsan Object.getOwnPropertySymbols ve Reflect.ownKeys bunları sana verir. Metadata için tam olarak istediğin davranış budur: aradığında görünür, sadece string anahtarları dolaşan kodlara ise görünmez.
Symbol.for ile Symbol Paylaşmak
Symbol() sana yerel, tek kullanımlık bir symbol üretir. Bazen bunun tam tersini istersin — programın her yerinde, tüm modüller arasında aynı olan bir symbol, böylece kodun farklı parçaları ortak bir anahtar üzerinde anlaşabilir. İşte Symbol.for tam bunun için var.
Symbol.for(key) global bir kayıt tablosuna bakar: o anahtara ait bir symbol zaten varsa onu döndürür, yoksa yeni bir tane oluşturup saklar. Symbol.keyFor ise tam tersini yapar — kayıtlı bir symbol'ün hangi anahtarla oluşturulduğunu söyler (kayıtlı değilse undefined döner).
Çoğu zaman ihtiyacınız olan şey düz Symbol() çağrısıdır. Symbol.for'a ancak bir symbol'ün gerçekten modül sınırları arasında paylaşılması gerektiğinde başvurun.
Well-Known Symbols: Dile Açılan Kancalar
Symbol'ler asıl değerini işte burada gösteriyor. JavaScript'in kendisinin nesnelerinizde aradığı, önceden tanımlanmış bir symbol kümesi vardır — bunlara well-known symbols denir. Bu anahtarlardan birine bir metot tanımladığınız anda, nesneniz doğrudan dilin yerleşik bir özelliğine bağlanır.
Bunların en kullanışlısı Symbol.iterator. Bu anahtarda bir metodu olan her nesne iterable hâle gelir: for...of, spread, destructuring ve Array.from ile sorunsuz çalışır.
range bir dizi değil. Tek bir özel anahtarlı metodu olan, sıradan bir nesne. Ama o metot bir iterator döndürdüğü için for...of de spread operatörü de bu nesnede sorunsuz çalışıyor — tıpkı dizilerde, stringlerde ve Map'te çalıştığı gibi. Anlaşma bu kadar basit: Symbol.iterator'ı implemente et, dil seni iterable olarak kabul etsin.
Bilmeye Değer Diğer Well-Known Symbol'ler
Her biri dilin farklı bir köşesine kanca atan birkaç tane daha var:
Symbol.iterator— bir nesneyi iterable (yinelenebilir) hale getirir.Symbol.asyncIterator— aynı işifor await...ofiçin yapar.Symbol.toPrimitive— bir nesnenin tür dönüşümü sırasında primitive bir değere (sayı, string ya da default) nasıl çevrileceğini belirler.Symbol.hasInstance—instanceofoperatörünün kendi sınıfın için ne döndüreceğini özelleştirmeni sağlar.Symbol.toStringTag—Object.prototype.toString.call(obj)çağrıldığında görünen etiketi ayarlar.
Bunları ezberlemen gerekmiyor. Var olduklarını bilmen yeterli — nesnenin yerleşik (built-in) bir tip gibi davranmasını istediğin bir durumla karşılaştığında, iş görecek bir well-known symbol neredeyse her zaman mevcuttur.
Symbol'ler String Değildir
Sık düşülen tuzaklardan biri: symbol'ler otomatik olarak string'e dönüşmez. Bir symbol'ü string ile birleştirmeye çalışırsan hata alırsın, string bekleyen bir API'ye verdiğinde de aynı şey olur:
Template literal'lar da aynı TypeError'ı fırlatır. Metin olarak kullanmak istediğinde String(symbol) ya da symbol.toString() demelisin. Bu sertlik bilinçli bir tercih — dil, benzersiz bir kimlik değerini kazara sıradan bir string veriymiş gibi kullanmanı engelliyor.
Symbol ne zaman kullanılır, ne zaman kullanılmaz?
Şu durumlarda symbol'lere uzanabilirsin:
- Sana ait olmayan nesnelere metadata iliştiriyorsan ve çakışmayacak bir anahtara ihtiyacın varsa.
- Bir protokol tasarlıyorsan — yani "kütüphanemle çalışmak isteyen nesneler şu symbol'deki metodu implemente etsin" diyorsan.
JSON.stringifyveyafor...ingibi yerlere sızmayan bir property istiyorsan.Symbol.iteratorya da başka bir well-known symbol'ü implemente ediyorsan.
Şu durumlarda symbol'lere hiç bulaşma:
- Sadece bir nesne anahtarı lazımsa ve çakışma riski yoksa. String daha basit, log'larda da olduğu gibi görünür.
- "Private" alanlar istiyorsan. Symbol anahtarlı property'ler gerçekten private değildir —
Object.getOwnPropertySymbolsonları bulur. Gerçek gizlilik için sınıflarda#privatealanlarını kullan. JSON.stringifysonrası hayatta kalması gereken veri saklıyorsan. Kalmaz.
JavaScript kodlarının çoğu, Symbol(...) yazmadan gayet güzel işini görüyor. Ama kendi nesneni for...of ile çalıştırmak ya da bir coercion senaryosunda doğal davranmasını istediğin an, o mekanizmaya açılan kapı symbol'lerdir.
Sıradaki konu: Fonksiyon tanımları
Symbol'ler, iterator'lar ve generator'lar — hepsi yoğun biçimde fonksiyonlara dayanıyor. Symbol.iterator altında duran metotlar, iterator döndüren factory'ler, function* biçimiyle çoğu boilerplate'i gizleyen generator'lar... Fonksiyonlar bir sonraki bölümün konusu; işe bunları tanımlamanın farklı yollarıyla ve her birinde neyin değiştiğiyle başlayacağız.
Sıkça Sorulan Sorular
JavaScript'te symbol nedir?
Symbol, her değeri benzersiz olan bir primitive tipidir. Symbol() veya Symbol('açıklama') ile oluşturursunuz ve aynı çağrıyı iki kez yapsanız bile sonuç asla birbirine eşit olmaz. En çok iki yerde işe yarar: başka anahtarlarla çakışmayacak nesne anahtarları tanımlarken ve Symbol.iterator gibi well-known symbol'ler aracılığıyla dilin kendi mekanizmalarına bağlanırken.
String anahtar yerine ne zaman symbol kullanmalıyım?
Bir nesneye, başka bir kodla isim çakışması yaşamadan özellik eklemek istediğinizde symbol kullanın — metadata ekleyen kütüphaneler, nesneleri etiketleyen framework'ler veya bilinçli olarak public API'ın dışında tutmak istediğiniz alanlar için idealdir. Okunabilirliğin önemli olduğu ve çakışma riski olmayan günlük anahtarlar içinse string hâlâ daha mantıklı.
Symbol.iterator ne işe yarar?
Symbol.iterator, for...of döngüsüne, spread operatörüne ve destructuring'e bir nesnenin nasıl gezileceğini söyleyen well-known bir symbol'dür. Nesnenizde Symbol.iterator anahtarında bir iterator döndüren method tanımlarsanız, o nesne iterable hâle gelir. Array, string, Map ve Set de perde arkasında tam olarak böyle çalışır.
Symbol() ile Symbol.for() arasındaki fark nedir?
Symbol('x') her çağrıldığında yepyeni bir symbol üretir — aynı açıklamayla iki kez çağırsanız bile sonuçlar birbirinden farklıdır. Symbol.for('x') ise global bir kayıt tablosuna bakar ve aynı anahtar için program genelinde aynı symbol'ü döndürür. Modüller veya realm'ler arasında ortak bir symbol'e ihtiyacınız varsa Symbol.for; sadece lokal benzersizlik yetiyorsa Symbol() kullanın.