MutationObserver API'sini Tanımak

Yayınlanan: 2022-03-10
Hızlı özet ↬ Karmaşık web uygulamalarında ve çerçevelerde bazen DOM'deki değişikliklerin izlenmesi gerekir. Etkileşimli demolarla birlikte açıklamalar aracılığıyla, bu makale size MutationObserver API'sini DOM değişikliklerini gözlemlemeyi nispeten kolay hale getirmek için nasıl kullanabileceğinizi gösterecektir.

Karmaşı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.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

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 ve observe() 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çin true 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 nesnesinin attributeName ö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.