함수 안과 밖의 변수, 화살표 함수, 그리고 처음 만난 배열까지.
오늘은 "초기식, 조건식, 증감식을 먼저 써 두고 시작하기"를 배웠다.
학습 네 번째 날이다. 오늘 배운 내용은 이렇다.
isNaN(): 숫자인지 판별하기length, for in어제 출제된 마지막 문제의 풀이로 하루를 시작했다. 문제는 구구단 중 짝수단만, 곱하는 수는 그 단의 숫자까지만 출력하는 것이다. (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에서 증감식을 직접 써야 한다는 점을 보여 준다.
전역 변수와 지역 변수라는 개념이 등장했다. (Day.3에서 간단히 정리했는데, 오늘은 직접 실험한 내용을 중심으로 다시 정리한다.)
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
실습 문제의 처음 코드는 이랬다.
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
연산자가 아닌 값('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으로 나눠도 오류를 내지 않는다.
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);
}
자동 형 변환 때문에 예상과 다르게 동작하는 입력이 있다.
console.log(isNaN('')); // false (빈 문자열은 숫자 0으로 변환된다)
console.log(isNaN(null)); // false (null도 0으로 변환된다)
console.log(isNaN(undefined)); // true
prompt에서 아무것도 입력하지 않고 확인하면 ""가 돌아오고, 취소하면 null이 돌아온다. 이 두 경우 모두 isNaN이 false라서 "숫자"로 통과된다. Day.1에서 정리한 prompt의 반환값과 연결되는 내용이다.
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문에서도 쓸 수 있다.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('$', 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처럼 따로 두는 것이 안전하다.
함수를 변수에 담아서 쓰는 방식이다.
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) | 93 | return 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
화살표 함수의 선언은 항상 (매개변수) => 실행할 내용 의 형태를 가진다. 매개변수가 없을 때는 빈 괄호 ()를 쓴다.
배열은 여러 개의 값을 저장하는 공간이다. 각 값에는 인덱스 번호로 접근하고, 인덱스는 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 (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의 +가 문자열을 이어 붙이던 성질이 또 나왔다.
같은 이름의 변수 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문 바깥에서는 사용할 수 없다. 중요한 건 초기식 자리에서 선언한 것만 해당한다는 점이다.
출제된 문제 중 헷갈렸던 것을 하나 가져왔다.
정확한 문자열 "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번을 썼다.
break는 자신을 감싸는 가장 가까운 switch나 반복문 하나만 빠져나간다. 이 문제는 값이 같은지 한 가지만 확인하면 되므로 if를 쓰면 더 간단하다.
while (true) {
let input = prompt('password를 입력해주세요');
if (input == 'password') {
alert('통과');
break; // while을 바로 탈출
}
}
오늘 수업 중에 함수를 메서드(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 메서드
오늘 가장 인상 깊었던 건 두 가지다.
string으로 받은 연산자를 case의 트리거로 삼는 방식. 문자 하나가 어떤 수식을 실행할지 결정하게 만든다는 발상이 새로웠다.while 블록 안과 밖, for 초기식까지, 변수를 어디서 선언하느냐에 따라 쓸 수 있는 범위가 달라진다는 것을 코드로 확인했다.다음 글에서는 이어서 정리할 예정이다.
Tags: JavaScript 함수 화살표함수 배열 변수스코프 개발자 학습기록