JavaScript_Day.5

조현기·4일 전

JavaScript_Day.5

문자열, 숫자, 날짜, 배열이 전부 "객체"라는 걸 알게 된 날.
오늘부터는 값만 다루던 코드에서 벗어나, 화면의 태그를 직접 골라 색을 바꾸기 시작했다.

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

  1. String 객체와 문자열 메소드
  2. 원시값과 객체의 비교 (==, ===)
  3. Number 변환, toFixed, Math 객체
  4. Date 객체
  5. Array 객체와 forEach
  6. DOM 요소 선택: getElementById, querySelector, getElementsByTagName

1. String 객체

문자열을 객체로 만들어 다루기 쉽게 해 주는 객체다. 선언 방식은 두 가지다.

// 변수식 선언 방식
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()앞뒤 공백 제거

2. substr과 substring: 생년월일 나누기

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)는 범위가 비어서 아무것도 출력되지 않는다.
    • 앞의 인수가 뒤의 인수보다 크면 두 값을 자동으로 swap한다. (4, 2)를 넣으면 (2, 4)처럼 동작한다.

추가로 정리: substr은 사용을 권장하지 않는다

substr은 MDN에서 "더 이상 권장하지 않는(deprecated)" 기능으로 분류한다. 수업 중에는 그냥 배웠지만, 실무 코드에서는 slice나 substring을 쓰는 편이 안전하다고 한다. 같은 결과를 slice로 쓰면 이렇다.

let num = '001026';
num.substr(2, 2);    // '10'  (시작 위치, 개수)
num.slice(2, 4);     // '10'  (시작 위치, 끝 위치 앞까지)

3. match와 charAt

match: 문자열 안에 해당 내용이 있는가

<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를 직접 반환한다.

charAt: 특정 인덱스의 글자

주민번호 뒷자리 첫 숫자로 성별을 구분하는 예제다.

<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에서 배운 ==와 ===의 차이가 여기서 쓰였다.)


4. indexOf, replace, trim, slice

<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') 쓸 수 있다.


5. 원시값과 객체: 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 == btrue둘 다 값 'hi'
a1 == b1false내용은 같지만 서로 다른 객체. 주소를 비교한다
a == a1true타입이 달라 ==가 객체를 값으로 변환한 뒤 비교
a1 === b1false서로 다른 객체이므로

여기서 "원시"란 객체로 선언하지 않은 기본 값(string 등)을 가리킨다.

필기 중에 a1 === b1이 false인 이유를 "자료형이 달라서"라고 적었는데, 둘 다 object라서 자료형은 같다. false인 진짜 이유는 서로 다른 객체를 가리키기 때문(주소 비교)이다. 이 부분은 바로잡았다.

concat

const str1 = 'Hello';
const str2 = 'World';

console.log(str1.concat(str2));        // HelloWorld
console.log(str2.concat(', ', str1));  // World, Hello
  • 문자열.concat(인자1, 인자2, …): 문자열 뒤에 인자들을 차례로 이어 붙인다. 구분자 같은 특별한 기능은 없고, ', '도 그냥 이어 붙일 문자열 중 하나다.

6. split: 문자열을 배열로

<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()을 쓴다.


7. Number 변환과 toFixed

문자열을 숫자로

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다.

toFixed

세 과목 점수를 입력받아 합계와 평균을 구하는 예제다.

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는 문자열을 돌려준다

toFixed의 결과는 숫자가 아니라 문자열이다.

let avg = (200 / 3).toFixed(2);
console.log(avg);            // 66.67
console.log(typeof(avg));    // string

평균을 다시 계산에 쓰려면 Number(avg)로 바꿔야 한다. 앞의 prompt 값이 문자열이었던 문제와 같은 종류의 함정이다.


8. Math 객체

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

random: 원하는 범위의 난수 만들기

Math.random()은 0 이상 1 미만의 실수를 무작위로 반환한다. 곱하는 수로 범위의 크기를, 더하는 수로 시작값을 조정한다.

let num = Math.random();    // 예: 0.4837...

필기에서는 Math.round로 아래 두 문제를 풀었는데, 풀면서 범위가 어긋나는 부분이 있어 Math.floor로 다시 정리했다.

목표필기의 풀이문제점바로잡은 풀이
100 이상 120 미만Math.round(Math.random()*20)+100100~120까지 나올 수 있다 (120 포함). 양 끝값은 나올 확률도 절반Math.floor(Math.random()*20)+100
50~80Math.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() * 개수) + 시작값으로 외우면 편하다.

  • 개수: 나올 수 있는 값이 몇 개인가 (50~80이면 31개)
  • 시작값: 가장 작은 값

random()은 1이 되지 않으므로 floor로 내리면 항상 시작값 이상 시작값+개수-1 이하로 나온다. 이번 실습의 1~9 곱셈 문제(Math.floor(Math.random()*9)+1)도 같은 공식이다.


9. Date 객체

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()(요일 번호)는 이름이 비슷하니 헷갈리지 않게 주의한다.

100일 후 날짜 계산

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가 알아서 다음 달로 넘겨 계산해 준다.


10. Array 객체

선언과 수정

// 변수식 선언
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)가 바로 그 경우다.

concat, pop, shift, push, unshift

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)두 배열을 합친 새 배열 반환유지됨

slice, sort, reverse, toString

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는 기본적으로 "문자" 기준이다

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]

11. forEach: 배열을 하나씩 돌기

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 안에서 바로 쓰였다. 함수를 값처럼 인자로 넘길 수 있다는 뜻이다.


12. DOM 요소 선택해서 스타일 바꾸기

여기서부터는 태그를 선택해서 스타일을 바꾸는 실습이다. Day.1에서 DOM이라는 개념을 처음 배웠는데, 오늘은 그 DOM의 요소를 직접 골라서 다뤘다.

getElementById

<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로 바뀐다는 점도 정리해 둔다.

CSSJS
colorstyle.color
background-colorstyle.backgroundColor
font-sizestyle.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문을 한꺼번에 사용한 예제다. 앞에서 배운 것들이 한 코드 안에서 함께 쓰였다.

querySelector와 querySelectorAll

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('선택자'): 조건에 맞는 모든 요소 (인덱스로 접근)

getElementsByTagName과 버튼 이벤트

글 중간의 일부 구간만 <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를 따로 붙이지 않아도 되는 방법이다.


13. 실습 정리: 구구단 퀴즈 만들기

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은 문자열을 코드로 실행하기 때문에, 사용자가 입력한 값을 그대로 넣으면 의도하지 않은 코드가 실행될 수 있다. 이 예제에서는 eval에 넣는 문자열을 프로그램이 직접 만든 숫자와 *로만 구성했으므로 문제가 없지만, prompt 입력값을 그대로 eval에 넘기는 코드는 피해야 한다고 알려져 있다. 학습용으로는 편하지만 실무에서는 거의 쓰지 않는다고 한다.


마무리

오늘의 큰 줄기는 "문자열, 숫자, 날짜, 배열이 모두 객체이고, 각자 쓸 수 있는 메소드가 있다"는 것이다.

  • 문자열 메소드는 대부분 원본을 바꾸지 않고 새 값을 반환한다.
  • 배열 메소드는 pop, shift, push, unshift, sort, reverse처럼 원본을 바꾸는 것과 concat, slice처럼 원본을 유지하는 것이 나뉜다.
  • 난수 범위는 Math.floor(Math.random() * 개수) + 시작값으로 계산한다.
  • Date의 월은 0부터 시작한다.
  • DOM 요소는 getElementById, querySelector, getElementsByTagName으로 골라서 style을 바꿀 수 있다.

메소드가 너무 많아 보이지만 "원본이 바뀌는가 안 바뀌는가"를 기준으로 나누면 외울 양이 줄어든다.


Tags: JavaScript String객체 Math Date 배열 forEach DOM 개발자 학습기록

profile
난 뭘까

0개의 댓글