JavaScript_Day.7

조현기·4일 전

JavaScript_Day.7

DOM 트리를 타고 이동하기, 이벤트 객체, 타이머, 그리고 구조 분해 할당까지.
어제까지가 "태그 하나를 골라서 바꾸기"였다면, 오늘은 "태그 사이의 관계를 따라 움직이기"였다.

학습 일곱 번째 날이다. 오늘 배운 내용은 이렇다.

  1. 입력값 계산: value와 eval
  2. DOM 탐색: 부모·자식·형제 노드, 텍스트 노드와 요소 노드의 차이
  3. 이벤트 객체: type, target, currentTarget, preventDefault()
  4. 폼 요소 다루기: name 접근, keyup, checked, disabled, label
  5. display 속성: block, inline, inline-block
  6. data- 사용자 정의 속성
  7. 타이머: setInterval, setTimeout, 외부 JS 파일, window.open, location
  8. classList
  9. 구조 분해 할당

1. 입력값으로 계산하기

클릭하면 정답이 나타나는 계산 문제

<h1>계산해봅시다</h1>
<hr>   <!-- 구분선 -->
<div><span class="ans">3+4=</span><span class="btn">답</span></div>
<div><span class="ans">5*20+60=</span><span class="btn">답</span></div>
<div><span class="ans">20/5-4*5=</span><span class="btn">답</span></div>

<script>
  // 같은 이름의 CSS 선택자가 여러 개 있으면 목록(배열처럼)으로 선택된다
  let ans = document.querySelectorAll('.ans');
  let btn = document.querySelectorAll('.btn');

  btn.forEach(function (btn, i) {
    btn.addEventListener('click', function () {
      let ans2 = ans[i].textContent.replace('=', '');   // '3+4=' → '3+4'
      btn.textContent = eval(ans2);                      // 계산 결과로 교체
    });
  });
</script>
식클릭 후
3+4=7
5*20+60=160
20/5-4*5=-16

querySelectorAll로 가져온 목록을 forEach로 돌면서, 각 버튼에 클릭 이벤트를 붙였다. forEach의 두 번째 인자 i(인덱스)로 같은 순서의 식(ans[i])을 찾는 것이 핵심이다. replace('=', '')로 =를 지워야 eval이 식으로 계산할 수 있다.

추가로 정리: forEach는 NodeList에는 되고, HTMLCollection에는 안 된다

가져오는 방법결과 타입forEach
querySelectorAllNodeList사용 가능
getElementsByTagName, getElementsByClassName, childrenHTMLCollection사용 불가

Day.5에서 getElementsByTagName 결과를 for in으로 돌 때 오류가 났던 것도 이 HTMLCollection 때문이다. HTMLCollection은 일반 for문으로 돌면 안전하다.

식을 입력해서 계산하기

식<input type="text" id="susik"><br>
값<input type="text" id="result"><br>
<input type="button" value="계산" onclick="calcu()">

<script>
  function calcu() {
    let susik = document.querySelector('#susik');
    let result = document.querySelector('#result');

    result.value = eval(susik.value);

    console.log(typeof(result.value));   // string
    console.log(typeof(susik.value));    // string
  }
</script>

input 요소의 값은 .value로 읽고 쓴다. value는 사용자가 직접 입력한 값이며, 숫자를 입력해도 문자열이다. input으로 계산 기능을 만들 때는 value를 쓴다.

필기에는 result.value = eval(...) 뒤의 typeof가 문자열이라고 되어 있다. 숫자 결과를 value에 넣으면 문자열로 바뀌기 때문이다.


2. DOM 탐색: 노드 사이를 이동하기

부모 노드: parentNode

<div class="frame">
  <h1 class="head">제목</h1>
  <hr>
  <p class="p">본문 문단이다.</p>
  <button id="bu1">클릭</button>
</div>

<script>
  let bu1 = document.querySelector('#bu1');

  bu1.addEventListener('click', function () {
    this.parentNode.style.backgroundColor = 'yellow';       // 버튼의 부모(div)
    this.parentNode.style.border = '3px solid blue';
  });
</script>

this는 클릭된 버튼이고, this.parentNode는 그 부모 노드(div.frame)다. border는 두께 모양 색 순서라서 3px solid blue는 "3px짜리 실선, 파란색 테두리"다.

탐색 속성 정리

속성설명
parentNode부모 노드
childNodes모든 자식 노드 (텍스트, 주석 포함)
children자식 요소 노드만
firstChild / lastChild첫 / 마지막 자식 노드 (텍스트, 주석 포함)
firstElementChild / lastElementChild첫 / 마지막 자식 요소 (공백 무시)
nextElementSibling다음 형제 요소 (공백 무시)
previousElementSibling이전 형제 요소 (공백 무시)

바로잡기: 필기에는 firstChild를 "첫번째 자손 노드", 이전 형제를 prevElementSibling이라고 적었다. firstChild는 바로 아래 단계인 자식 노드이고(자손은 손자까지 포함하는 말이다), 이전 형제 속성의 정확한 이름은 previousElementSibling이다.

텍스트 노드와 요소 노드

<div class="frame">
  <h1 class="head">제목</h1>
  <p class="p">본문 문단이다.</p>
  <button id="bu1">클릭</button>
</div>

<script>
  let f = document.querySelector('.frame');

  console.log(f.childNodes.length);       // 7
  f.childNodes[1].style.color = 'red';    // <h1>이 빨간색으로

  console.log(f.children.length);         // 3
</script>

childNodes가 7인 이유는 태그 사이의 줄바꿈과 공백도 텍스트 노드로 세기 때문이다.

인덱스노드
0텍스트 노드: <div> 여는 태그 뒤의 줄바꿈+공백
1요소 노드: <h1>
2텍스트 노드: </h1>와 <p> 사이의 줄바꿈+공백
3요소 노드: <p>
4텍스트 노드: </p>와 <button> 사이의 줄바꿈+공백
5요소 노드: <button>
6텍스트 노드: </button> 뒤의 줄바꿈+공백

children은 요소 노드만 세므로 h1, p, button으로 3이다. 그래서 childNodes[1]처럼 인덱스를 쓸 때는 공백 때문에 번호가 어긋나기 쉽고, 요소만 쓰고 싶다면 children이나 ~Element~ 속성을 쓰는 것이 편하다.

firstChild와 firstElementChild

<div id="frame">
  <p>one</p>
  <p>two</p>
</div>

<script>
  let f1 = document.getElementById('frame');
  let first = f1.firstElementChild;

  document.write(f1.firstChild + '<br>');   // [object Text]  (공백 텍스트 노드)
  document.write(first);                    // [object HTMLParagraphElement]  (<p>one</p>)
</script>
  • firstChild: 모든 노드 중 첫 번째. 텍스트와 주석도 포함한다.
  • firstElementChild: 요소 노드 중 첫 번째. 텍스트와 공백은 무시한다.

형제 노드와 텍스트 노드 바꾸기

<div class="frame">
  <!-- 처음으로 추가된 텍스트 -->
  <h1>javascript</h1>
  <div class="c">
    <h2>css</h2>
    <p>h2 다음에 오는 문단이다.</p>
  </div>
  <!-- 마지막에 추가된 텍스트 -->
</div>

<script>
  let f1 = document.querySelector('.frame');
  f1.firstChild.textContent = '처음으로 추가된 텍스트';
  f1.lastChild.textContent = '마지막에 추가된 텍스트';

  let c1 = document.querySelector('.c');
  let h2 = document.querySelector('.c h2');
  console.log(h2.textContent);   // css

  // h2의 다음 형제 요소 = p
  h2.nextElementSibling.style.border = '2px solid red';

  // div.c의 이전 형제 요소 = h1
  c1.previousElementSibling.style.border = '2px dotted blue';
</script>

여기서 firstChild와 lastChild는 주석이 아니라, 주석 앞뒤의 공백 텍스트 노드다. 그 노드의 textContent를 바꾼 것이라 화면에 글이 나타난다. h2.nextElementSibling은 h2와 같은 부모 아래에서 바로 다음에 있는 요소(p)다.


3. 이벤트 객체

<p id="mouse1">마우스</p>

<!-- html 인라인 이벤트에서는 매개변수 이름을 event로 쓴다 -->
<button onclick="on(event)">클릭!</button>

<script>
  function on(event) {      // event: 현재 발생한 이벤트 객체
    let event1 = event.type + '<br>'              // 이벤트 종류
               + event.target + '<br>'            // 이벤트가 발생한 요소
               + event.currentTarget + '<br>'     // 이벤트가 연결된 요소
               + event.defaultPrevented;          // 기본 동작이 취소되었는가
    document.querySelector('#mouse1').innerHTML = event1;
  }
</script>
속성설명이 예제의 값
event.type이벤트 종류click
event.target이벤트가 발생한 요소버튼
event.currentTarget이벤트 핸들러가 연결된 요소버튼
event.defaultPrevented기본 동작이 취소되었는지 (취소됨: true)false

이 예제에서는 버튼에 직접 연결했기 때문에 target과 currentTarget이 같다. 자식 요소를 클릭했는데 핸들러가 부모에 달려 있는 경우에는 둘이 달라질 수 있다.

preventDefault: 기본 동작 막기

태그에는 기본 동작이 있다. 링크는 클릭하면 페이지가 이동하고, 체크박스는 클릭하면 체크된다. 이 기본 동작을 코드로 막을 수 있다.

<!-- 링크의 기본 동작은 페이지 이동이다 -->
<!-- onclick이 true를 반환하면 이동, false를 반환하면 이동 취소 -->
<a href="https://example.com" onclick="return on()">이동할 거냐</a><br>

<!-- 체크박스의 기본 동작은 체크되는 것이다 -->
<input type="checkbox">커피<br>
<input type="checkbox" onclick="no(event)">빵

<script>
  function on() {
    let n = confirm('정말 이동할건가요?');   // 확인: true, 취소: false
    return n;
  }

  function no(e) {
    e.preventDefault();   // 이벤트의 기본 동작을 강제로 취소
  }
</script>
  • e.preventDefault(): 기본 동작을 취소시키는 메소드다.
  • e.defaultPrevented: 기본 동작이 취소되었는지 확인하는 값이다. 함수가 아니라 속성이므로 ()를 붙이지 않는다.

이름이 비슷해서 헷갈리는데, "막아라"는 preventDefault(), "막혔나?"는 defaultPrevented로 구분하기로 했다. 위 예제에서 "빵" 체크박스는 클릭해도 체크되지 않고, "커피"는 정상적으로 체크된다.


4. 폼 요소 다루기

폼 요소에 접근하는 3가지 방법

<form name="test">
  <input type="text" name="test2" placeholder="입력해주세요">
</form>

<script>
  // 1. document.폼이름.요소이름
  let n = document.test.test2;
  n.style.border = '2px solid red';

  // 2. document.forms['폼이름'].elements['요소이름']
  let n2 = document.forms['test'].elements['test2'];
  n2.style.backgroundColor = 'yellow';

  // 3. document.forms['폼이름']['요소이름']
  let n3 = document.forms['test']['test2'];
  n3.style.fontSize = '50px';
</script>

세 방법 모두 같은 input을 가리킨다. 형태가 짧은 순서대로 1번, 3번, 2번이다.

keyup: 입력할 때마다 실시간으로 반영하기

<form name="test">
  <input type="text" id="aa" name="test2" placeholder="입력해주세요">
</form>
<div id="div1"></div>

<script>
  let input = document.forms['test']['test2'];
  let n2 = document.querySelector('#div1');

  // 키보드를 입력했을 때의 이벤트 (고전 이벤트 방식)
  input.onkeyup = function () {
    let n3 = this.value;       // input에 입력한 값
    n2.textContent = n3;       // div 영역에 띄운다
  };
</script>

키를 눌렀다 뗄 때마다 input의 값이 div에 그대로 나타난다. 필기에서는 innerHTML로 넣었는데, 사용자 입력을 그대로 넣는 곳이라서 Day.6에서 정리한 이유로 textContent로 바꿨다.

disabled, label, checked

주민번호 뒷자리 첫 숫자를 입력하면 성별 라디오 버튼이 자동으로 선택되는 실습이다.

<form name="test" class="a1">
  <input type="text" name="input_1" value="001111" disabled>
  <!-- disabled: 입력하지 못하도록 막는다 -->
  <input type="text" name="input_2" placeholder="주민번호 뒷자리 입력하기">

  <input type="radio" name="ch" id="male">
  <label for="male">남</label>       <!-- label로 구분 -->
  <input type="radio" name="ch" id="female">
  <label for="female">여</label>
</form>

<script>
  let n = document.test.input_2;

  let male = document.querySelector('#male');
  let female = document.querySelector('#female');

  n.onkeyup = function () {
    let num = this.value;
    if (num >= 1 && num <= 4) {
      if (num == 1 || num == 3) {
        female.checked = false;
        male.checked = true;       // 체크한다
      } else {
        male.checked = false;
        female.checked = true;
      }
    }
  };
</script>
  • disabled: 입력을 막는 속성이다.
  • <label for="id">: for에 input의 id를 넣으면 글자를 눌러도 해당 입력이 선택된다.
  • radio나 checkbox 타입에는 checked라는 상태 필드가 있어서, true/false로 체크 상태를 바꾼다.

추가로 정리: 라디오 버튼은 한쪽만 true로 해도 된다

name이 같은 라디오 버튼은 하나만 선택되므로, male.checked = true만 해도 female은 자동으로 해제된다. 위 코드의 female.checked = false는 없어도 결과가 같다.


5. display 속성: 블록, 인라인, 인라인 블록

구분블록 박스인라인 박스인라인 블록 박스
속성display: blockdisplay: inlinedisplay: inline-block
예시 태그div, p, h1~h6label, a, span, strong(속성으로 지정)
새 라인에서 시작항상 새 라인못 함 (라인 안에 있음)못 함 (인라인 특성)
옆에 다른 요소 배치불가능가능가능 (인라인 특성)
width, height 조절가능불가능가능 (블록 특성)
padding, margin 조절가능위아래 margin은 적용 안 됨가능 (블록 특성)

필기에는 인라인 박스의 예로 textarea와 input도 적었는데, 이 둘은 인라인처럼 줄 안에 놓이지만 width와 height를 조절할 수 있다. 그래서 기본값이 사실상 인라인 블록에 가깝다. 위 표의 인라인 예시는 label, a, span, strong만 남겼다.

<style>
  div {
    border: 1px solid salmon;
    color: blue;
    background-color: yellow;
  }
</style>

css <div style="display: none;">재밌음</div> <br>
css <div style="display: inline; height: 50px;">재밌음</div>
css <div style="display: inline-block; height: 50px;">재밌음</div>
css <div style="display: block;">재밌음</div>

같은 div에 속성만 바꿔서 확인했다.

  • none: 화면에서 사라지고 자리도 차지하지 않는다.
  • inline: 글자 크기만큼만 차지한다. height: 50px을 줘도 적용되지 않는다.
  • inline-block: 옆에 붙으면서 height: 50px이 적용된다.
  • block: 새 줄에서 시작해 한 줄을 다 차지한다.

6. 폼 제출 이벤트로 목록 만들기

<h3>정보입력</h3>
<form id="info">
  <p>이름 <input type="text" id="name"></p>
  <p>주소 <input type="text" id="addr"></p>
  <input type="submit" value="정보입력">
</form>

<p id="output"></p>
<!-- 완성되면 이런 모양: <p>홍길동 , 서울</p> -->

<script>
  let form = document.getElementById('info');
  let nameInput = document.getElementById('name');
  let addr = document.getElementById('addr');
  let output = document.getElementById('output');

  // submit 버튼을 눌렀을 때 발생하는 이벤트 → onsubmit
  form.onsubmit = function (e) {
    e.preventDefault();   // 기본 제출 동작(새로고침)을 막는다

    let p = document.createElement('p');                 // <p></p> 동적 생성
    p.innerText = nameInput.value + ' , ' + addr.value;  // <p>홍길동 , 서울</p>

    output.appendChild(p);
  };
</script>

form 안의 input에서 엔터를 누르거나 제출 버튼을 누르면 해당 폼이 제출되면서 페이지가 자동으로 새로고침된다. 그러면 방금 추가한 p도 사라지기 때문에, e.preventDefault()로 기본 제출 동작을 막았다. Day.6에서 만든 "이름·전화번호 목록"도 같은 원리다. (그때는 form 태그 자체가 없어서 새로고침이 일어나지 않았다.)


7. data- 사용자 정의 속성

태그에 data-이름 형태로 원하는 정보를 붙여 둘 수 있다.

<div class="div1">
  <h2 data-num="에스프레소">더블샷</h2>
  <h2 data-num="라떼">라떼</h2>
  <h2 data-num="아메리카노">아메리카노</h2>
</div>

<script>
  let menu = document.querySelectorAll('.div1 h2');

  for (let i = 0; i < menu.length; i++) {
    menu[i].addEventListener('click', function () {
      let menu2 = this.dataset.num;
      // let menu2 = this.getAttribute('data-num');   // 같은 결과
      console.log(menu2);
    });
  }
</script>

화면에는 "더블샷"이 보이지만, 클릭하면 data-num에 담아 둔 "에스프레소"를 읽을 수 있다. 화면에 보이는 글자와 코드가 쓰는 값을 분리할 수 있다는 점이 핵심이다.

실습: 메뉴를 클릭하면 이름과 가격 표시

<div class="div1">
  <h2>주문</h2>
  <hr>
  <div class="div2">
    <h2 data-num="에스프레소" data-price="4500">더블샷</h2>
    <h2 data-num="라떼" data-price="6000">라떼</h2>
    <h2 data-num="아메리카노" data-price="5300">아메리카노</h2>
  </div>
  <hr>
  <p><span id="order">메뉴없음</span> <span id="price">0</span>원</p>
</div>

<script>
  let menu = document.querySelectorAll('.div2 h2');
  let order = document.querySelector('#order');
  let price = document.querySelector('#price');

  let mn = document.querySelector('.div2');
  mn.style.border = '3px solid black';
  mn.style.backgroundColor = 'brown';

  for (let i = 0; i < menu.length; i++) {
    menu[i].style.color = 'white';

    menu[i].addEventListener('click', function () {
      let menu2 = this.dataset.num;       // 선택한 메뉴
      let price2 = this.dataset.price;    // 선택한 가격
      // this.dataset['price']도 같은 결과

      order.textContent = menu2;
      price.textContent = price2;
    });
  }
</script>

dataset 이름 규칙

data- 뒤에 영문 단어가 두 개 이상이면 카멜 표기법으로 읽는다.

<h2 data-num-menu="라떼">라떼</h2>
this.dataset.numMenu    // 앞은 그대로, 뒷부분의 첫 글자를 대문자로

추가로 정리: dataset의 값은 항상 문자열

data-price="4500"을 읽으면 숫자 4500이 아니라 문자열 '4500'이다. 합계를 구하려면 Number(this.dataset.price)로 바꿔야 한다. prompt, input.value와 같은 종류의 함정이다.


8. 타이머와 브라우저 창

setInterval, setTimeout, clearInterval

<script>
  let n = 1;

  // setInterval(함수, 지연시간): 지정한 시간마다 함수를 반복 실행
  let out = setInterval(function () {
    document.write(n + '초 지났다.' + '<br>');
    n++;
  }, 1000);                     // ms(밀리초): 1000ms = 1초

  // setTimeout(함수, 지연시간): 지정한 시간 뒤에 한 번만 실행
  setTimeout(function () {
    clearInterval(out);         // 인터벌 out을 멈춘다
  }, 10000);                    // 10초 후
</script>
함수설명
setInterval(함수, ms)ms마다 반복 실행. 반환값(id)으로 멈출 수 있다
setTimeout(함수, ms)ms 뒤에 한 번만 실행
clearInterval(id)setInterval을 멈춘다

10초 뒤에 멈추려고 setTimeout 안에서 clearInterval(out)을 썼다.

외부 JS 파일로 분리하기: 1초마다 움직이는 시계

자바스크립트 코드를 별도 파일에 작성하고 <script src="파일명">으로 불러올 수 있다.

<!-- index.html -->
<script src="time.js"></script>
<div id="clock"></div>
// time.js
function updateClock() {
  let date = new Date();

  let hh = date.getHours();
  let mm = date.getMinutes();
  let ss = date.getSeconds();

  if (hh < 10) { hh = '0' + hh; }
  if (mm < 10) { mm = '0' + mm; }
  if (ss < 10) { ss = '0' + ss; }

  let div = document.querySelector('#clock');
  div.innerHTML = `${hh}:${mm}:${ss}`;       // 시:분:초
}
setInterval(updateClock, 1000);

한 자리 숫자 앞에 '0'을 붙여 09:05:03처럼 보이게 했다. 숫자에 '0' 문자열을 더하면 문자열이 되므로 hh가 문자열로 바뀐다.

추가로 정리: 시계 코드를 더 짧게, 그리고 첫 표시 지연

  • String(hh).padStart(2, '0')을 쓰면 두 자리가 안 될 때 앞을 '0'으로 채워 준다. if 세 개를 줄일 수 있다.
  • setInterval은 처음 실행이 1초 뒤라서, 페이지를 열고 1초 동안은 시계가 비어 있다. 바로 보이게 하려면 updateClock();을 setInterval 앞에 한 번 호출한다.

window.open과 close

let n = window.open('https://example.com', '_blank', 'width=400,height=700');
// 새 윈도우(팝업) 창을 연다

// 3초 후 팝업창 자동으로 닫기
setTimeout(function () {
  n.close();
}, 3000);

바로잡기: 필기 코드는 window.open(주소, '_blank', 'width=400', 'hegiht=700')로 되어 있었다. 창 크기 같은 옵션은 하나의 문자열에 쉼표로 이어서 세 번째 인자로 넣어야 하고('width=400,height=700'), 네 번째 인자는 쓰이지 않는다. hegiht도 height 오타다. 그리고 브라우저는 사용자의 클릭 없이 열리는 팝업을 차단하는 경우가 많아서, 실제로는 버튼 클릭 같은 동작 안에서 열어야 확실하다.

location: 현재 주소 확인과 페이지 이동

// 현재 페이지 URL
let n = location.href;
document.write(n);

// 5초 후에 구글로 이동
setTimeout(function () {
  location.href = 'https://www.google.com';
}, 5000);

location.href를 읽으면 현재 주소, 값을 대입하면 그 주소로 이동한다.


9. 마우스 이벤트: 로그인 화면 실습

<h1>로그인</h1>
<hr>
<label>아이디</label><br>
<input type="text" id="id" placeholder="아이디를 입력해주세요..."><br>
<label>패스워드</label><br>
<input type="password" id="pwd" placeholder="비밀번호를 입력해주세요..."><br><br>
<button id="btn" onclick="login()">로그인</button>

<script>
  let id = document.querySelector('#id');
  let pwd = document.querySelector('#pwd');
  let btn = document.querySelector('#btn');

  btn.style.backgroundColor = 'pink';

  function login() {
    if (id.value == '자바' && pwd.value == '1234') {
      alert('로그인');
    } else {
      alert('다시 입력');
    }
  }

  btn.onmouseover = function () {     // 마우스가 올라왔을 때
    this.style.backgroundColor = 'tomato';
  };
  btn.onmouseout = function () {      // 마우스가 벗어났을 때
    this.style.backgroundColor = 'pink';
  };
</script>
이벤트시점
onmouseover마우스가 요소 위로 올라왔을 때
onmouseout마우스가 요소에서 벗어났을 때
onclick클릭했을 때
onkeyup키를 눌렀다 뗐을 때

value로 아이디와 비밀번호를 가져와 &&로 둘 다 맞는지 비교한다.

필기 코드는 "아이디", "패스워드" 글자를 <il>이라는 태그로 감쌌는데, 이런 태그는 HTML에 없다. 위 코드는 <label>로 바꿨다. 또 이 예제는 연습용이다. 실제 로그인은 아이디와 비밀번호를 JS 코드 안에 적어 두고 비교하면 안 되고, 서버에서 검증해야 한다.


10. classList

<h1 class="a1 a2" id="b1">javascript</h1>

<script>
  let n = document.querySelector('#b1');
  console.log(n.classList.length);   // 2  (클래스 목록의 개수)
  console.log(n.classList[0]);       // a1 (인덱스 0의 클래스)

  n.addEventListener('click', function () {
    this.classList.add('active');       // 'active' 클래스를 추가
  });

  n.addEventListener('mouseenter', function () {
    this.classList.remove('active');    // 'active' 클래스를 제거
  });
</script>

필기 코드에는 <h1> 두 개가 모두 id="b1"이었다. id는 문서 안에서 하나만 있어야 하고, querySelector('#b1')은 첫 번째 것만 선택한다. 이 글에서는 하나만 남겼다.

toggle과 contains

<style>
  .active {
    background-color: yellow;
    color: blue;
  }
</style>

<h1 class="active">javascript</h1>
<h2>css</h2>

<script>
  let h1 = document.querySelector('h1');
  let h2 = document.querySelector('h2');

  h1.addEventListener('click', function () {
    this.classList.toggle('active');
    // active 클래스가 없으면 추가, 있으면 제거
  });

  const change = (e) => {
    let n = e.target.classList.contains('active');
    // 지정한 클래스가 요소의 class 속성에 있는지 확인 (true/false)
    alert(n);
  };

  h1.addEventListener('click', change);
  h2.addEventListener('click', change);
</script>
메소드설명
classList.add('이름')클래스 추가
classList.remove('이름')클래스 제거
classList.toggle('이름')없으면 추가, 있으면 제거
classList.contains('이름')클래스가 있는지 확인 (true/false)

style을 하나하나 바꾸는 것보다, 스타일은 CSS 클래스로 정의해 두고 JS에서는 클래스만 붙였다 뗐다 하는 쪽이 코드가 짧고 관리하기 쉽다.

필기의 CSS에 .h2 { color: red; }가 있었는데, 점(.)은 클래스 선택자다. class="h2"인 요소에만 적용되고, <h2> 태그에는 적용되지 않는다. 태그를 고르려면 h2 { color: red; }처럼 점 없이 쓴다.

선택자 규칙을 다시 정리하면 이렇다.

querySelector('#아이디명')   // id
querySelector('.클래스명')   // class
querySelector('태그명')      // tag

11. 구조 분해 할당

객체

const profile = {
  name: 'tom',
  age: 30
};
const msg = `내 이름은 ${profile.name}이고 나이는 ${profile.age}살이다.`;
console.log(msg);

객체의 값을 하나씩 꺼내 쓰려면 매번 profile.name처럼 써야 한다. 분할 대입(구조 분해 할당)을 쓰면 코드가 간결해지고, 필요한 값만 뽑아낼 수 있다.

const profile2 = {
  name2: 'jack',
  age2: 38,
  adr2: 'seoul'
};

// const { 속성명 } = 객체명
// 변수명과 속성명이 정확히 일치해야 값이 들어온다. 순서는 상관없다.
const { name2, age2, adr2 } = profile2;

// 위 한 줄은 아래 세 줄과 같다
// const name2 = profile2.name2;
// const age2 = profile2.age2;
// const adr2 = profile2.adr2;

console.log(`내 이름은 ${name2}이고 나이는 ${age2}살이다.`);
console.log(adr2);

별칭 붙이기

// 속성명 : 별칭
const { adr2: address, name2: proName } = profile2;

console.log(`내 이름은 ${proName}이고 사는 곳은 ${address}이다.`);

속성명 : 별칭으로 쓰면 다른 이름의 변수로 받을 수 있다. 이미 같은 이름의 변수가 있을 때 충돌을 피할 수 있다.

필기 코드의 세 번째 출력은 나이는 ${address}살이었다. 변수 address에는 나이가 아니라 주소(seoul)가 들어 있어서 "나이는 seoul살"이 되므로, 위처럼 문장을 고쳤다.

배열

const ary = ['gildong', 34];
console.log(`내 이름은 ${ary[0]}이고 나이는 ${ary[1]}살이다.`);

// 분할 대입
const [userName, userAge] = ary;
console.log(`내 이름은 ${userName}이고 나이는 ${userAge}살이다.`);

const [first] = ary;          // 변수 하나만 주면 [0]번 값
console.log(first);           // gildong

const [, second] = ary;       // 두 번째 값만 원하면 쉼표로 자리를 비운다
console.log(second);          // 34
구분객체배열
기호{ }[ ]
값을 찾는 기준속성 이름 (순서 무관)순서

배열은 이름이 없어서 순서 기준으로 들어오고, 객체는 이름 기준으로 들어온다.

추가로 정리: 리액트에서 자주 만난다

구조 분해 할당은 리액트에서 자주 쓰인다고 한다. 예를 들어 리액트의 const [count, setCount] = useState(0) 같은 코드가 바로 배열 구조 분해다. 오늘 배운 문법이 그대로 나오기 때문에 기억해 둘 가치가 있다.


마무리

오늘의 큰 줄기는 DOM 트리 안에서 노드 사이의 관계를 따라 움직이고, 이벤트 객체로 "무슨 일이 일어났는지"를 알아내는 것이다.

  • childNodes는 공백까지 세고, children은 요소만 센다.
  • firstChild/firstElementChild처럼, Element가 붙은 속성은 공백을 무시한다.
  • preventDefault()는 기본 동작을 막고, defaultPrevented는 막혔는지 확인한다.
  • input의 값은 .value이고, value와 dataset의 값은 항상 문자열이다.
  • 스타일은 CSS 클래스로 정의하고 classList로 붙였다 떼는 편이 관리하기 쉽다.
  • setInterval은 반복, setTimeout은 한 번, clearInterval은 반복 중지다.
  • 구조 분해 할당은 객체는 이름 기준, 배열은 순서 기준이다.

Tags: JavaScript DOM 이벤트객체 classList setInterval 구조분해할당 폼 개발자 학습기록

profile
난 뭘까

0개의 댓글