Mission: QuerySelector 만들기

Taehun Jeong·2023년 2월 22일
0
post-thumbnail

querySelector()

javascript에서 Element를 선택할 때 가장 애용하는 기능 중 하나인 querySelector는 선택자를 파라미터로 받아 그에 맞는 첫번째 element를 반환하는 함수다.
querySelector의 탐색은 깊이우선 전위 순회로, 문서의 첫 번째 요소부터 시작해 자식 노드의 수를 기준으로 순회한다.
querySelecotr를 직접 구현해보고 사용해보자.


DFS with ClassList

탐색 방법인 DFS를 구현하기 위해 재귀의 형태로 하위 노드들을 순회할 수 있도록 한다. 일단 부모 노드와 클래스 이름을 입력받으면 그에 해당하는 클래스가 있을 경우 반환하는 방식이다. 그렇지 않으면 childNodes를 차례로 순회하는 방식이다.

function QuerySelect(Node, ClassName) {
    if(Node.classList.contains(ClassName)) return Node;

    Node.childNodes.forEach((childNode) => {
        QuerySelect(childNode, ClassName);
    });
}

파라미터로 노드와 찾아야 할 클래스의 이름을 받아 Node.classList.contains로 노드에 찾아야 할 이름의 클래스가 포함되어 있는지 확인한다. 만약 포함되어 있으면 노드를 반환한다. 첫 요소를 반환하기 위해서는 종료 조건이 반드시 있어야 하니 종료 조건을 포함시켜 주자.

function QuerySelect(Node, ClassName) {
  if (Node.classList && Node.classList.contains(ClassName)) {
    return Node;
  }
  let result = null;
  Node.children.forEach((childNode) => {
    const childResult = QuerySelect(childNode, ClassName);
    if (childResult !== null) {
      result = childResult;
    }
  });
  return result;
}

조건에 일치하지 않으면 null을 반환하고 classList에 찾고자 하는 className이 포함되어 있다면 해당 노드를 반환할 것이다. 이대로 실행하면 어떻게 될까?

예상은 했지만 실행은 되지 않는다. 왜일까? Node.childNodes의 반환형태는 NodeList이다. NodeList는 forEach를 사용할 수 없기 때문이다. forEach는 Array에서 사용 가능하다. prototype을 사용해 HTMLCollection.prototype.forEachArray.prototype.forEach처럼 사용할 수 있게 해보자.

HTMLCollection.prototype.forEach=Array.prototype.forEach

function QuerySelect(Node, ClassName) {
  if (Node.classList && Node.classList.contains(ClassName)) {
    return Node;
  }
  let result = null;
  Node.children.forEach((childNode) => {
    const childResult = QuerySelect(childNode, ClassName);
    if (childResult !== null) {
      result = childResult;
    }
  });
  return result;
}

정상적으로 실행은 되지만 뭔가 이상하다... 조건에 부합하는 첫 번째 element를 반환해야 하지만 마지막 element를 반환하는 문제가 있다. 첫 번째 element를 찾는 즉시 함수를 중단해야 하지만 이 문제를 해결하기까지 생각보다 시간이 오래 걸렸다. 중단해야 할 점을 어디에 넣어야 하는지 계속 헤맸기 때문이다.

HTMLCollection.prototype.forEach=Array.prototype.forEach

function QuerySelect(Node, ClassName) {
  if (Node.classList && Node.classList.contains(ClassName)) {
    return Node;
  }
  let result = null;
  Node.children.forEach((childNode) => {
    if (result !== null) {
      return;
    }
    const childResult = QuerySelect(childNode, ClassName);
    if (childResult !== null) {
      result = childResult;
    }
  });
  return result;
}

forEach 순회를 돌면서 반복문의 시작에 result 값을 확인함으로써 원하는 결과를 얻을 수 있었다. 조건에 부합하는 element를 찾으면 result는 null이 아니게 되고 이후의 탐색은 조건문에 따라 함수를 실행하지 않는다. className 하나만을 찾는 데에도 생각보다 시간이 오래 걸렸다. 다음으로 id, tag 등으로 탐색을 진행해보자.


References

mdn web docs)Document.querySelector()
mdn web docs)NodeList.prototype.forEach()

profile
안녕하세요

0개의 댓글