Frontend/🔨 JS

getElementsByClassName 함수 구현하기 (+자바스크립트에서 유사배열과 배열, 변환 방법)

haeunkim.on 2025. 9. 3. 18:56

프론트엔드 라이브 코딩이나 화이트보드 테스트에서 자주 나오는 과제 중 하나가 DOM API를 직접 구현해보는 문제입니다. 그중 대표적인 것이 document.getElementsByClassName을 직접 만들어보라는 과제입니다. 이번 글에서는 이 문제를 어떻게 접근하면 좋을지 베이스케이스 → 엣지케이스 → 효율성 → 가독성 순서로 풀어봅니다.

 

 

문제 이해

목표는 주어진 className을 가진 모든 요소를 찾아 배열로 반환하는 함수입니다.
기본적으로는 DOM 트리를 순회하면서 조건에 맞는 요소를 모으면 됩니다.

function getElementsByClassName(className) {
  const bodyChildrenEls = document.body.children;
  const result = [];
  const stack = [...bodyChildrenEls];

  while (stack.length) {
    const currentElement = stack.pop();
    const currentElementChildren = currentElement.children;

    for (let i = 0; i < currentElementChildren.length; i++) {
      const currentChildrenEl = currentElementChildren[i];
      stack.push(currentChildrenEl);
    }

    if (currentElement.classList.contains(className)) {
      result.push(currentElement);
    }
  }

  return result;
}

 

 

1. 베이스케이스

 

  • 최상위 요소에만 클래스가 붙은 경우
  • DOM이 비어있을 때 (<body></body>)
  • 찾는 클래스가 없는 경우
function getElementsByClassName(className) {
  const bodyChildrenEls = document.body.children;
  const result = [];

  for (let i = 0; i < bodyChildrenEls.length; i++) {
    const currentEl = bodyChildrenEls[i];
    const currentElClassName = currentEl.className;

    if (currentElClassName === className) {
      result.push(currentEl);
    }
  }

  return result;
}

 

 

2. 앳지 케이스

  • 클래스가 여러개 붙은 경우
  • 중첩된 요소일 경우
  • 중첩 댑스가 깊어질 경우 ... 등 그 외에 실시간으로 추가되는 조건들 .. ? 

2-1. 중첩된 자식까지 순회 (DFS / Stack)

모든 하위 노드까지 탐색할 수 있도록 stack을 도입합니다.

function getElementsByClassName(className) {
  const bodyChildrenEls = document.body.children;
  const result = [];
  const stack = [...bodyChildrenEls];

  while (stack.length) {
    const currentElement = stack.pop();
    const children = currentElement.children;

    // 자식 요소 스택에 추가
    for (let i = 0; i < children.length; i++) {
      stack.push(children[i]);
    }

    if (currentElement.className === className) {
      result.push(currentElement);
    }
  }

  return result;
}
 

✅ 동작: 중첩 구조까지 탐색 가능
❌ 문제: className이 "a b"처럼 여러 개일 경우 정확히 일치하지 않음

 

2-2 다중클래스 대응 ("a b")

function getElementsByClassName(className) {
  const bodyChildrenEls = document.body.children;
  const result = [];
  const stack = [...bodyChildrenEls];

  while (stack.length) {
    const currentElement = stack.pop();
    const children = currentElement.children;

    for (let i = 0; i < children.length; i++) {
      stack.push(children[i]);
    }

    // classList.contains로 특정 클래스만 검사
    if (currentElement.classList.contains(className)) {
      result.push(currentElement);
    }
  }

  return result;
}

 

✅ 동작: <div class="a b"> 에서 getElementsByClassName("a") 호출 시 정상적으로 매칭됨

 

3. 효율성 (시간복잡도 & 공간복잡도)

  • 시간복잡도: 모든 노드를 한 번씩 방문하므로 O(N) (N은 DOM 노드 수)
  • 공간복잡도: 스택과 결과 배열에 최대 O(N)까지 저장 가능


4. 가독성 

면접 후반에는 코드의 깔끔함도 평가 요소가 될 수 있음 

  • stack → pendingNodes
  • result → matchedElements
  • currentElementChildren → children

 

 

 

+ 유사배열 정리 
자바스크립트를 쓰다 보면 배열처럼 보이는데 배열은 아닌 값들을 종종 만납니다. 흔히 유사배열(array-like) 이라고 부르는 것들입니다. 

유사배열이란?

유사배열은 length 속성과 인덱스 번호를 가진 객체입니다. 겉모습은 배열 같지만 실제 배열 메서드(map, forEach, filter 등)는 사용할 수 없습니다.

 

대표적인 예시:

  • 함수 안의 arguments
  • document.querySelectorAll 같은 DOM API 결과값
  • HTMLCollection
 
function example() {
  console.log(arguments); // [Arguments] { '0': 1, '1': 2, '2': 3 }
}

example(1, 2, 3);
const divs = document.querySelectorAll('div');
console.log(divs); // NodeList(3) [div, div, div]

 


배열과의 차이

배열은 Array.prototype을 상속받아 다양한 메서드를 쓸 수 있습니다. 반면 유사배열은 단순 객체라 메서드를 직접 사용하면 에러가 납니다.

 
const arr = [1, 2, 3];
arr.forEach(x => console.log(x)); // 정상 동작

const divs = document.querySelectorAll('div');
divs.forEach(x => console.log(x)); 
// TypeError: divs.forEach is not a function

유사배열 → 배열 변환 방법

1. Array.from()

ES6에서 도입된 가장 간단하고 직관적인 방법입니다.

 
const divs = document.querySelectorAll('div');
const divArray = Array.from(divs);

divArray.forEach(div => console.log(div));

2. 스프레드 연산자 [... ]

더 간결하게 배열로 만들 수 있습니다.

const argsToArray = (...args) => {
  const arr = [...arguments];
  console.log(arr);
};

argsToArray(1, 2, 3); // [1, 2, 3]
 

3. Array.prototype.slice.call()

ES6 이전에 많이 쓰이던 방식입니다.

 
function oldSchool() {
  const arr = Array.prototype.slice.call(arguments);
  console.log(arr);
}

oldSchool('a', 'b', 'c'); // ['a', 'b', 'c']

그 외 실제 활용 예시

DOM 조작

querySelectorAll로 선택한 결과를 배열로 변환해 map을 쓸 수 있습니다.

 
const buttons = document.querySelectorAll('button');

// 유사배열 그대로는 map 불가
// buttons.map(...) -> 에러 발생

// 변환 후 사용
const texts = [...buttons].map(btn => btn.innerText);
console.log(texts);

함수 인자 처리

arguments 객체를 배열로 바꾸면 편리하게 조작할 수 있습니다.

function sum() {
  const nums = Array.from(arguments);
  return nums.reduce((acc, cur) => acc + cur, 0);
}

console.log(sum(1, 2, 3, 4)); // 10

타입 판별하기

유사배열인지 배열인지 헷갈릴 때는 Array.isArray()를 쓰면 됩니다.

Array.isArray([1, 2, 3]);        // true
Array.isArray(document.querySelectorAll('div')); // false