29일차 - javascript (DOM 제어와 조작)

Yohan·2024년 4월 1일
0

코딩기록

목록 보기
40/156
post-custom-banner

2-2. DOM 제어와 조작

자식 노드탐색

  • firstChild, lastChild 는 사용 X (띄어쓰기 같은 것들까지 모두 자식으로 싸잡기 때문에 문제가 많음)
  • firstElementChild, lastElementChild로 사용

부모형제 노드탐색

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <div class="wrap">
      <ul id="fruits">
        <li class="fr apple">사과</li>
        <li class="fr banana">바나나</li>
        <li class="fr grape">포도</li>
      </ul>
    </div>

    <div id="wrapper">
      <section class="contents">
        <ul class="list">
          <li class="item"><a href="#" class="link">10</a></li>
          <li class="item"><a href="#" class="link active">20</a></li>
          <li class="item"><a href="#" class="link">30</a></li>
        </ul>
        <ul class="list">
          <li class="item"><a href="#0." class="link">40</a></li>
          <li class="item"><a href="#" class="link">50</a></li>
          <li class="item"><a href="#" class="link">60</a></li>
        </ul>
      </section>
    </div>
  • 위와 같은 html이 있고, active 클래스에서 60이라는 숫자가 있는 곳으로 가려고한다면 아래처럼 굉장히 복잡하게 가야만 한다.
const $active = document.querySelector('.active');
      console.log($active
            .parentElement
            .parentElement
            .nextElementSibling
            .lastElementChild
            .firstElementChild);
  • 그래서 이러한 불편함을 해결하기위해 기준태그로부터 위로 올라가면서 탐색하는 함수가 존재!
    ->closest('css selector');
  • 아래방향 탐색은 querySelector를 이용!
  • 그래서 두 함수를 이용해서 위와 같은 60이 있는 곳을 구한다면? 아래와 같이 구할 수 있다.
// 위로
const $contentSect = $active.closest('section.contents');
// 아래로
const $found = $contentSect
              .querySelector('ul.list:nth-child(2)')
              .querySelector('li.item:last-child');

텍스트, html 조작

  • 요소 노드의 textContent 프로퍼티를 참조하면 요소 내부의 모든 텍스트를 반환
    -> 이 때 마크업은 무시됨
  • 과거에 사용하던 innerText 는 유사한 원리로 작동하나 속도가 느리고 css에 의해 숨겨진 텍스트 (ex - 내부 태그에 작성된 텍스트)를 반환하지 않으니 사용 X
  • innerHTML 프로퍼티는 요소 노드 내부의 HTML 마크업을 취득하거나 변경
  • textContent는 HTML마크업을 무시하고 텍스트만 반환하지만 innerHTML은 마크업이 포함된 문자열을 그대로 반환
li<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .highlight {
      color: red;
    }
  </style>
</head>
<body>
  <div id="greet">
    Hello <span class="highlight">World</span>
  </div>

  <ul id="food-list">
    <li class="food">짜장면</li>
    <li class="food">파스타</li>
    <li class="food">볶음밥</li>
  </ul>

  <script>
    const $greet = document.getElementById('greet');
    console.log($greet.textContent.trim);

    // 안에 들어있는 자식태그들 다 사라질 수 있음
    // $greet.textContent = '안녕 자바스크립트!';
    $greet.querySelector('.highlight').textContent = '자바스크립트!';
    
    const $foodList = document.getElementById('food-list');
    // $foodList.innerHTML = '<div>메롱</div>';

    // 자식 태그 추가
    $foodList.innerHTML += '<li class="food">볶음밥</li>'

    // 자식 태그 전체 삭제
    $foodList.innerHTML = ['']

    const foods = ['연어회', '고등어찜', '오렌지', '딸기'];

    foods.forEach(f =>
        $foodList.innerHTML += `
          <li class="food">${f}</li>
        `)
  </script>
</body>
</html>
  • $greet.textContent = '안녕 자바스크립트!'; 위 html에서 이런식으로 작성하게된다면 내부에 있는 텍스트들까지 싸잡혀서 한꺼번에 수정되어 사라질 수 있음
    -> $greet.querySelector('.highlight').textContent = '자바스크립트!'; 와 같이 겹치지않게 최대한 자세하게 작성해주면 해결할 수 있음
  • 또한 innerHTML을 통해 자식 태그를 추가, 삭제 할 수 있음
    -> textContent가 안되는 이유는 마크업이 무시되고 텍스트만 반환되기 때문.

태그 생성 및 추가

  • 요소 노드 생성
  • createElement(tagName) 메서드는 요소 노드를 새롭게 생성하여 반환
  • 매개변수 tagName에는 태그 이름을 나타내는 문자열을 인수로 전달
  • 이 메서드로 생성된 요소 노드는 아무런 자식 노드도 가지지 않음. 즉, 텍스트 노드도 없는 상태 -> textContent를 이용해 텍스트 추가!
  • 해당 메서드는 요소 노드를 생성할 뿐 DOM에 추가하지 않으므로 DOM에 추가하는 사후 처리가 필요 !!!
  • appendChild(childNode) 메서드는 매개변수에게 전달한 자식 노드를 호출한 부모노드의 마지막 자식으로 추가
  • 자식 노드로 텍스트 노드를 전달하면 텍스트가 추가
<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <ul id="fruits">
    <li>사과</li>
  </ul>

  <script>

    /*
    // 태그 생성: <li></li>
    const $newLi = document.createElement('li');
    console.log($newLi);

    // 텍스트 추가하기: <li>바나나</li>
    $newLi.textContent = '바나나';
    console.log($newLi);

    // ul에 자식노드로 붙이기
    const $ul = document.getElementById('fruits');
    $ul.appendChild($newLi); // 맨 마지막 위치에 추가
	*/
    
    // 태그 이름과 텍스트를 전달받으면 태그를 생성하는 함수
    // tagName 전달받지못하면 defalut : div
    function makeNewTag(tagName='div', options={}) {
      const $newTag = document.createElement(tagName);
      if (options.text) $newTag.textContent = options.text;
      return $newTag;
    }

    // 태그를 추가해주는 함수
    function append($parent, tagName, options) {
      $parent.appendChild(makeNewTag(tagName, options));
    }


    const $ul = document.getElementById('fruits');
    append($ul, 'li', { text: '바나나' });
    append($ul, 'li', { text: '망고' });
    append($ul, 'li', { text: '오렌지' });
  
  </script>
</body>
</html>
  1. 태그 생성 (createElement)
  2. 텍스트 추가 (textContent)
  3. 자식노드로 붙이기 (appendChild)
    순서로 진행하면 태그 이름과 텍스트를 전달받으며 태그를 생성할 수 있다!
    -> 이처럼 한개씩 하는 것도 좋지만 함수를 만들어 한 번에 처리하는 것에 익숙해지면 더 편리해질 수 있음

태그 중간 삽입

  • insertBefore(newNode, childNode) 메서드는 첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달받은 노드 앞에 삽입
  • 두 번째 인수로 전달받은 노드는 반드시 insertBefore를 호출한 노드의 자식이어야 합니다. 그렇지 않으면 DOMException 에러가 발생
<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <ul id="fruits">
    <li>사과</li>
    <li>바나나</li>
    <li>포도</li>
  </ul>

  <script>
    const $fruits = document.getElementById('fruits');
    const [$apple, $banana, $grape] = [...$fruits.children];

    const $newLi = document.createElement('li'); // li태그 생성
    $newLi.textContent = '망고'; // li태그에 text 추가
    
    // 중간삽입: insertBefore
    // $fruits.insertBefore($newLi, $grape); // $grape앞에 삽입
    // $fruits.insertBefore($newLi, $fruits.firstElementChild); // 맨앞에 삽입
    $fruits.insertBefore($newLi, document.querySelector('li:nth-child(2)')); // 2번째 삽입
    
    // 노드 이동
    $fruits.appendChild($apple); // 맨 뒤로
    $fruits.insertBefore($banana, null); // null을 입력하면 appendChild와 동일한 효과
  </script>

</body>

</html>
  • appendChild를 이용해 추가하면 맨 뒤에 추가되는 것이므로 노드를 이동 시킬 수 있음
  • insertBefore에서 두 번째 인수 (위치)가 null이면 맨 뒤로 감
  • 위 예시에서 보다시피appendChild, insertBefore를 이용해서 노드 이동 가능~!

태그 교체, 삭제

  • replaceChild() 메서드는 자신을 호출한 노드의 자식 노드를 다른 노드로 교체
  • 없애버릴 자식노드를 oldChild에 인수로 전달, 그 자리를 새롭게 차지할 자식노드를 newChild에 전달
  • removeChild() 메서드는 child 매개 변수에 인수로 전달한 노드를 DOM에서 제거
  • 전달된 노드는 반드시 호출한 노드의 자식노드이어야 함
<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <ul id="fruits">
    <li>사과</li>
    <li>바나나</li>
    <li>포도</li>
  </ul>

  <script>
    const $fruits = document.getElementById('fruits');
    const [$apple, $banana, $grape] = [...$fruits.children];

    // 새 노드 생성
    const $newDiv = document.createElement('div');
    $newDiv.textContent = '빡스';

    // 노드 교체: replaceChild(new, old);
    $fruits.replaceChild($newDiv, $banana);

    // 노드 삭제:
    $fruits.removeChild($apple);

    // 자식노드 전체삭제
    // 메서드가 없다면? 만듦

    // 1.
    // $fruits.innerHTML = '';
    // children.forEach($f => {
    //   $fruits.removeChild($f)
    // });

    // 2.
    const children = [...$fruits.children];
    for (let i = 0; i < children.length; i++) {
      $fruits.removeChild(children[i]);
    }

  </script>

</body>

</html>
  • 자식노드 전체삭제하는 것이 없다면? 만들자!
    -> 유사배열을 [...] 을 통해 배열로 만든 후에 forEach를 이용해서 removeChild를 반복문 돌리면 됨
profile
백엔드 개발자
post-custom-banner

0개의 댓글