프론트 공부/JavaScript와 모던 JS

JavaScript로 HTML 다루기.

홍구리당당 2023. 10. 18. 17:56

0. 오늘의 배울 것.

javascript 는 웹과 사용자가 상호작용(interactive)하게 해준다!

javascript는 웹페이지에서 사용자가 페이지와 상호작용하게끔(이벤트를 받아 동작하게끔) 해준다! js로 html 태그를 직접 다룰 수도 있고, css 스타일을 다룰 수도 있고, 버튼이나 체크박스 같은 input으로부터 동작을 만들어낼 수 있다.

그래서 javascript는 interactive한 웹을 만들게 해준다!!

우리의 목표는 js를 더욱 공부하여 interactive 한 웹을 만드는 것이다.

예를 들어 todo list를 적는 웹 사이트가 있다고 할 때, input에 글자를 적고 엔터치면 input 값이 웹사이트 화면에 나타날 것이다. 이는 js가 input 값을 받아들여 그 값을 가지고 html 태그를 만들어서 웹페이지 상에 html 태그를 삽입하는 동작을 한 것이다!!

이렇게 js로 html을 다루는 방법을 알아보자.

  1. js로 html 태그 요소를 선택하는 법.
  2. js로 html 태그 요소를 생성, 수정 및 삽입 삭제하는 법.

이것들을 중점적으로 알아보자.


HTML 문법으로 태그 선택해서 js 파일로 가져오는 법.

1. JS로 html 태그 선택하기: id로 태그 선택하는 법

js에는 documnet라는 객체에 getElementById() 라는 메소드가 있다. 메소드 이름 그대로, id 값으로 태그를 가져오는 것이다.

<div id ="myNumber"> 1 </div>
const num = document.getElementById("myNumber");
console.log(num);

만약 없는 id에서 값을 가져오려 하면 오류가 아니라 null 값이 출력되니 주의하자. (undefined 아님!)


2. JS로 html 태그 선택하기: class로 태그 선택하는 법

id는 고유 태그 하나만 선택하기에, 여러 태그를 동시에 선택하려면 class를 활용하자. document 속성에서 getElementByClassName 메소드를 사용하면 된다. class는 여러 개니까 class 값들을 가져와서 배열처럼 대괄호로 묶어 값을 출력한다. 그런데 주의할 점!!!

getElementByClassName로 가져온 요소 모음은 진짜 배열은 아니다!!

겉보기엔 배열 같지만 배열이 아닌, 유사배열이다. 이런 유사배열은 배열 함수인 splice, push 등의 메소드는 못 쓰고, isArray 함수로 true false를 체크하면 false로 나온다. 대신 for of 문이나 tags.length 를 쓸 수는 있다. 이렇게 배열과 형태는 비슷하지만 배열은 아닌 것을 array like object, 유사 배열이라 한다.

이렇게 class 이름을 통해 값을 가져오면, html에서 각 태그들이 어디 있든간에 무조건 위에 쓰인 놈이 0번이다.

let tags = document.getElementByClassName("myTag");
for let tag of tags{
    console.log(tag);
}
// myTag class를 가진 요소들 중 제일 첫 번째 출력.
console.log(tag[0]);

getElementByClassName을 쓸 때 만약 해당 클래스를 가진 요소가 한 개 뿐이라 해도, 여전히 유사배열 형태로 묶여서 나온다.

만약 없는 class의 값을 불러오면 null, undefined도 아니고 그냥 비어있는 htmlCollection 배열이 출력되니 주의하자!!


유사 배열이란?

배열과 유사하지만 배열은 아닌 객체.

  1. 숫자 형태의 indexing이 가능함. tags[0], tags[1] 이렇게 불러올 수 있음.
  2. length 프로퍼티가 있음. tags.length 가능.
  3. 배열의 기본 메소드인 push, splice 사용 불가!!
  4. Array.isArray(tags) 출력시 false 값 나옴.

유사배열은 생각보다 다양하며 어떤 유사배열은 for of 문도 못 쓸 수도 있으니 주의하자.


3. JS로 html 태그 선택하기: html 태그로 태그 선택하는 법.

div, button 등 html 태그로도 요소를 불러올 수 있으며 클래스처럼 여러 태그가 선택되므로 유사배열 html collection 형태로 리턴된다.

// button 태그들 모두 가져오기
const btns = document.getElementsByTagName('button');

// html 내 모든 태그들 가져오기.
const allTags = document.getElementsByTagName('*');

CSS 문법으로 태그 선택해서 js 파일로 가져오는 법.

1. JS로 html 태그 선택하기: css 선택자로 태그 선택하는 법.

위에서는 html 태그에 준 속성들로 값을 받아왔기 때문에 document.getElementBy... 함수를 썼다면, 그거보다 간단한 게 css 선택자를 활용하는 법이다. querySelector, querySelectorAll 메소드에 넣는 인자에 css 선택자를 넣기 때문에 css에서 활용했던 선택자 이름 그대로 가져다 쓸 수 있다.

<div id ="myNumber"> 1 </div>
const num = document.querySelector('#mynumber');

// 클래스 선택하면 node list 형태로 반환.
document.querySelectorAll('.btn-class');

// 만약 클래스 이름으로 querySelector을 쓰면 가장 위에 있는 0번 인덱스의 요소만 반환하고 html collection 형태는 반환 안 함!!
document.querySelector('.btn-class');

// 이렇게도 활용 가능.
console.log(document.querySelectorAll('#list li'));

(참고로 document.querySelectorAll('.btnclass')[0] 과 document.querySelector('btnclass') 는 같은 거다.)


이벤트와 버튼 클릭

위에서는 html의 요소들을 js 파일로 가져오는 법을 배웠다. 이렇게 요소를 가져온다음 무엇을 할까?
특정 요소에 전달되는 이벤트 (클릭, 제출, 타이핑 등)을 인식받아서 그 값을 가져오고, 이벤트 함수를 등록하는 등 여러 일을 할 수 있다!!

여기서 이벤트란 웹페이지에서 일어날 수 있는 일을 말한다. 페이지 스크롤하기, 버튼 누르기, 마우스 클릭하기 등 모든 게 이벤트!!

<html>
    <head></head>
    <body>
        <button id = 'mybtn'>
            click
        </button>
        <script src="index.html"></script>
    </body>
</html>
const btn = document.querySelector('mybtn');
btn.onclick = function(){
    console.log('hello!');
}

지금 이 코드는 js로 해당 객체의 onclick 프로퍼티에 함수를 등록한 것이다!!

이렇게 태그를 선택해서 프로퍼티 자체에 함수를 할당할 수 있다.

이렇게 무슨 이벤트에 뭘 할지 정하는 걸 이벤트 핸들링이라 한다.

구체적으로 이벤트 핸들링이 일어나면 무슨 동작을 할지 코드로 나타낸 함수 부분이 이벤트 핸들러


브라우저 요소 다루기.

위에서 배운 방법으로 js 코드로 html 태그를 가져올 수 있다. 이제 이 html 태그를 수정하고, 삭제하고, 새로 생성하여 브라우저 화면에 보여주게 해보자. 이걸 하기 위해 먼저 브라우저 자체를 알아야 한다!!

우리가 사용하는 웹 브라우저 자체를 다루려면 윈도우라는 객체로 다룰 수 있다.

윈도우 객체 안에는 우리가 사용한 console이나 document 객체도 들어있다. 즉, 이 윈도우 객체는 js에서 최상위 객체인 것이다. 이 윈도우 안에 모든 객체들이 속해있다!! 그래서 윈도우는 js 어디서든 접근할 수 있는, 전역 객체(global object) 이다.

사실 console.log를 쓰려면 엄밀히 따지면 window.console.log 겠지만 어차피 모든 객체는 window 하위 객체이므로, 그냥 떼서 써도 괜찮다!!


브라우저는 DOM 형태로 나타난다.

DOM : document object model, 문서 객체 모델.

브라우저 안에 나타나는 content 부분을 웹 페이지라고도 부르지만, 웹 문서라고도 부른다.

여기서 웹 문서부분을 객체로 표현한 게 DOM 이고 웹문서 최상위 객체는 document 이다.

아까는 window가 최상위 객체라며 이번엔 document가 최상위 객체라니, 이게 무슨 말일까?

이건 나중에 깊게 배워야 하는 부분인데, window를 구성하는 하위 계층에는 DOM 말고도 BOM, Javascript 노드가 있다. 이 걸 모두 통틀어 브라우저 자체의 최상위 객체는 window이고, 이 밑에 있는 DOM 웹문서 객체에서의 최상위 객체는 document라는 것.

그래서 document 객체의 메소드를 가지고 html 태그를 가져오거나, 여러 조작을 할 수 있는 것이다.

html 파일을 계층 구조로 해석한 것을 DOM tree라 한다...

객체들은 최상위 객체인 window에서 뻗어나와 계층 구조를 이루는데, 이게 나무같이 생겼다고 해서 tree라고 한다. 그리고 이 밑에 웹 문서를 나타내는 트리의 가장 최상단 루트 객체가 바로 document 이다. 그리고 이 웹문서 트리를 DOM tree라 한다.
Alt text

tree로 연결된 각 객체를 node라고 하고, 각각 부모노드, 자식노드, 형제노드로 파악할 수 있다.
Alt text

노드 타입에는 12가지가 있는데

  1. 태그를 표현하는 노드는 요소 노드.
  2. 문자열을 표현하는 노드는 텍스트 노드.
  3. 코멘트 노드
  4. 문서노드 ..

여러 노드 타입이 있지만 요즘엔 크게 요소 노드와 텍스트노드 이 두 개만 사용한다.

일반적으로 요소 노드의 자식 노드로 텍스트 노드가 있으며, 텍스트 노드는 자식 노드를 가질 수 없어서 leaf 노드라고도 불린다.


(DOM 트리 최상단에는 document가 있고, 그 아래로 html 태그가 있다. head, h1, div, a 등 태그들을 요소 노드라 하고 요소 노드의 자식으로 존재하는 텍스트들을 텍스트 노드라고 한다.)

<body>
    <div>가나다</div>
</body>

위의 코드에서 부모 요소노드 div와 그 자식 텍스트 노드 가나다.


따라서 우리는 js 코드를 짜서 DOM의 노드들을 원하는 대로 선택해서 수정하고 고칠 수 있어야 한다. 그러기 위해 노드에 어떻게 접근할지 알아야 한다!

요소 노드에 접근하는 방법.

아래와 같은 html 태그들이 있다고 하자.
...
  <div id="content">
    <h2 id="title-1">Cat-1</h1>
    <ul id="list-1">
      <li>Ragdoll</li>
      <li>British Shorthair</li>
      <li>Scottish Fold</li>
      <li>Bengal</li>
      <li>Siamese</li>
    </ul>
    <h2 id="title-2">Cat-2</h1>
    <ul id="list-2">
      <li>Sphynx</li>
      <li>Munchkin</li>
      <li>Persian</li>
      <li>Norwegian Forset</li>
      <li>Turkish Angora</li>
    </ul>
  </div>
...
const tags = document.querySelector('#content');

// content의 자식 요소인 title1, list1, title2,list2가 선택되어 html collection 형태로 주어진다. 
console.log(tags.children);
console.log(tags.children[1]);
console.log(tags.lastElementChild);

// 부모 요소에 접근하기
// div의 부모인 body 출력
console.log(tags.parentElement);

// 형제 요소 접근
console.log(tags.previousElementSibling);
console.log(tags.nextElementSibling);

// 연결해서 쓸 수도 있다.
console.log(tags.parentElement.previousElementSibling);

요소 노드 말고 텍스트 노드에도 접근하려면 Element 말고 Node 이름이 붙은 메소드를 쓰자.

node.childNodes();
node.firstChild();
node.parentNode();
node.previousSibling();
node.nextSibling();

근데 텍스트 노드엔 접근을 잘 안하지 않는다. 공백이나 띄어쓰기 등도 개별적인 텍스트 노드라 인식할 수 있어 내가 원치 않은 텍스트노드에 접근할 수도 있기 때문이다.


요소 노드 자체 뿐만 아니라, 요소 노드의 프로퍼티에도 접근해보자.

요소 노드에는 다양한 프로퍼티가 있다.

const tags = document.querySelector('#list-1');
// tags의 내부 요소 출력
console.log(tags.innerHTML);
// tags의 내부 요소 재할당
tags.innerHTML = '<li>Exotic</li>'
tags.innerHTML += '<li>Ragdoll</li>'


// 2. tags 태그를 포함한 전체 요소 출력
console.log(tags.outerHTML);
// inner은 해당 태그 내부만 교체되지만 outer은 아예 새 태그로 재배치됨.
tags.outerHTML = '<div></div>'

// 3. textcontent은 inner과 비슷하긴 한데 inner과 달리 내부 태그들은 안 가져오고 텍스트만 가져온다. 
console.log(tags.textContent);
//얘도 수정은 가능한데 모든 글을 텍스트 취급해서 태그 넣어도 그냥 텍스트처럼 넣어진다.
tags.textContent('hello');

<li>Exotic</li> 라는 코드를 넣고 싶다고 하자. innerHtml 속성으로 값을 할당할 때는 html로 해석되면서 Exotic 섿트스를 가진 list 태그가 생성된다. 그런데 textContent로 값을 할당하면 그냥 "< li >Exotic< /li >" 자체가 텍스트로 보인다.

이렇게 inner, outer, textContent 속성으로 값을 할당하면 기존의 값은 사라지고 덮어쓰기가 된다.


요소 노드를 직접 추가해보자.

위에서는 기존의 요소 노드의 텍스트나 html을 수정해보았다. 이번에는 새로운 요소 노드를 생성하고 내가 원하는 위치에 추가해보자.

const tomorrow = document.querySelector('#day');

// 1. 요소 노드를 만들기.
const first = document.createElement('li');

// 2. 요소 노드 내부에 내용 넣기. innerHTML, textContent 등..
first.textContent = "처음";

// 3. 요소 노드 추가하기
tomorrow.prepend(first);
tomorrow.append(first);
tomorrow.before(first);
tomorrow.after(first);

// 요소 뿐만 아니라 텍스트도 추가된다.
tomorrow.append("hello");

// 요소를 여러 개 추가할 수 있다.
tomorrow.append("hi", "hello", first);

// 요소를 삭제할 수도 있다.
tomorrow.remove();
tomorrow.children[2].remove();

// 요소를 이동시킬 수도 있다. 복사해서 붙여넣기가 아니라 아예 새로운 위치로 해당 태그를 이동시킴.
// 추가 메소드와 똑같음!
today.append(tomorrow.children[1]);
tomorrow.children[1].after(today.children[1]);

정리하자면!!

요소노드를 추가하는 4가지 커맨드. 요소를 추가하는 게 아니라 다른 곳의 요소를 이동할 때에도 쓰인다.

  • prepend: 태그 내부 맨 처음에 추가
  • append: 태그 내부 맨 끝에 추가
  • before: 태그 형제 앞에 추가.
  • after: 태그 형제 뒤에 추가.
  • remove: 해당 요소 삭제.

요소 노드의 프로퍼티를 다뤄보자.

앞에서 배운 innerText, outerText 같은 프로퍼티는 요소 노드의 공통 프로퍼티이다. 이 말고도, html 태그들이 가지고 있는 속성들 (src, class, id, stlye 등) 이 또한 해당 요소 노드의 프로퍼티가 된다!!

이런 프로퍼티들은 점표기법이나 대괄호 표기법으로 접근 가능하다.

그런데 주의할 게 있다. 우리가 html 태그에 넣어준 속성이 무조건 자동으로 요소 노드의 프로퍼티로 생성되는 것은 아니란 것이다. 보통 요소 노드의 프로퍼티는 각 태그 속성과 이름이 같지만, 가끔 요소 노드의 프로퍼티로 생성되지 않는 놈들도 있다.

예를 들어,

<a href="" class="a-class"></a>
const tag = document.querySelector('.a-class');
console.log(tag.href);
// href 프로퍼티는 없으니 undefined 출력됨. 

a 태그에서 href 속성은 html 표준 속성이 아니라 a 요소 노드의 프로퍼티에는 href가 추가되지 않는다!! 이런 속성을 비표준 속성이라 하는데, 이렇게 프로퍼티에 직접 접근하는 방식으로는 비표준 속성에 접근할 수 없다.

그래서 getAttribute 메소드를 써서 비표준, 표준 모두에 접근해주자.

// 속성 값 알아내기
console.log(tag.getAttribute('href'));
console.log(tag.getAttribute('class'));

// 속성 추가하기. 이미 있는 속성이라면 값이 수정된다. 
tag.setAttribute('class', 'a2-tag');
tag.setAttribute('href', 'https....');

// 속성 제거하기.
tag.removeAttribute('class');
tag.removeAttribute('href');

html과 js는 굉장히 빠르게 변화한다. 그래서 이 속성이 비표준 속성인지, 표준인지, 원래는 비표준이었는데 언제 채택돼서 표준이 된건지 다 외울 수가 없다. 코드 통일성을 위해서라도 getAttribute, setAttribute 메소드를 활용하는 데에 익숙해지자.

그리고 주의할 것!!! 속성 이름은 대소문자를 구분하지 않기 때문에,

tag.removeAttribute('href');
tag.removeAttribute('HREF');
tag.removeAttribute('hrEf');

모두 작동한다. 물론 헷갈리니까 그냥 대소문자 구별해서 이름을 잘 써주는 것이 좋을듯 하다. ㄱ-;;

참고로 class 는 비표준 속성은 아니지만, class 키워드와 혼동될 것을 막기 위해 className 이라는 프로퍼티 명을 가진다.

// 여기서 태그의 클래스 명을 호출할 땐 element.class가 아니라 element.className 이라고 한다. 
console.log(mytag);
console.log(mytag.className);

이제 요소 노드에 스타일을 추가해보자.

위에서 이제 태그의 속성을 읽고 셋팅하는 방법도 알아냈는데, 이제 css를 적용해보자.
노드에 스타일을 적용하는 방법은 여러 가지가 있다.

  1. style 프로퍼티를 통해 직접적으로 스타일을 다룰 수 있다.
    알다시피 이 방법은 js와 css 코드의 분리가 되질 않아 좋지 않다.
    const today = document.querySelector('#today');
    const tomorrow = document.querySelector('#tomorrow');
    

// 단, 프로퍼티 속성 이름에 하이픈이 있어선 안되고 카멜체로 다 바꾸자!
today.children[0].style.textDecoration = 'line-through';
today.children[0].style.backgroundColor = '#ffffff';


2. className 속성을 활용해서 태그의 클래스를 변경하자.
그런데 이 경우 className이 아예 새로 덮어씌워져서 문제 생길 수 있다.
```js
today.children[1].className = 'Done';
  1. classList 프로퍼티를 활용하자. class의 속성값들을 유사 배열로 관리하는 방법. 여러 메소드도 활용 가능!!
    const tag = tomorrow.children[1];
    

// 새 클래스 추가하기
tag.classList.add('new-class');
// 클래스 여러개 추가
tag.classList.add('new', 'new2');
// 클래스명 중복되면 하나만 적용
tag.classList.add('new', 'new');

// 클래스명 삭제
tag.classList.remove('done');
tag.classList.remove('done', 'new');

// 클래스명 있으면 제거하고 없으면 추가하기.
tag.classList.toggle('done');
// 여러 클래스 이름 줄 수는 없다.
// tag.classList.toggle('done', 'new'); 이건 작동 안함.

// toggle에 true 값 주면 add 기능만 함.
tag.classList.toggle('done', true);
//false는 remove 기능만.
tag.classList.toggle('done', false);



# 비표준 속성을 써먹어보자.

비표준 속성을 어디다 써먹을까?

1. css 선택자로 활용할 수 있다. css 파일 선택자로 넣어서 스타일을 줄 수도 있고, css 선택자이기 때문에 querySelector 메소드의 인자로 넣어서 활용할 수도 있다. 
```html
<!-- b 태그의 field 속성은 비표준이다. -->
<b field="title"></b></p>
const fields = document.querySelectorAll('[field]');
console.log(fields);
  1. 어느 태그에 값을 넣을지 태그 구분할 때 활용할 수 있다.
    const fields = document.querySelectorAll('[field]');
    const task = {
    title: '코드 에디터 개발',
    manager: 'CastleRing, Raccoon Lee',
    status: '',
    };
    

for (let tag of fields) {
const field = tag.getAttribute('field');
tag.textContent = task[field];
}


3. 스타일, 데이터 변경에 활용할 수 있다. getAttribute, setAttribute 메소드로 이벤트 발생 시 실시간으로 데이터 변경하거나 스타일 변경할 수 있음. 이런 경우 때문에 class보다 setAttribute로 비표준 속성을 변경하는 게 스타일 다루기 더 편할 수도 있다!! 상황에 따라 비표준 속성을 잘 활용해보자. 

```js
const btns = document.querySelectorAll('.btn');
for (let btn of btns) {
  const status = btn.getAttribute('status');
  btn.onclick = function () {
    fields[2].textContent = status;
    fields[2].setAttribute('status', status);
  };
}

단, 이 비표준 속성이 나중에 js가 발전하면서 표준이 되어버리면 코드 에러가 날 수 있다. 그래서 비표준 속성을 사용하기 위해 약속된 방식이 있는데, 개발자가 비표준속성을 임의로 만들어 쓸 경우, 비표준 속성의 이름을 data-* 로 하자는 것이다.

이렇게 data- 로 시작하는 속성은 모두 dataset 이라는 프로퍼티에 자동으로 저장되어 안전하게 비표준 속성을 활용할 수 있다.

예를 들어 data-status 라는 속성이 있다면 element.dataset.status 라는 프로퍼티에 접근하여 값을 가져올 수 있다는 것!!

(꿀팁... 대괄호에 속성 이름을 적으면 그 속성 이름을 가진 태그를 선택할 수 있고, 속성값까지 써서 해당 태그들을 선택할 수 있다.)

<button class="btn" status="대기중">대기중</button>
<button class="btn" status="진행중">진행중</button>
<button class="btn" status="완료">완료</button>
// 비표준 속성을 선택할 때엔 css 선택자로 대괄호를 써주자.
[status] {
  padding: 5px 10px;
}

// 속성 값까지 같이 써줘서 태그 선택 가능!! 
[status="대기중"] {
  background-color: #FF6767;
  color: #FFFFFF;
}

[status="진행중"] {
  background-color: #5f62ff;
  color: #FFFFFF;
}

[status="완료"] {
  background-color: #07c456;
  color: #FFFFFF;
}