JavaScript Parse ve Execute Maliyeti Nedir?
JavaScript dosyasını küçültmek ve sıkıştırarak sunmak performansın tek boyutu değildir. Ağ üzerinden 80 KB olarak gelen bir dosya, tarayıcı belleğinde ayrıştırılıp çalıştırılmaya hazır hale gelirken çok daha uzun süre harcayabilir. İndirme süresi ile işleme maliyetini aynı sorun olarak ele almak, optimizasyon çabalarını yanlış yöne taşır: ağ hızını iyileştirirsiniz ama sayfa yine yavaş hissettirmeye devam eder.
Tarayıcı bir JavaScript dosyasını aldıktan sonra sırayla dört aşamadan geçirir: indirme (download), ayrıştırma (parse), derleme (compile) ve çalıştırma (execute). Her aşama farklı kaynak tüketir ve farklı darboğazlar üretir. İndirme bant genişliğine bağlıdır; parse ve compile CPU'ya, özellikle de main thread'e bağlıdır. Bu iki maliyet kategorisi birbirinden bağımsız olarak büyüyebilir ve birbirinden bağımsız olarak optimize edilebilir.
Orta düzey bir mobil cihazda 300 KB'lık sıkıştırılmamış JavaScript'i parse etmek genellikle 500 ms ile 1 saniye arasında sürer; aynı dosyanın ağ üzerinden indirilmesi ise bağlantı hızına göre çok daha kısa olabilir. Dosya boyutu küçüldüğünde indirme süresi düşer, ancak parse süresi de bunun orantılı karşılığını verir. Her ikisini de birlikte ele almak, sayfa yükleme performansında gerçek kazanım sağlar.
Parse aşamasında tarayıcı ne yapar
Tarayıcı bir JavaScript kaynağını aldığında ilk iş kaynak metni anlamlı birimlere ayırmaktır. Bu aşamaya tokenization denir: function, {, değişken isimleri, operatörler — hepsi ayrı token'lara parçalanır. Tokenization tamamlandıktan sonra sözdizimi analizi (parsing) başlar ve token'lardan soyut sözdizim ağacı (AST, Abstract Syntax Tree) oluşturulur. AST, motorun kodu anlamasını ve bytecode'a çevirmesini sağlayan yapısal temsilidir.
V8 gibi modern JavaScript motorları bu süreçte iki farklı parse stratejisi kullanır: eager parsing ve lazy parsing. Eager parsing, kodun tamamını hemen inceler ve AST'yi eksiksiz oluşturur; doğruluğunu anında doğrular ancak daha fazla zaman harcar. Lazy parsing ise işlev gövdelerini yalnızca çağrıldıkları anda tam olarak inceler; ilk parse geçişinde yalnızca işlevin sınırlarını tespit eder ve daha hızlı bir başlangıç sunar. Büyük bir bundle içinde ilk yüklemede çağrılmayan yüzlerce fonksiyon varsa, lazy parsing bu fonksiyonların parse maliyetini ertelemiş olur.
Parse aşamasının maliyeti dosya boyutuyla doğrusal bir ilişki içindedir. 100 KB JavaScript, 50 KB JavaScript'ten yaklaşık iki kat daha uzun parse sürer; ancak bu oran donanıma ve motorun optimizasyonlarına göre değişir. V8, Chrome 41'den itibaren script streaming özelliğiyle kaynak dosyasının indirilmesiyle eş zamanlı parse başlatabilir; bu, her iki aşamanın tam olarak sıralanması yerine kısmen çakıştırılması anlamına gelir. Ancak bu özellik yalnızca harici script dosyaları için geçerlidir; satır içi (inline) script'ler streaming'den yararlanamaz.
Execute maliyeti parse'tan neden bağımsızdır
Parse tamamlandıktan sonra motor kodu çalıştırmaya başlar. Ancak çalıştırma yalnızca bytecode'un sırayla işlenmesinden ibaret değildir; JIT (Just-In-Time) derleme devreye girer ve sık çalıştırılan kod parçaları için optimize edilmiş makine kodu üretir. Bu süreç ilk çalıştırmada daha yavaş, sonraki çalıştırmalarda daha hızlı işler; V8 bunu "warm-up" olarak tanımlar.
İlk sayfa yüklemesinde her fonksiyon ilk kez çalışır ve JIT optimizasyonundan henüz yararlanamaz. Bu nedenle execute maliyeti parse maliyetinden bağımsız olarak büyüyebilir: az sayıda karmaşık fonksiyon, çok sayıda basit fonksiyondan daha uzun süre çalıştırılabilir. Closure sayısı, prototip zinciri derinliği ve döngü yapıları bu maliyeti doğrudan etkiler. Fonksiyon sayısı aynı olsa bile birinin 10 ms, diğerinin 150 ms sürmesi olağandışı değildir.
Execute aşaması main thread'i meşgul ettiği sürece kullanıcı etkileşimleri yanıtsız kalır. Bir düğmeye tıklandığında, sayfada kaydırma yapıldığında ya da form alanına girildiğinde tarayıcı bu olayları işleyemez çünkü thread JS kodunu çalıştırmakla meşguldür. Parse maliyeti genellikle sayfa açılışında görünür; execute maliyeti ise hem açılışta hem de JavaScript'in sonradan tetiklendiği her anda ortaya çıkar. INP metriği, bu execute gecikmelerinin kullanıcı deneyimine doğrudan yansımasını ölçer.
Büyük bundle main thread'i nasıl etkiler
300 KB'lık bir bundle dosyasının parse + compile + execute döngüsü orta düzey bir cihazda kolaylıkla 3–5 adet long task üretebilir ve toplamda 500 ms ile 2 saniye arasında main thread'i bloke edebilir. Masaüstü ile mobil arasındaki fark bu noktada kritik hale gelir: modern bir masaüstü işlemcisi aynı JavaScript'i 3–5 kat daha hızlı işler. Geliştirme ortamında sorunsuz görünen bir bundle, gerçek cihazlarda kullanıcıyı uzun saniyeler boyunca bekletebilir. Long task mekanizması, TBT ve main thread blocking konuları ayrıca ele alınmıştır; burada parse ve execute maliyetinin bu soruna nasıl zemin hazırladığına odaklanıyoruz.
Maliyeti ölçmek: Performance paneli ve Coverage
Chrome DevTools'un Performance sekmesi, parse ve execute maliyetini doğrudan görünür kılar. Kaydırma başlatıldıktan sonra elde edilen flamegraph'ta sarı renkli "Scripting" bloklarını incelemek, hangi dosya ve fonksiyonların ne kadar süre harcadığını gösterir. "Evaluate Script" görevleri parse + ilk compile maliyetine karşılık gelir; "Function Call" etiketleri ise execute maliyetini temsil eder. Uzun süren görevler kırmızı köşe işaretiyle otomatik olarak vurgulanır.
Coverage sekmesi (Cmd+Shift+P → "Coverage") ise hangi JavaScript kodunun gerçekte çalıştırıldığını yüzde olarak gösterir. Kırmızı çizgiler hiç çalıştırılmayan satırları işaret eder; gri çizgiler indirilip parse edilmiş ancak o sayfada kullanılmamış kodu gösterir. Bundle analizi araçlarıyla birleştirildiğinde Coverage raporu, hangi modüllerin ilk yüklemeden çıkarılması gerektiğini somut biçimde ortaya koyar.
Manuel ölçüm için performance.mark ve performance.measure API'leri kullanılabilir. Kritik bir fonksiyonun öncesine ve sonrasına işaret ekleyerek execute süresini milisaniye cinsinden elde etmek mümkündür:
performance.mark('init-start');
initializeApp();
performance.mark('init-end');
performance.measure('init', 'init-start', 'init-end');
const [entry] = performance.getEntriesByName('init');
console.log(entry.duration + ' ms');
Lighthouse'un "Reduce unused JavaScript" ve "Avoid long main-thread tasks" uyarıları da bu maliyetin sayfa düzeyindeki etkisini raporlar. Ancak Lighthouse tek bir anlık görüntü sunar; gerçek kullanıcı verisini takip etmek için Chrome User Experience Report (CrUX) veya gerçek kullanıcı izleme (RUM) araçları daha güvenilir bir zemin sağlar.
Parse ve execute yükünü azaltmanın pratik adımları
En doğrudan yaklaşım, ilk yüklemede gereksiz kodu hiç göndermemektir. Code splitting, bundle'ı birden fazla parçaya böler ve her sayfanın yalnızca ihtiyaç duyduğu kodu indirip parse etmesini sağlar. React, Vue ve Angular gibi framework'ler rota bazlı bölünmeyi kutudan destekler; Webpack ve Vite ise yapılandırma düzeyinde chunk stratejileri sunar. 1 MB'lık tek bir bundle yerine 5 adet 100–150 KB'lık chunk, hem ilk parse maliyetini hem de long task sayısını belirgin biçimde azaltır.
Tree shaking, kullanılmayan export'ları bundle dışında bırakır. Bir utility kütüphanesinden yalnızca iki fonksiyon kullanıldığında kütüphanenin tamamının bundle'a girmesi parse maliyetini gereksiz yere büyütür. ES modül sözdizimi (ESM) bu analizin statik olarak yapılmasını sağlar; CommonJS modülleri ise dinamik yapıları nedeniyle tree shaking için daha az elverişlidir. Üretim build'inde tree shaking etkin olup olmadığını bundle analizi raporuyla doğrulamak faydalıdır.
Dynamic import, ilk yüklemede zorunlu olmayan modülleri talep anında yüklemeye ertelenebilir kılar. Modal, grafik kütüphanesi ya da yalnızca belirli bir kullanıcı eylemiyle tetiklenen işlev — bunların tümü import() ile asenkron olarak yüklenebilir. Yükleme anında küçük bir gecikme oluşur, ancak ilk parse ve execute maliyeti sıfıra düşer. Kullanıcıların büyük çoğunluğunun hiç tetiklemediği bir özellik için parse maliyeti ödemek, her ziyarette bu yükü taşımak anlamına gelir.
// Yalnızca butona tıklandığında yükle
document.querySelector('#open-chart').addEventListener('click', async () => {
const { renderChart } = await import('./chart.js');
renderChart(data);
});
Kullanılmayan JavaScript temizliği ise hem parse hem execute maliyetini aynı anda düşüren en kaba ama en etkili adımdır. Coverage raporunda kırmızı görünen büyük bloklar, silinmesi ya da ertelenmesi gereken kodun doğrudan göstergesidir. Yıllar içinde büyüyen projelerde artık kullanılmayan bağımlılıklar, eski feature flag'ler ve unutulmuş yardımcı fonksiyonlar bu kategoride toplanır.
Parse ve execute maliyeti, bundle optimizasyonunun en çok gözden kaçan boyutlarından biridir. Ağ transferini optimize ederek başlamak doğaldır; ancak küçültülmüş bir dosyanın tarayıcıda hâlâ yüzlerce milisaniye harcayabileceğini göz ardı etmek, performans kazanımlarının tamamlanmamış kalmasına yol açar.
Pratikte her projenin profili farklıdır. Ağırlıklı olarak metin içeriği sunan bir haber sitesinde parse maliyeti, etkileşimli grafik paneli olan bir SaaS ürününe kıyasla çok daha küçük bir sorun olabilir. Ölçüm olmadan yapılan her optimizasyon tahmine dayalıdır; Performance paneli ve Coverage birlikte kullanıldığında gerçek darboğazlar görünür hale gelir ve çaba doğru yere yönlendirilir.