Kısa cevap: DOMContentLoaded, HTML tamamen ayrıştırılıp ertelenmiş (defer) ve modül betikleri çalıştıktan sonra document üzerinde tetiklenir; görselleri, iframe'leri ve async betikleri beklemez. load ise stil dosyaları, betikler, iframe'ler ve görseller dahil tüm kaynaklar yüklendiğinde window üzerinde gelir. DOM'a erişmek için çoğu zaman DOMContentLoaded yeterlidir. Sayfadan ayrılırken veri kaydetmek ya da analiz göndermek için güvenilir son sinyal visibilitychange olayında document.visibilityState değerinin "hidden" olmasıdır; veriyi orada navigator.sendBeacon() ile gönderirsiniz. unload olayından kaçınılmalıdır.
Bu yazıda document.readyState değerlerini, yükleme olaylarının sırasını, betiğiniz geç çalıştığında kurulum kodunun nasıl kaçırılmayacağını, sekme gizlenince ne olduğunu, geri ileri önbelleği ile ilgili pagehide ve pageshow olaylarını, kaydedilmemiş değişiklik uyarısı için beforeunload kullanımını ve sendBeacon() sınırlarını MDN belgelerine göre anlatıyoruz. Örnekler tarayıcıya özgüdür ve çalıştırılmadı. Betik etiketinin defer ve async öznitelikleri HTML tarafıdır, HTML öğrenme rehberinde işlenir. Tüm seri için Web API öğrenme rehberi sayfasına bakın.
readyState: belgenin üç durumu

document.readyState belgenin yüklenme durumunu bir dize olarak verir ve değer her değiştiğinde document üzerinde readystatechange olayı tetiklenir. MDN üç değer tanımlar.
| Değer | Anlamı | Bu noktadan sonra gelen olay |
|---|---|---|
| loading | HTML ayrıştırıcı hâlâ çalışıyor | interactive durumuna geçiş |
| interactive | Belge ayrıştırıldı, ertelenmiş ve modül betikleri, görseller, stiller, çerçeveler hâlâ yükleniyor olabilir | Ertelenmiş betikler bitince DOMContentLoaded |
| complete | Belge ve tüm alt kaynaklar yüklendi | load olayı gelmek üzere |
MDN'nin DOMContentLoaded sayfasındaki ek not sırayı netleştirir: readyState, HTML ayrıştırıcısı bittikten sonra ve defer ya da type="module" betikleri çalışmadan önce "interactive" olur. DOMContentLoaded bu betikler çalıştıktan sonra, async betiklerden önce tetiklenir. "complete" değeri async betikler çalıştıktan sonra gelir. Yani ertelenmiş bir betik çalışırken readyState değeri "interactive" olsa bile o betik içinden eklenen DOMContentLoaded dinleyicisi yine normal şekilde tetiklenir.
document.addEventListener("readystatechange", (event) => {
if (event.target.readyState === "interactive") {
yukleyiciyiBaslat(); // DOM hazır, kaynaklar hâlâ yükleniyor
} else if (event.target.readyState === "complete") {
uygulamayiBaslat(); // her şey yüklendi
}
});DOMContentLoaded ile load farkı
DOMContentLoaded HTML belgesi tamamen ayrıştırıldığında ve ertelenmiş betikler indirilip çalıştırıldığında gelir. Stil dosyalarını beklemez; ancak MDN'nin belirttiği gibi ertelenmiş betikler stil dosyalarını bekler ve olay bu betiklerden sonra sıralandığı için dolaylı bir bekleme oluşur. defer ya da async olmayan normal betikler de daha önce ayrıştırılmış stil dosyalarının yüklenmesini bekler. Olay iptal edilemez ve diğer olayların çoğundan farklı olarak onDOMContentLoaded diye bir olay işleyici özelliği yoktur; yalnızca addEventListener() ile dinlenir.
load ise sayfanın tamamı, yani stil dosyaları, async, ertelenmiş ve modül betikleri, iframe'ler ve görseller yüklendiğinde gelir; tembel yüklenen (lazy) kaynaklar bunun dışındadır. Olay iptal edilemez ve kabarcıklanmaz. MDN bu konuda iki ilginç not düşer: load adlı hiçbir olay bubbles açık olsa bile window nesnesine yayılmaz ve ana belge yüklendiğinde window üzerine gönderilen load olayında geriye dönük uyumluluk nedeniyle target değeri document olur. MDN, DOMContentLoaded yeterliyken load kullanmanın yaygın bir hata olduğunu da vurgular.
| Özellik | DOMContentLoaded | load |
|---|---|---|
| Hedef | document | window |
| Görselleri ve iframe'leri bekler | Hayır | Evet, tembel yüklenenler hariç |
| async betikleri bekler | Hayır | Evet |
| Ertelenmiş ve modül betikleri | Onlar çalıştıktan sonra gelir | Onlardan sonra gelir |
| onevent özelliği | Yok | window.onload |
| İptal edilebilir | Hayır | Hayır |
Pek çok durumda ikisine de ihtiyacınız olmaz. MDN, betiği gövdenin sonuna, kapanış body etiketinden hemen önce koymanın betiği DOM hazır olduktan sonra çalıştırmak için yeterli olduğunu ve bir olay dinleyicisiyle sarmaya gerek bırakmadığını söyler. load olayını ise yalnızca görseller gibi dış kaynakların yüklenmesini beklemeniz gerektiğinde kullanmanızı önerir.
Betiğiniz geç çalışırsa: readyState kontrolü
Kurulum kodunu DOMContentLoaded dinleyicisine koyduysanız ama betik olay çoktan geçtikten sonra çalışıyorsa, dinleyici hiç tetiklenmez ve kurulum sessizce atlanır. MDN bu durumun tipik nedenlerini sıralar: belge yüklendikten sonra dinamik olarak içe aktarılan bir modül, async ile eklenen bir betik, sayfaya sonradan enjekte edilen bir betik ve await fetch(...) gibi bir asenkron işlemden sonra devam eden kod, modüllerdeki üst düzey await dahil. Çözüm, dinleyiciyi eklemeden önce readyState değerine bakan iki yollu bir kurgudur.
function kurulum() {
console.info("DOM hazır");
}
if (document.readyState === "loading") {
// Yükleme henüz bitmedi
document.addEventListener("DOMContentLoaded", kurulum);
} else {
// DOMContentLoaded zaten tetiklendi
kurulum();
}MDN burada bir yarış durumu olmadığını özellikle açıklar: JavaScript bir görevi sonuna kadar çalıştırdığı için belge, if kontrolü ile addEventListener() çağrısı arasında yüklenmiş duruma geçemez; geçiş bir sonraki döngü turunda olur ve o sırada dinleyici zaten eklenmiştir. Başlangıç işaretlemesinde bulunan async olmayan eşzamanlı betikler için bu sorun yaşanmaz, çünkü belge DOMContentLoaded göndermeden önce bu betiklerin çalışmasını bekler. await ve olay döngüsü için async/await yazısına ve event loop yazısına bakabilirsiniz.
visibilitychange ve Page Visibility API

visibilitychange olayı belgenin görünürlüğü değiştiğinde document üzerinde tetiklenir: Kullanıcı sekme değiştirdiğinde, başka bir sayfaya gittiğinde, tarayıcıyı küçülttüğünde ya da kapattığında, mobilde başka bir uygulamaya geçtiğinde. Olay yeni durumu taşımaz; durumu document.visibilityState özelliğinden okursunuz. Değer "visible" ya da "hidden" olur. document.hidden aynı bilgiyi boolean olarak verir.
MDN'nin en önemli tespiti şudur: hidden durumuna geçiş, sayfanın güvenilir şekilde gözlemleyebildiği son olaydır ve geliştiriciler bunu kullanıcı oturumunun muhtemel sonu olarak ele almalıdır. Analiz verisi göndermek, taslağı kaydetmek ya da arka planda gereksiz olan arayüz güncellemelerini durdurmak için doğru yer burasıdır. Görünürlük API'sinin kullanım örnekleri arasında kullanıcı sayfaya bakmıyorken ilerlemeyen bir görsel karuseli, sayfa görünmezken sunucuyu yoklamayı bırakan bir gösterge paneli ve gizlendiğinde duraklayan bir ses oynatıcı sayılır.
const ses = document.querySelector("audio");
let gizlenirkenCaliyordu = false;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
gizlenirkenCaliyordu = !ses.paused;
ses.pause();
} else if (gizlenirkenCaliyordu) {
ses.play(); // gizlenmeden önce çalıyorsa devam et
}
});MDN, tarayıcıların bu API'den bağımsız olarak arka plandaki sekmelere çeşitli kısıtlamalar uyguladığını da anlatır: Çoğu tarayıcı arka plan sekmelerine ve gizli iframe'lere requestAnimationFrame() geri çağrılarını göndermeyi durdurur, setTimeout() gibi zamanlayıcılar arka planda yavaşlatılır. Ses çalan sekmeler, WebSocket ya da WebRTC bağlantısı kullanan sekmeler ve IndexedDB işlemleri bu kısıtlamadan muaf tutulur. Bir iframe'in görünürlük durumu üst belgeyle aynıdır; iframe'i CSS ile gizlemek görünürlük olayı üretmez. blur ve focus olayları ise yalnızca pencerenin etkin olup olmadığını söyler, gizlenip gizlenmediğini değil.
pagehide, pageshow ve geri ileri önbelleği
pagehide, tarayıcı oturum geçmişinden başka bir sayfayı göstermek için mevcut sayfayı gizlerken window üzerine gönderilir; kullanıcı Geri düğmesine bastığında mevcut sayfa önceki sayfa gösterilmeden önce bu olayı alır. pageshow ise tarayıcı bir belgeye gittiğinde gelir: ilk yüklemede, aynı sekmede başka sayfadan gelindiğinde, mobilde dondurulmuş bir sayfa geri getirildiğinde, geri ileri düğmeleriyle dönüldüğünde, sayfa arka plan sekmesinde açıldığında ve hatta sayfa önceden işlenirken. İlk yüklemede pageshow, load olayından sonra gelir. Her iki olay da PageTransitionEvent türündedir ve persisted özelliği sayfanın atılmayıp yeniden kullanılmak üzere saklanıp saklanmadığını söyler.
window.addEventListener("pageshow", (event) => {
if (event.persisted) {
// sayfa geri ileri önbelleğinden döndü, verileri tazele
sepetSayisiniYenile();
}
});
window.addEventListener("pagehide", (event) => {
if (event.persisted) {
// sayfa atılmıyor, daha sonra yeniden kullanılabilir
}
});pagehide geri ileri önbelleğiyle uyumludur; dinleyici eklemek sayfanın bu önbelleğe alınmasını engellemez. Ancak MDN, unload ve beforeunload gibi bu olayın da mobilde güvenilir şekilde tetiklenmediğini belirtir. Klasik örnek: Kullanıcı sayfayı açar, başka bir uygulamaya geçer ve daha sonra tarayıcıyı uygulama yöneticisinden kapatır; bu senaryoda pagehide hiç gelmez. Oturum sonunu işaretlemek için en iyi olay visibilitychange olarak kalır; özel olarak sayfanın boşaltılmasını yakalamak istiyorsanız MDN pagehide olayını en iyi seçenek olarak gösterir.
beforeunload: kaydedilmemiş değişiklik uyarısı
beforeunload, pencere, belge ve kaynakları boşaltılmak üzereyken gelir; belge hâlâ görünürdür ve olay iptal edilebilir. Ana kullanım amacı, kullanıcı sayfayı kapatmaya, yenilemeye ya da başka yere gitmeye çalıştığında tarayıcının kendi onay penceresini göstermesini sağlayıp kaydedilmemiş verinin kaybolmasını önlemektir. MDN en iyi uygulama olarak olay nesnesinde preventDefault() çağırmayı, eski tarayıcılar için ayrıca returnValue değerini ayarlamayı önerir.
Modern uygulamaların iki kısıtı vardır: Pencere yalnızca sayfa yapışkan kullanıcı etkinliği (sticky activation) almışsa, yani kullanıcı sayfayla en az bir kez etkileşmişse gösterilir ve pencerede yalnızca tarayıcının belirlediği genel bir metin görünür, sayfa bu metni değiştiremez. MDN ayrıca bu dinleyicinin yalnızca kaydedilmemiş değişiklik varken eklenmesini ve gerek kalmayınca kaldırılmasını önerir. Kullanıcı etkinliği kavramı için güvenli bağlam ve kullanıcı etkinliği yazısına bakın.
<input type="text" name="ad" id="ad" />const ayrilmaUyarisi = (event) => {
event.preventDefault(); // önerilen yol
event.returnValue = true; // eski tarayıcılar için
};
const adAlani = document.querySelector("#ad");
adAlani.addEventListener("input", (event) => {
if (event.target.value !== "") {
window.addEventListener("beforeunload", ayrilmaUyarisi);
} else {
window.removeEventListener("beforeunload", ayrilmaUyarisi);
}
});unload neden kullanılmamalı?
MDN unload sayfasının en başına geliştiricilerin bu olaydan kaçınması gerektiği uyarısını koyar. Üç gerekçe vardır. Birincisi, özellikle mobilde güvenilir şekilde tetiklenmez. İkincisi, geri ileri önbelleğiyle uyumsuzdur: Bu olayı kullanan pek çok sayfa olaydan sonra sayfanın yaşamayacağını varsaydığı için Firefox gibi bazı tarayıcılar unload dinleyicisi olan sayfaları önbelleğe almaz ve bu performansa zarar verir. Üçüncüsü, MDN'ye göre Chrome varsayılan olarak unload olaylarını tetiklemeyi bırakmıştır; buna hâlâ bağımlı bir sayfa Permissions-Policy başlığının unload yönergesiyle yeniden açabilir. Yerine visibilitychange ya da pagehide kullanılmalıdır.
sendBeacon ile oturum sonu verisi göndermek
navigator.sendBeacon(url, data) sunucuya küçük bir miktar veri içeren asenkron bir HTTP POST isteği gönderir ve analiz ile tanılama verisi için tasarlanmıştır. Eskiden sayfalar boşaltma sırasında veri göndermek için eşzamanlı XMLHttpRequest, src atanmış bir img öğesi ya da birkaç saniyelik boş döngüler kullanıyordu; bunların hepsi boşaltmayı engelleyip bir sonraki sayfayı yavaşlatıyordu. sendBeacon() veriyi tarayıcı fırsat bulduğunda, boşaltmayı ya da sonraki gezinmeyi geciktirmeden iletir.
Yöntem verinin kuyruğa alınıp alınmadığını boolean olarak döndürür. Veri olarak ArrayBuffer, typed array, DataView, Blob, dize, FormData ya da URLSearchParams verilebilir. Kuyruktaki verinin toplam boyutu 64 KiB (65.536 bayt) ile sınırlıdır. POST dışında bir yönteme, isteğin başka özelliklerini değiştirmeye ya da sunucu yanıtına ihtiyacınız varsa MDN keepalive seçeneği true olan bir fetch() önerir; ayrıntılar için Fetch API yazısına bakın.
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
const veri = new URLSearchParams({ sayfa: location.pathname, sure: String(Math.round(performance.now())) });
navigator.sendBeacon("/log", veri);
}
});Bu kodda performance.now() sayfanın zaman kökeninden bu yana geçen süreyi milisaniye olarak verir; Performance API için Performance API yazısına bakabilirsiniz. Gönderilen veri sunucuda sıradan bir POST gövdesi olarak işlenir.
Uygulamalı mini örnek: olay günlüğü
MDN'nin canlı örneklerinden biri, yükleme olaylarının sırasını bir metin alanına yazar. Aşağıdaki sürüm görünürlük ve sayfa geçişi olaylarını da ekler; sayfayı açıp sekme değiştirerek, başka sayfaya gidip Geri düğmesiyle dönerek hangi olayların geldiğini görebilirsiniz.
<textarea id="gunluk" rows="10" cols="40" readonly></textarea>const gunluk = document.getElementById("gunluk");
const yaz = (metin) => {
gunluk.textContent += `${metin}\n`;
};
document.addEventListener("readystatechange", () => yaz(`readystate: ${document.readyState}`));
document.addEventListener("DOMContentLoaded", () => yaz("DOMContentLoaded"));
window.addEventListener("load", () => yaz("load"));
window.addEventListener("pageshow", (e) => yaz(`pageshow persisted=${e.persisted}`));
window.addEventListener("pagehide", (e) => yaz(`pagehide persisted=${e.persisted}`));
document.addEventListener("visibilitychange", () => yaz(`visibility: ${document.visibilityState}`));Betik gövdenin sonunda çalışıyorsa ilk satırlarda readystate: interactive, DOMContentLoaded, readystate: complete, load ve ardından pageshow görmeyi beklersiniz; bu sıra MDN'nin readyState ve pageshow açıklamalarından çıkarılmıştır. Betik bu olaylardan sonra yüklenirse ilgili satırlar görünmez, bu da yukarıdaki geç çalışma sorununun canlı bir gösterimidir.
Sık yapılan hatalar

- DOM erişimi için load beklemek: Görseller yüklenene kadar gereksiz bekleme olur;
DOMContentLoadedya da gövde sonundaki betik yeterlidir. - Geç yüklenen betikte yalnızca DOMContentLoaded dinlemek: Olay geçmişse kurulum hiç çalışmaz; önce
readyStatekontrol edin. - Analizi unload içinde göndermek: Mobilde çoğu zaman çalışmaz ve geri ileri önbelleğini bozar.
- beforeunload dinleyicisini kalıcı eklemek: Yalnızca kaydedilmemiş değişiklik varken ekleyin, sonra kaldırın.
- beforeunload penceresinde özel metin beklemek: Tarayıcı yalnızca kendi genel metnini gösterir.
- pageshow geldi diye sayfanın görüldüğünü varsaymak: Arka plan sekmesi ya da önceden işleme olabilir; görünürlük için
visibilityStatedeğerine bakın. - sendBeacon ile büyük veri göndermek: 64 KiB sınırını aşan veri kuyruğa alınmaz.
Sık Sorulan Sorular
window.onload ile DOMContentLoaded aynı şey mi?
Hayır. window.onload tüm kaynaklar yüklendiğinde çalışan load olayının işleyicisidir. DOMContentLoaded daha erken, belge ayrıştırılıp ertelenmiş betikler çalıştığında gelir ve onevent özelliği yoktur.
Kullanıcı sekmeyi kapattığında hangi olay kesin gelir?
MDN'ye göre güvenilir şekilde gözlemlenebilen son olay visibilityState değerinin hidden olmasıyla gelen visibilitychange olayıdır. beforeunload, pagehide ve unload özellikle mobilde tetiklenmeyebilir.
Geri düğmesiyle dönüldüğünde sayfam neden eski verileri gösteriyor?
Sayfa geri ileri önbelleğinden olduğu gibi geri getirilmiş olabilir. pageshow olayında event.persisted değeri true ise güncel olması gereken verileri yeniden yükleyin.
Sekme arka plandayken zamanlayıcılarım neden yavaşlıyor?
Tarayıcılar arka plandaki sekmelerde zamanlayıcıları kısıtlar ve requestAnimationFrame() geri çağrılarını durdurabilir. Çalışmanın arka planda sürmesi gerekmiyorsa visibilitychange ile durdurup sayfa görünür olunca devam ettirin; arka planda sürmesi gereken işler için Web Workers yazısına bakabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · addEventListener kullanımı · Fetch API kullanımı · Performance API · Service Worker yaşam döngüsü · Core Web Vitals