리액트에서 많이 쓰는 문법(const, 객체, 화살표 함수)을 먼저 짚고,
오늘의 주제는 "태그를 코드로 만들고, 지우고, 이벤트에 반응하게 하기"였다.
학습 여섯 번째 날이다. 오늘 배운 내용은 이렇다.
let과 const, 객체(JSON), 화살표 함수window.onloadinnerHTML, innerText, textContent의 차이this로 클릭한 요소 알아내기let n = '안녕하세요';
console.log(n);
n = '반갑습니다'; // 덮어쓰기(재할당)는 가능
console.log(n);
const n2 = '커피';
console.log(n2);
// n2 = '녹차'; // 오류 발생: const는 재할당 불가
let: 값을 덮어쓸 수 있다. 같은 이름으로 다시 선언하는 것은 안 된다.const: 값을 덮어쓸 수 없다. 재선언도 안 된다.const를 쓴다고 한다.const n3 = { // const로 객체 n3를 생성
name: '안녕',
say: '잘 부탁드립니다.'
};
console.log(n3);
n3.name = '반갑습니다'; // n3의 name 속성을 변경
console.log(n3); // 변경된 것을 확인할 수 있다
객체는 키 : 값으로 이루어져 있다. 이런 표기를 JSON(JavaScript Object Notation, 자바스크립트 객체 표기법)이라고 한다.
배열도 객체라서 const로 선언해도 요소를 바꾸거나 추가할 수 있다.
const ary1 = ['a', 'b', 'c'];
ary1[0] = 'd'; // 요소 변경 가능
console.log(ary1); // ['d', 'b', 'c']
console.log(typeof(ary1)); // object
ary1.push('e'); // 요소 추가 가능
console.log(ary1); // ['d', 'b', 'c', 'e']
바로잡기: 필기에는 "const 객체타입은 덮어쓰기(변수 재할당)가 가능하다"고 적었는데, 정확히는 다르다.
const는 변수에 다른 값을 다시 대입(재할당)하는 것을 막는다. 객체나 배열을const로 선언하면 그 안의 내용(속성, 요소)을 바꾸는 것은 가능하지만,n3 = {...}처럼 변수 자체에 새 객체를 대입하면 오류가 난다.
const는 변수가 가리키는 대상(주소) 을 고정한다. 그 대상 안의 내용까지 얼리는 것은 아니다.
const arr = [1, 2, 3];
arr.push(4); // 가능: 같은 배열의 내용을 바꾼 것
arr[0] = 100; // 가능
arr = [9, 9, 9]; // 오류: 다른 배열을 가리키게 하는 재할당
1~100 사이 난수 10개를 ary 배열에 저장하고, 가장 큰 수를 출력하는 문제다.
let ary = new Array();
for (let i = 1; i <= 10; i++) {
let a = Math.floor((Math.random() * 100) + 1); // 1~100
ary.push(a);
}
console.log(ary);
let max = 0; // 난수가 1 이상이라 0으로 시작해도 된다
for (let i in ary) {
if (max < ary[i]) {
max = ary[i];
}
}
console.log(max);
난수 범위는 어제 정리한 공식(Math.floor(Math.random() * 개수) + 시작값)을 그대로 썼다. 배열에 push로 쌓고, for in으로 하나씩 비교하며 더 큰 값이 나오면 max를 바꾼다.
필기 코드는
max <= ary[i]이고else { max = max; }도 있었는데,else쪽은 아무 일도 하지 않는 코드라 지웠다. 같은 값일 때 바꿀 필요도 없어서<로 정리했다.
console.log(Math.max(...ary)); // 배열의 요소를 펼쳐서 Math.max에 전달
...ary는 배열을 낱개로 펼치는 전개 연산자(spread)다. 리액트에서도 자주 보게 된다고 한다.
링크를 클릭하면 div 태그를 만들어 body에 붙이는 실습이다.
<body id="body">
본문 내용이 들어가는 영역이다.
<a href="#" onclick="on()">생성</a>
<script>
function on() {
let a = document.getElementById('body');
let m = document.createElement('div'); // div 태그를 만든다
m.innerHTML = '새로 생성함'; // <div>새로 생성함</div>
a.appendChild(m); // body의 자식으로 붙인다
m.setAttribute('id', 'me'); // id 속성 추가
m.style.background = 'orange';
}
</script>
</body>
| 메소드 | 설명 |
|---|---|
document.createElement('태그명') | 태그를 새로 만든다 (아직 화면에 붙기 전) |
부모.appendChild(자식) | 부모의 맨 뒤 자식으로 붙인다 |
요소.setAttribute('속성', '값') | 속성을 추가하거나 바꾼다 |
createElement로 만든 태그는 appendChild로 붙이기 전까지 화면에 나타나지 않는다.
필기의 코드는
<body id="'body">처럼 id에 따옴표가 하나 더 붙어 있어서getElementById('body')로 찾지 못한다. 위 코드는id="body"로 고쳤다. 또 수업에 쓴 뉴스 기사 본문은 이 글에서는 임의의 문장으로 바꿨다.
저장 버튼을 누를 때마다 입력한 이름과 전화번호가 ul > li 형태로 쌓이는 실습이다.
<body>
이름 : <input type="text" name="name1"><br>
전화번호 : <input type="tel" name="tel1"><br>
<input type="submit" value="저장" onclick="save()">
<input type="reset" value="취소">
<ul id="profile"></ul>
<script>
function save() {
let result = document.getElementById('profile'); // ul 객체
let pro = document.getElementsByName('name1')[0].value + ','
+ document.getElementsByName('tel1')[0].value;
let a = document.createElement('li'); // li 태그 생성
let b = document.createTextNode(pro); // 텍스트 노드 생성
a.appendChild(b); // li 안에 텍스트를 넣는다 → <li>홍길동,010-0000-0000</li>
result.appendChild(a); // ul 안에 li를 넣는다
}
</script>
</body>
input 요소의 .value: 사용자가 입력한 실제 값을 가져온다.getElementsByName('이름'): name 속성으로 요소를 찾는다. 결과가 여러 개일 수 있어 [0]으로 첫 번째를 고른다.createTextNode('문자열'): 문자열 노드를 만든다. 노드이기 때문에 DOM 트리에 붙일 때 appendChild가 필요하다.a.textContent = pro;로 쓰면 텍스트 노드를 따로 만들지 않고도 같은 결과를 얻을 수 있다.필기 코드의 취소 버튼에
onclick='re()'가 붙어 있었는데re함수를 만들지 않았다. 이 상태에서 취소를 누르면re is not defined오류가 난다.type="reset"버튼은 폼 입력값을 비우는 기능이 이미 내장되어 있어서, 함수 없이도 동작한다. 위 코드는onclick을 지웠다.
<div class="frame">
<h1 class="head">제목</h1>
<p class="p">이 문단은 삭제될 예정이다.</p>
</div>
<script>
let frame = document.querySelector('.frame'); // div
let head = document.querySelector('.head'); // h1
let p = document.querySelector('.p'); // p
frame.removeChild(p); // div 안의 p 삭제 (p 안의 내용도 전부 삭제)
let hr = document.createElement('hr'); // hr 태그 동적 생성
frame.appendChild(hr); // div의 자식으로 추가
let strong = document.createElement('strong'); // <strong></strong>
strong.textContent = 'h1 태그를 strong 태그로 변경';
frame.replaceChild(strong, head); // head 자리에 strong을 넣는다
</script>
| 메소드 | 설명 |
|---|---|
부모.removeChild(자식) | 자식 요소를 삭제한다 (내용도 같이 삭제) |
부모.replaceChild(새것, 옛것) | 옛것을 새것으로 교체한다. 새것이 먼저, 옛것이 나중 |
오늘 쓴 DOM 조작 메소드는 createElement, createTextNode, appendChild, removeChild, replaceChild 다섯 가지다.
필기에는
frame.append(strong)이 한 줄 더 있었는데, 같은 요소는 DOM에 한 군데에만 존재할 수 있다. 그래서replaceChild를 하면strong이 맨 뒤에서head자리로 옮겨 가는 것이라, 앞의append는 빼도 결과가 같다.
h3 태그를 클릭하면 글자색을 파랑, 배경을 검정으로 바꾸는 예제를 세 가지 방식으로 작성했다.
<h3 id="head">JavaScript</h3>
<script>
let head = document.getElementById('head');
// 1. 고전 이벤트 방식: 문서 객체에 바로 onclick을 할당 (익명 함수)
head.onclick = function () {
this.style.color = 'blue';
this.style.backgroundColor = 'black';
};
// 2. 이벤트 메소드 방식: 문서객체.addEventListener('이벤트타입', 콜백함수)
head.addEventListener('click', function () {
this.style.color = 'blue';
this.style.backgroundColor = 'black';
});
// 3. 이벤트 리스너 함수 등록: 이름 있는 함수를 연결
head.onclick = on;
function on() {
this.style.color = 'blue';
this.style.backgroundColor = 'black';
}
</script>
(실습에서는 한 번에 한 방식씩 주석을 풀어 확인했다. 위 코드는 세 방식을 모아 놓은 것이다.)
| 방식 | 형태 |
|---|---|
| 1. 고전 이벤트 방식 | 요소.onclick = function() { … } |
| 2. 이벤트 메소드 방식 | 요소.addEventListener('click', 콜백함수) |
| 3. 이벤트 리스너 함수 방식 | 요소.onclick = 함수명 |
세 번째 방식에서는 함수 이름 뒤에 ()를 붙이지 않는다. on()이라고 쓰면 등록하는 순간 함수가 바로 실행되어 버린다. 함수 자체를 값처럼 건네는 것이다.
onclick에 할당하면 마지막에 할당한 것 하나만 남는다. 다시 할당하면 앞의 것을 덮어쓴다.addEventListener는 같은 이벤트에 여러 개를 겹쳐서 등록할 수 있다.head.addEventListener('click', () => console.log('첫 번째'));
head.addEventListener('click', () => console.log('두 번째'));
// 클릭하면 '첫 번째', '두 번째'가 모두 출력된다
또 위 예제에서 this는 클릭된 요소(head)를 가리킨다. 화살표 함수는 this가 달라지므로 이 방식으로 this를 쓰려면 일반 함수로 써야 한다.
<ol type="A" id="ol">
<li>물 끓이기</li>
<li>라면, 스프 넣기</li>
<li>계란 넣기</li>
<li>맛있게 먹기</li>
</ol>
<script>
let ol = document.getElementById('ol');
// 1. 고전 방식
// ol.onclick = function () { ol.style.backgroundColor = 'yellow'; };
// 2. addEventListener
// ol.addEventListener('click', function () { ol.style.backgroundColor = 'yellow'; });
// 3. 이벤트 리스너 함수
ol.onclick = on;
function on() {
ol.style.backgroundColor = 'yellow';
}
</script>
같은 기능을 세 가지 방식으로 각각 구현했다.
스크립트가 <head>에 있거나 <body>의 앞쪽에 있으면, 아래쪽 태그가 아직 만들어지기 전에 실행되어 요소를 찾지 못한다. window.onload는 html 문서가 모두 로드된 이후에 안의 함수를 실행해 준다. "html 요소들이 다 만들어진 다음에 스크립트가 실행되도록" 하는 장치다.
<body>
<script>
window.onload = function () {
let n = document.getElementById('test');
n.style.color = 'red';
};
</script>
<p id="test">Test</p>
</body>
<p>가 스크립트보다 아래에 있어도 window.onload 안에서는 찾을 수 있다. 이 감싸기를 빼면 n이 null이 되어 오류가 난다. Day.1에서 배운 "스크립트 위치"가 중요했던 이유가 이것이다.
<script>
window.onload = function () {
let form = document.getElementById('form1');
// 폼이 전송될 때 발생하는 이벤트 (고전 이벤트 방식)
form.onsubmit = function () {
let pw = document.getElementById('pwd11').value;
let pw2 = document.getElementById('pwd22').value;
if (pw == pw2) {
alert('비밀번호 일치');
} else {
alert('비밀번호 불일치');
}
};
};
</script>
<form id="form1">
비번<input type="password" name="pwd1" id="pwd11"><br>
비번확인<input type="password" name="pwd2" id="pwd22"><br>
<input type="submit" value="제출">
</form>
submit 버튼을 눌렀을 때 처리되는 이벤트 이름이 onsubmit이다. 비밀번호와 비밀번호 확인에 쓴 값을 .value로 가져와 비교한다. (수업 예제의 알림 문구는 "로그인 성공/실패"였는데, 하는 일은 비밀번호 일치 확인이라 문구를 바꿨다.)
폼이 전송되면 기본 동작으로 페이지가 다시 로드된다. onsubmit 안에서 return false;를 쓰거나 이벤트 객체의 preventDefault()를 호출하면 전송 동작을 막을 수 있다. 서버로 보내기 전에 입력값을 검사할 때 쓰는 방법이다.
form.onsubmit = function (e) {
if (pw != pw2) {
e.preventDefault(); // 불일치하면 전송을 막는다
}
};
회원가입 화면에 쓰는 입력 태그를 한 번에 써 봤다.
이름 : <input type="text" placeholder="4자~10자 사이, 공백없이"><br>
이메일 : <input type="email"><br> <!-- @ 기호가 있어야 한다 -->
비밀번호 : <input type="password"><br>
가입경로 : <select>
<option>블로그</option>
<option>인스타</option>
</select>
메모 : <textarea></textarea><br>
<!-- 라디오 버튼: name을 같게 주면 하나만 선택된다 -->
<input type="radio" name="mail">메일수신
<input type="radio" name="mail">메일수신안함<br>
<!-- 체크박스: 복수 선택이 기본 -->
<input type="checkbox">메일수신
<input type="checkbox">메일수신안함<br>
<!-- submit: 입력한 값을 서버에 전송하는 버튼 -->
<input type="submit" value="가입하기">
<input type="reset" value="취소">
| 태그/속성 | 설명 |
|---|---|
type="text" + placeholder | 한 줄 입력. placeholder는 입력 전 안내 문구 |
type="email" | @가 있어야 하는 이메일 입력 |
type="password" | 입력한 글자가 가려진다 |
<select> + <option> | 목록에서 선택 |
<textarea> | 여러 줄 입력 |
type="radio" | 같은 name끼리 하나만 선택 |
type="checkbox" | 여러 개 선택 가능 |
type="submit" | 입력한 값을 서버에 전송 |
type="reset" | 입력한 값을 모두 비움 |
위 입력 태그들은 <form>으로 감싸야 submit 버튼이 실제로 값을 묶어서 전송한다. type="email"의 @ 검사도 form 안에서 submit할 때 동작한다. 바로 위의 onsubmit 예제가 <form id="form1">로 감싸져 있던 이유다.
태그 안의 내용을 바꾸는 속성이 세 가지다.
<h1 id="hi1">안녕</h1>
<h1 id="hi2">모쪼록</h1>
<h1 id="hi3">잘 부</h1>
<script>
let a = document.querySelector('#hi1');
let b = document.querySelector('#hi2');
let c = document.querySelector('#hi3');
a.innerHTML = '자바스크립트'; // <h1 id=hi1>자바스크립트</h1>
a.innerHTML = '안녕 <a href="#">링크1</a>'; // 태그로 인식 → 링크가 생긴다
b.innerText = '파이썬';
b.innerText = '안녕 <a href="#">링크1</a>'; // 태그 인식 안 됨, 글자 그대로 출력
c.textContent = '리액트';
c.textContent = '안녕 <a href="#">링크1</a>'; // 태그 인식 안 됨, 글자 그대로 출력
</script>
innerHTML: 문자열을 HTML로 해석한다. 태그를 넣으면 태그로 동작한다.innerText, textContent: 문자열을 그냥 글자로 넣는다. 태그를 넣어도 태그로 인식하지 않는다.(필기 코드에는 href가 herf로 오타가 나 있어 링크가 걸리지 않는다. 위 코드는 고친 것이다.)
innerText: 화면에 보이는 텍스트만 가져온다. display: none이나 hidden인 요소의 텍스트는 무시한다.textContent: CSS와 무관하게 DOM 안에 있는 모든 텍스트를 가져온다. 숨겨진 것까지 포함한다.<style>
.name { display: none; } /* name 클래스는 화면에서 숨긴다 */
</style>
<h3 id="head"> 반갑습니다. <span class="name">길동</span>님</h3>
<script>
let a = document.getElementById('head');
console.log(a.innerHTML);
// 반갑습니다. <span class="name">길동</span>님 (태그까지 전부)
console.log(a.innerText);
// 반갑습니다. 님 (숨겨진 '길동'이 빠지고, 화면 기준으로 공백이 정리됨)
console.log(a.textContent);
// " 반갑습니다. 길동님" (공백/줄바꿈과 숨겨진 텍스트까지 가공 없이 이어 붙임)
</script>
| 속성 | 태그 | 숨겨진 텍스트 | 공백 |
|---|---|---|---|
innerHTML | 태그까지 포함해서 가져오고, 쓸 때는 HTML로 해석 | 포함 | 그대로 |
innerText | 해석하지 않음 | 제외 | 화면 기준으로 정리 |
textContent | 해석하지 않음 | 포함 | 그대로 |
바로잡기: 필기의 주석에는
innerText결과가 "반갑습니다. 길동님"이라고 되어 있었는데, 바로 위에서 정리한 대로innerText는display: none인 요소를 무시하므로길동이 빠진 "반갑습니다. 님" 이 출력된다.
innerHTML은 문자열을 HTML로 해석하기 때문에, 사용자가 입력한 값을 그대로 넣으면 입력에 섞인 <script> 같은 코드가 실행될 수 있다고 알려져 있다. 사용자 입력은 textContent로 넣는 것이 안전하다. 이름·전화번호를 목록에 추가하는 앞의 실습에서 innerHTML 대신 createTextNode를 쓴 것도 같은 맥락으로 이해할 수 있다.
<p onclick="on()" id="lang" class="lang2">무슨 언어를 배울 거에요?</p>
<script>
function on() {
let input = prompt('언어를 입력해주세요');
document.write(input);
}
</script>
document.write는 페이지가 이미 로드된 뒤에 호출하면 기존 화면을 모두 지우고 새로 쓴다. 이 예제에서도 클릭 후에는 안내 문구가 사라지고 입력한 언어만 남는다. 화면 일부만 바꾸려면 오늘 배운textContent나createElement를 쓴다.
<h1>할 일 목록</h1>
<ul> <!-- unordered list: 순서가 정해지지 않은 리스트 -->
<li onclick="on(this)"><span class="check">v</span> 할 일 1</li>
<li onclick="on(this)"><span class="check">v</span> 할 일 2</li>
<li onclick="on(this)"><span class="check">v</span> 할 일 3</li>
<li onclick="on(this)"><span class="check">v</span> 할 일 4</li>
<li onclick="on(this)"><span class="check">v</span> 할 일 5</li>
</ul>
<script>
function on(a) { // a는 클릭한 객체
a.style.color = 'red';
}
</script>
this는 객체 자기 자신을 의미한다. onclick="on(this)"라고 쓰면, 클릭했을 때 어떤 요소를 클릭했는지 함수가 알 수 있다. 덕분에 li마다 id를 붙이고 따로 찾을 필요가 없다. ol이나 ul을 통째로 바꾼 이전 실습과 달리, 눌린 li만 색이 바뀐다.
(수업 예제에는 li마다 class="1", class="2" 같은 숫자 class가 붙어 있었다. HTML에서는 가능하지만 CSS 선택자로 쓸 때는 숫자로 시작하는 이름이 문제를 일으킬 수 있어서, 이 글의 코드에서는 뺐다.)
// 함수 선언식: 코드 실행 전에 함수 전체를 메모리에 미리 등록 → 위에서 호출해도 정상 작동
function fun1(val) {
return val;
}
document.write(fun1(10));
// document.write(fun2(10)); // 오류: 함수 표현식은 호이스팅이 안 된다
const fun2 = function (val) {
return val;
};
// document.write(fun3(10)); // 오류: 화살표 함수도 호이스팅이 안 된다
const fun3 = (val) => {
return val;
};
document.write(fun3(10));
// 매개변수가 하나일 때는 () 생략 가능
const fun4 = val => val;
document.write(fun4(15)); // 15
// 중괄호와 return을 쓴 형태
const fun5 = (val1, val2) => {
return val1 + val2;
};
// 실행문이 식 하나이면 {}와 return을 생략 가능
const fun6 = (val1, val2) => val1 + val2;
document.write(fun5(10, 20) + '<br>'); // 30
document.write(fun6(20, 40)); // 60
인자를 받아 객체를 만들어 반환하는 경우, 객체를 괄호 ()로 감싸야 한다.
const fun7 = (val1, val2) => ({
name: val1,
age: val2
});
console.log(fun7('홍길동', 25)); // { name: '홍길동', age: 25 }
=> { ... }의 중괄호는 JS에게 함수 본문의 시작으로 읽힌다. 그래서 { name: val1, age: val2 }를 그냥 쓰면 객체가 아니라 코드 블록으로 해석된다. 객체 리터럴을 바로 반환하려면 ({ ... })처럼 괄호로 감싸서 "이건 식(객체)이다"라고 알려 줘야 한다.
0~99 사이 수를 소수점 이하를 버려 10개 출력하는 문제다.
let show = (a) => { document.write(a + '<br>'); };
for (let i = 0; i < 10; i++) {
let num = Math.floor(Math.random() * 100); // 0~99
show(num);
}
난수 범위 공식에 대입하면 개수 100, 시작값 0이다. show는 화살표 함수로 만든 출력용 함수다.
<p>자바
<span id="java">스크립트</span>
</p>
<input type="button" value="버튼" onclick="on()">
<script>
function on() {
let a = document.getElementById('java');
a.style.fontSize = '30px';
a.style.color = 'coral';
a.style.display = 'block'; // 줄이 바뀌면서 출력
a.style.border = '3px dotted pink'; // 테두리: 두께 모양 색
}
</script>
<p>: 단락을 묶어 주는 태그display = 'block': span은 원래 글 사이에 이어서 나오는데, block으로 바꾸면 줄이 바뀌면서 출력된다.border는 두께 모양 색 순서로 한 줄에 쓴다.오늘의 큰 줄기는 DOM을 읽는 것에서 한 걸음 나아가, 코드로 만들고 지우고 반응시키는 것이다.
const는 재할당을 막지만, 객체와 배열의 내용 변경은 막지 않는다.createElement로 만들고 appendChild로 붙이며, removeChild와 replaceChild로 지우거나 바꾼다.addEventListener, 리스너 함수 등록 세 가지가 있고, window.onload로 문서가 다 로드된 뒤 실행시킬 수 있다.innerHTML뿐이다. 숨겨진 텍스트까지 가져오는 것은 textContent다.onclick="on(this)"로 클릭한 요소 자신을 함수에 넘길 수 있다.()로 감싼다.Tags: JavaScript DOM 이벤트 화살표함수 const innerHTML 폼 개발자 학습기록