JavaScript_Day.3

조현기·5일 전

JavaScript_Day.3

중첩 for문부터 함수, 객체, 생성자 함수까지 한 번에 달린 하루.
오늘의 키워드는 "이 코드는 왜 이렇게 동작하는가"였다.

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

  1. 반복문 활용: 중첩 for문, 반복문 + 조건문, 무한 반복문, break, continue
  2. 새로 배운 연산자와 함수: 삼항 조건 연산자, eval()
  3. 함수: 선언과 호출, 호이스팅, 매개변수, return
  4. 객체: 속성과 메서드, this, 생성자 함수
  5. 버튼 이벤트: onclick으로 함수 호출하기

오전

1. 복습: switch + for로 구구단 만들기

어제 배운 내용을 복습하기 위해 간단한 코드를 먼저 작성했다.
prompt로 정수를 입력받고(문자열이므로 parseInt로 변환), switch로 2단과 3단을 나눠서 출력한다.

let n = parseInt(prompt('정수를 입력해주세요'));

switch (n) {
  case 2:
    document.write('2단을 출력합니다.' + '<br>');
    for (let a = 1; a <= 9; a++) {
      document.write(n + 'x' + a + '=' + (n * a) + '<br>');
    }
    break;
  case 3:
    document.write('3단을 출력합니다.' + '<br>');
    for (let a = 1; a <= 9; a++) {   // case 2의 a와 같은 이름을 그대로 써도 된다
      document.write(n + 'x' + a + '=' + (n * a) + '<br>');
    }
    break;
  default:
    document.write('2,3단이 아니네');
}

for문 안에서 let a로 선언한 변수는 그 for문 블록 안에서만 쓰인다. 그래서 case 2와 case 3에서 같은 이름 a를 다시 써도 오류가 나지 않는다.

추가로 정리: <br>은 왜 붙일까

코드 곳곳에 붙은 '<br>'은 HTML의 줄바꿈 태그다. document.write는 내용을 HTML로 출력하기 때문에, 줄을 바꾸려면 \n이 아니라 <br>을 써야 한다. \n은 HTML에서 공백 하나로 처리된다. Day.1에서 document.writeln이 공백처럼 보였던 것과 같은 이유다.

document.write('첫째 줄\n둘째 줄');     // 화면: 첫째 줄 둘째 줄 (한 줄)
document.write('첫째 줄<br>둘째 줄');   // 화면: 두 줄로 나뉨

2. 중첩 for문

for문 안에 for문을 넣은 것이 중첩 for문이다. 바깥 for문이 한 번 돌 때마다, 안쪽 for문이 처음부터 끝까지 반복된다.

// 5단~9단 출력
document.write('구구단을 출력합니다.' + '<br>');
for (let i = 5; i <= 9; i++) {         // i=5일 때 t=1~9까지 반복, 그다음 i=6...
  for (let t = 1; t <= 9; t++) {
    document.write(`${i}x${t}=${i * t}` + '<br>');
  }
}

추가로 정리: 백틱(`)과 ${}

위 코드에서 `${i}x${t}=${i * t}`는 템플릿 리터럴이다. 백틱(`)으로 문자열을 감싸면 ${} 안에 변수나 계산식을 바로 넣을 수 있다. +로 문자열을 이어 붙이는 것보다 읽기 쉽다.

let i = 5, t = 3;
console.log(i + 'x' + t + '=' + (i * t));   // 5x3=15 (이어 붙이기)
console.log(`${i}x${t}=${i * t}`);          // 5x3=15 (템플릿 리터럴)

3. 반복문과 조건문 활용

반복문 안에 if문을 넣어서 조건에 맞는 수만 골라 출력하는 실습을 했다.

// 1~99 중 짝수만 (while)
let n = 1;
while (n <= 99) {
  if (n % 2 == 0) {
    document.write(`${n}` + '<br>');   // 2 4 ... 98
  }
  n++;
}

// 1~99 중 3의 배수만 (for)
for (let n = 1; n <= 99; n++) {
  if (n % 3 == 0) {
    document.write(`${n}` + '<br>');
  }
}

// 1~99 중 4의 배수만 (do while)
let a = 1;
do {
  if (a % 4 == 0) {
    document.write(a + '<br>');
  }
  a++;
} while (a <= 99);

그리고 1~100 중 5와 6의 공배수 출력도 해봤다. 정답은 30, 60, 90이다.

for (let n = 1; n <= 100; n++) {
  if (n % 5 == 0) {
    if (n % 6 == 0) {            // if를 중첩해도 되고
      document.write(n + '<br>');
    }
  }
}

// && 로 한 줄에 묶어도 같은 결과
for (let n = 1; n <= 100; n++) {
  if (n % 5 == 0 && n % 6 == 0) {
    document.write(n + '<br>');
  }
}

"X의 배수"는 n % X == 0(나머지가 0)으로 판별한다. Day.2에서 배운 %(나머지)와 &&(논리곱)가 여기서 같이 쓰였다.

4. 코드 분석, 면접에서 물어본다

실습하다가 중요한 이야기를 들었다. 개발자 면접에서는 자신이 작성한 코드에 대해 "이건 왜 이렇게 짰어요?" 하는 질문이 나온다고 한다.

그래서 앞으로는 코드가 동작하는 것에서 끝내지 않고, 왜 그렇게 동작하는지 설명할 수 있어야 한다. AI가 출력한 코드를 그대로 적용하는 방식으로는 이 질문에 답할 수 없다. 이 블로그에 실습 코드의 이유를 같이 적는 것도 그 연습이다.

5. 삼항 조건 연산자

새로운 개념이 등장했다. 삼항 조건 연산자다.
MDN에서 "삼항조건연산자"로 검색했더니 바로 안 나와서 일단 이해한 개념부터 적었다. (MDN에는 "조건(삼항) 연산자" 라는 이름으로 있다.)

(조건식) ? (참일 때 값) : (거짓일 때 값)

간단한 if...else를 한 줄로 줄여 쓰는 용도로 쓸 수 있다. 외워 두자.

let a = parseInt(prompt('첫번째 정수를 입력해주세요.'));
let b = parseInt(prompt('두번째 정수를 입력해주세요.'));

// 삼항 조건 연산자
let max = (a > b) ? a : b;   // 조건이 참이면 ? 뒤, 거짓이면 : 뒤
document.write(max);

// 같은 동작을 if...else로 쓰면
if (a > b) {
  document.write(a);
} else {
  document.write(b);
}

6. 무한 반복문

무한 반복문도 등장했다. 앞서 배운 while, for를 조건 없이 계속 반복시키는 것이다.

  • while (true) { ... }
  • for (;;) { ... }
  • do { ... } while (true);도 가능하지만 잘 쓰이지는 않는 것 같다.

중요한 점은 break;가 없으면 멈추지 않는다는 것이다. 어제 무한 반복으로 컴퓨터가 멈췄던 실수를 생각하면 조심해야 한다.

while (true) {                      // 무한 반복
  let name = prompt('이름을 입력해주세요');
  if (name == '길동') {
    break;                          // '길동'을 입력하면 반복 종료
  }
  document.write(name);
}

7. continue

반복문 실습과 함께 continue도 배웠다.
MDN 설명을 정리하면, 현재 반복에서 남은 코드를 건너뛰고 반복문의 처음으로 돌아가 다음 반복을 진행하게 하는 명령문이다. break가 반복문을 아예 끝내는 것과 다르다.

// 1~100 중 2의 배수만 제외하고 출력
for (let n = 1; n <= 100; n++) {
  if (n % 2 == 0) {
    continue;                       // 짝수면 아래 write를 건너뛰고 다음 반복
  }
  document.write(n + '<br>');
}

입력을 5번 받아서 음수는 합계에서 제외하는 코드도 작성했다.

let sum = 0;

for (let i = 0; i < 5; i++) {
  let n = parseInt(prompt('정수 입력'));   // 5번의 입력을 받는다

  if (n < 0) {
    continue;                      // 음수면 합산하지 않고 다음 반복으로
  } else {
    sum += n;
  }
}
document.write(`입력한 수의 총합은 ${sum}`);

여기서 확인한 점이 있다. for문의 증감식(i++)은 continue가 실행되어도 적용된다. 그래서 음수를 입력하면 그 입력 기회는 차감된다. 다시 입력할 기회를 주는 게 아니다.

8. 문제 풀이

강사님이 출제하신 문제 중 아쉽게 풀었던 2개를 가져왔다.

문제 2. n1을 50, n2를 100으로 초기화하고, 큰 수를 삼항 연산자로 출력

let n1 = 50;
let n2 = 100;

(n1 > n2) ? document.write(n1) : document.write(n2);   // 내 풀이
document.write((n1 > n2) ? n1 : n2);                    // 더 효율적인 풀이

삼항 연산자 안에 document.write를 두 번 쓰는 것보다, document.write 안에 삼항 연산자를 넣는 쪽이 더 효율적이다.

문제 4. 0~30 중 5의 배수를 제외하고 출력 (무한 반복문 + break + continue 모두 사용)

문제에서 무한 반복문을 쓰라고 했는데, 처음엔 반복문 자체를 안 쓰는 쪽으로 풀이해서 아쉬웠다. 그리고 풀이하다 막힌 부분이 있었다.

  • n % 5 == 0을 그대로 쓰면 0이 출력되지 않았다. 문제가 왜 1~30이 아니라 0~30일까 궁금했고, "0은 나와야 하는구나" 하고 n == 0 조건을 추가했다.
  • 그런데 나중에 찾아보니 0은 모든 정수의 배수였다. 0도 5의 배수라서 제외 대상이 맞다.
  • 또 30도 5의 배수라서 출력은 안 되지만, 30까지 반복해서 검사한 뒤 종료하는 구조로 짰어야 했다.

n == 0 예외를 따로 둘 필요가 없었다. 0도 n % 5 == 0이 참이다.

// 0~30 중 5의 배수 제외 (무한 반복 + break + continue)
let n = 0;

while (true) {
  if (n > 30) {
    break;                  // 30까지 검사한 뒤 종료
  }
  if (n % 5 == 0) {         // 0, 5, 10, ..., 30 모두 여기서 걸러짐
    n++;
    continue;
  }
  document.write(n + '<br>');
  n++;
}
// 출력: 1 2 3 4 6 7 8 9 11 ... 29

continue 전에 n++을 해 주지 않으면 같은 값에서 계속 continue가 실행되어 무한 반복에 빠진다. for문과 달리 while은 증감식이 자동으로 실행되지 않는다.

9. eval

오전 수업 끝에 eval()이 등장했다.
MDN 설명을 정리하면, 문자열로 된 자바스크립트 코드를 실행하고 그 결과값을 돌려주는 함수다.

let n = eval('1+2+3');          // 문자열이 실제로 계산된다
document.write(n + '<br>');     // 6

// 입력받은 수식을 계산하는 계산기
let calculator = prompt('수식입력');   // 1+2+3
let result = eval(calculator);
document.write(`${calculator}=${result}` + '<br>');

문자열과 숫자를 섞어서 여러 번 실험해 봤다. 문자열과 숫자를 구분해서 볼 것.

코드결과이유
eval('1+2+3')6문자열 "1+2+3"을 계산
eval('1+2' + 3)24먼저 '1+2'와 3이 합쳐져 "1+23"이 되고, 그걸 계산
eval('1+2+' + 3)6"1+2+3"이 되어 정상 계산
eval('1' + '2' + '3')123문자열이 이어 붙어 "123"이 되고, 그걸 계산
eval(1+2+3)6인자가 이미 숫자 6이라 그대로 반환
eval('abc') / eval(abc)오류abc라는 변수가 없음

외우기 힘든 함수라 복습이 필요하다.

추가로 정리: eval을 실무에서 조심해야 하는 이유

eval은 문자열을 코드로 실행하기 때문에, 사용자가 입력한 문자열을 그대로 eval에 넣으면 악의적인 코드가 실행될 수 있어서 위험하다. 실무에서는 가급적 쓰지 않는다. 오늘은 "문자열이 코드가 되는 과정"을 이해하는 용도로 익혔다.


오후

10. 함수

오후부터 함수를 배우기 시작했다. 함수의 정의는 이렇다.

데이터를 전달받아 처리한 후 결과를 돌려주는 코드 블록

반복해서 사용하는 코드를 한 덩어리로 만들어 두고, 필요할 때마다 호출해서 쓴다. 함수는 선언만으로는 실행되지 않고, 호출해야 실행된다.

function hello() {
  alert('안녕하세요');
}                          // 함수 선언

hello();                   // 함수 호출

호이스팅(hoisting)

함수는 호출을 선언보다 먼저 써도 호출이 된다. 이것을 호이스팅이라고 한다.
MDN 설명을 정리하면, 인터프리터가 코드를 실행하기 전에 함수, 변수 등의 선언을 해당 범위의 맨 위로 끌어올린 것처럼 보이는 현상이다.

fun1();                    // 선언보다 먼저 호출해도 동작한다

function fun1() {
  for (let i = 1; i <= 10; i++) {
    document.write(i);
  }
}

매개변수

함수 괄호 안의 name을 매개변수(= 입력값, 파라미터) 라고 한다. 함수가 호출될 때 외부에서 값을 받아 쓰는 변수다.

function fun(name) {                // name: 매개변수
  let msg = `안녕 ${name}`;
  console.log(msg);
}

fun('길동');                        // 호출해야 실행된다 → 안녕 길동

추가로 정리: 매개변수와 인수

헷갈리기 쉬운 용어가 하나 더 있다.

  • 매개변수(parameter): 함수를 정의할 때 괄호 안에 적은 변수 (name)
  • 인수(argument): 함수를 호출할 때 실제로 넘기는 값 ('길동')

return

return은 함수 실행을 종료하고, 주어진 값을 함수를 호출한 곳으로 돌려준다.

function add1(a, b) {
  let sum;
  sum = a + b;
  return sum;               // 이 값이 함수를 호출한 곳으로 돌아간다
}

let n = add1(3, 4);         // n = 7

모든 함수에 return이 필요한 건 아니지만, 앞으로는 대부분의 함수가 사용할 것 같다. 실습하다가 return을 빼먹는 경우가 종종 있었으므로, 함수를 만들 때 return을 꼭 작성하는 습관을 들이자.

추가로 정리: return이 없으면

return이 없는 함수를 호출해서 값을 받으면 undefined가 나온다.

function noReturn(a, b) { let sum = a + b; }
function withReturn(a, b) { return a + b; }

console.log(noReturn(1, 2));     // undefined
console.log(withReturn(1, 2));   // 3

11. 함수 실습

// 두 정수를 입력받아 크기를 비교하는 함수
let x = parseInt(prompt('첫번째 정수 입력'));
let y = parseInt(prompt('두번째 정수 입력'));

function maxXY(x, y) {
  if (x == y) {
    return '같다';
  } else if (x > y) {
    return `${x}가 ${y}보다 크다.`;
  } else {
    return `${x}가 ${y}보다 작다.`;
  }
}

alert(maxXY(x, y));

이 코드에서 함수의 매개변수 x, y와 바깥의 변수 x, y는 이름만 같을 뿐 다른 변수다. 매개변수는 함수 안에서만 쓰인다.

추가로 정리: 전역 변수와 지역 변수

변수는 어디서 선언했느냐에 따라 쓸 수 있는 범위가 달라진다. 이 범위를 스코프(scope) 라고 한다.

  • 전역 변수: 함수 바깥에서 선언한 변수. 어디서든 쓸 수 있다.
  • 지역 변수: 함수 안에서 선언한 변수(매개변수 포함). 그 함수 안에서만 쓸 수 있다.
let adr = '서울';            // 전역 변수

function show() {
  let msg = '안녕';          // 지역 변수
  console.log(adr, msg);
}

show();                      // 서울 안녕
console.log(adr);            // 서울
console.log(msg);            // ReferenceError: msg is not defined (함수 밖에서는 못 씀)

뒤에 나오는 생성자 함수 예제에서 adr을 함수 안이 아니라 바깥에 선언한 이유도 이것이다. 모든 객체가 같이 쓰는 값은 전역으로 둔다.

// prompt로 받은 값을 매개변수로 전달
function meter(ki) {
  return (ki / 100);
}

let ki = parseInt(prompt('당신의 키는 몇 cm십니까?'));
document.write(meter(ki) + 'm입니다.');
// 입력값에 따라 출력이 달라지는 함수
function total(x, y) {
  for (; x <= y; x++) {
    document.write(x + ' ');
  }
}

total(6, 10);                  // 6 7 8 9 10
document.write('<br>');
total(22, 100);                // 22 ~ 100
// 합계를 구해서 반환하는 함수
function total(a, b) {
  let sum = 0;
  for (; a <= b; a++) {        // a는 매개변수라 초기식 생략 가능
    sum += a;
  }
  return sum;                  // return은 함수 종료를 의미한다
}

document.write(total(1, 100)); // 5050

for문의 초기식 자리에 쓸 값이 이미 매개변수 a에 들어 있으니, 초기식을 비워 두어도 정상적으로 동작했다. 나이쨔.

마지막으로 삼항 조건 연산자 안에 삼항 조건 연산자를 한 번 더 넣을 수 있는지 실험했다.

// 두 수 중 큰 값 (삼항 연산자 중첩)
function big(x, y) {
  return (x > y) ? x : ((x == y) ? x : y);
}

document.write(big(4.4, 1.1));   // 4.4

추가로 정리: 삼항 연산자 안의 비교는 ==

삼항 연산자 안에서 값이 같은지 비교할 때는 =가 아니라 ==(또는 ===)를 써야 한다. =는 대입이라서 (x = y)라고 쓰면 x의 값이 바뀌어 버린다. Day.1에서 배운 =와 ==의 차이가 여기에도 적용된다.

그리고 사실 이 문제에서는 같을 때 어느 쪽을 반환해도 결과가 같으므로 (x > y) ? x : y만으로도 충분하다. 중첩은 형태를 확인하려는 실험이었다.

12. 함수 호출은 선언보다 위에 둬도 되는가: head와 body

실습하다 확인한 사실이다.

  • <head>에서 함수를 선언하고 <body>에서 호출하는 것은 정상 동작했다.
  • 반대로 <head>에서 호출하고 <body>에서 선언하는 방식은 오류가 났다.
<!-- 오류: head의 script가 먼저 실행되는데, 이때 add1은 아직 없다 -->
<head>
  <script>
    let n = add1(3, 4);
  </script>
</head>
<body>
  <script>
    function add1(a, b) { return a + b; }
  </script>
</body>

앞에서 배운 호이스팅은 같은 <script> 블록 안에서 적용된다. 서로 다른 <script> 블록은 위에서부터 차례로 실행되므로, 아직 실행되지 않은 아래 블록의 함수는 쓸 수 없다. Day.1에서 배운 "script 위치에 따른 실행 순서"와 같은 맥락이다.

13. 객체

객체(Object)는 이렇게 정의했다.

여러 개의 데이터(속성)와 기능(함수)을 하나로 묶어서 저장하는 자료 구조

TV로 예를 들면, 색, 모양, 크기는 속성(변수), 볼륨, 전원, 채널 변경은 기능(함수) 이다.

let car = {
  name: '소나타',
  color: ['red', 'black', 'blue'],      // 배열도 속성이 될 수 있다
  fuel: '전기',

  operating: function (e) {             // 기능 (메서드)
    if (e == 'on') {
      alert('시동 켜져있음');
    } else if (e == 'off') {
      alert('시동 꺼져있음');
    }
  }
};

document.write(car.color[1] + '<br>');  // black  (객체명.배열명[인덱스])
document.write(car.name + '<br>');      // 소나타  (객체명.속성명)
console.log(car['name']);               // 소나타  (객체명[속성명])
car.operating('off');                   // 메서드 호출 (객체명.함수명())

객체 안에 객체를 넣을 수도 있다.

let student = {
  gildong: { age: 22, job: 'programmer' },
  tom:     { age: 32, job: 'student' }
};

console.log(student);
console.log(student.tom.age);           // 32

추가로 정리: 용어

  • 속성(property): 객체 안에 저장된 데이터 (name, fuel)
  • 메서드(method): 객체 안에 저장된 함수 (operating)
  • 접근 방법은 두 가지다. 점 표기법(car.name)과 대괄호 표기법(car['name']).

14. this

this는 객체 자신을 의미한다. 메서드 안에서 this를 쓰면 그 메서드가 들어있는 객체를 가리킨다.

let student = {
  name: 'gildong',
  show: function () {
    console.log(student.name);   // 객체명으로 직접 접근
    console.log(this.name);      // this로 접근 (같은 결과)
  }
};

student.show();

this의 장점은 객체 이름이 바뀌어도 메서드 안의 코드를 바꾸지 않아도 된다는 점이다. 같은 구조의 객체가 여러 개일 때 특히 편하다.

let student = {
  name: 'tom',
  test: function () { console.log(`${this.name}`); }
};

let student2 = {
  name: 'bil',
  test: function () { console.log(`${this.name}`); }
};

student.test();     // tom
student2.test();    // bil

함수를 따로 만들어 두고 함수 이름만 등록해서 여러 객체가 공통으로 쓸 수도 있다.

let student = { name: 'tom', test: test };   // 함수의 이름만 등록
let student2 = { name: 'bil', test: test };

function test() {                            // 여러 객체가 공통으로 사용할 함수
  console.log(`${this.name}`);
}

student.test();     // tom
student2.test();    // bil

15. 생성자 함수

비슷한 객체를 여러 개 만들어야 할 때는 객체를 하나씩 쓰는 대신 생성자 함수를 쓴다.

function 생성자함수명(매개변수, 매개변수, ...) {
  this.속성 = 값;
  this.속성 = 값(매개변수);
  this.메서드명 = function () { ... };
}

let 변수명 = new 생성자함수명(값, 값, ...);
  • 생성자 함수 안에서는 this.속성 = 매개변수로 초기화를 하고 시작한다.
  • 객체를 만들 때는 반드시 new를 붙인다.
  • 생성자 함수명은 보통 대문자로 시작한다.
let adr = '서울';       // 변경사항이 없는 데이터는 함수 안에 넣지 않고 전역 변수로 선언

function Item(product, price, deliver) {
  this.product = product;      // this: new로 생성되는 객체 자신
  this.price = price;
  this.deliver = deliver;

  this.show = function () {
    console.log(`${adr} ${product} 가격은 ${price}이고 배송은 ${deliver}`);
  };
}

let item1 = new Item('핸드폰케이스', 30000, '로켓배송');
item1.show();
let item2 = new Item('식빵', 1000, '새벽배송');
item2.show();

console.log(item1.price);      // 30000 (생성된 객체의 정보를 호출)
function User(name, age, grade) {
  this.name = name;
  this.age = age;
  this.grade = grade;
  this.show = function () {
    console.log(`이름은 ${name}이고 나이는 ${age}살이다`);
  };
}

let n = new User('길동', 33, 'silver');
let n2 = new User('철수', 13, 'diamond');

n.show();
n2.show();

학점을 계산하는 메서드가 있는 생성자 함수도 만들었다. 삼항 연산자를 중첩해서 썼다.

function Student(name, score) {
  this.name = name;
  this.score = score;

  this.getgrade = function () {
    // 90점 이상 A, 80점 이상 B, 그 외 F
    return (this.score >= 90) ? 'A' : ((this.score >= 80) ? 'B' : 'F');
  };
}

let stu1 = new Student('juli', 50);
let stu2 = new Student('tom', 100);

document.write(stu1.name + ' 학점 ' + stu1.getgrade() + '<br>');   // juli 학점 F
document.write(stu2.name + ' 학점 ' + stu2.getgrade() + '<br>');   // tom 학점 A

추가로 정리: 메서드 안에서는 this.속성을 쓰는 것이 안전하다

위 Item의 show는 매개변수 product, price를 바로 썼다. 이렇게 쓰면 처음 값은 잘 나오지만, 나중에 객체의 속성을 바꾸면 바뀐 값이 반영되지 않는다.

let item = new Item('식빵', 1000, '새벽배송');
item.price = 2000;       // 속성을 바꿨지만
item.show();             // "...가격은 1000이고..." (매개변수의 처음 값이 출력됨)

this.price로 쓰면 바뀐 값이 나온다. 그래서 메서드 안에서는 this.속성명을 쓰는 습관이 더 좋다.

this.show = function () {
  console.log(`${adr} ${this.product} 가격은 ${this.price}이고 배송은 ${this.deliver}`);
};

16. 버튼 이벤트와 함수 연결

함수를 배운 뒤 HTML 버튼과 연결해 봤다. 버튼에 onclick을 쓰면 버튼을 클릭할 때 지정한 함수가 호출된다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <script>
    function on()  { alert('오전에 수업'); }
    function on2() { alert('오후에 수업'); }
  </script>
</head>
<body>
  <!-- 버튼을 클릭하면 on() 함수가 실행된다 -->
  <button onclick="on()">오전</button>
  <button onclick="on2()">오후</button>
</body>
</html>

<button> 태그는 버튼을 만들고, 태그 사이의 글자가 버튼에 표시되는 이름이다. 그리고 onclick 같은 동적인 명령은 그 버튼에 대한 동작을 정의한다.

버튼마다 value를 달고, this.value로 클릭한 버튼의 값을 함수에 넘길 수도 있다.

<button onclick="cafe(this.value)" value="americano">아메리카노</button>
<button onclick="cafe(this.value)" value="latte">라떼</button>
<button onclick="cafe(this.value)" value="smoothie">스무디</button>

<script>
  let cafe = (coffee) => {
    alert(coffee + '나왔습니다');
  };
</script>

이 코드에서 this는 클릭된 버튼 자신을 의미한다. 객체에서 배운 this와 같은 개념이다.

이 코드에서 let cafe = (coffee) => { ... }는 화살표 함수다. 아래에서 따로 정리했다.

추가로 정리: 함수 표현식과 화살표 함수

지금까지는 function 이름() { } 형태(함수 선언문)로 함수를 만들었다. 함수를 변수에 담는 방식도 있다. 이를 함수 표현식이라고 하고, 이것을 더 짧게 쓴 것이 화살표 함수(=>) 다.

function add(a, b) { return a + b; }            // 함수 선언문
let add2 = function (a, b) { return a + b; };   // 함수 표현식
let add3 = (a, b) => { return a + b; };         // 화살표 함수
let add4 = (a, b) => a + b;                     // 본문이 return 한 줄이면 { }와 return 생략 가능
let double = n => n * 2;                        // 매개변수가 하나면 ( ) 도 생략 가능

console.log(add(1, 2), add2(1, 2), add3(1, 2), add4(1, 2), double(4));   // 3 3 3 3 8

주의할 점이 두 가지 있다.

  • 호이스팅 차이: 함수 선언문은 선언보다 먼저 호출할 수 있지만, 변수에 담은 함수(표현식, 화살표 함수)는 담기 전에 호출하면 오류가 난다.
hello1();                          // 동작한다 (함수 선언문은 호이스팅됨)
hello2();                          // ReferenceError: Cannot access 'hello2' before initialization

function hello1() { console.log('hello1'); }
let hello2 = () => { console.log('hello2'); };
  • this 차이: 화살표 함수 안의 this는 객체 자신을 가리키지 않는다. 그래서 위에서 배운 객체의 메서드는 function으로 쓰는 것이 안전하다.

생성자 함수와 버튼을 합친 실습도 했다. 분식 메뉴 버튼을 누르면 해당 메뉴의 객체 정보를 출력한다.

<button onclick="food(this.value)" value="food1">라면</button>
<button onclick="food(this.value)" value="food2">떡볶이</button>
<button onclick="food(this.value)" value="food3">김밥</button>

<script>
  let restaurant = '분식집';                // 전역 변수

  function Menu(a, b, c) {                  // 생성자 함수
    this.a = a;
    this.b = b;
    this.c = c;

    this.result = function () {
      document.write(`${restaurant}에서 주문하신 메뉴는 ${a}이며 수량은 ${c}, 가격은 ${b}입니다.` + '<br>');
    };
  }

  let food1 = new Menu('치즈떡', '5000원', 2);   // 임의로 객체 생성
  let food2 = new Menu('츄러스', '3000원', 4);
  let food3 = new Menu('햄버거', '8000원', 1);

  function food(a) {
    switch (true) {
      case a == 'food1': food1.result(); break;   // 객체명.함수명()
      case a == 'food2': food2.result(); break;
      case a == 'food3': food3.result(); break;
    }
  }
</script>

17. 아쉬웠던 문제: switch의 case 묶기

출제된 문제 중 아쉽게 풀이했던 것이다. 1~4 사이의 정수를 입력받아, 1과 3은 "남자", 2와 4는 "여자"를 출력하는 문제였다.

// 내 풀이: 조건식을 case마다 길게 썼다
let n = parseInt(prompt('1~4 사이의 정수를 입력해주세요'));
switch (true) {
  case n == 1: alert('남자'); break;
  case n == 3: alert('남자'); break;
  case n == 2: alert('여자'); break;
  case n == 4: alert('여자'); break;
  default: alert('1~4 사이의 정수를 입력해주세요');
}

추가로 정리: switch (true)는 어떻게 동작할까

switch는 괄호 안의 값과 각 case의 값을 ===로 비교한다. case n == 1:에서 n == 1은 계산하면 true 또는 false가 된다. 그래서 switch (true)로 쓰면 조건식이 참인 case가 선택된다. if...else if를 switch로 바꿔 쓴 모양이다. 범위 조건에 쓸 수 있다.

let score = 85;

switch (true) {
  case score >= 90: console.log('A'); break;
  case score >= 80: console.log('B'); break;   // 출력: B
  default:          console.log('F');
}

다만 이 문제처럼 값이 같은지만 비교하는 경우에는 switch (n)에 case 1:로 쓰는 것이 더 간단하다.

더 효율적인 풀이는 switch(n)으로 값을 바로 비교하고, 같은 결과인 case를 묶는 것이다. Day.2에서 배운 "break가 없으면 아래로 이어서 실행된다"는 성질을 일부러 활용하는 방법이다.

switch (n) {
  case 1:
  case 3:
    alert('남자');     // 1이나 3이면 여기까지 이어서 실행되고
    break;             // 여기서 멈춤
  case 2:
  case 4:
    alert('여자');
    break;
  default:
    alert('1~4 사이의 정수를 입력해주세요');
}

마무리

오늘은 새로 배운 개념이 많았다. 정리하면 이렇다.

  • 반복문: 중첩 for문, 무한 반복문, break와 continue의 차이
  • 함수: 선언, 호출, 매개변수, return, 호이스팅
  • 객체: 속성과 메서드, this, 생성자 함수와 new
  • 이벤트: onclick으로 버튼과 함수 연결

가장 기억에 남는 건 "코드 분석" 이야기였다. 동작하는 코드를 짜는 것에서 끝나는 게 아니라, 왜 그렇게 짰는지 설명할 수 있어야 한다. 앞으로 이 블로그도 그 연습을 목표로 이어 가려고 한다.


Tags: JavaScript 함수 객체 반복문 개발자 학습기록

profile
난 뭘까

0개의 댓글