DOM이 바뀔 때 MutationObserver는 변경 내용을 MutationRecord로 기록하고, 같은 에이전트에 대기 중인 observer를 마이크로태스크에서 알립니다. 각 변경마다 콜백이 한 번씩 실행된다고 가정하지 마세요. DOM 표준은 대기열과 전달 절차를 정의하고, MDN observe() 설명은 설정 예시를 보입니다.

관찰 대상에서 변경 레코드가 모여 콜백으로 전달되는 개념도

개념 삽화이며 브라우저 실행 결과가 아닙니다.

observe 옵션이 관찰 범위를 정한다

observer.observe(target, options)에서 childList, attributes, characterData 중 어떤 변화를 기록할지 지정합니다. subtree:true는 대상 아래 자손의 변경도 관찰하겠다는 뜻입니다. 원하는 종류를 명시하지 않고 subtree만 켜면 유효한 관찰 유형이 없으므로 설정 오류가 될 수 있습니다. 한 observer는 여러 대상을 관찰할 수 있으며 같은 대상에 다시 observe하면 옵션을 갱신합니다.

변경은 묶여 전달된다

표준 알고리즘은 observer가 이미 대기 중이면 중복 마이크로태스크를 새로 예약하지 않습니다. 콜백에는 그 시점에 대기열에서 꺼낸 레코드 목록이 전달됩니다. 따라서 연속 변경의 수와 콜백 횟수는 일대일이 아닙니다. 콜백 안에서 다시 DOM을 바꾸면 새 레코드가 만들어져 후속 전달을 예약할 수 있습니다. 이는 특정 프레임이나 고정 지연 시간에 전달된다는 보장이 아닙니다.

자손 제거 직후의 관찰

subtree 관찰 중인 자손이 제거되면, 제거 기록이 전달되기 전까지 해당 분리 하위 트리의 변경도 기존 관찰에서 포착될 수 있습니다. 표준은 이를 위해 임시 등록 관찰자를 사용합니다. 제거 레코드가 콜백에 전달된 뒤에는 그 임시 연결이 정리됩니다. 이를 근거로 분리된 노드를 계속 감시할 수 있다고 일반화하지 마세요. 독립적으로 등록한 다른 observer의 관찰은 별개입니다.

필요한 레코드만 남기기

attributes, characterData, childList를 실제 목적에 맞게 고르고, attributeFilter로 관심 속성을 좁힐 수 있습니다. attributeOldValue:true는 이전 속성값이 필요할 때 사용합니다. attributes:false와 함께 지정하면 모순된 설정이고, 속성 필터나 이전 값 옵션의 존재는 attributes 관찰을 활성화할 수 있습니다. characterDataOldValue에도 같은 점검이 필요합니다. 사용이 끝난 observer는 disconnect()로 등록을 끊고, 연결을 끊기 전에 미전달 레코드가 필요한지 takeRecords()로 확인합니다. 무거운 DOM 전체 재검색을 매 콜백마다 반복하기보다 레코드의 target과 변경 유형으로 작업 범위를 정하세요.

옵션 조합과 레코드 내용 점검

예를 들어 목록 자식 노드 추가·삭제만 필요하다면 observer.observe(list, {childList: true, subtree: true})처럼 변경 종류를 명시합니다. 속성 변경과 이전 값이 모두 필요하면 attributes: true, attributeOldValue: true를 함께 설정하고, attributeFilter: ['class', 'aria-expanded']로 범위를 줄일 수 있습니다. 필터나 oldValue 옵션이 있으면 attributes를 생략해도 활성화될 수 있으므로 옵션 객체를 명확히 쓰세요. attributes: false와 attributeOldValue: true처럼 상충하는 조합은 예외를 일으킵니다. characterDataOldValue도 같은 방식으로 characterData 설정과 맞춰야 합니다. MDN의 observe 옵션표는 기본값과 오류 조건을 설명합니다.

MutationRecord 명세 설명에서 type, target, addedNodes, removedNodes, attributeName, oldValue를 확인할 수 있습니다. 콜백에서 record.type별로 필요한 대상만 처리하고, 큰 하위 트리를 매번 다시 비교하지 마세요. 콜백은 변경 알림이지 변경 전후 DOM 전체의 스냅샷은 아닙니다. oldValue를 요청하지 않았다면 과거 값을 레코드에서 복원할 수 없고, 참조된 노드의 현재 상태는 콜백 실행 시점에 이미 더 바뀌었을 수 있습니다.

한 번의 처리 직전에 동기적으로 남은 레코드를 읽어야 한다면 takeRecords()를 사용해 큐를 비울 수 있습니다. disconnect()는 이후 감시를 멈추지만 미전달 변경을 자동으로 애플리케이션 상태에 반영하지는 않습니다. 정리 전에 takeRecords() 결과를 처리할지 버릴지 정하세요. 반대로 콜백 처리 중 이미 받은 레코드를 무조건 다시 조회하면 같은 변경을 중복 반영할 수 있습니다.

전문가 관점: 알림과 상태 동기화 구분

MDN contributors는 제거된 하위 트리도 제거 레코드가 전달되기 전까지 감시될 수 있다고 설명합니다. 실무적으로 MutationObserver를 영구 상태 저장소로 취급하지 말고, 콜백의 레코드와 현재 DOM을 구분하세요. 애플리케이션의 기준 상태를 별도로 유지하면 제거·재연결 경계에서 누락이나 중복 반영을 줄일 수 있습니다.

출처