Aynı Sözdizimi, İki Farklı Görev
Modern JavaScript kodunda üç noktayı (...) çok sık görürsün ve nerede kullanıldığına göre birbirinin tam tersi iki iş yapar. Aslında mantığı yakaladığında, ... operatörünün her kullanımı şu iki kategoriden birine düşer:
- Rest: Alıcı tarafta
...isimşeklinde yazıldığında, birden fazla değeri tek bir dizi ya da nesnede toplar. - Spread: Veren tarafta
...değerşeklinde yazıldığında, bir diziyi ya da nesneyi tek tek parçalarına ayırır.
Zihin modelinin tamamı bu kadar. Sayfanın geri kalanı sadece her iki kullanıma dair örnekler ve pratikte sürekli başvuracağın kalıplardan oluşuyor.
Rest Parametreleri: Argümanları Toplamak
Fonksiyon tanımında kullanılan bir rest parametresi, gelen argümanları — kaç tane olursa olsun — gerçek bir dizi hâlinde toplar:
nums artık sıradan bir dizi. .map edebilir, .filter edebilir, .length'ine bakabilir, başka bir fonksiyona geçirebilirsin — dizilerle ne yapıyorsan hepsi geçerli.
Rest parametrelerini normal parametrelerle birlikte kullanabilirsin; tek şart, rest parametresinin en sonda yer alması:
label ilk argümanı yakalar; geri kalan her şey items içine düşer. Rest parametresini son sıranın dışında bir yere yazarsan syntax error alırsın.
Rest parametresi ve eski arguments nesnesi
Eski JavaScript kodlarında, normal fonksiyonların içinde arguments adında sihirli bir değişken kullanılır. Görünüşte diziye benzer ama aslında dizi değildir; bu yüzden dizi metotlarını doğrudan üzerinde çalıştıramazsın. Rest parametreleri bunun yerine çok daha temiz bir çözüm sunuyor:
Arrow fonksiyonlarının arguments objesi bile yoktur; dolayısıyla değişken sayıda argüman almanın tek yolu rest parametreleridir. Yeni yazdığın kodda ...args kullanmayı tercih et.
Fonksiyon Çağrılarında Spread Kullanımı
Spread tam tersini yapar: bir diziyi alır ve çağrı sırasında elemanlarını tek tek argümanlara açar.
Math.max tek tek sayıları parametre olarak alır, bir dizi değil. Spread operatörü gelmeden önce Math.max.apply(null, nums) yazmak zorundaydık. Artık bir ... yeterli, iş tamam.
Dikkat ederseniz, aynı ... operatörü fonksiyon tanımında rest, fonksiyon çağrısında ise spread olarak davranıyor — hangisi olduğunu bulunduğu konumdan anlıyoruz.
Dizi Literallerinde Spread Kullanımı
Bir dizi literaline spread yaparak diziyi kopyalayabilir veya birden fazla diziyi birleştirebilirsiniz:
[...a] ile orijinal diziye dokunmadan aynı elemanları içeren yepyeni bir dizi elde edersin. Sıralama veya başka değişiklikler yapmak istediğinde tam aradığın şey:
scores dizisine hiç dokunulmadı; çünkü .sort kopya üzerinde çalıştı. Küçük bir alışkanlık ama sürpriz yan etkileri olmayan kod yazmak istediğinde faydası büyük.
Nesne Literallerinde Spread Kullanımı
Spread operatörü düz nesnelerde de çalışır; özellikleri alıp yeni bir nesneye birleştirir:
Sonradan yazılan anahtar kazanır. updates.age, user.age değerini ezer; city de yanında geliverir. Sonucu belirleyen şey spread'lerin sırasıdır — varsayılanları ve geçersiz kılmaları üst üste bindirirken bunu aklınızda tutun:
Önce varsayılanlar, ardından kullanıcı tercihleri geliyor. fontSize değerini kullanıcı belirliyor, theme ise varsayılandan miras alınıyor.
Shallow Copy Tuzağı
Spread operatörü sadece bir seviye derinliğe kopyalar. İç içe geçmiş nesneler ve diziler, orijinal ile kopya arasında hâlâ paylaşılır:
Her iki dizide de yeni etiketin görünmesinin sebebi şu: copy.tags ile original.tags aslında aynı diziyi işaret ediyor. Spread, iç içe listeyi klonlamıyor; sadece referansı kopyalıyor.
Düz veriyi gerçekten derin kopyalamak istiyorsan structuredClone imdadına yetişir:
Artık iki dizi birbirinden tamamen bağımsız. structuredClone modern tarayıcılarda ve Node'da hazır olarak geliyor, iç içe yapılarla da sorunsuz başa çıkıyor; yani shallow copy'nin yetmediği her durumda doğru tercih bu.
Destructuring İçinde Rest Kullanımı
Rest, destructuring ile de gayet güzel çalışır; geriye kalan eleman ya da özellikleri tek seferde toplar:
Bir nesneden birkaç alanı çekip geri kalanını tek bir nesnede toplamak, prop'ları ileri aktarırken, hassas alanları ayıklarken ya da verinin yamalı bir sürümünü oluştururken sıkça karşılaştığımız bir kalıptır:
password çekilip kenara atılıyor (yok sayılıyor); geri kalan her şey safe içinde kalıyor. Ne mutasyon var, ne de elle kopyalama.
Kısa Bir Özet
- Parametre listesinde veya destructuring kalıbında kullanılan
...namerest'tir: toplar. - Fonksiyon çağrısında, dizi literal'ında veya nesne literal'ında kullanılan
...valueise spread'dir: yayar. - Spread ile yapılan kopyalar shallow (yüzeysel) kopyadır. İç içe yapılar hâlâ paylaşılır. Derin kopya için
structuredClonekullan. - Rest parametreleri gerçek birer array'dir —
argumentsyerine bunları tercih et. - Nesne literal'larında sonra gelen spread öncekini ezer; "varsayılanlar + override" desenini bu şekilde kurarsın.
Sırada: Closure'lar
JavaScript'te fonksiyonlar sadece girdi alıp çıktı üretmez — tanımlandıkları scope'u da hatırlar. İşte bu hafızaya closure denir. Callback'lerin, factory fonksiyonlarının ve bir sonraki sayfada karşılaşacağın pek çok desenin arkasındaki mekanizma da budur.
Sıkça Sorulan Sorular
JavaScript'te rest ve spread arasındaki fark nedir?
İkisi de aynı ... sözdizimini kullanır ama işleri birbirinin tam tersidir. Rest, birden fazla değeri toplayıp tek bir diziye koyar; yani fonksiyon parametre listelerinde ve destructuring içinde karşınıza çıkar. Spread ise bir iterable'ı ya da nesneyi parçalarına açar; fonksiyon çağrılarında, dizi literal'lerinde ve nesne literal'lerinde görünür. Kısacası: ... alıcı taraftaysa rest'tir, veren taraftaysa spread'tir.
Rest parametreleri fonksiyon içinde nasıl çalışır?
function sum(...nums) şeklinde yazılan bir rest parametresi, fonksiyona gönderilen tüm argümanları toplayıp nums adında gerçek bir diziye koyar. Parametre listesinin sonunda olmak zorundadır. Eski arguments nesnesinin aksine rest parametresi tam anlamıyla bir dizidir, dolayısıyla .map, .filter ve .reduce gibi metotlar doğrudan üzerinde çalışır.
Spread operatörü deep copy yapar mı?
Hayır. Spread yalnızca tek seviyeli kopyalama yapar, yani shallow copy alırsınız. { ...user } ifadesi üst seviye anahtarları yeni bir nesneye taşır ama içerideki iç içe nesneler ve diziler hâlâ aynı referansı paylaşır. Gerçek bir deep copy için structuredClone(value) kullanabilirsiniz; düz veri için JSON üzerinden serileştirme de iş görür.