ทำความรู้จักกับ MutationObserver API

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ บางครั้งจำเป็นต้องมีการตรวจสอบการเปลี่ยนแปลง DOM ในเว็บแอปและเฟรมเวิร์กที่ซับซ้อน บทความนี้จะแสดงให้คุณเห็นว่าคุณสามารถใช้ MutationObserver API ในการสังเกตการเปลี่ยนแปลง DOM ได้อย่างไรโดยใช้คำอธิบายร่วมกับการสาธิตเชิงโต้ตอบ

ในเว็บแอปที่ซับซ้อน การเปลี่ยนแปลง DOM สามารถเกิดขึ้นได้บ่อยครั้ง ด้วยเหตุนี้ มีบางกรณีที่แอปของคุณอาจต้องตอบสนองต่อการเปลี่ยนแปลง DOM ที่เฉพาะเจาะจง

ในบางครั้ง วิธีที่ยอมรับในการค้นหาการเปลี่ยนแปลงใน DOM คือการใช้คุณลักษณะที่เรียกว่า Mutation Events ซึ่งขณะนี้เลิกใช้แล้ว การแทนที่ที่ได้รับการอนุมัติจาก W3C สำหรับ Mutation Events คือ MutationObserver API ซึ่งเป็นสิ่งที่ฉันจะพูดถึงในรายละเอียดในบทความนี้

บทความและข้อมูลอ้างอิงที่เก่ากว่าจำนวนหนึ่งกล่าวถึงสาเหตุว่าทำไมคุณลักษณะเก่าจึงถูกแทนที่ ดังนั้นฉันจะไม่ลงรายละเอียดในที่นี้ (นอกเหนือจากข้อเท็จจริงที่ว่าฉันจะไม่สามารถดำเนินการได้อย่างยุติธรรม) MutationObserver API มีการสนับสนุนเบราว์เซอร์เกือบครบถ้วน ดังนั้นเราจึงสามารถใช้งานได้อย่างปลอดภัยในเกือบทุกโครงการ หากมีความจำเป็น

ไวยากรณ์พื้นฐานสำหรับ A MutationObserver

MutationObserver สามารถใช้ได้หลายวิธี ซึ่งฉันจะอธิบายโดยละเอียดในส่วนที่เหลือของบทความนี้ แต่ไวยากรณ์พื้นฐานสำหรับ MutationObserver จะมีลักษณะดังนี้:

 let observer = new MutationObserver(callback); function callback (mutations) { // do something here } observer.observe(targetNode, observerOptions);

บรรทัดแรกสร้าง MutationObserver ใหม่โดยใช้ตัวสร้าง MutationObserver() อาร์กิวเมนต์ที่ส่งผ่านไปยังคอนสตรัคเตอร์คือฟังก์ชันเรียกกลับที่จะถูกเรียกใช้ในการเปลี่ยนแปลง DOM แต่ละรายการที่มีคุณสมบัติเหมาะสม

วิธีการกำหนดคุณสมบัติสำหรับผู้สังเกตการณ์เฉพาะคือการใช้บรรทัดสุดท้ายในโค้ดด้านบน ในบรรทัดนั้น ฉันใช้เมธอด observe() ของ MutationObserver เพื่อเริ่มการสังเกต คุณสามารถเปรียบเทียบสิ่งนี้กับบางอย่างเช่น addEventListener() ทันทีที่คุณแนบ Listener หน้าจะ 'ฟัง' สำหรับเหตุการณ์ที่ระบุ ในทำนองเดียวกัน เมื่อคุณเริ่มสังเกต หน้าจะเริ่ม 'การสังเกต' สำหรับ MutationObserver ที่ระบุ

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

วิธีการ observe() ใช้สองอาร์กิวเมนต์: เป้าหมาย ซึ่งควรเป็นโหนดหรือโหนดทรีที่จะสังเกตการเปลี่ยนแปลง และอ็อบเจ็กต์ options ซึ่งเป็นอ็อบเจ็กต์ MutationObserverInit ที่ให้คุณกำหนดคอนฟิกูเรชันสำหรับผู้สังเกตการณ์

ฟีเจอร์พื้นฐานของคีย์ขั้นสุดท้ายของ MutationObserver คือวิธี disconnect() สิ่งนี้ทำให้คุณสามารถหยุดสังเกตการเปลี่ยนแปลงที่ระบุได้ และดูเหมือนว่า:

 observer.disconnect();

ตัวเลือกในการกำหนดค่า MutationObserver

ดังที่กล่าวไว้ วิธีการ observe() ของ MutationObserver ต้องการอาร์กิวเมนต์ที่สองที่ระบุตัวเลือกเพื่ออธิบาย MutationObserver ต่อไปนี้คือลักษณะที่อ็อบเจ็กต์ options จะมีลักษณะเป็นคู่ของคุณสมบัติ/ค่าที่เป็นไปได้ทั้งหมดรวมอยู่ด้วย:

 let options = { childList: true, attributes: true, characterData: false, subtree: false, attributeFilter: ['one', 'two'], attributeOldValue: false, characterDataOldValue: false };

เมื่อตั้งค่าตัวเลือก MutationObserver ไม่จำเป็นต้องรวมบรรทัดเหล่านี้ทั้งหมด ฉันได้รวมข้อมูลเหล่านี้ไว้เพื่อการอ้างอิงเท่านั้น คุณจึงสามารถดูได้ว่ามีตัวเลือกใดบ้างและค่าประเภทใดบ้างที่สามารถใช้ได้ อย่างที่คุณเห็น ทั้งหมดยกเว้นอันเดียวคือบูลีน

เพื่อให้ MutationObserver ทำงานได้ ต้องตั้งค่า childList attributes หรือ characterData อย่างน้อยหนึ่งรายการ true มิฉะนั้นจะเกิดข้อผิดพลาด อีกสี่คุณสมบัติทำงานร่วมกับหนึ่งในสามคุณสมบัติดังกล่าว (เพิ่มเติมในเรื่องนี้ในภายหลัง)

จนถึงตอนนี้ ฉันได้เพียงแต่กลบเกลื่อนไวยากรณ์เพื่อให้ภาพรวมแก่คุณ วิธีที่ดีที่สุดในการพิจารณาว่าคุณลักษณะแต่ละอย่างทำงานอย่างไรโดยการให้ตัวอย่างโค้ดและการสาธิตสดที่รวมตัวเลือกต่างๆ นั่นคือสิ่งที่ฉันจะทำในส่วนที่เหลือของบทความนี้

การสังเกตการเปลี่ยนแปลงองค์ประกอบย่อยโดยใช้ childList

MutationObserver แรกและที่ง่ายที่สุดที่คุณสามารถเริ่มต้นได้คือโหนดที่ค้นหาโหนดย่อยของโหนดที่ระบุ (โดยปกติคือองค์ประกอบ) ที่จะเพิ่มหรือลบ สำหรับตัวอย่างของฉัน ฉันจะสร้างรายการที่ไม่เรียงลำดับใน HTML ของฉัน และฉันต้องการทราบทุกครั้งที่มีการเพิ่มหรือลบโหนดย่อยออกจากองค์ประกอบรายการนี้

HTML สำหรับรายการมีลักษณะดังนี้:

 <ul class="list"> <li>Apples</li> <li>Oranges</li> <li>Bananas</li> <li class="child">Peaches</li> </ul>

JavaScript สำหรับ MutationObserver ของฉันมีดังต่อไปนี้:

 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);

นี่เป็นเพียงส่วนหนึ่งของรหัส เพื่อความกระชับ ฉันกำลังแสดงส่วนที่สำคัญที่สุดที่เกี่ยวข้องกับ MutationObserver API เอง

สังเกตว่าฉันกำลังวนซ้ำอาร์กิวเมนต์ mutations ซึ่งเป็นอ็อบเจ็กต์ MutationRecord ที่มีคุณสมบัติแตกต่างกันมากมาย ในกรณีนี้ ฉันกำลังอ่านคุณสมบัติ type และบันทึกข้อความที่ระบุว่าเบราว์เซอร์ตรวจพบการกลายพันธุ์ที่มีคุณสมบัติเหมาะสม นอกจากนี้ ให้สังเกตว่าฉันกำลังส่งองค์ประกอบ mList (การอ้างอิงไปยังรายการ HTML ของฉัน) เป็นองค์ประกอบเป้าหมาย (เช่น องค์ประกอบที่ฉันต้องการสังเกตการเปลี่ยนแปลง)

  • ดูการสาธิตเชิงโต้ตอบแบบเต็ม →

ใช้ปุ่มต่างๆ เพื่อเริ่มและหยุด MutationObserver ข้อความบันทึกช่วยชี้แจงสิ่งที่เกิดขึ้น ความคิดเห็นในโค้ดยังให้คำอธิบายบางอย่างอีกด้วย

สังเกตประเด็นสำคัญสองสามข้อที่นี่:

  • ฟังก์ชันเรียกกลับ (ซึ่งฉันตั้งชื่อว่า mCallback เพื่อแสดงให้เห็นว่าคุณสามารถตั้งชื่ออะไรก็ได้ตามต้องการ) จะเริ่มทำงานทุกครั้งที่ตรวจพบการกลายพันธุ์ที่ประสบความสำเร็จและหลังจากดำเนินการเมธอด observe()
  • ในตัวอย่างของฉัน 'ประเภท' ของการกลายพันธุ์เดียวที่มีคุณสมบัติคือ childList ดังนั้นจึงควรมองหาสิ่งนี้เมื่อวนซ้ำผ่าน MutationRecord การค้นหาประเภทอื่นในกรณีนี้จะไม่ช่วยอะไร (ประเภทอื่นจะถูกนำมาใช้ในการสาธิตครั้งต่อๆ ไป)
  • เมื่อใช้ childList ฉันสามารถเพิ่มหรือลบโหนดข้อความออกจากองค์ประกอบเป้าหมาย และสิ่งนี้ก็จะมีคุณสมบัติเช่นกัน ดังนั้นจึงไม่จำเป็นต้องเป็นองค์ประกอบที่เพิ่มหรือลบออก
  • ในตัวอย่างนี้ เฉพาะโหนดย่อยในทันทีเท่านั้นที่จะมีคุณสมบัติ ในบทความต่อไป ฉันจะแสดงให้คุณเห็นว่าสิ่งนี้สามารถนำไปใช้กับโหนดย่อย ลูกหลาน และอื่นๆ ได้อย่างไร

การสังเกตการเปลี่ยนแปลงคุณสมบัติขององค์ประกอบ

การกลายพันธุ์ทั่วไปอีกประเภทหนึ่งที่คุณอาจต้องการติดตามคือเมื่อแอตทริบิวต์ในองค์ประกอบที่ระบุเปลี่ยนแปลง ในการสาธิตเชิงโต้ตอบครั้งต่อไป ฉันจะสังเกตการเปลี่ยนแปลงแอตทริบิวต์ในองค์ประกอบย่อหน้า

 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);
  • ลองเดโม →

อีกครั้ง ฉันได้ย่อรหัสเพื่อความชัดเจน แต่ส่วนที่สำคัญคือ:

  • ออบเจ็กต์ options กำลังใช้คุณสมบัติ attributes ตั้งค่า true เพื่อบอก MutationObserver ว่าฉันต้องการค้นหาการเปลี่ยนแปลงแอตทริบิวต์ขององค์ประกอบเป้าหมาย
  • ประเภทการกลายพันธุ์ที่ฉันกำลังทดสอบในลูปของฉันคือ attributes ซึ่งเป็นประเภทเดียวที่มีคุณสมบัติในกรณีนี้
  • ฉันยังใช้คุณสมบัติ attributeName ของอ็อบเจกต์ mutation ซึ่งช่วยให้ฉันค้นหาได้ว่าแอททริบิวใดที่มีการเปลี่ยนแปลง
  • เมื่อฉันเรียกผู้สังเกตการณ์ ฉันจะส่งองค์ประกอบย่อหน้าโดยการอ้างอิงพร้อมกับตัวเลือก

ในตัวอย่างนี้ ปุ่มใช้เพื่อสลับชื่อคลาสในองค์ประกอบ HTML เป้าหมาย ฟังก์ชันเรียกกลับในตัวสังเกตการกลายพันธุ์จะถูกทริกเกอร์ทุกครั้งที่มีการเพิ่มหรือลบคลาส

สังเกตการเปลี่ยนแปลงข้อมูลตัวละคร

การเปลี่ยนแปลงอีกอย่างที่คุณอาจต้องการค้นหาในแอปของคุณคือการกลายพันธุ์ของข้อมูลตัวละคร นั่นคือ เปลี่ยนเป็นโหนดข้อความเฉพาะ สิ่งนี้ทำได้โดยการตั้งค่าคุณสมบัติ characterData true ในอ็อบเจ็กต์ options นี่คือรหัส:

 let options = { characterData: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation.type === 'characterData') { // Do something here... } } }

โปรดสังเกตอีกครั้งว่า type ที่ต้องการค้นหาในฟังก์ชันเรียกกลับคือ characterData

  • ดูการสาธิตสด →

ในตัวอย่างนี้ ฉันกำลังมองหาการเปลี่ยนแปลงในโหนดข้อความเฉพาะ ซึ่งฉันกำหนดเป้าหมายผ่าน element.childNodes[0] นี่เป็นแฮ็คเล็กน้อย แต่จะทำสำหรับตัวอย่างนี้ ข้อความที่ผู้ใช้แก้ไขได้ผ่านแอตทริบิวต์ contenteditable ในองค์ประกอบย่อหน้า

ความท้าทายเมื่อสังเกตการเปลี่ยนแปลงข้อมูลตัวละคร

หากคุณเคยเล่นซอกับ contenteditable คุณอาจทราบว่ามีแป้นพิมพ์ลัดที่อนุญาตให้แก้ไข Rich Text ได้ ตัวอย่างเช่น CTRL-B ทำให้ข้อความเป็นตัวหนา CTRL-I ทำให้ข้อความเป็นตัวเอียง และอื่นๆ สิ่งนี้จะแบ่งโหนดข้อความออกเป็นโหนดข้อความหลายโหนด ดังนั้นคุณจะสังเกตเห็นว่า MutationObserver จะหยุดตอบสนอง เว้นแต่คุณจะแก้ไขข้อความที่ยังถือว่าเป็นส่วนหนึ่งของโหนดดั้งเดิม

ฉันควรชี้ให้เห็นว่าหากคุณลบข้อความทั้งหมด MutationObserver จะไม่เรียกการโทรกลับอีกต่อไป ฉันถือว่าสิ่งนี้เกิดขึ้นเพราะเมื่อโหนดข้อความหายไป องค์ประกอบเป้าหมายจะไม่มีอยู่อีกต่อไป เพื่อต่อสู้กับสิ่งนี้ การสาธิตของฉันจะหยุดสังเกตเมื่อข้อความถูกลบออก แม้ว่าสิ่งต่าง ๆ จะติดขัดเล็กน้อยเมื่อคุณใช้ทางลัดข้อความที่มีการจัดรูปแบบ

แต่อย่ากังวล ในบทความนี้ ฉันจะพูดถึงวิธีที่ดีกว่าในการใช้ตัวเลือก characterData โดยไม่ต้องจัดการกับสิ่งแปลก ๆ เหล่านี้ให้มาก

การสังเกตการเปลี่ยนแปลงคุณสมบัติที่ระบุ

ก่อนหน้านี้ฉันแสดงให้คุณเห็นถึงวิธีการสังเกตการเปลี่ยนแปลงแอตทริบิวต์ในองค์ประกอบที่ระบุ ในกรณีนั้น แม้ว่าการสาธิตจะทริกเกอร์การเปลี่ยนชื่อคลาส แต่ฉันสามารถเปลี่ยนแอตทริบิวต์ใดๆ ในองค์ประกอบที่ระบุได้ แต่ถ้าฉันต้องการสังเกตการเปลี่ยนแปลงของคุณลักษณะเฉพาะอย่างใดอย่างหนึ่งโดยไม่สนใจคุณลักษณะอื่นๆ

ฉันสามารถทำได้โดยใช้คุณสมบัติตัวเลือก attributeFilter ตัวกรองในวัตถุ option นี่คือตัวอย่าง:

 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... } } }

ดังที่แสดงไว้ด้านบน คุณสมบัติ attributeFilter ยอมรับอาร์เรย์ของแอตทริบิวต์เฉพาะที่ฉันต้องการตรวจสอบ ในตัวอย่างนี้ MutationObserver จะทริกเกอร์การเรียกกลับทุกครั้งที่มีการแก้ไขแอตทริบิวต์ hidden , contenteditable หรือ data-par อย่างน้อยหนึ่งรายการ

  • ดูการสาธิตสด →

ฉันกำลังกำหนดเป้าหมายองค์ประกอบย่อหน้าเฉพาะอีกครั้ง สังเกตเมนูแบบเลื่อนลง Select ซึ่งเลือกแอตทริบิวต์ที่จะเปลี่ยน แอตทริบิวต์ที่ draggable ได้เป็นแอตทริบิวต์เดียวที่ไม่ผ่านเกณฑ์เนื่องจากฉันไม่ได้ระบุแอตทริบิวต์นั้นในตัวเลือกของฉัน

สังเกตในรหัสว่าฉันใช้คุณสมบัติ attributeName ของวัตถุ MutationRecord อีกครั้งเพื่อบันทึกว่าแอตทริบิวต์ใดที่มีการเปลี่ยนแปลง และแน่นอน เช่นเดียวกับการสาธิตอื่นๆ MutationObserver จะไม่เริ่มตรวจสอบการเปลี่ยนแปลงจนกว่าจะคลิกปุ่ม "เริ่ม"

อีกสิ่งหนึ่งที่ฉันควรชี้ให้เห็นในที่นี้คือ ฉันไม่จำเป็นต้องตั้งค่า attributes true ในกรณีนี้ มันบอกเป็นนัยเนื่องจาก attributesFilter ตัวกรองถูกตั้งค่าเป็นจริง นั่นเป็นสาเหตุที่อ็อบเจกต์ options ของฉันอาจมีลักษณะดังนี้ และมันจะทำงานเหมือนกัน:

 let options = { attributeFilter: ['hidden', 'contenteditable', 'data-par'] }

ในทางกลับกัน หากฉันตั้งค่า attributes false อย่างชัดเจนพร้อมกับอาร์เรย์ attributeFilter ตัวกรอง มันจะไม่ทำงานเพราะค่า false จะมีความสำคัญเหนือกว่าและตัวเลือกตัวกรองจะถูกละเว้น

การสังเกตการเปลี่ยนแปลงของโหนดและแผนผังย่อย

จนถึงตอนนี้เมื่อตั้งค่า MutationObserver แต่ละรายการ ฉันได้จัดการกับองค์ประกอบที่เป็นเป้าหมายเท่านั้น และในกรณีของ childList ที่อยู่ใกล้เคียงขององค์ประกอบ แต่อาจมีกรณีที่ฉันต้องการสังเกตการเปลี่ยนแปลงอย่างใดอย่างหนึ่งต่อไปนี้:

  • องค์ประกอบและองค์ประกอบย่อยทั้งหมด
  • แอตทริบิวต์อย่างน้อยหนึ่งรายการบนองค์ประกอบและองค์ประกอบย่อย
  • โหนดข้อความทั้งหมดภายในองค์ประกอบ

ทั้งหมดข้างต้นสามารถทำได้โดยใช้คุณสมบัติ subtree ของอ็อบเจ็กต์ตัวเลือก

childList พร้อมทรีย่อย

ก่อนอื่น ให้มองหาการเปลี่ยนแปลงโหนดย่อยขององค์ประกอบ แม้ว่าจะไม่ใช่โหนดย่อยในทันทีก็ตาม ฉันสามารถเปลี่ยนออบเจกต์ options ให้มีลักษณะดังนี้:

 options = { childList: true, subtree: true }

อย่างอื่นในโค้ดจะเหมือนกันมากหรือน้อยกับตัวอย่าง childList ก่อนหน้า พร้อมด้วยมาร์กอัปและปุ่มเพิ่มเติม

  • ดูการสาธิตสด →

ที่นี่มีสองรายการ หนึ่งซ้อนอยู่ภายในอีกรายการหนึ่ง เมื่อ MutationObserver เริ่มทำงาน การเรียกกลับจะทริกเกอร์การเปลี่ยนแปลงในรายการใดรายการหนึ่ง แต่ถ้าฉันต้องเปลี่ยนคุณสมบัติ subtree กลับ false (ค่าเริ่มต้นเมื่อไม่มีอยู่) การเรียกกลับจะไม่ดำเนินการเมื่อมีการแก้ไขรายการที่ซ้อนกัน

คุณสมบัติ ด้วยทรีย่อย

นี่เป็นอีกตัวอย่างหนึ่ง คราวนี้ใช้ subtree ที่มี attributes และ attributeFilter ตัวกรอง สิ่งนี้ทำให้ฉันสามารถสังเกตการเปลี่ยนแปลงของแอตทริบิวต์ไม่เฉพาะในองค์ประกอบเป้าหมาย แต่ยังรวมถึงแอตทริบิวต์ขององค์ประกอบย่อยขององค์ประกอบเป้าหมายด้วย:

 options = { attributes: true, attributeFilter: ['hidden', 'contenteditable', 'data-par'], subtree: true }
  • ดูการสาธิตสด →

ซึ่งคล้ายกับการสาธิตแอตทริบิวต์ก่อนหน้านี้ แต่คราวนี้ ฉันได้ตั้งค่าองค์ประกอบการเลือกที่แตกต่างกันสองรายการ รายการแรกแก้ไขแอตทริบิวต์ในองค์ประกอบย่อหน้าเป้าหมาย ในขณะที่อีกรายการแก้ไขแอตทริบิวต์ในองค์ประกอบลูกภายในย่อหน้า

อีกครั้ง หากคุณต้องตั้งค่าตัวเลือก subtree กลับ false (หรือลบออก) ปุ่มสลับที่สองจะไม่ทริกเกอร์การเรียกกลับ MutationObserver และแน่นอน ฉันสามารถละเว้น attributeFilter ได้ และ MutationObserver จะมองหาการเปลี่ยนแปลงของแอตทริบิวต์ ใดๆ ในแผนผังย่อย แทนที่จะเป็นรายการที่ระบุ

characterData พร้อมทรีย่อย

โปรดจำไว้ว่าในการสาธิต characterData ก่อนหน้านี้ มีปัญหาบางอย่างกับโหนดเป้าหมายที่หายไปและ MutationObserver ไม่ทำงานอีกต่อไป แม้ว่าจะมีวิธีหลีกเลี่ยงปัญหานั้น แต่การกำหนดเป้าหมายองค์ประกอบโดยตรงง่ายกว่าโหนดข้อความ จากนั้นใช้คุณสมบัติ subtree เพื่อระบุว่าฉันต้องการข้อมูลอักขระทั้งหมดภายในองค์ประกอบนั้น ไม่ว่าจะซ้อนอยู่ลึกแค่ไหนก็ตาม การเรียกกลับ MutationObserver

ตัวเลือกของฉันในกรณีนี้จะมีลักษณะดังนี้:

 options = { characterData: true, subtree: true }
  • ดูการสาธิตสด →

หลังจากที่คุณเริ่มการสังเกต ลองใช้ CTRL-B และ CTRL-I เพื่อจัดรูปแบบข้อความที่แก้ไขได้ คุณจะสังเกตเห็นว่าการทำงานนี้มีประสิทธิภาพมากกว่าตัวอย่าง characterData ก่อนหน้านี้ ในกรณีนี้ โหนดย่อยที่เสียหายจะไม่ส่งผลกระทบต่อผู้สังเกต เนื่องจากเรากำลังสังเกตโหนดทั้งหมดภายในโหนดเป้าหมาย แทนที่จะเป็นโหนดข้อความเดียว

การบันทึกค่าเก่า

บ่อยครั้งเมื่อสังเกตการเปลี่ยนแปลงใน DOM คุณจะต้องจดบันทึกค่าเก่าและอาจเก็บไว้หรือใช้ที่อื่น ซึ่งสามารถทำได้โดยใช้คุณสมบัติที่แตกต่างกันสองสามอย่างในอ็อบเจ็กต์ options

คุณลักษณะOldValue

ขั้นแรก ให้ลองออกจากระบบค่าแอตทริบิวต์เดิมหลังจากที่มีการเปลี่ยนแปลง ตัวเลือกของฉันจะมีลักษณะอย่างไรพร้อมกับการโทรกลับของฉัน:

 options = { attributes: true, attributeOldValue: true } function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } }
  • ดูการสาธิตสด →

สังเกตการใช้ attributeName และ oldValue ของอ็อบเจ็กต์ MutationRecord ลองใช้การสาธิตโดยป้อนค่าต่างๆ ลงในช่องข้อความ สังเกตว่าบันทึกจะอัปเดตเพื่อสะท้อนถึงค่าก่อนหน้าที่เก็บไว้อย่างไร

characterDataOldValue

ในทำนองเดียวกัน ตัวเลือกของฉันจะมีลักษณะอย่างไรหากฉันต้องการบันทึกข้อมูลอักขระเก่า:

 options = { characterData: true, subtree: true, characterDataOldValue: true }
  • ดูการสาธิตสด →

สังเกตว่าข้อความบันทึกระบุค่าก่อนหน้า สิ่งต่าง ๆ ค่อนข้างว่องไวเมื่อคุณเพิ่ม HTML ผ่านคำสั่ง Rich Text ในการมิกซ์ ฉันไม่แน่ใจว่าพฤติกรรมที่ถูกต้องควรเป็นอย่างไรในกรณีนี้ แต่จะตรงไปตรงมากว่านี้หากสิ่งเดียวที่ในองค์ประกอบคือโหนดข้อความเดียว

การสกัดกั้นการกลายพันธุ์โดยใช้ takeRecords()

อีกวิธีหนึ่งของวัตถุ MutationObserver ที่ฉันยังไม่ได้กล่าวถึงคือ takeRecords() วิธีนี้ช่วยให้คุณสามารถสกัดกั้นการกลายพันธุ์ที่ตรวจพบได้มากหรือน้อยก่อนที่จะประมวลผลโดยฟังก์ชันเรียกกลับ

ฉันสามารถใช้คุณสมบัตินี้โดยใช้บรรทัดดังนี้:

 let myRecords = observer.takeRecords();

เก็บรายการการเปลี่ยนแปลง DOM ในตัวแปรที่ระบุ ในการสาธิตของฉัน ฉันกำลังดำเนินการคำสั่งนี้ทันทีที่คลิกปุ่มที่แก้ไข DOM สังเกตว่าปุ่มเริ่มต้นและปุ่มเพิ่ม/ลบไม่ได้บันทึกอะไรเลย เนื่องจากตามที่กล่าวไว้ ฉันกำลังสกัดกั้นการเปลี่ยนแปลง DOM ก่อนที่พวกเขาจะประมวลผลโดยการโทรกลับ

อย่างไรก็ตาม โปรดสังเกตว่าฉันกำลังทำอะไรในตัวฟังเหตุการณ์ที่หยุดผู้สังเกต:

 btnStop.addEventListener('click', function () { observer.disconnect(); if (myRecords) { console.log(`${myRecords[0].target} was changed using the ${myRecords[0].type} option.`); } }, false);

อย่างที่คุณเห็น หลังจากที่หยุดผู้สังเกตการณ์โดยใช้ observer.disconnect() ฉันกำลังเข้าถึงบันทึกการกลายพันธุ์ที่ถูกสกัดกั้น และฉันกำลังบันทึกองค์ประกอบเป้าหมายรวมถึงประเภทของการกลายพันธุ์ที่บันทึกไว้ ถ้าฉันสังเกตการเปลี่ยนแปลงหลายประเภท เร็กคอร์ดที่เก็บไว้จะมีมากกว่าหนึ่งรายการในนั้น แต่ละรายการมีประเภทของตัวเอง

เมื่อบันทึกการกลายพันธุ์ถูกสกัดกั้นด้วยวิธีนี้โดยการเรียก takeRecords() คิวของการกลายพันธุ์ที่ปกติจะถูกส่งไปยังฟังก์ชันเรียกกลับจะว่างเปล่า ดังนั้น หากคุณจำเป็นต้องสกัดกั้นระเบียนเหล่านี้ด้วยเหตุผลบางประการก่อนที่จะประมวลผล takeRecords() จะมีประโยชน์

การสังเกตการเปลี่ยนแปลงหลายอย่างโดยใช้ผู้สังเกตการณ์คนเดียว

โปรดทราบว่าหากฉันกำลังมองหาการกลายพันธุ์บนโหนดที่แตกต่างกันสองโหนดบนหน้าเว็บ ฉันสามารถทำได้โดยใช้ผู้สังเกตการณ์คนเดียวกัน ซึ่งหมายความว่าหลังจากที่ฉันเรียกตัวสร้าง ฉันสามารถรันเมธอด observe() สำหรับองค์ประกอบได้มากเท่าที่ต้องการ

ดังนั้นหลังจากบรรทัดนี้:

 observer = new MutationObserver(mCallback);

จากนั้นฉันสามารถมีการเรียกใช้ observe() หลายครั้งโดยมีองค์ประกอบต่างกันเป็นอาร์กิวเมนต์แรก:

 observer.observe(mList, options); observer.observe(mList2, options);
  • ดูการสาธิตสด →

เริ่มผู้สังเกตการณ์ จากนั้นลองใช้ปุ่มเพิ่ม/ลบสำหรับทั้งสองรายการ สิ่งเดียวที่จับได้คือถ้าคุณกดปุ่ม "หยุด" ปุ่มใดปุ่มหนึ่ง ผู้สังเกตการณ์จะหยุดสังเกตทั้งสองรายการ ไม่ใช่แค่ปุ่มที่กำหนดเป้าหมายเท่านั้น

การย้ายต้นไม้ฐานที่กำลังถูกสังเกต

สิ่งสุดท้ายที่ฉันจะชี้ให้เห็นก็คือ MutationObserver จะยังคงเฝ้าสังเกตการเปลี่ยนแปลงของโหนดที่ระบุ แม้ว่าโหนดนั้นจะถูกลบออกจากองค์ประกอบหลักแล้ว

ตัวอย่างเช่น ลองใช้การสาธิตต่อไปนี้:

  • ดูการสาธิตสด →

นี่เป็นอีกตัวอย่างหนึ่งที่ใช้ childList เพื่อตรวจสอบการเปลี่ยนแปลงองค์ประกอบย่อยขององค์ประกอบเป้าหมาย สังเกตปุ่มที่ยกเลิกการเชื่อมต่อรายการย่อย ซึ่งเป็นปุ่มที่ถูกสังเกต คลิกปุ่ม “เริ่ม…” จากนั้นคลิกปุ่ม “ย้าย…” เพื่อย้ายรายการที่ซ้อนกัน แม้ว่ารายการจะถูกลบออกจากพาเรนต์แล้ว MutationObserver ยังคงสังเกตการเปลี่ยนแปลงที่ระบุ ไม่น่าแปลกใจเลยที่สิ่งนี้จะเกิดขึ้น แต่เป็นสิ่งที่ควรคำนึงถึง

บทสรุป

ซึ่งครอบคลุมคุณลักษณะหลักเกือบทั้งหมดของ MutationObserver API ฉันหวังว่าการดำน้ำลึกนี้จะเป็นประโยชน์สำหรับคุณในการทำความคุ้นเคยกับมาตรฐานนี้ ดังที่กล่าวไว้ การสนับสนุนเบราว์เซอร์นั้นแข็งแกร่ง และคุณสามารถอ่านเพิ่มเติมเกี่ยวกับ API นี้ได้ในหน้าของ MDN

ฉันได้ใส่การสาธิตทั้งหมดสำหรับบทความนี้ไว้ในคอลเล็กชัน CodePen หากคุณต้องการให้มีที่ที่ง่ายในการยุ่งกับการสาธิต