25.02.27 - 몽키 패치

강연주·2025년 2월 27일

📚 TIL

목록 보기
142/186

몽키 패치(Monkey Patch)란?

몽키 패치(Monkey Patching)는 기존에 존재하는 객체나 라이브러리의 메서드를 직접 수정하여 새로운 기능을 추가하거나 기존 동작을 변경하는 기법이다.

아래 코드에서는 Node.prototype.removeChild와 Node.prototype.insertBefore의 기본 동작을 변경한다. 이 방식은 강력하지만, 원래 기능을 예상치 못한 방식으로 변경할 수 있어 유지보수가 어려워지는 단점을 가진다.


패치 코드

https://github.com/facebook/react/issues/11538#issuecomment-417504600

🖥️ js

if (typeof Node === 'function' && Node.prototype) {
  const originalRemoveChild = Node.prototype.removeChild;
  Node.prototype.removeChild = function(child) {
    if (child.parentNode !== this) {
      if (console) {
        console.error('Cannot remove a child from a different parent', child, this);
      }
      return child;
    }
    return originalRemoveChild.apply(this, arguments);
  }

  const originalInsertBefore = Node.prototype.insertBefore;
  Node.prototype.insertBefore = function(newNode, referenceNode) {
    if (referenceNode && referenceNode.parentNode !== this) {
      if (console) {
        console.error('Cannot insert before a reference node from a different parent', referenceNode, this);
      }
      return newNode;
    }
    return originalInsertBefore.apply(this, arguments);
  }
}

① typeof Node === 'function' && Node.prototype

🖥️ js

if (typeof Node === 'function' && Node.prototype) {
  • Node : 브라우저의 DOM API에서 모든 노드 요소(예: div, p, span 등)가 상속하는 기본 객체.
  • typeof Node === 'function' : Node가 함수인지 확인.
    (자바스크립트에서 클래스도 function 타입이다.)
  • Node.prototype : Node가 존재하고, 그 프로토타입이 정의되어 있는지 확인한다.
    이 조건이 참이면, Node.prototype을 직접 수정할 수 있는 환경이라는 의미.

② 기존 메서드를 저장하기

🖥️ js

const originalRemoveChild = Node.prototype.removeChild;
  • Node.prototype.removeChild : DOM에서 특정 노드를 제거하는 메서드.
  • 원래의 removeChild를 originalRemoveChild 변수에 저장.

→ 이렇게 하면, 나중에 원래 기능을 유지하면서 변경 가능.

🖥️ js

const originalInsertBefore = Node.prototype.insertBefore;
  • insertBefore : 특정 부모 노드에서 newNode를 referenceNode 앞에 삽입하는 메서드.
  • 기존의 동작을 유지하기 위해 원래 메서드를 변수에 저장.

③ removeChild 메서드 패치

🖥️ js

Node.prototype.removeChild = function(child) {

Node.prototype.removeChild를 덮어써서 새로운 동작을 정의


🔹 부모가 다르면 에러 메시지를 출력

🖥️ js

if (child.parentNode !== this) {
  if (console) {
    console.error('Cannot remove a child from a different parent', child, this);
  }
  return child;
}
  • child.parentNode !== this
    : 현재 노드(this)가 child의 부모가 아니면, 즉 잘못된 부모가 삭제를 시도하면 에러 메시지를 출력.
  • console.error(...)를 사용해서 경고를 띄움.

🔹 정상적인 경우, 원래의 removeChild 실행

🖥️ js

return originalRemoveChild.apply(this, arguments);
  • apply(this, arguments) : 원래의 removeChild를 현재 컨텍스트(this)와 원래 인자(arguments)로 실행.즉, 정상적인 경우에는 원래의 동작을 유지한다.

④ insertBefore 메서드 패치

🖥️ js

Node.prototype.insertBefore = function(newNode, referenceNode) {
  • insertBefore도 마찬가지로 원래 동작을 유지하면서 부모 노드 검사를 추가.

🔹 부모가 다르면 에러 메시지를 출력

🖥️ js

if (referenceNode && referenceNode.parentNode !== this) {
  if (console) {
    console.error('Cannot insert before a reference node from a different parent', referenceNode, this);
  }
  return newNode;
}

referenceNode가 존재하면서, referenceNode.parentNode가 현재 노드(this)가 아니면 오류 메시지를 출력한다.
즉, 다른 부모 노드에 있는 referenceNode 앞에 삽입하려 하면 막는 것.


🔹 정상적인 경우, 원래의 insertBefore 실행

🖥️ js
return originalInsertBefore.apply(this, arguments);

원래의 insertBefore를 그대로 실행해서 정상적인 경우에는 동작이 바뀌지 않도록 한다.


Google Translate오류

Google Translate 확장 프로그램은 웹사이트의 DOM을 직접 조작해서 번역된 내용을 삽입하는데,
이 과정에서 removeChild나 insertBefore 같은 메서드가 엉뚱한 부모 노드를 대상으로 실행될 수 있다.

예를 들어

🖥️ js

someParent.removeChild(someChild); // someChild의 부모가 someParent가 아닐 수도 있음

이런 문제의 경우 브라우저 콘솔에 Uncaught DOMException 같은 에러 발생.

위 패치 코드는

  1. 부모가 다를 경우 에러를 출력하고 실행을 막음
    → 의도치 않은 DOM 조작을 방지.
  2. 정상적인 경우에는 원래 기능을 그대로 유지
    → 사이트의 원래 기능을 해치지 않음.

  • 몽키 패치 : 기존의 메서드를 변경하는 기법.
  • 코드 역할 : removeChild와 insertBefore가 잘못된 부모에서 호출되는 것을 방지.
  • 동작 방식 :
    • 기존 메서드를 저장 (originalRemoveChild, originalInsertBefore)
    • 새로운 메서드를 덮어씀 (Node.prototype.removeChild, Node.prototype.insertBefore)
    • 부모 노드가 다르면 에러 메시지를 출력하고 실행을 막음
    • 정상적인 경우에는 원래 메서드를 실행함
profile
아무튼, 개발자

0개의 댓글