Fonksiyonlar Birer Değerdir
JavaScript'te fonksiyonlar, sayı veya string gibi tıpkı diğer değerler gibidir. Bir değişkene atayabilir, bir dizinin içine koyabilir, başka bir fonksiyona argüman olarak geçirebilir ya da bir fonksiyondan geri döndürebilirsin. Yalnızca bu özellik bile bambaşka bir programlama tarzının kapısını aralıyor:
Fonksiyonları birer değer gibi düşünmeye alıştığınızda, higher order function kavramı bir anda gizemini kaybediyor. Higher order function dediğimiz şey aslında çok basit: ya argüman olarak bir fonksiyon alan, ya geriye bir fonksiyon döndüren, ya da ikisini birden yapan fonksiyonlara verilen isim. Tanımı bu kadar.
Fonksiyonu Argüman Olarak Geçirmek
En sık karşılaştığımız kullanım şekli bu: bir callback alıp onu sizin yerinize çalıştıran fonksiyonlar. Aslında farkında olmadan bunları zaten kullanıyorsunuz.
forEach bir higher-order fonksiyondur — senin fonksiyonunu alır ve her eleman için bir kez çağırır. setTimeout da higher-order bir fonksiyondur — fonksiyonunu alır, belirli bir gecikmeden sonra çalıştırır. Sen sadece ne yapılacağına odaklanırsın; ne zaman ve kaç kez çalışacağını onlar halleder.
Kendi higher-order fonksiyonunu yazmak da aynı mantıkla çalışır. Aşağıda, verilen callback'i yalnızca koşul true olduğunda çalıştıran küçük bir örnek var:
action, içinde bir fonksiyon tutan sıradan bir parametre. action() diye çağırdığında, dışarıdan ne geçildiyse o çalışır.
Pratikte Kullanacağın Üçlü: map, filter, reduce
Dizilerin (array) üzerinde, yazacağın for döngülerinin büyük kısmının yerini alan higher-order metotlar var. Bu üçünü kavradığında, günlük kodun önemli bir bölümü hem kısalır hem de çok daha okunaklı olur.
map — her elemanı dönüştür
map, her eleman için fonksiyonunuzu bir kez çağırır ve dönen değerleri yeni bir dizide toplar. Uzunluk aynı kalır, içerik dönüşür. Orijinal dizi değişmez.
filter — şartı sağlayanları alın
filter, callback fonksiyonundan true dönen elemanları tutar, gerisini atar. Geriye yeni bir dizi döner; eleman sayısı azalmış olabilir.
reduce — diziyi tek bir değere indirgemek
reduce ise bu işin çok amaçlı aracı. Callback fonksiyonu, o ana kadar birikmiş değeri (accumulator) ve sıradaki elemanı alır; sen ne return edersen bir sonraki adımın accumulator'ı o olur. İkinci argüman (burada 0) başlangıç değeridir.
Bunları zincirleyebilirsin de. İşte bu yaklaşımın asıl kazandırdığı nokta burada ortaya çıkıyor:
Yukarıdan aşağıya tek nefeste okunuyor: önce ödenmiş siparişleri al, fiyatı çek, topla. Ne döngü var, ne sayaç, ne de "acaba bir eksik mi saydım" derdi.
Fonksiyondan Fonksiyon Döndürmek
Higher-order konusunun diğer yarısı. Yani başka bir fonksiyon üretip geri döndüren fonksiyonlar:
multiplyBy(2) bir kez çalışır ve sana yepyeni bir fonksiyon döndürür. O yeni fonksiyon hâlâ factor değerini hatırlar — işte buna closure deniyor ve ona ayrı bir bölümde değineceğiz. Şimdilik aklında kalması gereken şey şu: multiplyBy'ı farklı argümanlarla çağırarak aynı kalıptan türetilmiş, farklı işlere özelleşmiş fonksiyonlar elde edebilirsin.
Bu desen gerçekten her yerde karşına çıkıyor:
Tek bir tanım, iki kullanılabilir fonksiyon. warn ve info'yu elle yazıp senkron tutmaya çalışmaktan çok daha temiz.
İsimli fonksiyonlar mı, inline callback mi?
Callback'i ya inline bir arrow function olarak geçebilirsin ya da fonksiyonu adıyla verebilirsin. İkisi de çalışır — hangisi daha okunaklı duruyorsa onu seç:
isEven (parantezsiz) yazdığında, fonksiyonun kendisini devretmiş olursun. () eklersen fonksiyon anında çalışır ve dönen değeri geçirmiş olursun — yeni başlayanların sıkça düştüğü bir tuzak:
nums.filter(isEven); // doğru: fonksiyonu geçirir
nums.filter(isEven()); // yanlış: isEven'i argümansız çağırır, sonucu geçirir
Callback fonksiyon birkaç satırı geçmeye başladıysa dışarı çıkarıp ona bir isim verin. Etraftaki kod da genelde bundan kazançlı çıkar.
Uçtan Uca Bir Örnek
Higher-order function'lar asıl gücünü küçük parçaları bir araya getirdiğinizde gösteriyor. Diyelim ki elinizde bir ürün listesi var ve stokta olan uygun fiyatlı ürünlerin adlarını büyük harfle almak istiyorsunuz:
Her yardımcı fonksiyonun tek bir işi var. Her dizi metodu tek bir dönüşüm yapıyor. Pipeline, nasıl döngü kurulacağını değil, ne istediğinizi anlatan bir spesifikasyon gibi okunuyor.
Ne Zaman Kullanmamalı?
Higher-order metotlar harika — ama her durumda döngülerin yerine geçmezler:
- Ortada bir yerde durmanız gerekiyorsa,
forEachiçinden çıkmaya çalışmak yerineforya dafor...ofilebreakkullanmak çok daha temiz olur. - Callback içinde async bir iş yapıyorsanız,
mapveforEachbunuawaitetmez. Bu durumda yafor...ofileawaitkullanın ya damapilePromise.all'a başvurun. - Callback paylaşılan state'i değiştiriyorsa (mutasyon), zaten bu stilin güçlü yanlarından uzaklaşıyorsunuz demektir. Ya normal bir döngüye dönün ya da yeni değer döndürecek şekilde refactor edin.
Yerinde kullanıldığında map, filter ve reduce günlük kodunuzdaki döngü tekrarının büyük kısmını ortadan kaldırır. Ama her yere sıkıştırmaya çalışırsanız okunabilirliği baltalamaya başlarlar. Niyeti en net ortaya koyan aracı seçin.
Sırada: Nesneler
Üzerine inşa edilecek tek değerli şey fonksiyonlar değil. İlgili veri ve davranışları bir arada tutmak için JavaScript'in baş aktörü nesnelerdir — ve az önce filter'layıp map'lediğiniz dizilerin içi büyük ihtimalle zaten bunlarla doluydu. Bir sonraki sayfanın konusu da bu.
Sıkça Sorulan Sorular
JavaScript'te higher-order function ne demek?
Higher-order function, şu iki işten en az birini yapan fonksiyondur: ya başka bir fonksiyonu argüman olarak alır, ya da sonuç olarak bir fonksiyon döndürür. Array.prototype.map, setTimeout ve addEventListener bunun tipik örnekleri — hepsi bir callback alıyor ve onu senin yerine çağırıyor.
map, filter ve reduce arasındaki fark nedir?
map dizideki her elemanı dönüştürür ve aynı uzunlukta yeni bir dizi döndürür. filter ise callback'in truthy döndürdüğü elemanları tutar, sonuç genelde daha kısa bir dizi olur. reduce diziyi tek bir değere indirger; elemanları teker teker birleştirerek sonuca ulaşır. Üçü de callback alan higher-order function'lardır.
Neden bir fonksiyondan başka bir fonksiyon döndürelim?
Aynı mantığı tekrar yazmadan, küçük ve ayarlanabilir yardımcı fonksiyonlar üretmek için. Örneğin multiplyBy(n) içinde n ile çarpan yeni bir fonksiyon döner; böylece multiplyBy(2) ve multiplyBy(10) tek bir tanımdan iki özel fonksiyon verir. Bu kalıp closure'a dayanır ve event handler'larda, middleware'lerde ve utility kütüphanelerinde bolca karşına çıkar.