문자열, 숫자, 날짜, 배열이 전부 "객체"라는 걸 알게 된 날.
오늘부터는 값만 다루던 코드에서 벗어나, 화면의 태그를 직접 골라 색을 바꾸기 시작했다.
학습 다섯 번째 날이다. 오늘 배운 내용은 이렇다.
==, ===)toFixed, Math 객체forEachgetElementById, querySelector, getElementsByTagName문자열을 객체로 만들어 다루기 쉽게 해 주는 객체다. 선언 방식은 두 가지다.
// 변수식 선언 방식
let 변수명 = '문자열';
// 생성자 함수식 선언 방식
let 변수명 = new String('문자열');
String 객체와 함께 쓰는 메소드가 많다. 처음엔 "너무 많은데?" 싶었다. 일단 표로 정리해 두고, 아래에서 하나씩 써 본다.
| 메소드 | 설명 |
|---|---|
charAt(index) | 지정한 인덱스의 문자를 반환 |
substr(idx[, len]) | idx부터 len개 문자를 새 문자열로 반환. len을 생략하면 끝까지 |
substring(idxA, idxB) | idxA부터 idxB 앞까지의 문자열 반환 |
slice(idxA[, idxB]) | idxA부터 idxB 앞까지. 음수 인덱스도 가능 |
match(str) | 문자열 안에 해당 내용이 있는지 확인 |
toLowerCase() / toUpperCase() | 소문자 / 대문자로 변환한 새 문자열 반환 |
indexOf(s[, idx]) | 앞에서부터 특정 문자의 위치를 찾음 |
replace(a, b) | a를 b로 바꾼 문자열 반환 |
concat(a, b, …) | 현재 문자열 뒤에 a, b, …를 차례로 이어 붙인 문자열 반환 |
split([separator[, limit]]) | 문자열을 분리해 배열로 반환 |
trim() | 앞뒤 공백 제거 |
prompt로 생년월일 6자리를 입력받아 월과 일을 뽑는 예제다.
<script>
let num = prompt('생년월일 입력'); // 001026
let a = num.length; // 6
console.log(a); // length는 글자 수를 나타낸다
let month = num.substr(2, 2); // 인덱스 2부터 2글자 → 10
let day = num.substring(4); // 인덱스 4부터 끝까지 → 26
if (a == 6) {
alert(`${month}월 ${day}일`);
} else {
alert('에러');
}
</script>
substr(인자1, 인자2): 인덱스 [인자1]부터 인자2개만큼 출력한다.substring(인자1, 인자2): 인덱스 [인자1]부터 인덱스 [인자2] 앞까지 출력한다. 두 번째 인자는 "미만"이라 포함되지 않는다.substring(2, 2)는 범위가 비어서 아무것도 출력되지 않는다.(4, 2)를 넣으면 (2, 4)처럼 동작한다.substr은 MDN에서 "더 이상 권장하지 않는(deprecated)" 기능으로 분류한다. 수업 중에는 그냥 배웠지만, 실무 코드에서는 slice나 substring을 쓰는 편이 안전하다고 한다. 같은 결과를 slice로 쓰면 이렇다.
let num = '001026';
num.substr(2, 2); // '10' (시작 위치, 개수)
num.slice(2, 4); // '10' (시작 위치, 끝 위치 앞까지)
<script>
let str = prompt('언어 입력');
if (str.match('자바') || str.match('데이터베이스')) {
alert('백엔드');
} else if (str.match('react')) { // 대소문자를 구분한다
alert('프론트');
}
</script>
입력한 문자열 안에 일치하는 내용이 있으면 조건이 참으로 판단된다.
match('인자'): 같은 내용이 있는지 확인한다. 대소문자를 구분한다.바로잡기: 필기에는 "true/false를 반환한다"고 적었는데, 정확히는
match는 일치하면 일치한 내용이 담긴 배열을, 없으면null을 반환한다. 배열은 참으로,null은 거짓으로 취급되기 때문에if조건에서는 true/false처럼 동작한다. 단순히 포함 여부만 보고 싶다면includes('자바')가 true/false를 직접 반환한다.
주민번호 뒷자리 첫 숫자로 성별을 구분하는 예제다.
<script>
let str = prompt('주민번호 입력'); // 예: 0010261234567
console.log(str.charAt(2)); // 인덱스 2의 글자 → 1
let n = str.charAt(6); // 인덱스 6 = 뒷자리 첫 숫자
if (n == 1 || n == 3) {
alert('남자');
} else if (n == 2 || n == 4) {
alert('여자');
} else {
alert('출생 신고가 안되신거 같아요.');
}
</script>
str.charAt(인자): 인덱스 [인자]의 값을 반환한다.charAt이 반환하는 값은 문자열이라 '1'이다. 그런데 n == 1이 참이 되는 건 ==가 타입이 달라도 값을 변환해서 비교하기 때문이다. (Day.1에서 배운 ==와 ===의 차이가 여기서 쓰였다.)
<script>
let n = 'computer C';
console.log(n.indexOf('C')); // 9 (앞에서부터 센 인덱스, 대소문자 구분)
console.log(n.indexOf('m')); // 2
console.log(n.replace('C', 'c')); // computer c
let m = ' coffee';
console.log(m); // [ coffee]
console.log(m.trim()); // [coffee]
console.log(m.slice(2)); // [ coffee] (인덱스 2부터 끝까지, 앞 공백 두 칸이 잘림)
console.log(m.substr(2)); // [ coffee] (위와 동일)
console.log(m.slice(2, 4)); // [ c] (인덱스 2~3)
console.log(m.substring(2, 4)); // [ c] (위와 동일)
</script>
indexOf(인자): 해당 인자의 인덱스 번호를 반환한다.replace(인자1, 인자2): 인자1을 인자2로 바꾼 결과를 반환한다.trim(): 앞뒤 공백을 제거한다.slice(인자): 인덱스 [인자]부터 끝까지 출력한다.start > end일 때 substring은 두 값을 swap하지만, slice는 swap하지 않고 빈 문자열을 반환한다.
위 메소드들은 전부 새 문자열을 반환할 뿐, 원본 문자열은 바뀌지 않는다. 문자열 자체가 한번 만들어지면 수정할 수 없는 값이기 때문이다.
let s = 'Hello';
s.toUpperCase(); // 반환값은 'HELLO'지만
console.log(s); // Hello (원본 그대로)
s = s.toUpperCase(); // 바꾸고 싶다면 다시 대입해야 한다
console.log(s); // HELLO
toLowerCase(), toUpperCase()도 같은 방식이다. 입력값의 대소문자를 통일해서 비교할 때(str.toLowerCase() == 'react') 쓸 수 있다.
new String은 뭐가 다를까문자열은 눈으로 보면 단순한 값(기본형)처럼 보인다. 그런데도 .concat() 같은 메소드를 쓸 수 있는 이유는, JS가 내부에서 오토 박싱(자동 박싱)으로 잠깐 객체로 감싸서 처리하기 때문이다.
const str1 = 'Hello'; // 원시값(primitive) string
console.log(typeof(str1)); // string
const str2 = new String('World');
console.log(typeof(str2)); // object
비교 결과를 정리해 보면 이렇다.
let a = 'hi';
let b = 'hi';
let a1 = new String('hi');
let b1 = new String('hi');
console.log(a === b); // true 원시 - 원시: 값 비교
console.log(a1 === b1); // false 객체 - 객체: 서로 다른 객체(주소) 비교
console.log(a == b); // true
console.log(a1 == b1); // false 객체 - 객체: 참조(주소) 비교
console.log(a == a1); // true 타입이 다르면 객체를 원시값으로 변환해서 값 비교
| 비교 | 결과 | 이유 |
|---|---|---|
a == b | true | 둘 다 값 'hi' |
a1 == b1 | false | 내용은 같지만 서로 다른 객체. 주소를 비교한다 |
a == a1 | true | 타입이 달라 ==가 객체를 값으로 변환한 뒤 비교 |
a1 === b1 | false | 서로 다른 객체이므로 |
여기서 "원시"란 객체로 선언하지 않은 기본 값(string 등)을 가리킨다.
필기 중에
a1 === b1이 false인 이유를 "자료형이 달라서"라고 적었는데, 둘 다object라서 자료형은 같다. false인 진짜 이유는 서로 다른 객체를 가리키기 때문(주소 비교)이다. 이 부분은 바로잡았다.
const str1 = 'Hello';
const str2 = 'World';
console.log(str1.concat(str2)); // HelloWorld
console.log(str2.concat(', ', str1)); // World, Hello
문자열.concat(인자1, 인자2, …): 문자열 뒤에 인자들을 차례로 이어 붙인다. 구분자 같은 특별한 기능은 없고, ', '도 그냥 이어 붙일 문자열 중 하나다.<script>
let n = new String('공부는 어렵지만, 재밌네요');
console.log(typeof(n)); // object
document.write(n.charAt(6) + '<br>'); // 인덱스 6의 한 글자
document.write(n.substr(0, 3) + '<br>'); // 공부는
document.write(n.slice(0, 4) + '<br>'); // 공부는 (공백 포함 4글자)
document.write(n.split(',')[0] + '<br>'); // 공부는 어렵지만
document.write(n.split(',')[1] + '<br>'); // 재밌네요
document.write(n.split(',', 1) + '<br>'); // 쉼표 기준으로 나누되 1개까지만
document.write(n.split(',', 2) + '<br>'); // 2개까지
document.write(n.split(',', 3) + '<br>'); // 3개를 요청했지만 2개뿐이라 2개만
</script>
문자열.split(인자): 인자를 기준으로 문자열을 배열로 나눈다. 앞에서부터 인덱스 [0]으로 정리된다. 문자열을 분리하는 용도로 쓴다.split(구분자, limit): 나눈 결과를 최대 limit개까지만 반환한다.바로잡기: 필기에는 "쉼표 기준으로 공백이 있으면 공백은 버려진다"고 적었는데, 공백은 버려지지 않는다.
split(',')[1]의 결과는' 재밌네요'처럼 앞 공백이 그대로 남는다. 공백까지 없애고 싶다면split(',')[1].trim()을 쓴다.
prompt로 받은 값은 문자열이라 그냥 더하면 이어 붙여진다.
let n = '3';
let m = '4';
console.log(n + m); // 34 (문자열 이어 붙임)
console.log(Number(n) + Number(m)); // 7
console.log(parseInt(n) + parseInt(m)); // 7
console.log(Number('3.5')); // 3.5
console.log(parseInt('3.5')); // 3
console.log(Number('3abc')); // NaN
console.log(parseInt('3abc')); // 3
Number()는 전체가 숫자로 변환될 수 있어야 한다. parseInt()는 앞에서부터 읽을 수 있는 정수까지만 읽는다.
// 1. 변수식 선언 방식
let a = 100;
let b = 'hi';
let c = [1, 2, 3];
// 2. 객체형 선언 방식(생성자 함수식)
let a1 = new Number('100'); // 문자열 '100' → 숫자 100
let b1 = new String('hi');
let c1 = new Array(3);
console.log(typeof(a1)); // object
console.log(typeof(b1)); // object
console.log(typeof(c1)); // object
typeof로 보면 new로 만든 것은 전부 object다.
세 과목 점수를 입력받아 합계와 평균을 구하는 예제다.
let kor = prompt('국어점수');
let math = prompt('수학점수');
let eng = prompt('영어점수');
let result = Number(kor) + Number(math) + Number(eng);
console.log(result);
console.log(typeof(result));
console.log((result / 3).toFixed(2)); // 소수점 2자리까지 출력
값.toFixed(인자): 소수점 인자자리까지 출력한다. (반올림된다.)toFixed의 결과는 숫자가 아니라 문자열이다.
let avg = (200 / 3).toFixed(2);
console.log(avg); // 66.67
console.log(typeof(avg)); // string
평균을 다시 계산에 쓰려면 Number(avg)로 바꿔야 한다. 앞의 prompt 값이 문자열이었던 문제와 같은 종류의 함정이다.
let a = 44.44;
let b = 22.53;
let c = 21;
// ceil: 무조건 올림
console.log(Math.ceil(a)); // 45
console.log(Math.ceil(b)); // 23
// floor: 무조건 내림 (정수는 내릴 게 없음)
console.log(Math.floor(a)); // 44
console.log(Math.floor(b)); // 22
console.log(Math.floor(c)); // 21
// round: 반올림
console.log(Math.round(a)); // 44
console.log(Math.round(b)); // 23
Math.random()은 0 이상 1 미만의 실수를 무작위로 반환한다. 곱하는 수로 범위의 크기를, 더하는 수로 시작값을 조정한다.
let num = Math.random(); // 예: 0.4837...
필기에서는 Math.round로 아래 두 문제를 풀었는데, 풀면서 범위가 어긋나는 부분이 있어 Math.floor로 다시 정리했다.
| 목표 | 필기의 풀이 | 문제점 | 바로잡은 풀이 |
|---|---|---|---|
| 100 이상 120 미만 | Math.round(Math.random()*20)+100 | 100~120까지 나올 수 있다 (120 포함). 양 끝값은 나올 확률도 절반 | Math.floor(Math.random()*20)+100 |
| 50~80 | Math.round(Math.random()*31+50) | 최대 81까지 나올 수 있다 | Math.floor(Math.random()*31)+50 |
let num1 = Math.floor(Math.random() * 20) + 100; // 100 ~ 119
let num2 = Math.floor(Math.random() * 31) + 50; // 50 ~ 80
Math.floor(Math.random() * 개수) + 시작값으로 외우면 편하다.
random()은 1이 되지 않으므로 floor로 내리면 항상 시작값 이상 시작값+개수-1 이하로 나온다. 이번 실습의 1~9 곱셈 문제(Math.floor(Math.random()*9)+1)도 같은 공식이다.
let day = new Date(); // 오늘 날짜와 시간
console.log(day); // Mon Aug 31 2026 12:06:27 GMT+0900 (한국 표준시)
let day2 = new Date(2026, 0, 25); // 특정 날짜
console.log(day2); // Sun Jan 25 2026 00:00:00 GMT+0900
let day3 = new Date(2026, 1, 17);
console.log(day3); // Tue Feb 17 2026 00:00:00 GMT+0900
월은 0부터 시작한다. 0이 1월, 1이 2월, 11이 12월이다. 날짜는 그대로 쓴다.
필기의 주석에
new Date(2026,0,25)의 출력이 "Thu Jun 25"로 적혀 있었다. 월 인덱스 0은 1월이므로 정확한 출력은Sun Jan 25 2026이다.
console.log(day.toLocaleDateString()); // 2026. 8. 31.
console.log(day.toLocaleTimeString()); // 오후 12:11:22
console.log(day.toLocaleString()); // 2026. 8. 31. 오후 12:12:02
let today = new Date();
let yyyy = today.getFullYear(); // 연도
let mm = today.getMonth(); // 월 (0~11)
let mm2 = today.getMonth() + 1; // 실제 월
let day = today.getDate(); // 일
console.log(`${yyyy}년 ${mm2}월 ${day}일이다.`);
let hh = today.getHours(); // 시
let mi = today.getMinutes(); // 분
let sec = today.getSeconds(); // 초
console.log(`${hh}시 ${mi}분 ${sec}초다.`);
let date = today.getDay(); // 요일
console.log(date); // 일요일: 0, 월요일: 1 ~ 토요일: 6
getMonth()는 0~11이라 화면에 보여 줄 때는 +1을 해 줘야 한다. getDate()(일)와 getDay()(요일 번호)는 이름이 비슷하니 헷갈리지 않게 주의한다.
let today = new Date();
document.write(today.toLocaleDateString() + '<br>');
// 오늘 포함 100일 후 → 오늘 날짜에 99를 더한다
today.setDate(today.getDate() + 99);
document.write(today.toLocaleDateString() + '<br>');
setDate(값): 날짜(일) 값을 수정한다. 반환값은 Date 객체가 아니라 타임스탬프(ms 숫자)다. 예를 들어 1796787365791처럼 큰 숫자가 나온다.필기에서 "setDate 후에 today.getDate()를 해도 같은 일자가 나오지 않을까?"라고 의문을 적어 뒀는데, 그렇지 않다. setDate는 today 자체를 바꾸기 때문에 이후 today.getDate()는 바뀐 날짜의 '일' 숫자를 돌려준다. 예를 들어 8월 31일에 99일을 더해 12월 8일이 되었다면, 이후 getDate()는 8을 반환한다. setDate(31 + 99)처럼 한 달 일수를 넘겨도 JS가 알아서 다음 달로 넘겨 계산해 준다.
// 변수식 선언
let fruit = ['apple', 'banana', 'Mango'];
// 생성자 함수식(객체형) 선언
let fruit2 = new Array('Apple', 'Banana', 'Mango');
console.log(fruit.length); // 3
console.log(fruit2.length); // 3
fruit2[2] = 'PineApple'; // Mango → PineApple로 변경
console.log(fruit2); // ['Apple', 'Banana', 'PineApple']
new Array('인자1', '인자2', …)로 선언할 수 있다.
주의:
new Array(3)처럼 숫자 하나만 넣으면 "3이 들어 있는 배열"이 아니라 "빈 칸 3개짜리 배열"이 만들어진다. 위의typeof(c1)예제에서 쓴new Array(3)가 바로 그 경우다.
let fruit = ['apple', 'banana', 'mango'];
let fruit2 = ['orange', 'kiwi'];
console.log(fruit.concat(fruit2));
// ['apple', 'banana', 'mango', 'orange', 'kiwi'] (원본은 유지된다)
fruit.pop(); // 맨 뒤 요소 제거
console.log(fruit); // ['apple', 'banana']
fruit.shift(); // 맨 앞 요소 제거
console.log(fruit); // ['banana']
fruit2.push('PineApple'); // 맨 뒤에 요소 추가
console.log(fruit2); // ['orange', 'kiwi', 'PineApple']
fruit2.unshift('Strawberry'); // 맨 앞에 요소 추가
console.log(fruit2); // ['Strawberry', 'orange', 'kiwi', 'PineApple']
이게 생각보다 자주 쓰인다고 하니 조금 신경 써서 외우기로 했다.
| 메소드 | 동작 | 원본 |
|---|---|---|
배열.pop() | 맨 뒤 요소 제거 | 변경됨 |
배열.shift() | 맨 앞 요소 제거 | 변경됨 |
배열.push('요소') | 맨 뒤에 요소 추가 | 변경됨 |
배열.unshift('요소') | 맨 앞에 요소 추가 | 변경됨 |
배열.concat(배열2) | 두 배열을 합친 새 배열 반환 | 유지됨 |
let ary = new Array('tom', 'juli', 'jack');
let ary1 = new Array('bob');
let n;
function show(n) {
document.write(n + '<br>');
}
n = ary.concat(ary1); // tom,juli,jack,bob (원본 ary 변경 안 됨)
show(n);
n = ary.slice(1, 2); // juli (인덱스 1부터 2 미만, 원본 변경 안 됨)
show(n);
n = ary.sort(); // jack,juli,tom (문자 기준 오름차순, 원본 변경됨)
show(n);
n = ary.reverse(); // tom,juli,jack (순서 뒤집기, 원본 변경됨)
document.write(typeof(n) + '<br>'); // object
show(n);
n = ary.toString(); // 배열을 문자열로 변환
document.write(typeof(n) + '<br>'); // string
show(n); // tom,juli,jack
sort()와 reverse()는 원본 배열 자체를 바꾼다. concat()과 slice()는 새 배열을 반환하고 원본을 그대로 둔다.
sort()는 아무 인자 없이 쓰면 요소를 문자열로 바꿔서 사전순으로 정렬한다. 숫자 배열에 그대로 쓰면 결과가 이상할 수 있다.
let nums = [10, 9, 100, 1];
nums.sort();
console.log(nums); // [1, 10, 100, 9] (문자열 '10'이 '9'보다 앞이다)
숫자 크기 순으로 정렬하려면 비교 함수를 넘긴다.
nums.sort((a, b) => a - b);
console.log(nums); // [1, 9, 10, 100]
let arr = [1, 2, 3, 4, 5];
// 함수를 따로 만들어 전달
function ch(a, b) {
console.log(`${arr[b]}의 인덱스는? ${b}`);
}
arr.forEach(ch);
// 화살표 함수로 안에서 구현
arr.forEach((a, b, c) => {
console.log(a, b, c); // a: 요소, b: 인덱스, c: 원본 배열
});
// 함수 표현식으로 구현
arr.forEach(function (a, b, c) {
console.log(a, b, c);
});
forEach는 배열을 순회하면서 함수(요소, 인덱스, 배열) 형태로 호출해 준다. ch(a, b)처럼 두 개만 받으면 세 번째 인자인 배열은 무시된다. 형태는 배열명.forEach(함수명)이고, (element, index, array)는 각각 현재 요소, 인덱스, 원본 배열이다.
Day.4에서 배운 화살표 함수가 forEach 안에서 바로 쓰였다. 함수를 값처럼 인자로 넘길 수 있다는 뜻이다.
여기서부터는 태그를 선택해서 스타일을 바꾸는 실습이다. Day.1에서 DOM이라는 개념을 처음 배웠는데, 오늘은 그 DOM의 요소를 직접 골라서 다뤘다.
<h1 id="t1">Test1</h1>
<h2 id="t2">Test2</h2>
<h3 id="t3">Test3</h3>
<h4 id="t4">Test4</h4>
<script>
let t2 = document.getElementById('t2'); // id가 t2인 요소를 가져온다
let t4 = document.getElementById('t4');
document.write(t4 + '<br>'); // [object HTMLHeadingElement]
document.write(typeof(t2)); // object
t4.style.color = 'pink'; // 글자색
t4.style.backgroundColor = 'black'; // 배경색
let t3 = document.getElementById('t3');
t3.style.fontSize = '100px'; // 글자 크기
</script>
가져온 요소의 typeof는 object다. 태그 하나하나가 객체로 다뤄진다는 뜻이다.
바로잡기: 필기 코드에는
let t3 = ...로 받아 놓고test3.style.FontSize를 쓴 부분이 있었다. 변수 이름이 맞지 않아test3을 찾을 수 없다는 오류가 나고, 속성도FontSize가 아니라fontSize(소문자로 시작하는 camelCase)여야 한다. 위 코드는 둘 다 고친 것이다.
CSS 속성 이름이 JS에서는 camelCase로 바뀐다는 점도 정리해 둔다.
| CSS | JS |
|---|---|
color | style.color |
background-color | style.backgroundColor |
font-size | style.fontSize |
<body id="body">
<script>
let body = document.getElementById('body');
let date = new Date();
let hours = date.getHours();
if (hours < 12) {
body.style.backgroundColor = 'yellow'; // 오전
} else {
body.style.backgroundColor = 'green'; // 오후
}
</script>
</body>
Date의 getHours()와 DOM 선택, if문을 한꺼번에 사용한 예제다. 앞에서 배운 것들이 한 코드 안에서 함께 쓰였다.
CSS 선택자로 요소를 가져온다. class는 앞에 ., id는 앞에 #을 붙인다.
<div class="di1">
<h2>Test1</h2>
<h2>Test2</h2>
<h2>Test3</h2>
<h2>Test4</h2>
</div>
<h2 id="a">Test5</h2>
<h2 id="a1">Test6</h2>
<script>
let n = document.querySelector('.di1 h2');
// 일치하는 요소가 여러 개여도 첫 번째 요소만 선택된다
n.style.color = 'blue';
</script>
여러 개를 한꺼번에 가져오려면 querySelectorAll을 쓴다. 결과는 인덱스로 접근한다.
let n = document.querySelectorAll('.di1 h2');
n[0].style.color = 'tomato';
n[1].style.color = 'chocolate';
n[2].style.color = 'salmon';
document.write(n.length); // 4
querySelector('선택자'): 조건에 맞는 첫 번째 요소 하나querySelectorAll('선택자'): 조건에 맞는 모든 요소 (인덱스로 접근)글 중간의 일부 구간만 <span>으로 묶어서 스타일을 바꾸는 실습이다. <span>은 글자의 일부분을 묶는 태그라서, 묶인 영역만 따로 꾸밀 수 있다.
<button onclick="on()">눌러!!</button>
<p>
오늘 배운 내용 중 <span>가장 어려웠던 부분</span>은 아직 눈에 익지 않다.
그래도 <span>반복해서 써 보면</span> 점점 읽힌다. 마지막으로
<span>복습</span>이 중요하다.
</p>
<script>
let n = document.getElementsByTagName('span'); // span 태그 3개
let on = () => { // 버튼을 클릭해야 작동한다
for (let i = 0; i < n.length; i++) {
n[i].style.color = 'orange';
n[i].style.fontSize = '30px';
}
};
</script>
getElementsByTagName('태그명')은 해당 태그를 전부 가져온다.
바로잡기: 필기에서는
for (let i in n)로 반복했는데, 이 방식은getElementsByTagName결과에서 문제가 생긴다.for in은 인덱스(0, 1, 2)뿐 아니라length,item같은 속성 이름까지 돌기 때문에, 그 차례에서n['length'].style이 없어서 콘솔에 오류가 난다. 요소를 하나씩 순회할 때는 일반for문에n.length를 쓰거나for (let el of n)을 쓰는 것이 안전하다. (예전 필기에서 주석 처리한i <= classname1.length도<=면 마지막에 존재하지 않는 인덱스를 읽게 되므로<가 맞다.)또, 수업 예제의 기사 본문은 저작물이라 이 글에서는 임의로 쓴 문장으로 바꿨다.
<body id="body">
<button onclick="on()">배경색 바꾸기!</button>
<script>
let on = () => {
let a = document.getElementById('body');
let str = ['red', 'blue', 'yellow', 'green', 'white', 'black']; // 6개
let ran = Math.floor(Math.random() * 6); // 0~5
a.style.backgroundColor = str[ran];
};
</script>
</body>
바로잡기: 필기 코드는
Math.round(Math.random()*7)로 0~7을 뽑았는데, 배열 요소는 6개(인덱스 0~5)뿐이다. 6이나 7이 나오면str[ran]이undefined가 되어 색이 바뀌지 않는다. 배열 길이만큼 곱한 뒤floor로 내려야 항상 존재하는 인덱스만 나온다. (Math.floor(Math.random() * str.length)로 쓰면 색을 추가해도 코드를 고칠 필요가 없다.)
<ol type="A" id="ramen" onclick="on()">
<li>물을 끓인다.</li>
<li>라면 넣는다.</li>
<li>계란 넣는다.</li>
<li>맛있게 먹어.</li>
</ol>
<script>
let n = document.getElementsByTagName('li'); // li 4개
for (let i = 0; i < n.length; i++) {
n[i].style.color = 'green';
}
// ol을 클릭하면 배경색을 yellow로
let on = () => {
let a = document.getElementById('ramen');
a.style.backgroundColor = 'yellow';
};
</script>
<ol>은 ordered list(순서 있는 목록)고, type="A"면 번호 대신 A, B, C…가 붙는다. 이 예제도 for in이 아니라 일반 for를 쓴 형태로 정리했다. 이 반복문이 중간에 오류로 멈추면, 그 아래에 있는 on 함수가 정의되지 않아서 클릭 이벤트도 작동하지 않는다는 점이 for in의 더 큰 문제다.
let n = new Date();
let min = n.getMinutes();
if (min % 2 == 0) {
document.body.style.backgroundColor = 'yellow';
} else {
document.body.style.backgroundColor = 'red';
}
document.write('지금 ' + min + '분이다');
document.body로 body에 바로 접근할 수도 있다. id를 따로 붙이지 않아도 되는 방법이다.
Math.random, eval, prompt, if를 합친 문제다.
<head>
<script>
// 1~9까지 정수만 뽑기(소수점은 버린다)
let calcu = () => {
let num = Math.floor((Math.random() * 9) + 1);
return num;
};
</script>
</head>
<body>
<script>
let n = calcu() + '*' + calcu(); // 예: '3*4'
let n1 = prompt(n + '정답은');
if (n1 == null) { // 취소를 누르면 null
document.write('종료');
} else {
let result = eval(n); // '3*4' → 12
// typeof(result) → number, typeof(n1) → string
if (result == n1) { // 타입이 달라도 == 는 자동 형 변환 후 비교
document.body.style.backgroundColor = 'blue';
document.write('정답!');
} else {
document.body.style.backgroundColor = 'red';
document.write('오답...');
}
}
</script>
</body>
짚어 둘 점은 이렇다.
calcu()는 1~9의 정수를 반환한다. Math.random()*9는 0 이상 9 미만이고, +1로 1~9가 되며, floor로 소수점을 버린다.calcu() + '*' + calcu()는 숫자와 '*'이 이어져 문자열 '3*4'가 된다. (숫자+문자열은 문자열 이어 붙이기가 된다.)eval('3*4')는 문자열로 전달받은 수식이나 JS 문장을 실행하고 결과를 반환한다. 결과 타입은 number다.prompt는 입력값을 문자열로 돌려주고, 취소를 누르면 null을 돌려준다. 그래서 null 검사를 가장 먼저 한다.result == n1은 숫자와 문자열을 비교하지만 ==가 자동 형 변환을 해 주기 때문에 12 == '12'가 참이 된다. 오늘 배운 == 비교 규칙이 이런 곳에서 쓰인다.eval은 문자열을 코드로 실행하기 때문에, 사용자가 입력한 값을 그대로 넣으면 의도하지 않은 코드가 실행될 수 있다. 이 예제에서는 eval에 넣는 문자열을 프로그램이 직접 만든 숫자와 *로만 구성했으므로 문제가 없지만, prompt 입력값을 그대로 eval에 넘기는 코드는 피해야 한다고 알려져 있다. 학습용으로는 편하지만 실무에서는 거의 쓰지 않는다고 한다.
오늘의 큰 줄기는 "문자열, 숫자, 날짜, 배열이 모두 객체이고, 각자 쓸 수 있는 메소드가 있다"는 것이다.
pop, shift, push, unshift, sort, reverse처럼 원본을 바꾸는 것과 concat, slice처럼 원본을 유지하는 것이 나뉜다.Math.floor(Math.random() * 개수) + 시작값으로 계산한다.Date의 월은 0부터 시작한다.getElementById, querySelector, getElementsByTagName으로 골라서 style을 바꿀 수 있다.메소드가 너무 많아 보이지만 "원본이 바뀌는가 안 바뀌는가"를 기준으로 나누면 외울 양이 줄어든다.
Tags: JavaScript String객체 Math Date 배열 forEach DOM 개발자 학습기록