MutationObserver API'sini Tanımak
Yayınlanan: 2022-03-10Karmaşık web uygulamalarında DOM değişiklikleri sık olabilir. Sonuç olarak, uygulamanızın DOM'deki belirli bir değişikliğe yanıt vermesi gerekebileceği durumlar vardır.
Bir süredir, DOM'deki değişiklikleri aramanın kabul edilen yolu, artık kullanımdan kaldırılan Mutasyon Olayları adı verilen bir özellik aracılığıylaydı. Mutasyon Olayları için W3C onaylı ikame, bu makalede ayrıntılı olarak tartışacağım olan MutationObserver API'dir.
Bazı eski makaleler ve referanslar, eski özelliğin neden değiştirildiğini tartışıyor, bu yüzden burada bunun ayrıntılarına girmeyeceğim (ayrıca bunun hakkını veremem). MutationObserver
API'si neredeyse eksiksiz tarayıcı desteğine sahiptir, bu nedenle ihtiyaç olması durumunda tüm projelerde olmasa da çoğunda güvenle kullanabiliriz.
MutationObserver İçin Temel Sözdizimi
Bir MutationObserver
, bu makalenin geri kalanında ayrıntılı olarak ele alacağım bir dizi farklı şekilde kullanılabilir, ancak bir MutationObserver
için temel sözdizimi şöyle görünür:
let observer = new MutationObserver(callback); function callback (mutations) { // do something here } observer.observe(targetNode, observerOptions);
İlk satır, MutationObserver()
yapıcısını kullanarak yeni bir MutationObserver
oluşturur. Yapıcıya iletilen argüman, uygun olan her DOM değişikliğinde çağrılacak bir geri çağırma işlevidir.
Belirli bir gözlemci için neyin uygun olduğunu belirlemenin yolu, yukarıdaki koddaki son satırı kullanmaktır. Bu satırda, gözlemlemeye başlamak için MutationObserver
observe()
yöntemini kullanıyorum. Bunu addEventListener()
gibi bir şeyle karşılaştırabilirsiniz. Bir dinleyici ekler eklemez, sayfa belirtilen olayı 'dinleyecektir'. Benzer şekilde, gözlemlemeye başladığınızda, sayfa belirtilen MutationObserver
için 'gözlemlemeye' başlayacaktır.
observe()
yöntemi iki bağımsız değişken alır: Değişikliklerin gözlemleneceği düğüm veya düğüm ağacı olması gereken hedef ; ve gözlemci için yapılandırmayı tanımlamanıza izin veren bir MutationObserverInit
nesnesi olan bir options nesnesi.
MutationObserver
son temel temel özelliği,connect disconnect()
yöntemidir. Bu, belirtilen değişiklikleri gözlemlemeyi bırakmanıza izin verir ve şöyle görünür:
observer.disconnect();
MutationObserver Yapılandırma Seçenekleri
Belirtildiği gibi, bir MutationObserver
observe()
yöntemi, MutationObserver
tanımlama seçeneklerini belirten ikinci bir argüman gerektirir. Seçenekler nesnesi, dahil edilen tüm olası özellik/değer çiftleriyle birlikte şu şekilde görünür:
let options = { childList: true, attributes: true, characterData: false, subtree: false, attributeFilter: ['one', 'two'], attributeOldValue: false, characterDataOldValue: false };
MutationObserver
seçeneklerini ayarlarken, tüm bu satırları dahil etmek gerekli değildir. Bunları yalnızca referans amacıyla ekliyorum, böylece hangi seçeneklerin mevcut olduğunu ve ne tür değerler alabileceklerini görebilirsiniz. Gördüğünüz gibi, biri hariç tümü Boolean'dır.
Bir MutationObserver
çalışması için childList
, attributes
veya characterData
öğelerinden en az birinin true
olarak ayarlanması gerekir, aksi takdirde bir hata verilir. Diğer dört özellik, bu üçünden biri ile birlikte çalışır (bununla ilgili daha fazla bilgi daha sonra).
Şu ana kadar size bir genel bakış sağlamak için sözdizimi üzerinde sadece parladım. Bu özelliklerin her birinin nasıl çalıştığını düşünmenin en iyi yolu, farklı seçenekleri içeren kod örnekleri ve canlı demolar sağlamaktır. Bu yüzden bu makalenin geri kalanında yapacağım şey bu.
ChildList Kullanarak Alt Öğelerdeki Değişiklikleri Gözlemleme
Başlatabileceğiniz ilk ve en basit MutationObserver
, eklenecek veya kaldırılacak belirli bir düğümün (genellikle bir öğe) alt düğümlerini arayan biridir. Örneğim için, HTML'mde sırasız bir liste oluşturacağım ve bu liste öğesine ne zaman bir alt düğüm eklendiğini veya kaldırıldığını bilmek istiyorum.
Listenin HTML'si şöyle görünür:
<ul class="list"> <li>Apples</li> <li>Oranges</li> <li>Bananas</li> <li class="child">Peaches</li> </ul>
MutationObserver
JavaScript'i aşağıdakileri içerir:
let mList = document.getElementById('myList'), options = { childList: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation.type === 'childList') { console.log('Mutation Detected: A child node has been added or removed.'); } } } observer.observe(mList, options);
Bu, kodun yalnızca bir parçasıdır. Kısaca, MutationObserver
API'sinin kendisiyle ilgilenen en önemli bölümleri gösteriyorum.
Bir dizi farklı özelliğe sahip bir MutationRecord
nesnesi olan mutations
argümanında nasıl dolaştığıma dikkat edin. Bu durumda, type
özelliğini okuyorum ve tarayıcının uygun bir mutasyon tespit ettiğini belirten bir mesaj kaydediyorum. Ayrıca, mList
öğesini (HTML listeme bir referans) hedeflenen öğe (yani değişiklikleri gözlemlemek istediğim öğe) olarak nasıl ilettiğime dikkat edin.
- Tam etkileşimli demoyu görün →
MutationObserver
başlatmak ve durdurmak için düğmeleri kullanın. Günlük mesajları, neler olduğunu netleştirmeye yardımcı olur. Koddaki yorumlar da bazı açıklamalar sağlar.
Burada birkaç önemli noktaya dikkat edin:
- Geri arama işlevi (
mCallback
adını verdim, onu istediğiniz şekilde adlandırabileceğinizi göstermek için), başarılı bir mutasyon her algılandığında veobserve()
yöntemi yürütüldükten sonra tetiklenir. - Örneğimde, nitelenen tek mutasyon türü
childList
, bu nedenle MutationRecord'da döngü yaparken bunu aramak mantıklıdır. Bu örnekte başka bir tür aramak hiçbir şey yapmaz (diğer türler sonraki demolarda kullanılacaktır). -
childList
kullanarak, hedeflenen öğeye bir metin düğümü ekleyebilir veya kaldırabilirim ve bu da uygun olur. Yani eklenen veya çıkarılan bir unsur olmak zorunda değildir. - Bu örnekte, yalnızca acil alt düğümler hak kazanacaktır. Makalenin ilerleyen bölümlerinde, bunun tüm alt düğümler, torunlar vb. için nasıl geçerli olabileceğini size göstereceğim.
Bir Öğenin Niteliklerindeki Değişiklikleri Gözlemleme
İzlemek isteyebileceğiniz başka bir yaygın mutasyon türü, belirli bir öğedeki bir özniteliğin değişmesidir. Bir sonraki etkileşimli demoda, bir paragraf öğesindeki niteliklerdeki değişiklikleri gözlemleyeceğim.
let mPar = document.getElementById('myParagraph'), options = { attributes: true }, observer = new MutationObserver(mCallback); function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } } observer.observe(mPar, options);
- Demoyu deneyin →
Yine, netlik için kodu kısalttım, ancak önemli kısımlar:
-
options
nesnesi,MutationObserver
hedeflenen öğenin özniteliklerindeki değişiklikleri aramak istediğimi söylemek içintrue
olarak ayarlanmışattributes
özelliğini kullanıyor. - Döngümde test ettiğim mutasyon türü, bu durumda
attributes
tek kişi olan niteliklerdir. - Ayrıca, hangi özniteliğin değiştirildiğini bulmama izin veren,
mutation
nesnesininattributeName
özelliğini kullanıyorum. - Gözlemciyi tetiklediğimde, seçeneklerle birlikte referans olarak paragraf öğesini geçiyorum.
Bu örnekte, hedeflenen HTML öğesinde bir sınıf adını değiştirmek için bir düğme kullanılmıştır. Mutasyon gözlemcisindeki geri çağırma işlevi, sınıf her eklendiğinde veya kaldırıldığında tetiklenir.
Karakter Veri Değişikliklerinin Gözlenmesi
Uygulamanızda aramak isteyebileceğiniz başka bir değişiklik de karakter verilerindeki mutasyonlardır; yani, belirli bir metin düğümüne değişir. Bu, options
nesnesinde characterData
özelliği true
olarak ayarlanarak yapılır. İşte kod:
let options = { characterData: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation.type === 'characterData') { // Do something here... } } }
Geri arama işlevinde aranan type
characterData
olduğuna tekrar dikkat edin.
- Canlı demoyu görün →
Bu örnekte, element.childNodes[0]
aracılığıyla hedeflediğim belirli bir metin düğümünde değişiklik arıyorum. Bu biraz zor ama bu örnek için yapacak. Metin, bir paragraf öğesindeki contenteditable
özniteliği aracılığıyla kullanıcı tarafından düzenlenebilir.
Karakter Veri Değişikliklerini Gözlemlerken Karşılaşılan Zorluklar
contenteditable
ile uğraştıysanız, zengin metin düzenlemeye izin veren klavye kısayolları olduğunu biliyor olabilirsiniz. Örneğin, CTRL-B metni kalın yapar, CTRL-I metni italik yapar vb. Bu, metin düğümünü birden çok metin düğümüne böler, bu nedenle, hala orijinal düğümün parçası olarak kabul edilen metni düzenlemediğiniz sürece MutationObserver
yanıt vermeyi keseceğini fark edeceksiniz.
Ayrıca tüm metni silerseniz MutationObserver
artık geri aramayı tetiklemeyeceğini de belirtmeliyim. Bunun olduğunu varsayıyorum çünkü metin düğümü bir kez kaybolduğunda, hedef öğe artık mevcut değil. Bununla mücadele etmek için demom, zengin metin kısayollarını kullandığınızda işler biraz yapışkan olsa da, metnin ne zaman kaldırıldığını gözlemlemeyi durdurur.
Ama merak etmeyin, bu makalenin ilerleyen kısımlarında, bu tuhaflıkların çoğuyla uğraşmak zorunda kalmadan characterData
seçeneğini kullanmanın daha iyi bir yolunu tartışacağım.
Belirtilen Niteliklerdeki Değişikliklerin Gözlenmesi
Daha önce, belirli bir öğedeki niteliklerdeki değişiklikleri nasıl gözlemleyeceğinizi gösterdim. Bu durumda, demo bir sınıf adı değişikliğini tetiklese de, belirtilen öğedeki herhangi bir özniteliği değiştirebilirdim. Ancak, diğerlerini görmezden gelirken bir veya daha fazla belirli özellikteki değişiklikleri gözlemlemek istersem ne olur?
Bunu, option
nesnesindeki isteğe bağlı attributeFilter
özelliğini kullanarak yapabilirim. İşte bir örnek:
let options = { attributes: true, attributeFilter: ['hidden', 'contenteditable', 'data-par'] }, observer = new MutationObserver(mCallback); function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } }
Yukarıda gösterildiği gibi, attributeFilter
özelliği, izlemek istediğim bir dizi belirli özniteliği kabul ediyor. Bu örnekte, MutationObserver
, bir veya daha fazla hidden
, contenteditable
veya data-par
özniteliği değiştirildiğinde geri aramayı tetikleyecektir.
- Canlı demoyu görün →
Yine belirli bir paragraf öğesini hedefliyorum. Hangi özelliğin değiştirileceğini seçen seçim açılır menüsüne dikkat edin. draggable
öznitelik, seçeneklerimde bunu belirtmediğim için kalifiye olmayacak tek özniteliktir.
Kodda, hangi özniteliğin değiştirildiğini günlüğe kaydetmek için MutationRecord
nesnesinin attributeName
özelliğini tekrar kullandığıma dikkat edin. Ve elbette, diğer demolarda olduğu gibi, MutationObserver
"başlat" düğmesine tıklanana kadar değişiklikleri izlemeye başlamayacaktır.
Burada belirtmem gereken başka bir şey de, bu durumda attributes
değerini true
olarak ayarlamama gerek olmadığıdır; attributesFilter
true olarak ayarlanması nedeniyle ima edilir. Bu yüzden options nesnem aşağıdaki gibi görünebilir ve aynı şekilde çalışır:
let options = { attributeFilter: ['hidden', 'contenteditable', 'data-par'] }
Öte yandan, attributes
bir attributeFilter
dizisiyle birlikte açıkça false
olarak ayarlarsam, false
değeri öncelikli olacağı ve filtre seçeneği yoksayılacağı için bu işe yaramaz.
Düğümlerdeki ve Alt Ağacındaki Değişikliklerin Gözlenmesi
Şimdiye kadar her MutationObserver
öğesini kurarken, yalnızca hedeflenen öğenin kendisiyle ve childList
durumunda öğenin yakın çocukları ile ilgileniyordum. Ancak kesinlikle aşağıdakilerden birinde değişiklik olup olmadığını gözlemlemek isteyebileceğim bir durum olabilir:
- Bir öğe ve tüm alt öğeleri;
- Bir öğede ve onun alt öğelerinde bir veya daha fazla nitelik;
- Bir öğenin içindeki tüm metin düğümleri.
Yukarıdakilerin tümü, options nesnesinin subtree
özelliği kullanılarak gerçekleştirilebilir.
alt ağaçlı çocuk listesi
İlk önce, bir öğenin alt düğümlerinde, hemen alt öğe olmasalar bile değişiklikleri arayalım. Seçenekler nesnemi şöyle görünecek şekilde değiştirebilirim:
options = { childList: true, subtree: true }
Koddaki diğer her şey, bazı ekstra işaretlemeler ve düğmelerle birlikte, önceki childList
örneğiyle aşağı yukarı aynıdır.
- Canlı demoyu görün →
Burada biri diğerinin içine yerleştirilmiş iki liste var. MutationObserver
başlatıldığında, her iki listedeki değişiklikler için geri arama tetiklenir. Ancak subtree
özelliğini tekrar false
olarak değiştirirsem (mevcut olmadığında varsayılan), iç içe liste değiştirildiğinde geri arama yürütülmez.
Alt ağaçlı nitelikler
İşte başka bir örnek, bu sefer subtree
attributes
ve attributeFilter
Filtresi kullanılarak. Bu, yalnızca hedef öğedeki niteliklerdeki değişiklikleri değil, aynı zamanda hedef öğenin herhangi bir alt öğesinin niteliklerindeki değişiklikleri gözlemlememe olanak tanır:
options = { attributes: true, attributeFilter: ['hidden', 'contenteditable', 'data-par'], subtree: true }
- Canlı demoyu görün →
Bu, önceki öznitelikler demosuna benzer, ancak bu sefer iki farklı seçme öğesi ayarladım. Birincisi, hedeflenen paragraf öğesindeki nitelikleri değiştirirken, diğeri paragraf içindeki bir alt öğedeki nitelikleri değiştirir.
Yine, subtree
seçeneğini tekrar false
olarak ayarlarsanız (veya kaldırırsanız), ikinci geçiş düğmesi MutationObserver
geri aramasını tetiklemeyecektir. Ve elbette, attributeFilter
Filtresi'ni tamamen atlayabilirim ve MutationObserver
, belirtilenler yerine alt ağaçtaki herhangi bir nitelikte değişiklik arayacaktır.
alt ağaç ile karakterVeri
Önceki characterData
demosunda, hedeflenen düğümün kaybolmasıyla ilgili bazı sorunlar olduğunu ve ardından MutationObserver
artık çalışmadığını unutmayın. Bunu aşmanın yolları olsa da, bir metin düğümü yerine doğrudan bir öğeyi hedeflemek daha kolaydır, ardından ne kadar iç içe geçmiş olursa olsun, o öğenin içindeki tüm karakter verilerinin tetiklenmesini istediğimi belirtmek için subtree
özelliğini kullanın. MutationObserver
geri araması.
Bu durumda seçeneklerim şöyle görünür:
options = { characterData: true, subtree: true }
- Canlı demoyu görün →
Gözlemciyi başlattıktan sonra, düzenlenebilir metni biçimlendirmek için CTRL-B ve CTRL-I'yi kullanmayı deneyin. Bunun, önceki characterData
örneğinden çok daha etkili çalıştığını fark edeceksiniz. Bu durumda, parçalanmış alt düğümler gözlemciyi etkilemez çünkü tek bir metin düğümü yerine hedeflenen düğümün içindeki tüm düğümleri gözlemliyoruz.
Eski Değerleri Kaydetme
Genellikle DOM'deki değişiklikleri gözlemlerken, eski değerleri not almak ve muhtemelen bunları saklamak veya başka bir yerde kullanmak isteyeceksiniz. Bu, options
nesnesindeki birkaç farklı özellik kullanılarak yapılabilir.
nitelikOldValue
İlk olarak, değiştirildikten sonra eski öznitelik değerini oturumu kapatmayı deneyelim. Seçeneklerim geri aramamla birlikte şu şekilde görünecek:
options = { attributes: true, attributeOldValue: true } function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } }
- Canlı demoyu görün →
MutationRecord
nesnesinin attributeName
ve oldValue
özelliklerinin kullanımına dikkat edin. Metin alanına farklı değerler girerek demoyu deneyin. Günlüğün, depolanan önceki değeri yansıtacak şekilde nasıl güncellendiğine dikkat edin.
karakterDataOldValue
Benzer şekilde, eski karakter verilerini günlüğe kaydetmek istersem seçeneklerim şöyle görünür:
options = { characterData: true, subtree: true, characterDataOldValue: true }
- Canlı demoyu görün →
Günlük mesajlarının önceki değeri gösterdiğine dikkat edin. Karışıma zengin metin komutları aracılığıyla HTML eklediğinizde işler biraz sarpa sarıyor. Bu durumda doğru davranışın ne olması gerektiğinden emin değilim, ancak öğenin içindeki tek şeyin tek bir metin düğümü olması daha basittir.
TakeRecords() Kullanarak Mutasyonları Yakalama
MutationObserver
nesnesinin henüz bahsetmediğim bir diğer yöntemi ise takeRecords()
. Bu yöntem, geri arama işlevi tarafından işlenmeden önce tespit edilen mutasyonları aşağı yukarı engellemenize izin verir.
Bu özelliği şöyle bir satır kullanarak kullanabilirim:
let myRecords = observer.takeRecords();
Bu, belirtilen değişkendeki DOM değişikliklerinin bir listesini saklar. Demomda, DOM'yi değiştiren düğmeye tıklanır tıklamaz bu komutu yürütüyorum. Başlat ve ekle/kaldır düğmelerinin hiçbir şeyi günlüğe kaydetmediğine dikkat edin. Bunun nedeni, belirtildiği gibi, DOM değişikliklerini geri arama tarafından işlenmeden önce yakalıyor olmamdır.
Ancak, gözlemciyi durduran olay dinleyicisinde ne yaptığıma dikkat edin:
btnStop.addEventListener('click', function () { observer.disconnect(); if (myRecords) { console.log(`${myRecords[0].target} was changed using the ${myRecords[0].type} option.`); } }, false);
Gördüğünüz gibi, gözlemciyi observer.disconnect()
kullanarak durdurduktan sonra, yakalanan mutasyon kaydına erişiyorum ve hedef öğenin yanı sıra kaydedilen mutasyon türünü de günlüğe kaydediyorum. Birden fazla değişiklik türünü gözlemliyor olsaydım, saklanan kaydın içinde her biri kendi türüne sahip birden fazla öğe olurdu.
takeRecords()
çağrılarak bir mutasyon kaydı bu şekilde yakalandığında, normalde geri çağırma işlevine gönderilecek olan mutasyonların sırası boşalır. Bu nedenle, herhangi bir nedenle bu kayıtları işlenmeden önce engellemeniz gerekiyorsa, takeRecords()
kullanışlı olacaktır.
Tek Bir Gözlemci Kullanarak Çoklu Değişiklikleri Gözlemleme
Sayfadaki iki farklı düğümde mutasyon arıyorsam, bunu aynı gözlemciyi kullanarak yapabileceğimi unutmayın. Bu, yapıcıyı çağırdıktan sonra, istediğim kadar eleman için observe()
yöntemini çalıştırabileceğim anlamına gelir.
Böylece, bu satırdan sonra:
observer = new MutationObserver(mCallback);
Daha sonra, ilk argüman olarak farklı öğelerle birden çok observe()
çağrısı yapabilirim:
observer.observe(mList, options); observer.observe(mList2, options);
- Canlı demoyu görün →
Gözlemciyi başlatın, ardından her iki liste için de ekle/kaldır düğmelerini deneyin. Buradaki tek sorun, “durdur” düğmelerinden birine basarsanız, gözlemcinin yalnızca hedeflediği listeyi değil, her iki listeyi de gözlemlemeyi bırakacağıdır.
Gözlemlenen Bir Düğüm Ağacını Taşıma
Belirteceğim son bir şey, bir MutationObserver
belirli bir düğümdeki değişiklikleri o düğüm üst öğesinden kaldırıldıktan sonra bile gözlemlemeye devam edeceğidir.
Örneğin, aşağıdaki demoyu deneyin:
- Canlı demoyu görün →
Bu, bir hedef öğenin alt öğelerindeki değişiklikleri izlemek için childList
kullanan başka bir örnektir. Gözlenen alt listenin bağlantısını kesen düğmeye dikkat edin. “Başlat…” düğmesini tıklayın, ardından iç içe listeyi taşımak için “Taşı…” düğmesini tıklayın. Liste üst öğesinden kaldırıldıktan sonra bile MutationObserver
belirtilen değişiklikleri gözlemlemeye devam eder. Bunun olması büyük bir sürpriz değil, ancak akılda tutulması gereken bir şey.
Çözüm
Bu, MutationObserver
API'sinin hemen hemen tüm temel özelliklerini kapsar. Umarım bu derin dalış, bu standarda aşina olmanız için faydalı olmuştur. Belirtildiği gibi, tarayıcı desteği güçlüdür ve bu API hakkında daha fazla bilgiyi MDN'nin sayfalarında okuyabilirsiniz.
Demolarla uğraşmak için kolay bir yere sahip olmak istiyorsanız, bu makalenin tüm demolarını bir CodePen koleksiyonuna koydum.