Bağlanan her JavaScript görünümüne yeni bir AbortController verin, dinleyicilerini bu denetleyicinin sinyaliyle kaydedin ve görünüm kaldırılırken denetleyiciyi iptal edin. Bu eğitimde küçük bir tarayıcı görünümü oluşturuluyor ve Mac’te Chrome’da tekrarlanan bağlama döngülerinin nasıl denetleneceği gösteriliyor.
Bir görünüm yeniden açıldığında olay işleyicileri neden birikir?
Bir görünüm ilk açılışında doğru çalışabilir, ancak yeniden açıldıktan sonra aynı olaya iki kez yanıt vermeye başlayabilir. Yaygın bir neden, her bağlamada yeni bir geri çağırım oluşturup önceki kaydı kaldırmayan kurulum kodudur. Kaynak kodları aynı olsa bile ayrı ayrı oluşturulan işlevler farklı dinleyici nesneleridir. MDN’nin addEventListener() belgeleri bu geri çağırım kimliği kuralını açıklar.
DOM, aynı hedefte aynı olay türü, geri çağırım ve capture değeriyle yapılan yinelenen kayıtları engeller. Ancak her kurulum yeni bir geri çağırım oluşturuyorsa bu koruma tekrarlanan bağlama sorununu çözmez. Standart ayrıca, sağlanan sinyalin iptal edilmesinin dinleyiciyi kaldırdığını belirtir. Bu kurallar pratik bir tasarımı destekler: tek bir denetleyici, tek bir bağlı kalma aralığındaki tüm yerel olay dinleyicilerini yönetir. DOM Standardı: EventTarget.
Bu eğitimde okuyucunun amacı, fazladan düğme veya pencere işleyicileri eklemeden düz JavaScript sayaç görünümünü açmak, kapatmak ve yeniden açmaktır. Örnek yapay etiketler kullanır, herhangi bir çerçeve gerektirmez ve sayaç değerlerini bağlamalar arasında korur.
Görünüm hangi yaşam döngüsü sözleşmesini izlemeli?
Kurulumu uygulamadan önce yinelenen yaşam döngüsü çağrılarının ne anlama geleceğine karar verin. Aşağıdaki sözleşme, yanlışlıkla yinelenen çağrıların zararsız olmasını sağlar ve temizleme işleminin sahibini netleştirir.
| İşlem | Gerekli davranış |
|---|---|
İlk mount() |
Görünümü ekleyin ve tek bir dinleyici kümesi kaydedin. |
Bağlı durumdayken başka bir mount() çağrısı |
Hiçbir şeyi değiştirmeden dönün. |
unmount() |
Bağlı kalma aralığının dinleyicilerini kaldırın, ardından görünümü ayırın. |
Başka bir unmount() çağrısı |
Hiçbir şeyi değiştirmeden dönün. |
Görünüm kaldırıldıktan sonra mount() |
Yeni bir denetleyiciyle yeni bir dinleyici kümesi kaydedin; sayaçları koruyun. |
destroy() |
Görünümü kalıcı olarak kaldırın; daha sonra bağlama girişimi hata verir. |
Bunlar uygulama tasarımıyla ilgili kararlardır. Tarayıcı olay kaydı ve kaldırma işlemlerini sağlar; görünüm ise yaşam döngüsü yöntemlerini ve sıralarını belirler. Daha büyük bir uygulamada mount() çağrısını yapan bileşen veya yönlendirici, eşleşen unmount() çağrısını da düzenlemelidir.
Yeni bir denetleyici önemlidir, çünkü iptal edilmiş bir sinyal yeniden etkinleştirilemez. addEventListener() iptal edilmiş bir sinyal aldığında DOM kayıt algoritması dinleyiciyi eklemeden geri döner. Bu nedenle eski denetleyiciyi yeniden kullanmak, yeniden açılan görünümün denetimlerinin çalışmamasına yol açabilir. DOM Standardı: olay dinleyicisi ekleme.
Mac’inizde Chrome’da eksiksiz bir örnek oluşturun
Yeni bir Chrome sekmesi açın, adres çubuğuna about:blank yazın ve Return tuşuna basın. Sayfa odaktayken Console’u açmak için Command + Option + J tuşlarına basın. Google, Mac kısayolunu Chrome DevTools klavye kısayolları başvuru sayfasında belgeliyor.
Aşağıdaki programın tamamını Console’da çalıştırın. Arayüzü JavaScript üzerinden oluşturduğu için belge işaretlemesi, paket kurulumu veya derleme adımı gerekmez. Google’ın Console’da JavaScript çalıştırma eğitiminde gösterildiği gibi Chrome konsolu JavaScript’i değerlendirebilir ve geçerli sayfanın DOM’unu değiştirebilir.
(() => {
// Clean up the previous demo if this entire program is run again.
window.lifecycleDemo?.destroy();
const host = document.body;
const root = document.createElement("section");
const button = document.createElement("button");
const output = document.createElement("p");
button.type = "button";
button.textContent = "Add one";
root.append(button, output);
let controller = null;
let destroyed = false;
let clicks = 0;
let resizes = 0;
function render() {
output.textContent =
`Clicks: ${clicks}; resize events: ${resizes}`;
}
function mount() {
if (destroyed) {
throw new Error("This view has been destroyed.");
}
if (controller !== null) return;
const next = new AbortController();
const options = { signal: next.signal };
try {
button.addEventListener("click", () => {
clicks += 1;
render();
}, options);
window.addEventListener("resize", () => {
resizes += 1;
render();
}, options);
render();
host.append(root);
controller = next;
} catch (error) {
// Roll back any registrations made before setup failed.
next.abort();
root.remove();
throw error;
}
}
function unmount() {
if (controller === null) return;
controller.abort();
controller = null;
root.remove();
}
function destroy() {
unmount();
destroyed = true;
}
window.lifecycleDemo = {
mount,
unmount,
destroy,
button,
get counts() {
return { clicks, resizes };
}
};
mount();
})();
Sayfada artık Add one düğmesi ve bir sayaç satırı bulunuyor. Düğmeye tıklayın: tıklama sayısı bir artmalıdır. Chrome penceresini yeniden boyutlandırın: yeniden boyutlandırma olayı sayısı değişmelidir. Fiziksel yeniden boyutlandırma birkaç olay oluşturabilir; dolayısıyla bu değer fare hareketlerinin değil, olayların sayısını gösterir.
Düğme dinleyicisiyle pencere dinleyicisi aynı sinyali paylaşır. Kaldırma işlemi, kök öğeyi ayırmadan önce bu denetleyiciyi iptal eder. Sonraki bağlamada başka bir denetleyici ve başka bir geri çağırım kümesi oluşturulur. Sayaçlar görünümün closure’ında kaldığından yeniden açıldığında değerleri korunur; programın tamamını yok edip yeniden çalıştırmak yeni bir örnek başlatır.
Kurulum bloğu kısmi bir hatayı da ele alır: dinleyici kaydedildikten sonra kurulum hata verirse catch bloğu yeni denetleyiciyi iptal eder, kök öğeyi ayırır ve hatayı yeniden fırlatır. Böylece başarısız bir bağlamanın kurulumunun bir bölümünü fark ettirmeden etkin bırakması önlenir.
Tekrarlanan bağlamalarda temizliği nasıl doğrularsınız?
Önce yaşam döngüsünü elle deneyin. Ana programı yükledikten sonra aşağıdaki sırayı çalıştırın. Görünüm kaybolur, tekrarlanan kaldırma işleminin başka bir etkisi olmaz ve yeniden açıldığında önceki sayaç değerleriyle geri gelir.
window.lifecycleDemo.unmount();
window.lifecycleDemo.unmount();
window.lifecycleDemo.mount();
window.lifecycleDemo.mount();
Ardından aynı konsolda bu eksiksiz denetimi çalıştırın. Ana programın yukarıda çalıştırılmış olmasını gerektirir. Her döngü kurulum ve temizleme işlemlerini bilerek iki kez çağırır, görünüm bağlıyken her hedefe bir olay gönderir ve görünüm bağlı değilken aynı olayları gönderir. Herhangi bir uyumsuzluk hemen hata verir.
(() => {
const view = window.lifecycleDemo;
if (!view) throw new Error("Run the main program first.");
function check(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
for (let cycle = 0; cycle < 20; cycle += 1) {
view.mount();
view.mount();
const before = view.counts;
view.button.click();
window.dispatchEvent(new Event("resize"));
check(view.counts.clicks, before.clicks + 1, "Mounted click");
check(view.counts.resizes, before.resizes + 1, "Mounted resize");
view.unmount();
view.unmount();
const stopped = view.counts;
// The button reference remains available while detached.
view.button.click();
window.dispatchEvent(new Event("resize"));
check(view.counts.clicks, stopped.clicks, "Unmounted click");
check(view.counts.resizes, stopped.resizes, "Unmounted resize");
}
view.mount();
console.log("Passed 20 mount/unmount cycles.");
})();
Yapay yeniden boyutlandırma olayı, pencereyi hareket ettirmeden beklenen değişikliği belirli hâle getirir. Bu örnek yeniden bağlamak için düğmeyi bilerek sakladığından, düğme ayrılmış durumdayken yapılan denetim yararlıdır. Her iki denetim de başarılı olmalıdır: yalnızca görünen düğmeyi sınamak, pencere dinleyicisini incelemeden bırakır. Test her sayaca 20 ekler ve görünümü bağlı durumda bırakır.
Doğrudan inceleme için Chrome, DevTools konsolunda getEventListeners(object) aracını sunar. Belirtilen nesneye kaydedilmiş dinleyicileri olay türüne göre gruplandırarak döndürür. Bu bir DevTools yardımcı aracıdır; uygulama koduna eklemeyin. Chrome Console Utilities API başvurusu.
Ana programı çalıştırdıktan sonra bu inceleme dizisi, temizleme öncesi ve sonrasındaki kayıtları karşılaştırır. Boş gösterim sayfasında, görünüm bağlıyken bir düğme tıklama dinleyicisi ve bir pencere yeniden boyutlandırma dinleyicisi; görünüm kaldırıldığında ise her ikisinden de sıfır tane bekleyin.
(() => {
const view = window.lifecycleDemo;
if (!view) throw new Error("Run the main program first.");
view.mount();
console.log("Mounted", {
button: (getEventListeners(view.button).click ?? []).length,
window: (getEventListeners(window).resize ?? []).length
});
view.unmount();
console.log("Unmounted", {
button: (getEventListeners(view.button).click ?? []).length,
window: (getEventListeners(window).resize ?? []).length
});
view.mount();
})();
Mevcut bir uygulama sayfasında başka kodlar pencere dinleyicilerine sahip olabilir. Toplamın sıfıra ulaşması gerektiğini varsaymak yerine, önceki ve sonraki farkı karşılaştırıp geri çağırımları inceleyin.
Bunun yerine ne zaman açıkça kaldırma kullanmalısınız?
Zaten geri çağırımları saklayan mevcut kodla bütünleşirken veya gerekli ortam dinleyici signal seçeneğini desteklemiyorsa removeEventListener() kullanın. Temizlik için özgün hedefi, olay türünü, geri çağırım başvurusunu ve capture değerini koruyun. MDN, capture değerinin eşleşmesi gerektiğini; passive gibi seçeneklerin ise kaldırma eşleşmesini belirlemediğini belgeliyor. MDN’nin removeEventListener() başvurusu.
Örneğin kurulum bind() kullanıyorsa, bağlı bir geri çağırımı bir kez saklayın. Temizlik sırasında yeniden bind() çağrısı yapmak farklı bir işlev başvurusu oluşturur. Benzer şekilde, kaldırma çağrısında aynı ok işlevini yeniden yazmak daha önce kaydedilen işlevi bulmanızı sağlamaz. Açık temizleme kodunu kurulumun yanında tutun; böylece ikisi birlikte güncellenebilir.
Denetleyici kalıbı, sinyaliyle kaydedilen yerel dinleyicileri kapsar. Görünüm zamanlayıcılar, gözlemciler veya kitaplık abonelikleri de oluşturuyorsa, bunların belgelenmiş temizleme işlemlerini aynı kaldırma sınırına bağlayın. Yerel dinleyicilere sinyal geçirmek, ilgisiz kaynakların temizleneceği anlamına gelmez.
Geri çağırımlar hâlâ çoğalıyorsa neleri denetlemelisiniz?
Birden fazla yaşam döngüsü yöntemini aynı anda değiştirmek yerine, belirtilere bakarak incelemeyi daraltın.
| Belirti | Sıradaki denetim |
|---|---|
| Tek tıklama sayacı iki artırıyor | İkinci bir kayıt yoluna veya etkin durumda olan ikinci bir görünüm örneğine bakın. |
| Yeniden açıldığında denetimler çalışmayı durduruyor | Kurulumun iptal edilmiş bir sinyali yeniden kullanıp kullanmadığını denetleyin. |
| Görünüm kaldırıldıktan sonra sayaçlar değişiyor | Her olay hedefini ve her kaydın amaçlanan sinyali alıp almadığını denetleyin. |
| Açık kaldırma hiçbir şeyi değiştirmiyor | Hedefi, olay türünü, geri çağırım kimliğini ve capture değerini karşılaştırın. |
| İlk işlem çalışıyor, ancak sonrakiler hiçbir şey yapmıyor | Dinleyicinin once: true kullanıp kullanmadığını denetleyin. |
once seçeneği, ilk çalıştırılmasının ardından dinleyiciyi kaldırır. Gerçekten tek kullanımlık bir etkileşime uygun olabilir; ancak bir görünümün bağlı kalma süresini ifade etmez: kaldırılmadan önce olay hiç gerçekleşmeyebilir. MDN, addEventListener() seçenekleri başvurusunda hem once seçeneğini hem de sinyal temelli kaldırmayı açıklar.
Gösterimi bitirdiğinizde görünümü ayırıp dinleyicilerini kaldırmak için window.lifecycleDemo.destroy() çalıştırın. Kendi görünümünüzde de aynı pratik sözleşmeyi koruyun: tek bir etkin kurulum, yinelenebilir temizleme, yeniden açıldığında yeni kayıtlar ve hem görünür denetimleri hem de daha uzun ömürlü olay hedeflerini çalıştıran bir test.
Kaynaklar
- DOM Standardı — WHATWG.
- EventTarget: addEventListener() yöntemi — MDN Web Docs.
- EventTarget: removeEventListener() yöntemi — MDN Web Docs.
- Klavye kısayolları — Chrome for Developers, Google.
- Console’da JavaScript çalıştırma — Chrome for Developers, Google.
- Console Utilities API başvurusu — Chrome for Developers, Google.