JavaScript | DOM

SURI·2021년 11월 26일

1. 개념


1.1 DOM이란?

DOM은 Document Object Model의 약자로, HTML 요소를 Object(JavaScript Object)처럼 조작(Manipulation)할 수 있는 Model이다. 쉽게 말하면, 프로그래밍 언어인 자바스크립트와 DOM을 이용해 HTML 문서의 엘리먼트에 접근하거나, 새로운 엘리먼트를 생성/삭제 할 수 있다. 결과적으로, 웹 페이지를 동적으로 움직이게 만들 수 있다.

  • 프로그래밍 언어인 자바스크립트로 DOM을 이용해서 HTML 문서를 조작해서 동적인 페이지를 만들 수 있다. DOM은 HTML 엘리먼트들을 트리 형식으로 재구조화해서 객체로 사용할 수 있는 모델이다? (나의 첫 번째 정리)
  • HTML 문서를 조작할 수 있도록 마련된 객체 형태의 구조, 모델
  • DOM과 같은 잘 만들어진 규칙, 객체가 존재하지 않으면 프로그래밍 언어로 접근하기가 매우 어려워진다.
  • 선배 개발자들이 만든 api를 우리가 가져와서 사용한다고 생각한다.
  • DOM(Dom Object Model 문서 객체 모델)은 이미 만들어진 객체이고, 그 안에 속성과 메소드를 우리가 사용하면 된다.
  • JS에 대한 HTML 인터페이스다.
  • 웹에 사용자와 상호작용을 부여하고자 하는 아이디어에서 출발
  • 웹을 빌드하기 위해 자바스크립트, 돔, 봄이 필요하다.
    • WEB API
      • DOM : 문서 객체를 다루기 위한 속성, 메소드 제공
      • BOM : 브라우저 객체를 다루기 위한 속성, 메소드 제공

1.2 DOM의 구조

DOM은 HTML 문서의 아주 작은 부분까지 접근할 수 있는 구조다. DOM은 부모가 자식을 여러 개 가진 패턴이 반복되는 트리 구조다. 회사의 조직도를 연상하면 쉽다.

  • HTML 문서의 요소들을 파악하기 위해 HTML 문서를 보면서 일일이 찾아볼 수도 있지만, DOM을 이용해 컴퓨터에게 그 일을 시킬 수도 있다.
  • 자바스크립트에서 DOM은 document 객체에 구현되어 있다.
  • DOM의 구조를 조회할 때는 console.dir이 유용하다.
  • HTML 엘리먼트에 지정할 수 있었던 속성들이 객체 내에 이미 존재하고 있다고 생각한다.

HTML과 DOM의 차이

DOM과 JavaScript의 차이

1.3 script 요소의 위치

  • script 요소는 등장과 함께 실행된다. 즉, 삽입된 위치에서 실행된다.
    HTML 해석을 중단하고, scrip 요소를 먼저 실행한다.
    • 렌더링 과정
      • 브라우저가 한 줄씩 코드를 읽어내려가다(parsing) 자바 스크립트를 만나면 렌더링을 멈추고 파일을 다운로드 하고(fetching) 그걸 실행한다(executing) 실행을 마치고 다시 한 줄씩 읽어 내려간다(parsing)
  • script 태그는 head 태그에 추가하거나 body 태그가 끝나기 전에 추가할 수 있다.
    • script 태그의 위치에 따라 실행 결과가 달라진다.
    • HTML 엘리먼트를 이용하려면 body 태그가 끝나기 전에 script 태그를 추가하는 방법을 사용해야 한다.

이 부분 이전 챕터에서 개발자 면접 질문에 단골로 나온다고 했었던 기억이 있다.

Async, Defer

HTML문서를 읽기 전에 스크립트 요소를 미리 읽어와야 하는 경우도 있을 것이다. 이런 이야기를 하면서 나온 용어들. 면접 질문으로 대비하면 좋다.

1.4 DOM으로 HTML 요소 찾기

자식 엘리먼트 찾기

  • console.dir(document.body)
  • console.dir(document.body.children)

부모 엘리먼트 찾기

let newsContents = document.body.children[1];
// 변수에 담기
console.log(newsContents.parentElement)
// parentElement로 부모관계의 요소 찾기

DOM 순회하기

JavaScript에서 DOM 구조를 순회하기 위해서 부모가 가진 하나 혹은 여러 개의 자식들을 반복해서 조회해 나가는 로직을 수도코드로 작성했다.

1.5 DOM으로 HTML 조작하기

document 객체를 통해서 HTML 엘리먼트를 만들고(CREATE), 조회하고(READ), 갱신하고(UPDATE), 삭제할(DELETE) 수 있다. DOM에는 HTML에 적용(APPEND)하는 메소드가 따로 있다.

======createElement - <CREATE>======
  
const oneDiv = document.createElement('div') // 새로운 div 태그 생성 + 변수를 선언해 작업의 결과를 할당

======append, appendChild - <APPEND>======

document.body.append(oneDiv) // 새로 생성한 div 태그를 트리 구조와 연결

======querySelector, querySelectorAll - <READ>======
// DOM으로 HTML 엘리먼트 정보를 조회하기 위해서는 querySelector의 첫 번째 인자에 셀렉터를 전달한다. query는 질문하다라는 의미다.

const target = document.querySelector('.classname'); // classname 이라는 클래스명을 가진 엘리먼트(아마 최상단)를 가져오는데 하나만 가져오게 된다. 여러 개를 가져오고 싶다면?

const targets = document.querySelectorAll('.classname');
// 이 엘리먼트들은 배열처럼 반복문을 사용할 수 있다. 배열은 아니다. '배열 아닌 배열', '유사 배열' '배열형 객체' 'Array-like Object'이다. 

// getElementsByTagName, getElementsByClassName

const target1 = document.getElementByID('id') // 비슷한 역할을 하는 옛날 방식
const target2 = document.querySelector('#id);
target1 === target2 //true                                                                                   
const container = document.querySelector('#container');
const oneDiv = document.createElement('div');
container.append(onediv);

======textContent, id, classList, setAttribute - <UPDATE>======

oneDiv.textContent = 'hello suri'; // textContent로 엘리먼트에 문자열을 입력한다.

oneDiv.classList.add('hide'); // 요소에 style속성을 담은 'hide'라는 클래스 이름을 추가한다.

oneDiv.id = 'username'; // 요소에 아이디 부여하기
oneDiv.classname = 'col'; // 이렇게 클래스 이름을 부여하면 다 사라지고 리셋이 되는 것 같다.
oneBtn.setAttribute('name', 'fruit') // 버튼 요소에 name 속성을 추가할 수 있다. setAttribute를 이용하면 태그 안에 어떤 속성이든 추가할 수 있는 마법이라고 생각하면 될까.
  
=====remove, removeChild, innerHTML = "", textContent = ""- <DELETE>=====

oneDiv.remove() // 엘리먼트 삭제하기

const container = document.querySelector('#container');
container.innerHTML = ''; // container 안에 모든 엘리먼트 삭제하기. innerHTML은 보안상의 문제가 있을 수 있다.

while (container.firstchild) {
	container.removeChild(container.firstchild); // 자식 엘리먼트 지정해서 삭제하기
} // 첫 번째 자식 엘리먼트가 존재하면, 첫 번째 자식 엘리먼트를 삭제한다.

while (container.children.length > 1) {
	container.removeChild(container.lastChild);
} // 자식 엘리먼트가 1개만 남을 때까지, 마지막 자식 엘리먼트를 삭제한다.

const tweets = document.querySelectorAll('.tweet');
for (let tweet of tweets) {
	tweet.remove();
}

DOM과 반복문

<!DOCTYPE html>
<html>
  <head>
    <title>DOM Review</title>
    <meta charset="UTF-8" />
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
      }

      li {
        list-style: none;
        margin: 20px 0px;
      }

      .country {
        font-weight: bold;
      }

      div span {
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <ul class="city-list"></ul>
    </div>
    <script>
      const places = [
        {
          id: 1,
          city: "Seoul"
          country: "Republic of Korea"
          address: "jung-gu"
        },
        {
          id: 2,
          city: "New York"
          country: "Unites States"
          address: "-"
        },
        {
          id: 3,
          city: "Ilsan",
          country: "Republic of Korea",
          address: "Sik-sa"
        }
      ];

      const cityList = document.querySelector(".city-list");

      for (let el of places) {
        const li = document.createElement("li");
        const div = document.createElement("div");
        const span1 = document.createElement("span");
        const span2 = document.createElement("span");
        const span3 = document.createElement("span");

        div.append(span2, span3);
        li.append(span1, div);
        cityList.append(li);

        span1.classList.add("country");
        span1.textContent = el.country;
        span2.classList.add("city");
        span2.textContent = "City : " + el.city;
        span3.classList.add("address");
        span3.textContent = "Address : " + el.address;
      }
    </script>
  </body>
</html>
  • 반복문을 활용해 DOM으로 HTML 추가하는 연습은 처음 해 본 것 같다. 낱개의 태그들만 생성해보았는데, 반복문으로 여러개를 만들려니 처음엔 약간 버벅거렸다.

more++

querySelector의 부모는 꼭 document 객체여야만 하나?

그렇지 않다. 자식 요소를 가진 부모 요소를 조회해서 변수에 담고 거기에서 querySelector를 사용할 수도 있었다.

innerHTML과 textContent

  • textContent는 노드와 그 자손의 텍스트 콘텐츠를 표현한다.
  • innerHTML은 태그 자체를 반환하고 접근할 수 있는 메소드라, 보안상에 문제가 있을 수 있다. (요소 내에 포함된 모든 HTML을 반환한다.)

element와 node

Node는 Element의 상위 개념입니다. DOM 관련 객체는 대부분 Node에서 파생되었다고 봐도 과언이 아닙니다. Node의 기능을 적용(implements)한 객체는 여러 타입이 있는데, 그 중 가장 많이 사용되는 타입이 Element입니다.

tweets에 forEach는 되는데, reduce는 안되는 이유

노드리스트는 배열이 아니라, 노드의 콜렉션입니다. 그래서 배열 메소드들을 사용할 수 없다.

children과 childNodes의 차이

현재 노드에서 자식 노드에 접근하기 위해서 children 혹은 childNodes 메소드를 사용한다. children은 태그 노드를 리턴하고, childNodes는 태그 노드에 텍스트도 함께 리턴한다.

removeChild와 remove의 차이

remove는 메모리 단위에서 삭제하는 것이라 반환값이 없다. undefined가 뜬다. removeChild는 삭제한 값을 반환한다.

append와 appendChild 차이

append는 한 번에 여러개를 추가할 수 있다.

tweets를 유사 배열에서 배열로 바꾸는 방법

  • API와 렌더링 엔진 (면접 준비)
  • form element reference

1.6 이벤트 객체

이벤트 객체는 사용자의 입력으로 발생한 이벤트 정보를 담은 객체이다.

이벤트

이벤트 : 웹을 탐색하면서 사용자가 클릭이나 드래그 등을 하는 일

onclick onsubmit onchange onmousedown onmouseup onkeyup onkeydown event.preventDefault

이벤트 핸들러

엘리먼트에 특정 이벤트가 발생했을 때 이벤트 핸들러가 동작하도록 한다.

onclick 에 직접 할당하는 것과 addEventListener의 차이

===1===
element.onclick = () => {
  console.log('hello');
}

===2===
element.onclick = handler;  

function handler() {
  console.log('hello');
}

===3===
  // 이게 더 모던한 방식이다.
element.addEventListener('click', () => {
  console.log('hello')
});  
  • addEventListener : 이벤트 핸들러를 할당했다가 해지하는 게 더 쉽고, 한꺼번에 2개 이상의 이벤트를 걸 수도 있다.
  • element.onclick : 뒤에 똑같은 이벤트를 걸면 앞에 걸었던 이벤트를 지워버린다. 해지하는 것도 까다롭다.

이벤트 핸들러의 첫 번째 인자 사용하기

	<button>Organic Tea</button>
    <button>Hot Coffee</button>

    <script>
      let beverages = document.querySelectorAll("button"); 

      let btnOrganicTea = beverages[0];
      let btnHotCoffee = beverages[1];

      function handler(event) {
        console.log(event);
        console.log(event.target);
        console.log(event.target.textContent);       
      }

      btnOrganicTea.onclick = handler;
      btnHotCoffee.onclick = handler; 
  • 브라우저에서 버튼을 클릭했을 때, 핸들러 함수가 호출이 되어서 중괄호 안에 코드가 실행이 된다. 그 내용을 살펴보면, event 객체가 담고 있는 정보를 알 수 있다.

2. 에러로그


핸들러 함수는 이벤트가 발생할 때마다 호출이 되는 부분

3. 질문


createDocumentFragment를 활용하여, 더 효율적으로 DOM을 제어
HTML5 template tag 사용법
같은 엘리먼트를 appendChild 하면, 기존 엘리먼트를 복사할까?
좌표 정보 조회 - offsetTop...
크기 정보 조회 - offsetWidth...
profile
Every step to become a better version of me 🚶‍♂️ 블로그 이사중

0개의 댓글