Little Jay

[JS] Vanila JS로 querySelector 구현 본문

FrontEnd

[JS] Vanila JS로 querySelector 구현

Jay, Lee 2023. 1. 11. 10:13

설명

 elementSeparotor는 attribute를 파싱하는 함수입니다. class는 "."으로, id는 "#"으로, tag이름은 알파벳으로 시작하기 때문에 이에 따라서 return 값을 다르게 반환합니다. 

 getElems는 querySelectorAll을 모방한 것입니다. elementSeparator을 기반으로 받은 toFind와, attr을 받아 이를 바탕으로 element를 찾기 시작합니다. querySelectorAll은 element의 배열을 반환하기 때문에 최종적으로는 elementsArr을 반환합니다. 본격적인 logic은 다음과 같습니다. 시작점을 default arugment로 body 태그에서부터 찾기 시작했는데, 성능을 위해 시작점을 임의로 지정해줄 수 있습니다. bfs 알고리즘처럼 해당 start를 배열에 추가해서 해당 item을 찾습니다. 없다면 배열에 push합니다. element.childrend은 HTMLCollection을 반환하기 때문에 해당 element의 요소에서 getAttribute로 attr을 검색합니다. 해당 요소를 가지고 있다면 elementsArr에 push하게 됩니다. 

 getElem의 로직은 getElems와 비슷하지만, 해당 요소를 찾으면 바로 return을 한다는 차이점이 있습니다. 

 

문제점

 querySelector와는 다르게 tag 이름을 가지고 찾는 것은 할 수 없습니다. getAttribute는 class, id같이 tag 안에 있는 속성에 대해서 작동하기 때문에 tag을 찾을 수 없었습니다. 어차피 styling이 들어가면 모든 tag에 class 혹은 id가 들어가기 때문에 tag로서 찾는 부분은 넣지 않았습니다. 

const elementSeparator = (attr) => {
  const firstChar = attr.charAt(0);
  const sliced = attr.slice(1);
  if (firstChar === ".") return { toFind: "class", attr: sliced };
  if (firstChar === "#") return { toFind: "id", attr: sliced };
  return { toFind: "tagname", attr };
};

const getElems = (attributes, start = document.body) => {
  const { toFind, attr } = elementSeparator(attributes);
  const elementsArr = [];
  const queue = [start];
  const visited = {};
  visited[start] = true;
  while (queue.length) {
    for (let i = 0; i < queue.length; i = i + 1) {
      const parent = queue.shift();
      for (const child of parent.children) {
        if (String(child.getAttribute(toFind)).includes(attr))
          elementsArr.push(child);
        else {
          visited[child] = true;
          queue.push(child);
        }
      }
    }
  }
  return elementsArr.length === 0 ? null : elementsArr;
};

const getElem = (attributes, start = document.body) => {
  const { toFind, attr } = elementSeparator(attributes);
  const queue = [start];
  const visited = {};
  visited[start] = true;
  while (queue.length) {
    for (let i = 0; i < queue.length; i = i + 1) {
      const parent = queue.shift();
      for (const child of parent.children) {
        if (String(child.getAttribute(toFind)).includes(attr)) return child;
        else {
          visited[child] = true;
          queue.push(child);
        }
      }
    }
  }
  return "not found";
};
Comments