[TIL] 2023/07/28

yongkini ·2023년 7월 28일
2

Today I Learned

목록 보기
143/173

  • DOM Node들을 sorting 하는 방법에는 뭐가 있을까?
    : 혼자 토이 프로젝트로 vanilaJS 만을 써서 todolist와 비슷한걸 만들고자 했다. 이 때, todolist 제목을 기준으로 정렬하는 기능도 만들었는데, 여기서 dom api 가 제공하는 childNodes 를 가지고 sort를 하려면 어떻게 해야할지가 바로 떠오르지 않았다. 그래서 일단 떠올린건 childNodes, 즉, NodeList는 실제로 배열이 아닌 유사배열 객체이기 때문에 Array.prototype.sort를 사용할 수 없다는 점이다. 따라서
      Array.prototype.sort
        .call(bodyElement.childNodes, (a, b) => {
          const bookNameNode1 = a.childNodes[1];
          const bookNameNode2 = b.childNodes[1];
          return bookNameNode1.innerText > bookNameNode2.innerText
            ? 1
            : bookNameNode1.innerText < bookNameNode2.innerText
            ? -1
            : 0;
        })
        .forEach((node) => bodyElement.appendChild(node));

위와 같이 코드를 짜봤다. 하지만,

Uncaught TypeError: Failed to set an indexed property on 'NodeList': Indexed property setter is not supported.

위와 같은 에러를 리턴했다. 찾아보니 브라우저 별로 다르긴 하지만, nodelist에 대해서 인덱스를 변경하는 행위를 브라우저 단에서 막는다고 한다. 생각해보면 이 접근법은 접근 자체가 틀린게 sort를 하기전에 일단 유사 배열 객체를 아예 배열로 먼저 바꿔 줬어야했다. 따라서 다른 방법을 떠올려야 했고, 결론적으로 이와 같이 해결했다.

        [...bodyElement.children]
          .sort((a, b) => {
            const bookNameNode1 = a.childNodes[1];
            const bookNameNode2 = b.childNodes[1];
            return bookNameNode1.innerText > bookNameNode2.innerText
              ? 1
              : bookNameNode1.innerText < bookNameNode2.innerText
              ? -1
              : 0;
          })
          .forEach((node) => bodyElement.appendChild(node));

위와 같이 먼저 유사배열 객체를 Array.prototype을 가리키는 실제 JS 내에 배열로 만들어줬고, 그 다음에 sort를 적용했다. 이 때, 유사배열 객체를 배열로 바꾸는 방법은 아래와 같이도 해줄 수 있다.

Array.prototype.slice.call(bodyElement.children)

처음에 sort를 할 때 바로 유사배열객체를 this 바인딩 커스텀으로 해결하려 했던게 문제였고, 잘못 접근했음을 알고 수정하여 해결했다.

  • bodyElement.childNodes vs bodyElement.children : 이 부분에 대해서 생각해본적이 없는데, 둘은 엄연히 다른 기능을 한다(당연하겠지..). children은 순수 Element Nodes(요소 노드들)만 리턴하고, childNodes는 요소, 비요소(주석 등을 포함한) 노드를 모두 리턴하는 점에서 차이를 보인다. 그래서 위의 케이스 같은 경우에는 children을 쓰는게 맞다. 왜냐하면 요소 노드만을 필요로 하는 상황이기 때문이다. 이 때, 예시를 같이 볼건데, 기본 베이스 HTML은 다음과 같다
<!DOCTYPE html>
<html>
  <head>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app">
      Hello, world!
      <div>Hello, world!</div>
    </div>

    <script src="src/index.js"></script>
  </body>
</html>

그리고 아래는 js 파일이다.

const divElement = document.querySelector("#app");
console.log(divElement.innerHTML);
console.log(divElement.innerText);
console.log(divElement.textContent);
  • innerHTML vs innerText vs textContent : 작업을 하다가 innerText와 textContent를 두개다 별 차이없이 쓰고 있는 나를 발견했다. 그래서 innerHTML까지 해서 차이점들을 알아보고자 한다. 먼저, innerHTML은 해당 html tag 내의 전체 내용을 가져온다. 예시의 결과를 보면
     Hello, world!
      <div>Hello, world!</div>

다음과 같이 출력된걸 볼 수 있다. 즉, 위에서 말한대로 innerHTML은 해당 속성을 호출한 노드 내부의 전체 내용을 string 형태로 가져온다. 다음으로 innerText와 textContent를 보면, 아래와 같이 출력이 동일하다.

Hello, world!
Hello, world! 

그럼 둘은 똑같은걸까? nope.

    <div id="app">
      Hello, world!
      <div style="display: none;">Hello, world!</div>
    </div>

이런식으로 id = app 인 div 내에 또 다른 div에 display:none; 속성을 줘봤다. 그랬더니 결과가 달라졌다. 먼저 innerText에서는

Hello, world! 

이렇게 출력됐다. 이는 innerText가 보이는 내부 텍스트만 출력하는 것을 알 수 있는 부분이다. 그럼 textContent는 ?

Hello, world!
Hello, world! 

여전히 보이는, 보이지 않는 텍스트 모두를 출력하고 있었다. 이에 따라 둘의 차이를 알 수 있었다.

  • Node.appendChild(specificNode) 를 썼을 때, 특정 Node의 children 중에 'specificNode' 가 이미 있다면, 그 노드를 새로운 위치(appendChild니까 맨 끝에)에 추가하는 식으로 동작한다. 즉, specificNode이 이미 있다했을 때 새로운 노드를 append 하는게 아니라 이미 있는걸 새로운 위치에 append 하는거다.
        [...bodyElement.children]
          .sort((a, b) => {
            const bookNameNode1 = a.children[1];
            const bookNameNode2 = b.children[1];
            return bookNameNode1.innerText > bookNameNode2.innerText
              ? 1
              : bookNameNode1.innerText < bookNameNode2.innerText
              ? -1
              : 0;
          })
          .forEach((node) => bodyElement.appendChild(node));

위에서 sort 로직을 위와 같이 짰었는데, 생각해보니까 sorting을 한 array에 대해 forEach로 appendChild를 하면 직관적으로 생각하면 같은 node가 새로운 위치에 하나 더 추가돼서 중복되는 노드들이 생겨 이전보다 2배 많은 노드들이 생기는 결과가 나와야하지 않나? 했는데 appendChild 자체가 동작 방향이 앞서 말한 것처럼 이미 있는걸 바탕으로 움직이므로(이미 있는 경우에 한하여) 저렇게 코딩해도 정상적으로 작동함을 알 수 있었다.

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

1개의 댓글

comment-user-thumbnail
2023년 7월 28일

많은 도움이 되었습니다, 감사합니다.

답글 달기