프론트엔드 첫걸음

forEach(parent.appendChild.bind(parent)) 이해하기 본문

개발 공부/React

forEach(parent.appendChild.bind(parent)) 이해하기

차정 2023. 3. 3. 12:53

개요

김민태 React 강의를 보다가 DOM을 추가하는 코드에서 forEach(bind()) 부분이 이해되지 않았다. 처음에는 forEach(child => parent.appendChild(child))는 이해가 됐는데, forEach(parent.appendChild.bind(parent))가 같은 동작을 한다는 점이 납득되지 않았다. 그래서 이 부분을 공부한 내용을 정리해 본다.

function createDOM(vdom) {
  const element = document.createElement(vdom.tag);  // element가 생성됨

  vdom.children
    .map(createDOM)  // 각 자식 요소를 재귀적으로 DOM 요소로 변환
    .forEach(element.appendChild.bind(element)); //변환된 요소들을 부모에 추가

  return element;  //현재 노드를 반환 → 이게 상위 노드의 자식이 됨
}

const vdom = {
  tag: 'p',
  children: [
    {
      tag: 'h1',
      children: ["React 만들기"],
    },
  ],
};

const rootElement = createDOM(vdom);
document.querySelector('#root').appendChild(rootElement);

 

forEach 함수의 특징

forEach는 내부적으로 배열 요소를 콜백 함수의 첫 번째 인자로 전달한다.

array.forEach(callback);

이 코드에서 callback 함수는 item을 매개변수로 받게 된다.

예제

아래 두 코드는 같은 동작을 한다.

const items = ["A", "B", "C"];
items.forEach(console.log);
const items = ["A", "B", "C"];
items.forEach(item => console.log(item));

두 번째 코드는 item을 명시적으로 전달했지만, 첫 번째 코드에서도 forEach()가 내부적으로 배열 요소를 console.log()의 첫 번째 인자로 넘겨준다.

실행 결과:

A B ["A", "B", "C"]
B 1 ["A", "B", "C"]
C 2 ["A", "B", "C"]

이는 console.log가 기본적으로 여러 개의 인자를 받을 수 있기 때문이다. forEach (item, index, array) 세 개의 값을 넘겨주기 때문에 console.log에 의해 인덱스와 전체 배열까지 출력된다.

즉, forEach는 내부적으로 item을 자동으로 넘겨준다. 이것이 forEach(parent.appendChild.bind(parent))가 동작하는 핵심 원리다.

 
 
 

bind()를 사용하여 this를 고정

appendChild는 this가 필요하다.

appendChild this가 부모 요소를 가리켜야 정상적으로 실행된다.
하지만, 
forEach(parent.appendChild)를 직접 실행하면 오류가 발생한다.

bind 없이 실행하면 오류 발생

const parent = document.createElement("div");
const child1 = document.createElement("p");
const child2 = document.createElement("span");

[parent, child1, child2].forEach(parent.appendChild); // 오류 발생!

이렇게 하면 appendChild this parent가 아니라 window undefined가 되어서 실행되지 않는다.

bind(parent)로 this 고정

const parent = document.createElement("div");
const child1 = document.createElement("p");
const child2 = document.createElement("span");

[child1, child2].forEach(parent.appendChild.bind(parent));

이제 내부적으로 실행되는 동작은 다음과 같다:

parent.appendChild(child1);
parent.appendChild(child2);

즉, bind(parent)를 하면 this parent로 고정되어 올바르게 실행된다.

 

예시

// 부모 요소 생성
const parent = document.createElement("div");

// 자식 요소 생성
const child1 = document.createElement("p");
const child2 = document.createElement("span");

// 방법 1: 일반적인 forEach 사용
[child1, child2].forEach(child => parent.appendChild(child));

// 방법 2: bind 사용
[child1, child2].forEach(parent.appendChild.bind(parent));

// 결과 확인
console.log(parent.outerHTML);

실행 결과

<div>
  <p></p>
  <span></span>
</div>

두 방법 모두 같은 결과를 만들어냄을 확인할 수 있다.


결론

  • forEach()는 배열 요소를 자동으로 callback의 첫 번째 인자로 전달한다.
  • bind(parent)를 사용하면 this parent로 고정할 수 있다.
  • 결국 forEach(child => parent.appendChild(child)) forEach(parent.appendChild.bind(parent))는 같은 동작을 한다.

이제 forEach(bind())가 왜 동작하는지 확실히 이해되었다.
화살표 함수는 상위 스코프의 this를 그대로 사용하기 때문에 bind 안해줘도 된다.
나라면   .forEach(child => element.appendChild(child)); 했을 것 같다.