만약에 웹 페이지가 열리고 setTimeout으로 2초 후에 이미지 하나가 삽입이 된다고 했을 때
그것을 어떻게 감지할 수 있을까요? 오늘은 그것을 감지할 수 있는 2가지 방법을 공부해보겠습니다.
1. Mutation Events
이건 옛날 방식입니다. (IE를 지원해야 하는 경우에는 이것을 활용하면 좋을 것입니다.)* (주의) MDN 문서에서 Mutation Events 리스너를 추가하면 해당 문서에 대한 추가 DOM 수정의 성능이 크게 저하됩니다.(1.5배 - 7배 느려짐) 또한 리스너를 제거해도 손상에 대해서 다시 되돌릴 수 없다고 합니다.
- DOM level 2에서 도입되었습니다. 현재는 Deprecated 되었고 Mutation Observer를 권장합니다. 아래는 Mutation Events List입니다. 이것을 활용해서 DOM요소의 추가, 삭제, 속성변경 등을 알아낼 수 있습니다
- DOMAttrModified
- DOMAttributeNameChanged
- DOMCharacterDataModified
- DOMElementNameChanged
- DOMNodeInserted
- DOMNodeInsertedIntoDocument
- DOMNodeRemoved
- DOMNodeRemovedFromDocument
- DOMSubtreeModified
코드
function createFunc() { // 2500개의 엘리먼트를 만들어서 삽입.
var root = document.querySelector('#root');
var tempNode = document.createDocumentFragment();
var createNumber = 2500;
var i= 0;
var p;
while(i < createNumber) {
p = (p === undefined) ? document.createElement('p'): p.cloneNode(false);
p.innerText = 'hi';
tempNode.appendChild(p);
i++;
}
root.appendChild(tempNode);
}
function handleEvents() {
var root = document.querySelector('#root');
root.addEventListener('DOMNodeInserted', function(e) {
console.log('돔 변화 감지');
});
}
function main() {
handleEvents();
setTimeout(createFunc, 1000);
}
main();
위의 코드를 실행하면 총 2500개의 "돔 변화 감지"라는 문자열이 console창에 찍힐 것입니다.
문제점
1. Mutation Events는 동기적입니다. 이 이벤트가 호출될 때 다른 이벤트가 실행되지 않을 수 있습니다.
많은 노드를 추가하거나 제거하면 어플리케이션이 지연되거나 중단될 수 있습니다.
2. 이벤트는 캡처링, 버블링을 통해 전파됩니다. 캡처링, 버블링은 DOM을 수정하는 다른 이벤트 리스너를 트리거할 수 있습니다. 이렇게 되면 많은 Mutation Events를 일으킬 수 있습니다. -> javascript쓰레드를 막히게 만들거나 브라우저 충돌이 일어날 가능성이 높습니다.
--> 이런 문제점을 해결하기 위해서 나온 것이 Mutation Observer입니다!
Mutation Observer
이것은 무엇이 다른지 한번 살펴보겠습니다.
- Mutation Observer는 비동기적으로 작동합니다. 이벤트가 발생할 때 마다 실행되지 않습니다.
- 다른 스크립트가 완료될 때까지 기다립니다.
- 배열안에 담아서 기록을 합니다. 모든 노드의 변화를 관찰하거나 특정한 노드만 관찰하는 것이 가능합니다.
- 이벤트가 아니기 때문에 다른 이벤트와 충돌을 하지 않아 UI가 멈추거나 브라우저 충돌을 일으킬 가능성이 적습니다.
위에서 본 코드를 Mutation Observer를 이용해서 실행을 하면 과연 console에 2500개가 찍힐까요?
function createFunc() {
var root = document.querySelector('#root');
var tempNode = document.createDocumentFragment();
var createNumber = 2500;
var i= 0;
var p;
while(i < createNumber) {
p = (p === undefined) ? document.createElement('p'): p.cloneNode(false);
p.innerText = 'hi';
tempNode.appendChild(p);
i++;
}
root.appendChild(tempNode);
}
function handleMutationObserver() {
console.log('옵저버 시작');
var targetNode = document.querySelector('#root');
var config = {
attributes: true,
childList: true,
subtree: true
};
var callback = function(mutationList) {
for(var mutation of mutationList) {
console.log('start loop');
console.log(mutationList); // length -> 2500 array
if(mutation.type === 'childList') {
console.log('A child node has been added or removed');
}
console.log('end loop');
}
}
var observer = new MutationObserver(callback);
observer.observe(targetNode, config);
}
function main() {
handleMutationObserver();
setTimeout(createFunc, 1000);
}
main();
console에는 하나만 출력이 되고 mutationList배열에 2500개가 들어가 있을 것입니다.
observe 메소드를 실행한 뒤 요소가 변할 때 지금은 선택한 노드의 childList, attributes, subtree를 추적할 수 있도록 했습니다. 하지만 더 다양한 설정들이 가능합니다.
// 타겟 element id -> targeting
MutationRecords = {
addedNodes: [], // 추가된 자식 노드, ex) NodeList [div]
attributeName: null, // 변경된 속성명 ex) style, class
attributeNamespace: null, // 변경된 속성네임스페이스
nextSibling: null, // 다음 형제 태그
previousSibling: null, // 이전 형제 태그
oldValue: null, // 변경전 값 // 이것을 얻고 싶다면 설정값에서 attributeOldValue/characterDataOldValue.
//를 먼저 true로 변경할 것 그렇지 않으면 콜백에 새로운 값만 넘어감.
removedNodes: [], // 제거된 자식 노드
target: div#targeting, // 대상 태그
type: 'attributes' || 'childList' || 'characterData' // 어떤 종류가 변경되었는지
}
이런 종류의 config가 있으니 필요한 곳에 잘 갖다 쓰면 될거같습니다.
'코딩 > JavaScript' 카테고리의 다른 글
input 안에 내용 출력하기(자바스크립트) + 예제 (0) | 2021.09.18 |
---|---|
자바스크립트 배열 요소 다루기(push,shift,unshift,pop,concat,splice) (0) | 2021.09.17 |
자바스크립트 클로저(클로저를 쓰는 이유) (0) | 2021.09.16 |
NodeList vs HTMLCollection ( javascript ) (0) | 2021.09.14 |
javascript로 css 가져오기 (0) | 2021.09.13 |