Mutation observer
MutationObserver — bu DOM daraxtini kuzatib turuvchi va unda o'zgarish ro'y berganda (element qo'shilganda yoki o'chirilganda, atribut o'zgarganda, matn tahrirlanganda) sizga xabar beruvchi maxsus obyekt. U ayniqsa sahifaga tashqaridan kelgan kodni (masalan, uchinchi tomon skriptlari yoki reklama bloklari) nazorat qilishda, redaktorlarni yozishda va kutubxonalarni integratsiya qilishda juda qadrli.
Nima uchun kerak
Tasavvur qiling, siz sahifaga uchinchi tomon skriptini qo'shdingiz — u foydali kontent bilan birga o'zining reklamasini yoki keraksiz <div>larini ham qo'shadi. Bu skriptning ichiga kira olmaysiz. Lekin uning DOMga qanday o'zgarish kiritishini kuzatib, keraksiz elementni darhol o'chirib tashlashingiz mumkin. Aynan shu vazifani MutationObserver bajaradi.
Yana bir keng tarqalgan holat — sintaksisni ranglaydigan kutubxonalar (Prism.js, highlight.js kabi). Yangi kod bloki sahifaga qo'shilganda uni avtomatik ravishda bo'yash kerak bo'ladi. Buni MutationObserver orqali eshitib turish mumkin.
Sintaksis
Avval kuzatuvchi yaratamiz, so'ng uni biror tugunga (node) bog'laymiz:
let observer = new MutationObserver(callback);
observer.observe(node, config);
callback — o'zgarish ro'y berganda chaqiriladigan funksiya. U ikkita argument oladi: o'zgarishlar ro'yxati (mutationRecords) va kuzatuvchining o'zi.
observe(node, config) — qaysi tugunni va aynan qanday o'zgarishlarni kuzatishni belgilaydi. config — bu mantiqiy (boolean) bayroqlar to'plami:
childList—nodening bevosita bolalari (child) qo'shilishi/o'chirilishi;subtree—nodening nafaqat bolalari, balki barcha avlodlari (nabiralari va h.k.);attributes—nodeatributlaridagi o'zgarishlar;attributeFilter— faqat sanab o'tilgan atributlar massivini kuzatish;characterData—node.data(matn tugunidagi matn) o'zgarishi;attributeOldValue— atributning eski qiymatini ham yozib berish;characterDataOldValue— matnning eski qiymatini ham yozib berish.
configda kamida childList, attributes yoki characterDatadan bittasi true bo'lishi shart. Aks holda observe xatolik beradi — chunki nima kuzatilishi noaniq bo'lib qoladi.Oddiy misol
Quyida biror <div>ning ichidagi barcha o'zgarishlarni (butun poddaraxtni) kuzatamiz. E'tibor bering, bu kod brauzerda ishlaydi — bu yerda u statik namuna sifatida keltirilgan:
let elem = document.getElementById('kuzatiladigan');
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // o'zgarishlar ro'yxati
});
// har qanday o'zgarishni kuzatishni boshlaymiz
observer.observe(elem, {
childList: true, // to'g'ridan-to'g'ri bolalar
subtree: true, // barcha avlodlar ham
characterData: true, // matn o'zgarishlari
attributes: true // atributlar
});
// endi elem ichini o'zgartiramiz
elem.innerHTML = '<b>Salom</b> dunyo';
Kod ishlaganda konsolga MutationRecord obyektlaridan iborat massiv chiqadi.
MutationRecord obyekti
callbackga tushadigan har bir o'zgarish yozuvi (record) quyidagi asosiy xususiyatlarga ega:
type— o'zgarish turi:'attributes','characterData'yoki'childList';target— o'zgarish qaysi tugunda ro'y berdi (atribut uchun element, matn uchun matn tuguni);addedNodes/removedNodes— qo'shilgan va olib tashlangan tugunlar ro'yxati;previousSibling/nextSibling— qo'shilgan tugunlarning yonidagi tugunlar;attributeName— o'zgargan atribut nomi;oldValue— eski qiymat (faqatattributeOldValueyokicharacterDataOldValueyoqilgan bo'lsa).
Masalan, atribut o'zgarishini eski qiymati bilan qo'lga olamiz:
let observer = new MutationObserver(records => {
for (let record of records) {
if (record.type === 'attributes') {
console.log(
record.attributeName + ' atributi o'zgardi. ' +
'Eski qiymat: ' + record.oldValue
);
}
}
});
observer.observe(elem, {
attributes: true,
attributeOldValue: true
});
callback chaqiruvida bir nechta o'zgarish yig'ilib kelishi mumkin. Shuning uchun records — bu massiv. Brauzer o'zgarishlarni to'plab, keyin bir yo'la yetkazadi (mikrovazifa sifatida — bu haqda uchinchi darsda gaplashamiz).Qo'llanish: keraksiz elementni o'chirish
Sahifaga qo'shilayotgan reklama <div>larini avtomatik o'chiruvchi kuzatuvchi. childList va subtree yordamida yangi qo'shilgan tugunlarni ko'ramiz:
let observer = new MutationObserver(records => {
for (let record of records) {
for (let node of record.addedNodes) {
// faqat elementlar bilan ishlaymiz (matn tugunlarini o'tkazamiz)
if (!(node instanceof HTMLElement)) continue;
if (node.classList.contains('reklama')) {
node.remove();
console.log('Reklama olib tashlandi');
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
Qo'llanish: kutubxona integratsiyasi
Faraz qilaylik, bizda highlight() funksiyasi bor — u <pre> ichidagi kodni bo'yaydi. Yangi kod bloki sahifaga qanday yo'l bilan qo'shilishidan qat'i nazar (innerHTML, append yoki boshqa skript orqali), biz uni avtomatik bo'yashni istaymiz:
function highlight(block) {
// ... kodni bo'yash mantig'i ...
console.log('Bloklandi va bo'yaldi');
}
let observer = new MutationObserver(records => {
for (let record of records) {
for (let node of record.addedNodes) {
if (!(node instanceof HTMLElement)) continue;
// qo'shilgan tugunning o'zi <pre> bo'lsa
if (node.matches('pre[class*="language-"]')) {
highlight(node);
}
// yoki uning ichida <pre>lar bo'lsa
for (let pre of node.querySelectorAll('pre[class*="language-"]')) {
highlight(pre);
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
observe konfiguratsiya kombinatsiyalari
Ba'zi keng tarqalgan sozlamalar:
- Faqat bolalarning kelib-ketishi:
{ childList: true }; - Butun ichki daraxt (istalgan chuqurlikda):
{ childList: true, subtree: true }; - Faqat
classvastyleatributlari:{ attributes: true, attributeFilter: ['class', 'style'] }; - Matn tahrirlarini eski qiymati bilan:
{ characterData: true, characterDataOldValue: true }.
disconnect va takeRecords
Kuzatishni to'xtatish uchun disconnect() chaqiriladi:
observer.disconnect();
takeRecords() — hali callbackga yetkazilmagan, ammo qayd etilgan o'zgarishlar ro'yxatini oladi va navbatni bo'shatadi. Bu odatda disconnect()dan oldin, "oxirgi paytida" yig'ilib qolgan o'zgarishlarni ham qo'lda qayta ishlash uchun ishlatiladi:
// hali qayta ishlanmagan o'zgarishlarni olamiz
let pending = observer.takeRecords();
process(pending);
// keyin kuzatishni to'xtatamiz
observer.disconnect();
disconnect() chaqirilgach, takeRecords() endi bo'sh massiv qaytaradi — chunki navbat allaqachon bo'shatilgan bo'ladi. Shu bois takeRecords()ni disconnect()dan oldin chaqiring.MutationObserver kuzatuvchi obyekti kuzatilayotgan tugunlarga kuchsiz (weak) bog'lanmaydi — u tugunga qattiq ishora saqlaydi. Kuzatuvchini disconnect() qilmasangiz, u va u ushlab turgan tugunlar xotirada qolib ketishi mumkin (xotira sizib chiqishi). Kerak bo'lmaganda albatta uzib qo'ying.Xotira boshqaruvi
Muhim jihat: kuzatuvchi kuzatayotgan tugunlar DOMdan olib tashlansa ham, ular xotirada saqlanib qolishi mumkin — chunki kuzatuvchi ularga ishora saqlaydi. Element endi kerak bo'lmasa, uni kuzatishdan to'xtatish yaxshi amaliyot. Odatda komponent "o'chirilganda" (unmount) disconnect() chaqiriladi.
Xulosa
MutationObserver— DOM daraxtidagi o'zgarishlarni kuzatuvchi obyekt;observer.observe(node, config)bilan kuzatish boshlanadi;confignimani kuzatishni belgilaydi (childList,subtree,attributes,characterDatava h.k.);callbackgaMutationRecordobyektlari massivi keladi:type,target,addedNodes,removedNodes,oldValueva boshqalar;- Asosiy qo'llanishlar: keraksiz elementlarni tozalash, kutubxonalarni integratsiya qilish, DOM o'zgarishlarini kuzatib turish;
disconnect()kuzatishni to'xtatadi;takeRecords()qayta ishlanmagan o'zgarishlarni oladi;- Xotira sizishining oldini olish uchun kerak bo'lmaganda kuzatuvchini uzing.