JavaScript_Day.4

조현기·5일 전

JavaScript_Day.4

함수 안과 밖의 변수, 화살표 함수, 그리고 처음 만난 배열까지.
오늘은 "초기식, 조건식, 증감식을 먼저 써 두고 시작하기"를 배웠다.

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

  1. 무한 반복문 풀이: 초기식, 조건식, 증감식을 먼저 쓰는 습관
  2. 전역 변수와 지역 변수
  3. 함수 활용: 사칙연산 함수, 문자 반복 출력 함수
  4. isNaN(): 숫자인지 판별하기
  5. 함수 표현식과 화살표 함수
  6. 배열: 인덱스, length, for in

1. 무한 반복문 풀이: 짝수단 구구단

어제 출제된 마지막 문제의 풀이로 하루를 시작했다. 문제는 구구단 중 짝수단만, 곱하는 수는 그 단의 숫자까지만 출력하는 것이다. (2단은 2까지, 4단은 4까지.)

강사님의 풀이 과정에서 얻은 점이 있다. 무한 반복문을 쓸 때는 초기식, 조건식, 증감식을 먼저 써 두고 시작하면 구성하기 편하다.

let dan = 2;                          // 초기식

while (true) {
  if (dan == 10) {                    // 조건식
    break;
  }
  for (let i = 1; i <= dan; i++) {    // 곱하는 수가 단의 숫자와 같아질 때까지만
    document.write(dan + '*' + i + '=' + (dan * i) + '<br>');
  }
  dan += 2;                           // 증감식
}
단출력되는 범위
2단21 ~ 22
4단41 ~ 44
6단61 ~ 66
8단81 ~ 88

dan이 10이 되면 break로 빠져나간다. dan += 2를 빼먹으면 dan이 영원히 2라서 무한 반복에 빠진다. Day.2에서 겪은 무한 반복 실수와 같은 구조다. 이 풀이는 while에서 증감식을 직접 써야 한다는 점을 보여 준다.


2. 전역 변수와 지역 변수

전역 변수와 지역 변수라는 개념이 등장했다. (Day.3에서 간단히 정리했는데, 오늘은 직접 실험한 내용을 중심으로 다시 정리한다.)

  • 전역 변수(global variable): 함수 밖에서 선언한 변수. 프로그램 전체에서 사용할 수 있다.
  • 지역 변수(local variable): 함수 안에서 선언한 변수. 선언된 그 함수 안에서만 사용할 수 있다.
let n = 10;                           // 전역 변수

function f() {
  let m = 1;                          // 지역 변수
  document.write(m);                  // 1
  document.write('<br>');
  document.write(n + '<br>');         // 10 (전역 변수는 함수 안에서도 쓸 수 있다)
}

f();

출력은 1, 10이다. 이어서 함수 안에서 선언한 m을 함수 밖에서 호출해 봤다.

f();
document.write(m);                    // 출력되지 않는다

화면에는 출력되지 않았다. F12 콘솔에는 ReferenceError: m is not defined 오류가 표시된다. 지역 변수는 함수 밖에서 호출할 수 없다.

다른 함수의 지역 변수도 마찬가지다.

let n = 10;                           // 전역 변수

function f() {
  let m = 1;                          // f의 지역 변수
  document.write(m + '<br>');
  document.write(n + '<br>');
  document.write(a + '<br>');         // 오류: a는 e 안에서만 쓸 수 있다
}

function e() {
  let a = 10;                         // e의 지역 변수
}

f();

m은 함수 f의 지역 변수이고, a는 함수 e의 지역 변수다. 함수 안에서 선언한 변수는 그 함수 안에서만 동작한다.

let name = 'tom';                     // 전역 변수: 전체 코드에서 사용 가능

function call() {
  let msg = 'hi';                     // 지역 변수: 이 함수에서만 사용 가능
  console.log(`${msg} ${name}`);      // hi tom
}

call();
console.log(msg);                     // ReferenceError: msg is not defined

3. 함수 활용 1: 사칙연산 함수

실습 문제의 처음 코드는 이랬다.

let a = prompt('사칙연산 중 하나 입력해라');   // +, -, *, /
let b = parseInt(prompt('첫번째 정수 입력'));
let c = parseInt(prompt('두번째 정수 입력'));

b, c는 number 타입이고 a는 string 타입이다. 이 값들로 계산하는 함수를 어떻게 만들어야 할지 한동안 고민했다. 강사님이 함수 안에 switch case를 넣고 case에 '+', '-'를 쓰신 것을 보고 이해가 됐다. string으로 받은 연산자를 트리거로 삼아서 수식이 계산되게 하는 방식이다.

function cal(x, y, z) {
  let calculator;
  switch (x) {
    case '+':
      calculator = (y + z);
      break;
    case '-':
      calculator = (y - z);
      break;
    case '*':
      calculator = (y * z);
      break;
    case '/':
      calculator = (y / z);
      break;
  }
  return calculator;
}

let a = prompt('사칙연산 중 하나 입력해라');   // +, -, *, /
let b = parseInt(prompt('첫번째 정수 입력'));
let c = parseInt(prompt('두번째 정수 입력'));

document.write(cal(a, b, c));                   // 예: +, 3, 4 → 7

추가로 정리: default를 넣으면 더 안전하다

연산자가 아닌 값('x' 등)을 입력하면 어떤 case에도 해당하지 않아서 calculator가 비어 있는 채로 반환되고, 결과는 undefined가 된다. default를 넣어 처리하면 좋다.

switch (x) {
  case '+': calculator = y + z; break;
  case '-': calculator = y - z; break;
  case '*': calculator = y * z; break;
  case '/': calculator = y / z; break;
  default:  calculator = '지원하지 않는 연산자';   // 그 외 입력
}

또 /에서 나누는 수를 0으로 넣으면 오류가 아니라 Infinity가 나온다. 자바스크립트는 0으로 나눠도 오류를 내지 않는다.


4. isNaN(): 숫자가 아닌지 판별

Day.2에서 NaN(Not-a-Number)이 나왔는데, 오늘은 그 값을 판별하는 isNaN()을 배웠다.
isNaN(값)은 수식이나 값이 NaN이면 true, 아니면 false를 반환한다. 판단하기 전에 매개변수를 먼저 숫자로 변환(자동 형 변환) 한다.

먼저 비교 연산자에서의 자동 형 변환을 복습했다.

document.write(('100' > 50) + '<br>');   // true  (문자열이 숫자로 자동 변환됨)
document.write(('100' < '50') + '<br>'); // true  (문자열 < 문자열이라 첫 문자 '1'과 '5'를 비교. '5'가 더 큼)

그다음 isNaN을 실험했다.

document.write(isNaN(100) + '<br>');     // false
document.write(isNaN('100') + '<br>');   // false (문자열 "100"이 숫자 100으로 변환되기 때문)
document.write(isNaN('abc') + '<br>');   // true  (숫자로 바꿀 수 없음)
document.write(isNaN('33') + '<br>');    // false
코드결과이유
isNaN(100)false숫자다
isNaN("100")false숫자 100으로 변환됨
isNaN("abc")true숫자로 변환할 수 없음
isNaN("33")false숫자 33으로 변환됨

prompt로 입력받은 값에 적용하면 숫자인지 확인할 수 있다.

let n = 100;
let m = prompt('수 입력');       // '100'은 자동 형 변환되어 숫자로 취급된다

if (isNaN(m)) {                  // 숫자가 아니면 true
  alert('다시 입력');
} else {                         // 숫자이면
  alert(n * m);
}

추가로 정리: isNaN이 헷갈릴 수 있는 경우

자동 형 변환 때문에 예상과 다르게 동작하는 입력이 있다.

console.log(isNaN(''));          // false (빈 문자열은 숫자 0으로 변환된다)
console.log(isNaN(null));        // false (null도 0으로 변환된다)
console.log(isNaN(undefined));   // true

prompt에서 아무것도 입력하지 않고 확인하면 ""가 돌아오고, 취소하면 null이 돌아온다. 이 두 경우 모두 isNaN이 false라서 "숫자"로 통과된다. Day.1에서 정리한 prompt의 반환값과 연결되는 내용이다.


5. 함수 활용 2: 입력 검증과 반복 출력

구구단 함수: 1~9 사이만 입력받기

isNaN을 활용해서, 1~9 사이의 정수가 입력될 때까지 계속 입력받은 뒤 해당 단을 출력하는 함수를 만들었다.

function gugu() {
  let n;                                  // 지역 변수
  while (true) {
    n = parseInt(prompt('정수 입력'));    // 정수를 입력받는다

    // n이 1보다 작거나, 9보다 크거나, 숫자가 아닐 경우
    if (n < 1 || n > 9 || isNaN(n)) {
      alert('1~9사이만 입력');
    } else {
      break;                              // 무한루프 종료
    }
  }

  for (let i = 1; i <= 9; i++) {
    document.write(`${n}x${i}=${n * i}` + '<br>');
  }
}

gugu();

여기서 중요한 점이 두 가지다.

  • parseInt('abc')는 NaN이다. NaN은 <, > 비교가 모두 false가 되기 때문에 n < 1 || n > 9만으로는 걸러지지 않는다. 그래서 isNaN(n)을 같이 써야 한다.
  • n은 while 바깥, 함수 안에 선언해야 한다. 그래야 while이 끝난 뒤 아래 for문에서도 쓸 수 있다.

추가로 정리: n을 while 안에서 선언하면

let으로 선언한 변수는 선언된 중괄호 { } 블록 안에서만 쓸 수 있다. 이것을 블록 스코프라고 한다. n을 while 블록 안에서 선언하면 밖의 for문에서 쓸 수 없다.

function gugu2() {
  while (true) {
    let n = parseInt(prompt('정수 입력'));   // while 블록 안의 변수
    if (n >= 1 && n <= 9) break;
  }
  for (let i = 1; i <= 9; i++) {
    document.write(`${n}x${i}=${n * i}` + '<br>');   // ReferenceError: n is not defined
  }
}

show 함수: 문자 반복 출력

show('$', 10)을 호출하면 $를 10번 이어서 출력하는 함수다.

function show(x, y) {
  let str = '';                    // 빈 문자열로 시작
  for (let i = 1; i <= y; i++) {
    str += x;                      // 문자열에 계속 이어 붙인다
  }
  return str;
}

document.write(show('$', 10));     // $$$$$$$$$$
document.write(show('*', 5));      // *****

문자를 연속으로 출력하는 개념을 이해하게 된 문제였다. 빈 문자열 변수를 먼저 만들고, 거기에 문자를 +=로 계속 이어 붙이면 같은 문자가 $$$$$$$$$$처럼 쌓인다. 문자 + 문자는 이어 붙는다는 Day.1, Day.2의 내용이 여기서 쓰였다.

추가로 정리: 초기값을 빈 문자열로 두는 이유

let str;처럼 초깃값 없이 선언하면 str은 undefined다. 이 상태에서 str += '$'를 하면 "undefined$"가 된다. 그래서 빈 문자열 ''로 시작해야 한다.

또 매개변수 y를 for (y = 1; ...)처럼 초기식에서 다시 대입하면 호출할 때 넘긴 값이 덮어씌워진다. show('$', 5)를 호출해도 10번 출력된다. 횟수를 받는 매개변수는 조건식에서 비교만 하고, 반복용 변수는 i처럼 따로 두는 것이 안전하다.


6. 함수 표현식과 화살표 함수

변수식 함수(함수 표현식)

함수를 변수에 담아서 쓰는 방식이다.

let 변수명 = function () {
  // 실행할 코드
};
변수명();                            // 변수명으로 호출
let sum = function (a, b) {
  return (a + b);
};

console.log(sum(3, 4));              // 7

화살표 함수

화살표 함수는 function 키워드 없이 =>로 함수를 만드는 방식이다. 코드가 훨씬 짧아져서 인상 깊었다. Day.3에서 간단히 소개했는데, 오늘 본격적으로 배웠다.

let 변수명 = (a, b) => {
  return a + b;
};

return이 한 줄일 때는 { }와 return을 모두 생략할 수 있다.

// 함수 표현식
// let add = function (a, b) { return a + b; }

let add  = (a, b) => { return a + b; };
let add2 = (a, b) => { a + b; };       // { } 안에 return이 없다
let add3 = (a, b) => a + b;            // { }와 return 생략

alert(add3(89, 4));                    // 93

세 개를 비교해서 확인한 내용이다.

함수호출 결과이유
add(89, 4)93return a + b
add2(89, 4)undefined{ }를 쓰면 return을 직접 써야 한다. 계산만 하고 값을 돌려주지 않음
add3(89, 4)93{ }를 생략하면 식의 결과가 자동으로 반환됨

매개변수가 없는 화살표 함수도 만들었다.

// alert로 'hi'를 출력하는 함수
let hi = () => alert('hi');            // 매개변수가 없어도 () 는 반드시 쓴다
hi();

// 두 값을 ,로 이어 출력하는 함수
let profile = (a, b) => a + ',' + b;
alert(profile(44, 3));                 // 44,3

화살표 함수의 선언은 항상 (매개변수) => 실행할 내용 의 형태를 가진다. 매개변수가 없을 때는 빈 괄호 ()를 쓴다.


7. 배열

배열이란

배열은 여러 개의 값을 저장하는 공간이다. 각 값에는 인덱스 번호로 접근하고, 인덱스는 0부터 시작한다.

let 배열이름 = ['값1', '값2', '값3'];   // 문자열 배열
let 배열이름 = [값1, 값2, 값3];          // 숫자 배열

자바스크립트의 자료형은 숫자, 문자열, undefined, 객체(배열), 함수 등이 있고, 지금 배우는 배열은 객체에 해당한다.

let ary = [1, 2, 3, 4, 5];

document.write(typeof(ary) + '<br>');                            // object (타입이 객체)
document.write(ary[0] + ' ' + ary[1] + ' ' + ary[2] + '<br>');   // 1 2 3
document.write(ary.length + '<br>');                              // 5

배열명.length는 배열의 길이(요소의 개수) 다. 인덱스는 0부터 시작하므로 마지막 인덱스는 length - 1이다. 그래서 for문의 조건식을 i < ary.length로 쓰면 모든 요소를 확인할 수 있다.

for (let i = 0; i < ary.length; i++) {
  document.write(ary[i]);            // ary[0] ~ ary[4] → 12345
}

추가로 정리: 배열인지 확인하기

typeof는 배열도 object라고만 알려 준다. 배열인지 정확히 확인하려면 Array.isArray()를 쓴다.

console.log(typeof [1, 2, 3]);           // object
console.log(Array.isArray([1, 2, 3]));   // true
console.log(Array.isArray({ a: 1 }));    // false (일반 객체)

for in문

배열의 인덱스를 하나씩 꺼내서 변수에 저장하며 반복하는 for in문도 배웠다.

for (let i in 배열명) {
  // 반복할 식
}
let arr = ['aa', 'bb', 'cc', 'dd'];

// for (let i = 0; i < arr.length; i++) 과 같은 동작
for (let i in arr) {
  console.log(i);                    // 0, 1, 2, 3 (인덱스)
  console.log(arr[i]);               // aa, bb, cc, dd (값)
}

for in으로 변수에 저장되는 인덱스 값은 string 타입이다. 그래서 숫자처럼 더하면 계산이 아니라 이어 붙인다.

let ary = [1, 2, 3, 4, 5];

for (let i in ary) {
  if (ary[i] == 4) {
    console.log(i + 9);              // "39" (i는 문자열 '3'이라 3+9=12가 아니라 이어 붙음)
  }
}

인덱스를 숫자로 계산하려면 parseInt(i) 또는 Number(i)로 변환해야 한다. Day.2의 +가 문자열을 이어 붙이던 성질이 또 나왔다.

for문 초기식의 변수는 for 안에서만 쓴다

같은 이름의 변수 i를 for문 두 개에서 연달아 선언해도 오류가 나지 않았다.

for (let i = 0; i <= 2; i++) {
  console.log(i);
}

for (let i = 0; i <= 2; i++) {       // 같은 이름 i를 다시 선언해도 된다
  console.log(i);
}

console.log(i);                      // ReferenceError: i is not defined

for문의 초기식 자리에서 let이나 const로 선언한 변수는 그 for문 안에서만 쓰는 지역 변수가 되어, for문 바깥에서는 사용할 수 없다. 중요한 건 초기식 자리에서 선언한 것만 해당한다는 점이다.


8. 문제 풀이: break는 어디까지 빠져나갈까

출제된 문제 중 헷갈렸던 것을 하나 가져왔다.

정확한 문자열 "password"가 입력될 때까지 계속 prompt()로 문자열을 입력받는다. "password"가 입력되면 "통과"라고 출력한다. (while(true), break 사용)

let pas = 0;

while (true) {
  let input = prompt('password를 입력해주세요');
  switch (true) {
    case input == 'password':
      alert('통과');
      pas += 1;
      break;                         // ① switch를 탈출
  }
  if (pas == 1) {
    break;                           // ② while(true)를 탈출
  }
}

이 코드에서 break를 두 개 쓴 이유는, switch 안의 break는 switch만 탈출하기 때문이다. while(true)를 끝내려면 while 블록 안에서 break를 한 번 더 써야 한다. 그래서 switch를 탈출하는 break 1번, while을 탈출하는 break 1번, 총 2번을 썼다.

추가로 정리: if를 쓰면 break가 하나로 줄어든다

break는 자신을 감싸는 가장 가까운 switch나 반복문 하나만 빠져나간다. 이 문제는 값이 같은지 한 가지만 확인하면 되므로 if를 쓰면 더 간단하다.

while (true) {
  let input = prompt('password를 입력해주세요');
  if (input == 'password') {
    alert('통과');
    break;                           // while을 바로 탈출
  }
}

9. 메서드라는 이름

오늘 수업 중에 함수를 메서드(method) 라고도 부른다는 이야기를 들었다.

추가로 정리: 함수와 메서드의 차이

정확히는 객체 안에 들어 있는 함수를 메서드라고 부른다. Day.3에서 객체의 operating처럼 객체 안에 있는 함수가 메서드라고 정리했다. 지금까지 쓴 document.write()는 document 객체의 메서드이고, console.log()는 console 객체의 메서드다.

function hello() { }                 // 함수

let obj = {
  hello: function () { }             // 메서드 (객체 obj 안에 있는 함수)
};

obj.hello();                         // 객체명.메서드명()
document.write('hi');                // document 객체의 write 메서드
console.log('hi');                   // console 객체의 log 메서드

마무리

오늘 가장 인상 깊었던 건 두 가지다.

  1. string으로 받은 연산자를 case의 트리거로 삼는 방식. 문자 하나가 어떤 수식을 실행할지 결정하게 만든다는 발상이 새로웠다.
  2. 변수가 살아 있는 범위. 함수 안과 밖, while 블록 안과 밖, for 초기식까지, 변수를 어디서 선언하느냐에 따라 쓸 수 있는 범위가 달라진다는 것을 코드로 확인했다.

다음 글에서는 이어서 정리할 예정이다.


Tags: JavaScript 함수 화살표함수 배열 변수스코프 개발자 학습기록

profile
난 뭘까

0개의 댓글