Mert Özen Her Satırda Daha İleri
Frontend JavaScript Orta — #02

JavaScript Array Method'ları Derinlemesine: map, filter, reduce, toSorted ve Object.groupBy

Mert Özen 3 Kas 2026 9 dk 5 görüntülenme
JavaScript Array Method'ları Derinlemesine: map, filter, reduce, toSorted ve Object.groupBy

map, filter, reduce, find ailesi, some/every ve flatMap'i üretim bandı benzetmesiyle anlıyor; array'i bozmayan toSorted ve with method'larını (ES2023) ve Object.groupBy ile gruplamayı (ES2024) deniyoruz.

Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.

Önce Tahmin Et

Masum görünen tek bir satırla başlayalım. String'lerden oluşan bir array'i sayıya çevirmek istiyoruz:

console.log(["1", "2", "3"].map(parseInt));

Bu kod konsola ne yazdırır? Okumaya devam etmeden önce bir tahmin yap. Çoğu kişi [1, 2, 3] der. Cevabı map'in callback'i nasıl çağırdığını gördükten sonra konuşacağız.

Bir Üretim Bandı Gibi Düşün: map

Daha önce array'leri for döngüsüyle ve forEach ile gezmiştik. Bu yazıdaki method'lar aynı işi farklı bir zihniyetle yapar: "nasıl dolaşacağım" yerine "her element'e ne olacak" sorusuna odaklanırsın. Bir fabrika bandı düşün. Ürünler banttan tek tek geçer, her istasyon onlara bir şey yapar ve bandın sonunda yeni bir koli çıkar. Eski koli olduğu gibi durur.

Yazı boyunca aynı küçük ürün listesini kullanacağız. Bunu tarayıcının konsoluna yapıştır:

const products = [
  { name: "Pen", category: "stationery", price: 25, stock: 120 },
  { name: "Notebook", category: "stationery", price: 60, stock: 0 },
  { name: "Headphones", category: "electronics", price: 900, stock: 8 },
  { name: "Mouse", category: "electronics", price: 450, stock: 15 },
  { name: "Thermos", category: "kitchen", price: 380, stock: 4 }
];

const names = products.map((p) => p.name);
console.log(names); // ["Pen", "Notebook", "Headphones", "Mouse", "Thermos"]

const withTax = products.map((p) => ({ ...p, price: p.price * 1.2 }));
console.log(withTax[0].price, products[0].price); // 30 25

map her element için verdiğin fonksiyonu çağırır ve fonksiyonun döndürdüğü değeri yeni bir array'in aynı sırasına koyar. Sonuç her zaman orijinalle aynı uzunlukta bir array'dir. İkinci örnekte arrow function bir object döndürdüğü için object'i parantez içine aldık; aksi hâlde engine süslü parantezi fonksiyon gövdesi sanar. Spread sayesinde orijinal ürünlere de dokunmadık.

Şimdi baştaki soruya dönelim. Cevap [1, NaN, NaN]. Çünkü map callback'e yalnızca element'i değil, üç argüman verir: element, index ve array'in kendisi. parseInt ise ikinci argümanı sayı tabanı olarak kullanır. Yani engine aslında parseInt("1", 0), parseInt("2", 1) ve parseInt("3", 2) çalıştırır. Taban 0 "varsayılanı kullan" demektir, 1 geçersiz bir tabandır, ikilik tabanda da "3" diye bir rakam yoktur. Çözüm, ne istediğini açıkça yazmak: .map((s) => parseInt(s, 10)) ya da kısaca .map(Number).

map, filter, reduce, some ve every aslında eski dostlar; 2009'daki ES5 ile geldiler. Arrow function'larla birlikte (ES2015) bugünkü kısa ve okunaklı hâllerine kavuştular.

filter, find ve Arkadaşları

filter bir kalite kontrol istasyonudur. Callback true (ya da truthy) döndürürse element yeni array'e geçer, aksi hâlde elenir. Sonuç her zaman bir array'dir; hiçbir şey geçmezse boş array.

const inStock = products.filter((p) => p.stock > 0);
console.log(inStock.length); // 4

const expensive = products.find((p) => p.price > 400);
console.log(expensive.name); // "Headphones"

console.log(products.findIndex((p) => p.price > 400));      // 2
console.log(products.findLast((p) => p.price > 400).name);  // "Mouse"
console.log(products.findLastIndex((p) => p.price > 400));  // 3
console.log(products.find((p) => p.price > 5000));          // undefined

find ilk eşleşmede durur ve element'in kendisini döndürür; bulamazsa undefined. findIndex aynı aramayı yapar ama index döndürür, bulamazsa -1. ES2015 ile gelen bu ikiliye ES2023'te sondan aramaya başlayan findLast ve findLastIndex eklendi. Önceden bunun için array'i ters çevirmek gerekiyordu; şimdi en son eklenen hatalı kaydı ya da son mesajı doğrudan bulabilirsin.

some ve every: Evet mi, Hayır mı?

Bazen element'lerin kendisine değil, tek bir cevaba ihtiyacın vardır. some "en az biri koşulu sağlıyor mu?", every "hepsi sağlıyor mu?" diye sorar. İkisi de cevabı öğrendiği anda durur.

console.log(products.some((p) => p.stock === 0));  // true
console.log(products.every((p) => p.price > 10)); // true

Daha önce gördüğümüz includes belirli bir değeri arar; some ise bir koşulu. Object'lerden oluşan bir array'de "fiyatı 900 olan var mı?" sorusunu includes ile soramazsın, some ile sorarsın.

reduce: Bütün Array'i Tek Değere İndirmek

reduce genelde en çok korkulan method'dur ama mantığı basit. Karda yuvarlanan bir kartopu düşün. Her element'in üzerinden geçtikçe biraz daha büyür ve sonunda elinde tek bir top kalır. Bu topa accumulator (biriktirici) denir. Callback her adımda o ana kadarki birikimi ve sıradaki element'i alır, yeni birikimi döndürür.

const totalValue = products.reduce((sum, p) => sum + p.price * p.stock, 0);
console.log(totalValue); // 18470

// adım adım:
// sum = 0     + 25 * 120  = 3000
// sum = 3000  + 60 * 0    = 3000
// sum = 3000  + 900 * 8   = 10200
// sum = 10200 + 450 * 15  = 16950
// sum = 16950 + 380 * 4   = 18470

Sondaki 0 başlangıç değeridir ve neredeyse her zaman yazmalısın. Accumulator'ın bir sayı olması da gerekmiyor; object, array, ne istersen olabilir. Bir anketteki oyları saymak için:

const votes = ["yes", "no", "yes", "yes"];
const counts = votes.reduce((acc, vote) => {
  acc[vote] = (acc[vote] ?? 0) + 1;
  return acc;
}, {});
console.log(counts); // { yes: 3, no: 1 }

Süslü parantezli gövdede return acc satırını unutma; unutursan bir sonraki adımda accumulator undefined olur. Bir de dürüst bir not: reduce güçlüdür ama her işin aracı değildir. Bir callback'in içinde üç farklı şey yapıyorsan, düz bir for...of döngüsü çoğu zaman daha okunaklıdır. Ondalık sayıların hassas toplamı için de daha önce gördüğümüz ES2026 yeniliği Math.sumPrecise'ı hatırla.

flat, flatMap ve Array.from

İç içe array'lerle karşılaştığında flat onları düzleştirir. Varsayılan olarak bir seviye iner; daha derine inmesini istersen sayı verirsin, "sonuna kadar" demek için de Infinity:

const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());         // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

const orders = [
  { id: 1, items: ["Pen", "Notebook"] },
  { id: 2, items: ["Thermos"] }
];
console.log(orders.flatMap((o) => o.items)); // ["Pen", "Notebook", "Thermos"]

flatMap, map ve ardından bir seviyelik flat yapar. Her element'ten sıfır, bir ya da birden fazla element üretmek istediğinde biçilmiş kaftandır; callback'ten boş array döndürürsen o element sonuçta hiç yer almaz. İkisi de ES2019 ile geldi.

ES2015'ten beri var olan Array.from ise array olmayan ama array gibi davranan şeyleri gerçek bir array'e çevirir. İkinci argüman olarak bir eşleme fonksiyonu da alabilir:

console.log(Array.from("cat"));                          // ["c", "a", "t"]
console.log(Array.from({ length: 5 }, (_, i) => i * 2)); // [0, 2, 4, 6, 8]

const paragraphs = Array.from(document.querySelectorAll("p"));
console.log(paragraphs.map((el) => el.textContent.length));

Son örneği herhangi bir sayfanın konsolunda dene. querySelectorAll'un döndürdüğü NodeList'te forEach vardır ama map ve filter yoktur. Array.from ile gerçek bir array'e çevirdiğinde bu yazıdaki bütün method'lar emrine amade olur.

Method'ları Zincirlemek

Bu method'ların çoğu yeni bir array döndürdüğü için, birinin çıktısını doğrudan diğerine verebilirsin. Üretim bandına istasyon eklemek gibi:

const report = products
  .filter((p) => p.stock > 0)
  .map((p) => `${p.name}: ${p.price} USD`)
  .join(", ");

console.log(report);
// "Pen: 25 USD, Headphones: 900 USD, Mouse: 450 USD, Thermos: 380 USD"

Her method'u ayrı bir satıra yazmak zinciri bir tarif gibi okunur kılar: stokta olanları al, metne çevir, birleştir. Her adım arada yeni bir array oluşturur; birkaç bin element'lik array'lerde bunu dert etmene gerek yok. Zincir uzayıp anlaşılmaz hâle gelirse ara sonuçlara isim vermek her zaman iyi bir fikirdir.

Array'i Bozmadan Değiştirmek: toSorted, toReversed, toSpliced, with

Daha önce sort'un array'i yerinde değiştirdiğini ve bunun sürpriz hatalara yol açtığını gördük. reverse ve splice da aynı huydadır. ES2023 bu üçünün kopya döndüren kardeşlerini ve bir element'i değiştirip kopya döndüren with'i getirdi:

const prices = [380, 25, 900, 60];

console.log(prices.toSorted((a, b) => a - b)); // [25, 60, 380, 900]
console.log(prices.toReversed());               // [60, 900, 25, 380]
console.log(prices.toSpliced(1, 2, 999));       // [380, 999, 60]
console.log(prices.with(0, 400));               // [400, 25, 900, 60]
console.log(prices.with(-1, 70));               // [380, 25, 900, 70]

console.log(prices); // [380, 25, 900, 60], dokunulmadı

Eskiden aynı güvenliği [...prices].sort() gibi önce kopyalayıp sonra sıralayarak sağlardık. Yeni method'lar niyeti doğrudan söylüyor. Ekrandaki bir listenin sıralı hâlini göstermek ama asıl veriyi korumak gibi durumlarda tercihin bunlar olmalı:

const cheapestFirst = products
  .toSorted((a, b) => a.price - b.price)
  .map((p) => p.name);
console.log(cheapestFirst); // ["Pen", "Notebook", "Thermos", "Mouse", "Headphones"]

with negatif index'i sondan sayar; array'in sınırları dışında bir index verirsen RangeError fırlatır.

Gruplamak: Object.groupBy ve Map.groupBy

"Ürünleri kategoriye göre grupla" eskiden klasik bir reduce alıştırmasıydı. ES2024 ile gelen Object.groupBy bunu tek satıra indirdi. Callback her element için bir grup adı döndürür, sonuç da her grup adının altında o gruba düşen element'lerin array'ini tutan bir object'tir:

const byCategory = Object.groupBy(products, (p) => p.category);
console.log(Object.keys(byCategory)); // ["stationery", "electronics", "kitchen"]
console.log(byCategory.electronics.map((p) => p.name)); // ["Headphones", "Mouse"]

const byStock = Object.groupBy(products, (p) => (p.stock > 0 ? "available" : "soldOut"));
console.log(byStock.soldOut[0].name); // "Notebook"

Dikkat edersen bu bir array method'u değil, Object üzerinde statik bir fonksiyon. Array method'u olarak eklenmesi eski sitelerdeki kodlarla çakıştığı için bu yola gidildi. Dönen object'in prototype'ı da yoktur; yani sadece senin grupların vardır, araya miras kalan bir property karışmaz.

Grup key'inin string değil de başka bir değer, örneğin bir boolean ya da bir object olmasını istiyorsan kardeşi Map.groupBy var. Sonucu bir Map döner:

const byPrice = Map.groupBy(products, (p) => p.price >= 400);
console.log(byPrice.get(true).map((p) => p.name)); // ["Headphones", "Mouse"]

Map'i ilerleyen yazılarda ayrıntılı göreceğiz. İki fonksiyon da Baseline'da "geniş destek" seviyesinde, yani güncel tarayıcıların hepsinde gönül rahatlığıyla kullanabilirsin.

Sık Düşülen Tuzaklar

Süslü parantez açıp return'ü unutmak. products.map((p) => { p.name }) beş tane undefined döndürür, çünkü süslü parantez bir fonksiyon gövdesi açar ve gövdede hiçbir şey döndürülmüyor. Ya parantezi kaldır ya da return yaz.

Başlangıç değeri olmadan reduce. Boş bir array'de [].reduce((a, b) => a + b) doğrudan TypeError verir. Object'lerden oluşan bir array'de ise ilk object accumulator olur ve sonuç "[object Object]..." gibi anlamsız bir string'e dönüşebilir.

map'i döngü niyetine kullanmak. Sonucunu kullanmayacaksan, yani sadece her element için bir şey yapacaksan forEach ya da for...of yaz. Tersi de geçerli: forEach her zaman undefined döndürür, onun sonucunu bir değişkene atamanın anlamı yok.

Boş array'de every. [].every((x) => x > 100) true döner, some ise false. Mantıken doğru (koşulu bozan tek bir element bile yok) ama "bütün siparişler ödenmiş mi?" kontrolünü boş bir listede yapıyorsan sürpriz olabilir.

find ile filter'ı karıştırmak. find tek bir element ya da undefined, filter her zaman bir array döndürür. if (products.filter(...)) boş array bile truthy olduğu için her zaman çalışır.

Küçük Bir Deneme

★☆☆ products array'inden stoğu 10'dan az olan ürünlerin sadece isimlerini içeren bir array üret. İpucu: önce filter, sonra map.

★★☆ Çalıştırmadan önce tahmin et: const a = [3, 1, 2]; const b = a.toSorted(); const c = a.sort(); console.log(a, b, c, a === c); Hangi değişken hangi array'i gösteriyor?

★★☆ reduce kullanarak en pahalı ürünün object'ini bul. Math.max ya da sıralama kullanma.

★★★ Object.groupBy ile ürünleri kategorilere ayır. Sonra her kategori için toplam stok değerini (fiyat × stok) içeren { stationery: 3000, electronics: 13950, kitchen: 1520 } gibi bir object üret. Object.entries, map ve ES2019'dan beri var olan Object.fromEntries işini görecek.

Bu yazıda callback'leri bol bol başka fonksiyonlara verdik ve onlar dışarıdaki değişkenlere, örneğin products'a, sorunsuzca erişti. Bir fonksiyonun hangi değişkenleri görebildiğini belirleyen kurallar, değişkenlerin tanımlanmadan önce neden bazen "var" gibi davrandığı ve fonksiyonların doğdukları ortamı yanlarında nasıl taşıdığı bir sonraki yazının konusu: scope, hoisting ve closure.