
1. 제어문
1. 중첩 반복문에서의 break와 continue
문법
break와continue는 반복문 안에서 흐름을 조절하는 문법이다.
break: 반복문을 끝낸다.continue: 이번 한 번만 건너뛰고 다음 반복으로 넘어간다.반복문이 하나만 있을 때는 비교적 단순하다.
그런데 반복문이 두 겹, 세 겹으로 들어가면 조금 더 조심해서 봐야 한다.여기서 가장 중요한 기준은
기본break와continue는 자기와 가장 가까운 반복문에만 영향을 준다는 점이다.즉, 안쪽 반복문 안에서
break를 만나면
바깥 반복문 전체가 끝나는 것이 아니라
안쪽 반복문만 끝난다.
동작 흐름중첩 반복문에서는 지금
break나continue가
몇 번째 반복문 안에 들어 있는지를 먼저 보는 습관이 중요하다.
- 바깥 반복문 : 큰 줄기
- 안쪽 반복문 : 그 안에서 도는 작은 줄기
이 구조에서 안쪽 반복문 안에
break가 있으면
작은 줄기만 끊기고 바깥쪽은 계속 다음 차례로 진행된다.
continue도 마찬가지다.
안쪽 반복문 안에서continue를 만나면
안쪽 반복문의 현재 한 번만 건너뛰고 다음 안쪽 반복으로 넘어간다.
코드 예제예제:
NestedBreakTestfor (let i = 1; i <= 3; i++) { console.log("바깥 반복문 시작:", i); for (let j = 1; j <= 3; j++) { if (j === 2) { break; // 안쪽 반복문만 끝낸다. } console.log(" 안쪽 반복문:", i, j); } } // 출력결과 // 바깥 반복문 시작: 1 // 안쪽 반복문: 1 1 // 바깥 반복문 시작: 2 // 안쪽 반복문: 2 1 // 바깥 반복문 시작: 3 // 안쪽 반복문: 3 1이 코드에서는
j === 2가 되는 순간break가 실행된다.
그래서 안쪽 반복문은j = 1까지만 돌고 끝난다.하지만 바깥 반복문은 끝나지 않는다.
그래서i = 2,i = 3도 계속 진행된다.즉, 이 예제의 핵심은
break가 가장 가까운 반복문만 종료한다는 점이다.
예제:
NestedContinueTestfor (let i = 1; i <= 2; i++) { console.log("바깥 반복문:", i); for (let j = 1; j <= 3; j++) { if (j === 2) { continue; // 안쪽 반복문의 이번 차례만 건너뛴다. } console.log(" 안쪽 반복문:", i, j); } } // 출력결과 // 바깥 반복문: 1 // 안쪽 반복문: 1 1 // 안쪽 반복문: 1 3 // 바깥 반복문: 2 // 안쪽 반복문: 2 1 // 안쪽 반복문: 2 3이 코드는
j === 2일 때만 출력하지 않고 건너뛴다.
하지만 안쪽 반복문 전체를 끝내지는 않는다.그래서
j = 3은 다시 정상적으로 실행된다.즉,
continue는
끝내는 문법이 아니라, 이번 한 번만 건너뛰는 문법이다.
헷갈리기 쉬운 부분초보자가 가장 많이 하는 오해는
안쪽에서break를 쓰면 바깥 반복문까지 다 끝난다고 생각하는 것이다.하지만 기본
break는 그렇지 않다.
기본continue도 바깥 반복문으로 바로 가는 것이 아니라
가장 가까운 반복문의 다음 차례로 넘어간다.즉, 중첩 반복문에서는
“지금 이break와continue가 어느 반복문에 붙어 있는가”를 항상 봐야 한다.
2. label
문법
label은 반복문 앞에 이름을 붙이는 문법이다.문법은 이렇게 생겼다.
label이름: for (...) { for (...) { break label이름; } }
label이 필요한 이유는
중첩 반복문에서 기본break만으로는 바깥 반복문까지 한 번에 끝낼 수 없기 때문이다.
왜 필요한가예를 들어 2중 반복문을 돌다가
특정 값을 찾는 순간 전체 반복을 전부 끝내고 싶을 수 있다.이때 안쪽 반복문 안에서 그냥
break를 쓰면
안쪽 반복문만 끝나고 바깥 반복문은 계속 돈다.즉,
- 그냥
break→ 안쪽만 종료break label→ 지정한 바깥 반복문까지 종료이 차이 때문에
label이 필요하다.
동작 흐름
label은 “이 반복문 이름은 이것이다”라고 붙여 두는 것이다.그다음 안쪽에서
break label이름;을 만나면
그 이름이 붙은 반복문 전체를 빠져나간다.그래서 중첩 반복문에서
“어느 반복문까지 멈출 것인지”를 정확하게 지정할 수 있다.
코드 예제예제:
LabelBreakTestouter: for (let i = 1; i <= 3; i++) { for (let j = 1; j <= 3; j++) { console.log(i, j); // 현재 반복 위치 출력 if (i === 2 && j === 2) { break outer; // outer라는 이름이 붙은 바깥 반복문까지 한 번에 끝낸다. } } } // 출력결과 // 1 1 // 1 2 // 1 3 // 2 1 // 2 2이 코드는
(2, 2)를 만나는 순간 전체 반복을 끝낸다.만약 여기서
break;만 썼다면
j반복문만 끝나고, 바깥의i반복문은 계속 돌아서
다음 줄기가 이어졌을 것이다.하지만
break outer;를 썼기 때문에
outer라는 이름이 붙은 바깥 반복문까지 한 번에 종료된다.즉, 이 예제의 핵심은
label이 있으면 끊고 싶은 반복문을 정확히 지정할 수 있다는 점이다.
헷갈리기 쉬운 부분
label은 자주 쓰는 문법은 아니지만,
중첩 반복문 흐름을 이해할 때는 도움이 된다.처음에는 어렵게 느껴질 수 있지만,
사실은 “반복문에 이름을 붙이고 그 이름을 불러서 멈춘다”라고 생각하면 된다.그리고
label은 보통break와 함께 많이 보지만,
필요하면continue와 함께 써서 바깥 반복문의 다음 차례로 넘어가게 할 수도 있다.지금 단계에서는 이 정도만 먼저 잡으면 된다.
- 기본
break는 가까운 반복문만 끝낸다.- 바깥 반복문까지 끝내고 싶으면
label이 필요할 수 있다.
2. 함수
1. 함수란 무엇인가
개념
함수는 한 번 만들어 두고 필요할 때마다 다시 실행할 수 있는 코드 묶음이다.
같은 작업을 여러 번 해야 할 때 코드를 계속 복붙하는 대신,
그 작업에 이름을 붙여 두고 필요할 때 호출해서 사용한다.예를 들어 어떤 문장을 여러 번 출력해야 한다면,
그 코드를 함수로 만들어 두고f1()처럼 부르면 된다.
즉, 함수는
코드를 정리하기 쉽게 만들고, 같은 로직을 반복해서 재사용하게 해 주는 도구라고 보면 된다.
왜 필요한가함수가 없으면 같은 코드가 여기저기 반복된다.
그러면 수정할 때도 여러 군데를 다 고쳐야 하고,
어디서 어떤 코드가 실행되는지 보기도 어려워진다.
반면 함수를 사용하면
- 같은 작업을 이름으로 묶을 수 있고
- 필요할 때마다 다시 실행할 수 있고
- 값을 받아 다른 결과를 만들 수도 있고
- 실행 결과를 돌려줄 수도 있다
그래서 함수는 단순한 문법 하나가 아니라
프로그램을 읽기 쉽게 만드는 기본 구조라고 이해하는 것이 좋다.
핵심 특징
- 함수는 호출해야 실행된다.
- 함수는 값을 받을 수 있다.
- 함수는 값을 돌려줄 수도 있고, 돌려주지 않을 수도 있다.
JavaScript에서는 함수도 하나의 값처럼 다룰 수 있다.
2. 선언적 함수 정의
문법선언적 함수 정의는
function키워드로 이름 있는 함수를 바로 만드는 방식이다.function 함수이름(매개변수들) { // 실행할 코드 }여기서 먼저 알아야 할 것은
함수는 정의만 해서는 실행되지 않고, 호출해야 실행된다는 점이다.즉,
function f1() { ... }→ 함수 정의f1();→ 함수 호출이렇게 보면 된다.
호출할 때는 함수 이름 뒤에 괄호()를 붙인다.f1();만약 값을 전달해야 하는 함수라면 괄호 안에 값을 넣어서 호출한다.
f2(10, 20);즉, 어떤 함수를 정의하든
실제로 실행할 때는함수이름()형태로 호출한다고 먼저 이해하면 된다.
이 방식은 함수 이름이 분명하게 보이기 때문에 처음 배울 때 가장 이해하기 쉽다.
코드 예제예제:
exam10function f1() { document.write('f1() 호출<br>'); // f1()이 실행되면 화면에 이 문장을 출력한다. } function f2(p1, p2) { document.write('f2() 호출-' + (p1 + p2) + '<br>'); // 전달받은 p1, p2를 더한 결과를 함께 출력한다. } f1(); // 인수 없이 f1() 호출 f2(10, 20); // p1=10, p2=20 전달 → 30 출력 document.write('<hr>'); // 구분선 출력 var result1 = f1(); // f1() 실행 결과를 result1에 저장 // return이 없으므로 result1에는 undefined가 들어간다. var result2 = f2(100, 200); // f2() 실행 결과를 result2에 저장 // 화면에는 300이 출력되지만 return이 없으므로 result2도 undefined다. document.write('result1 : ' + result1 + ' result2 : ' + result2); // 두 변수에 저장된 값을 출력 → 둘 다 undefined if (result1 == undefined) { document.write('<br>리턴값이 없군요!!'); // f1()이 값을 돌려주지 않았기 때문에 이 문장이 출력된다. } document.write('<hr>'); // 다시 구분선 출력 f1(100); // f1()은 매개변수가 없으므로 100을 넘겨도 사용하지 않고 그냥 실행된다. f2(100); // p1에는 100이 들어가고 p2에는 undefined가 들어간다. // 그래서 100 + undefined가 되어 결과는 NaN이 출력된다.
코드 해설이 예제는 선언적 함수의 가장 기본 형태를 보여 준다.
f1()은 매개변수가 없는 함수다.
호출하면 화면에f1() 호출만 출력한다.
f2(p1, p2)는 값을 두 개 받아서 더한 뒤 출력한다.
그래서f2(10, 20)을 호출하면30이 붙어서 출력된다.여기서 먼저 봐야 할 것은
함수를 먼저 만들고, 그다음 이름으로 호출한다는 흐름이다.즉,
function f1() { ... }→ 함수 만들기f1();→ 함수 실행하기이 순서라고 보면 된다.
그리고 이 예제의 뒤쪽에서는
함수가 값을 돌려주지 않으면 호출 결과가undefined가 된다는 점도 함께 보여 준다.
f1()과f2()는 화면에 출력만 할 뿐return이 없기 때문에,
var result1 = f1();,var result2 = f2(100, 200);처럼 저장하면
두 변수에는 모두undefined가 들어간다.또
f2(100)처럼 값을 하나만 넘기면
두 번째 매개변수p2에는undefined가 들어가고,
100 + undefined가 되어 결과는NaN이 된다.
헷갈리기 쉬운 부분함수를 만들었다고 해서 바로 실행되는 것은 아니다.
반드시f1();,f2(10, 20);처럼 호출해야 실행된다.또 화면에 출력했다고 해서 그 값이 함수의 결과가 되는 것은 아니다.
return이 없으면 함수 호출 결과는undefined다.
3. 익명(표현식) 함수 정의
문법익명 함수는 이름 없는 함수를 만들어 변수에 담는 방식이다.
var 변수명 = function (매개변수들) { // 실행할 코드 };이 방식은 함수를 하나의 값처럼 변수에 저장해서 사용하는 형태다.
코드 예제예제:
exam10_1var f1 = function () { document.write('f1() 호출<br>'); // f1 변수에 저장된 함수를 실행하면 이 문장을 출력한다. }; var f2 = function (p1, p2) { document.write('f2() 호출-' + (p1 + p2) + '<br>'); // 전달받은 p1, p2를 더한 결과를 함께 출력한다. }; f1(); // f1 변수에 들어 있는 함수 호출 f2(10, 20); // p1=10, p2=20 전달 → 30 출력 document.write('<hr>'); // 구분선 출력 var result1 = f1(); // f1() 실행 결과를 result1에 저장 // return이 없으므로 result1에는 undefined가 들어간다. var result2 = f2(100, 200); // f2() 실행 결과를 result2에 저장 // 화면에는 300이 출력되지만 return이 없으므로 result2도 undefined다. document.write('result1 : ' + result1 + ' result2 : ' + result2); // 두 변수에 저장된 값을 출력 → 둘 다 undefined if (result1 == undefined) { document.write('<br>리턴값이 없군요!!'); // f1()이 값을 돌려주지 않았기 때문에 이 문장이 출력된다. } document.write('<hr>'); // 다시 구분선 출력 f1(100); // f1()은 매개변수가 없으므로 100을 넘겨도 사용하지 않고 그냥 실행된다. f2(100); // p1에는 100이 들어가고 p2에는 undefined가 들어간다. // 그래서 100 + undefined가 되어 결과는 NaN이 출력된다. document.write(typeof f1 + '<br>'); document.write(typeof f2 + '<br>'); // f1, f2에 들어 있는 값의 타입을 확인하면 둘 다 function이 나온다. // 즉, 익명 함수도 변수에 저장되면 함수처럼 호출할 수 있다.
코드 해설이 예제는 함수가 변수에 저장될 수 있다는 점을 보여 준다.
겉모양은 선언적 함수와 다르지만,
한 번 변수에 함수가 저장되고 나면f1(),f2()처럼 똑같이 호출할 수 있다.즉,
- 선언적 함수 → 이름으로 바로 만든 함수
- 익명 함수 → 함수 값을 변수에 담아 놓고 부르는 함수
라고 이해하면 된다.
또 이 예제에서는 함수가 값을 돌려주지 않으면 호출 결과가
undefined가 된다는 점도 함께 확인할 수 있다.
f1()과f2()는 화면에 출력만 할 뿐return이 없기 때문에,
var result1 = f1();,var result2 = f2(100, 200);처럼 저장하면
두 변수에는 모두undefined가 들어간다.그리고
f2(100)처럼 인수를 하나만 넘기면
두 번째 매개변수p2에는undefined가 들어가고,
100 + undefined가 되어 결과는NaN이 된다.마지막의
typeof f1,typeof f2결과가 둘 다function인 것도 중요하다.
즉,f1,f2안에 들어 있는 값 자체가 함수라는 뜻이다.
헷갈리기 쉬운 부분익명 함수라고 해서
“이름이 없으니 호출할 수 없다”라고 생각하면 안 된다.함수 자체에는 이름이 없지만,
그 함수가f1,f2변수에 저장되어 있으므로
변수 이름으로 호출할 수 있다.또 화면에 출력한 값과 함수가 돌려준 값은 다르다.
return이 없으면 함수 호출 결과는undefined다.
4. 선언적 함수와 익명 함수의 차이
비교 기준둘의 가장 큰 차이는
스크립트가 함수를 미리 어떻게 준비하느냐에 있다.겉으로 보면 둘 다 함수지만,
JavaScript는 스크립트를 읽을 때 둘을 똑같이 처리하지 않는다.이 차이를 이해하려면
호이스팅을 같이 봐야 한다.
스크립트가 읽히는 순서와 호이스팅
호이스팅은 스크립트가 실행되기 전에
일부 선언을 먼저 준비해 두는 동작이라고 이해하면 된다.여기서 중요한 차이는 이렇다.
- 선언적 함수는 함수 본문까지 함께 미리 준비된다.
var변수는 이름만 먼저 준비되고, 값은 아직 없다.즉,
function f1() {}이런 선언적 함수는 스크립트 아래쪽에 있어도
위에서 먼저 호출할 수 있다.
반면var f2 = function () {};이 코드는
f2라는 변수 이름만 먼저 준비되고,
실제 함수 값이 들어가는 것은 그 줄을 실행했을 때다.그래서 그 전에
f2()를 호출하면 문제가 생긴다.
코드 예제예제:
exam10_2f1(); // 선언적 함수 f1은 아래에 정의되어 있어도 미리 준비되므로 정상 호출된다. f2(10, 20); // var f2는 변수 이름만 먼저 준비되고, 아직 함수가 대입되기 전이다. // 그래서 이 시점의 f2는 undefined이며, 함수처럼 호출하면 오류가 난다. document.write('<hr>'); var result1 = f1(); // f1()은 return이 없으므로 result1에는 undefined가 들어간다. var result2 = f2(100, 200); // 여기까지 실행되려면 앞에서 f2 호출 오류가 없어야 한다. // 하지만 실제로는 위의 f2(10, 20)에서 이미 오류가 나므로 이 줄까지 오지 못한다. document.write('result1 : ' + result1 + ' result2 : ' + result2); // result1, result2 값을 출력하는 코드 if (result1 == undefined) { document.write('<br>리턴값이 없군요!!'); // f1()이 값을 돌려주지 않았을 때 출력되는 문장 } document.write('<hr>'); f1(100); // f1은 매개변수가 없으므로 100을 넘겨도 사용하지 않는다. f2(100); // f2가 정상적으로 함수가 된 뒤라면 p2에는 undefined가 들어가서 NaN이 될 수 있다. // 하지만 이 예제는 앞에서 오류가 발생하므로 실제로 여기까지 실행되지 않는다. document.write(typeof f1 + '<br>'); document.write(typeof f2 + '<br>'); // f1은 function, f2는 이 줄이 실행되는 시점에 따라 값이 달라질 수 있다. // 그러나 현재 예제는 중간에 오류가 나므로 이 줄도 실행되지 않는다. function f1() { document.write('f1() 호출<br>'); // 선언적 함수는 함수 본문까지 미리 준비된다. } var f2 = function (p1, p2) { document.write('f2() 호출-' + (p1 + p2) + '<br>'); // 익명 함수는 이 대입문이 실행된 뒤에야 f2에 함수가 들어간다. };
코드 흐름이 예제는 그냥 위에서 아래로만 보면 이해가 잘 안 된다.
그래서 스크립트가 준비되는 흐름을 먼저 따라가야 한다.브라우저는
<script>를 읽을 때 대략 이렇게 준비한다.
- 전역 영역에서 선언된 것들을 먼저 확인한다.
- 선언적 함수
f1은 함수 본문까지 미리 준비한다.var f2는 변수 이름만 먼저 준비한다.- 이 시점의
f2값은 아직 함수가 아니라undefined다.- 그다음 위에서 아래로 실제 실행을 시작한다.
f1();은 이미 준비된 함수라서 정상 호출된다.f2(10, 20);은 아직undefined상태인데 함수처럼 호출하려 하므로 오류가 난다.즉,
exam10_2는
선언적 함수는 미리 준비되지만, 익명 함수는 변수에 대입되기 전까지 함수가 아니라는 점을 보여 주는 예제다.여기서는
f2(10, 20);에서 실제로 오류가 발생하므로
스크립트 실행이 그 자리에서 끊긴다.
즉, 뒤쪽 코드는 예외 처리를 하지 않으면 실행되지 않는다.
예제:
exam10_3var f2 = function (p1, p2) { document.write('f2() 호출-' + (p1 + p2) + '<br>'); // 익명 함수를 f2 변수에 먼저 저장해 둔다. // 이제부터는 f2를 함수처럼 호출할 수 있다. }; f1(); // 선언적 함수 f1은 아래에 있어도 미리 준비되므로 정상 호출된다. f2(10, 20); // f2에는 이미 함수가 저장되어 있으므로 정상 호출된다. // p1=10, p2=20이 전달되어 30이 출력된다. document.write('<hr>'); // 구분선 출력 var result1 = f1(); // f1()은 화면에만 출력하고 return이 없으므로 result1에는 undefined가 들어간다. var result2 = f2(100, 200); // f2()도 화면에만 출력하고 return이 없으므로 result2에도 undefined가 들어간다. // 화면에는 300이 출력된다. document.write('result1 : ' + result1 + ' result2 : ' + result2); // 두 변수에 저장된 값을 출력 → 둘 다 undefined if (result1 == undefined) { document.write('<br>리턴값이 없군요!!'); // f1()이 값을 돌려주지 않았으므로 이 문장이 출력된다. } document.write('<hr>'); // 다시 구분선 출력 f1(100); // f1은 매개변수가 없으므로 100을 넘겨도 사용하지 않고 그냥 실행된다. f2(100); // p1에는 100이 들어가고 p2에는 undefined가 들어간다. // 그래서 100 + undefined가 되어 결과는 NaN이 출력된다. document.write(typeof f1 + '<br>'); document.write(typeof f2 + '<br>'); // f1과 f2에 들어 있는 값의 타입을 출력한다. // 둘 다 function이므로 함수로 사용할 수 있다는 점을 확인할 수 있다. function f1() { document.write('f1() 호출<br>'); // 선언적 함수는 함수 본문까지 미리 준비된다. }코드 흐름
이 예제는
f2에 함수 값을 먼저 넣어 둔 뒤 호출한다.
그래서 이번에는f2()도 정상적으로 실행된다.그리고
f1()도 정상 호출된다.
왜냐하면f1은 선언적 함수이므로 아래에 있어도 이미 미리 준비되어 있기 때문이다.즉,
exam10_3는
f1은 선언적 함수라서 먼저 호출 가능f2는 익명 함수지만 이미 대입이 끝난 뒤 호출했으므로 사용 가능이 두 흐름이 함께 보이는 예제다.
헷갈리기 쉬운 부분
호이스팅을 “모든 것이 다 위로 올라간다”라고 외우면 오히려 헷갈린다.지금 단계에서는 이렇게만 정확히 잡으면 된다.
- 선언적 함수 → 함수 본문까지 미리 준비
var에 담긴 익명 함수 → 변수 이름만 먼저 준비, 함수 값은 나중에 대입
5. 함수 호출과 undefined
리턴값이 없을 때함수는 실행만 하고 끝날 수도 있고, 실행한 결과를 호출한 곳으로 돌려줄 수도 있다.
이때 사용하는 키워드가
return이다.function add(a, b) { return a + b; }이 함수는 계산 결과를 돌려준다.
하지만
return이 없으면
함수는 호출 결과로 특별한 값을 직접 돌려주지 않는다.
이때JavaScript에서는 자동으로undefined가 돌아온다.
예를 들어f1()과f2()는 화면에 출력만 할 뿐,
return으로 어떤 값도 돌려주지 않는다.그래서
var result1 = f1(); var result2 = f2(100, 200);이렇게 쓰면 둘 다
undefined가 들어간다.즉,
화면에 출력하는 것과 값을 돌려주는 것은 다른 일이다.
값을 덜 넘겼을 때
JavaScript는 함수 호출 시 전달한 인수 개수와
함수에 선언된 매개변수 개수가 꼭 같아야만 하는 언어는 아니다.그래서 값이 부족하면 못 받은 매개변수 자리에
undefined가 들어간다.
예를 들어function f2(p1, p2) {} f2(100);이렇게 호출하면
p1→100p2→undefined가 된다.
그래서 함수 안에서는 이런 계산이 일어난다.
100 + undefined이 결과는
NaN이다.즉,
값이 부족하다고 바로 에러가 나는 것이 아니라,
부족한 자리에undefined가 들어가고 그 뒤 계산 결과가 이상해질 수 있다고 이해하면 된다.
값을 더 넘겼을 때반대로 값이 더 많이 들어오면,
그 값들은 함수 안에서 따로 사용하지 않는 한 그냥 넘어간다.예를 들어 매개변수가 없는
f1()에100을 넘겨도
f1은 그 값을 전혀 사용하지 않으므로 무시된다.즉,
- 덜 넘기면 → 못 받은 자리에
undefined- 더 넘기면 → 남는 값은 사용하지 않으면 넘어감
이라고 이해하면 된다.
코드 예제예제:
exam10,exam10_1var result1 = f1(); var result2 = f2(100, 200); document.write('result1 : ' + result1 + ' result2 : ' + result2); if (result1 == undefined) { document.write('<br>리턴값이 없군요!!'); } document.write('<hr>'); f1(100); f2(100);
코드 해설
f1()은 호출되면 문장을 출력하지만return은 없다.
그래서result1에는undefined가 들어간다.
f2()도 마찬가지다.
p1 + p2를 계산해서 출력은 하지만,
그 결과를return으로 돌려주지는 않는다.그래서
result2도undefined다.그리고
f2(100)은 값을 하나만 준 경우다.
그래서p2가undefined가 되고, 결과는NaN이 된다.
헷갈리기 쉬운 부분많은 초보자가
document.write()로 어떤 값이 보이면 그게 곧 함수 결과라고 생각한다.하지만 함수 결과를 바깥으로 주려면 반드시
return이 있어야 한다.
없으면undefined다.
6. 매개변수와 인수
개념함수를 만들 때 괄호 안에 적는 이름을 매개변수(
parameter) 라고 한다.
그리고 함수를 실제로 호출하면서 넘기는 값을 인수(argument) 라고 한다.예를 들어
function f2(p1, p2) {}여기서
p1,p2는 매개변수다.
그리고f2(10, 20);여기서
10,20은 인수다.즉,
- 함수 정의 쪽 → 매개변수
- 함수 호출 쪽 → 인수
라고 보면 된다.
코드 예제예제:
exam10function f2(p1, p2) { document.write('f2() 호출-' + (p1 + p2) + '<br>'); } f2(10, 20);
코드 해설이 코드에서
f2는 값을 두 개 받도록 만들어져 있다.그래서
f2(10, 20)이 실행되면
p1에10p2에20이 들어간다.
그다음
(p1 + p2)를 계산하므로30이 된다.즉, 매개변수는 함수 안에서 사용할 이름이고,
인수는 그 자리에 실제로 들어오는 값이다.
헷갈리기 쉬운 부분매개변수와 인수를 같은 말처럼 쓰기 쉽지만,
엄밀히 보면 위치가 다르다.
p1,p2→ 함수 정의할 때의 이름10,20→ 호출할 때 넘기는 실제 값
7. arguments
개념
arguments는 함수에 실제로 전달된 인수들을 모아 두는 내장 객체다.함수를 만들 때 매개변수를 두 개만 써 놓았더라도, 호출할 때 세 개, 네 개가 들어올 수 있다.
이때 전달된 값들을 전체로 확인하고 싶으면arguments를 사용할 수 있다.
이 부분부터는 기본 함수 예제를 바탕으로
한 단계 더 확장되는 내용이라고 보면 된다.
왜 필요한가매개변수 이름만으로는 선언한 개수까지만 보기 쉽다.
하지만 실제로는 몇 개가 들어올지 모르는 경우도 있다.그럴 때
arguments를 사용하면
“실제로 전달된 값 전체”를 확인할 수 있다.
arguments.length와 함수이름.length여기서 같이 봐야 할 것은 두 개다.
arguments.length→ 실제로 전달된 인수 개수함수이름.length→ 함수 정의에 적어 둔 매개변수 개수즉,
arguments는 실제로 들어온 값 기준,
함수이름.length는 처음 정의한 자리 개수 기준이라고 보면 된다.
코드 예시function printAll() { document.write('실제로 전달된 인수 개수 : ' + arguments.length + '<br>'); document.write('함수에 선언된 매개변수 개수 : ' + printAll.length + '<br>'); for (let i = 0; i < arguments.length; i++) { document.write(arguments[i] + '<br>'); } } printAll('둘리', 20, true);
코드 해설이 함수는 매개변수를 아예 적지 않았지만,
호출할 때 들어온 값 세 개를 모두 확인할 수 있다.즉,
arguments는
함수 정의에 적어 둔 이름보다
실제로 전달된 값 목록에 더 가깝다.
헷갈리기 쉬운 부분
arguments는 배열처럼 보이지만
완전히 같은 배열은 아니다.지금 단계에서는
“전달된 인수들을 모아 둔 유사 배열 객체” 정도로 이해하면 충분하다.
8. 함수의 다양한 활용
함수를 값처럼 다루는 방식
JavaScript에서는 함수도 하나의 값이다.
그래서 변수에 담을 수 있고, 다른 함수에 넘길 수도 있고, 함수가 함수를 돌려줄 수도 있다.이 점이
JavaScript함수의 중요한 특징이다.
콜백 함수다른 함수에 인수로 넘기는 함수를 보통 콜백 함수라고 한다.
즉,
“필요할 때 나중에 실행해 달라고 넘겨 두는 함수”라고 보면 된다.
함수를 리턴하는 흐름함수는 값을 돌려줄 수 있으므로,
그 값 자리에 또 다른 함수가 올 수도 있다.이렇게 되면
- 함수를 인수로 받는 함수
- 함수를 결과로 돌려주는 함수
같은 형태가 가능해진다.
이 부분도 기본 함수 정의를 익힌 뒤에 이어지는
확장 흐름으로 이해하면 된다.
코드 예시function test1(callback) { if (callback != undefined) { callback(3); // callback 자리에 함수가 전달되면 그 함수를 실행하면서 3을 넘긴다. } } test1(function (num) { // 이름 없는 함수를 test1에 전달한다. // test1 안에서 callback(3)이 실행되므로 num에는 3이 들어간다. if (num % 2 == 1) { alert(num + ': Odd'); // num이 홀수이면 Odd를 붙여서 출력한다. } else { alert(num + ': Even'); // num이 짝수이면 Even을 붙여서 출력한다. } }); function test2() { return function (str) { alert(str + '!!'); // 전달받은 문자열 뒤에 !!를 붙여서 출력하는 함수를 반환한다. }; } let result1 = test2(); // test2()를 호출하면 함수 하나가 반환된다. // 그 반환된 함수가 result1에 저장된다. result1('둘리'); // result1 안에 저장된 함수를 호출한다. // str에 '둘리'가 들어가서 '둘리!!'가 출력된다. result1('또치'); // 같은 함수를 다시 호출한다. // str에 '또치'가 들어가서 '또치!!'가 출력된다. test2()('희동이'); // 1. test2()를 호출해서 함수를 하나 반환받고 // 2. 그 반환된 함수를 바로 이어서 호출한다. // 그래서 '희동이!!'가 출력된다.
코드 해설
test1은 함수 하나를 인수로 받는다.
그리고 그 함수를 나중에 실행한다.즉,
callback자리에 들어온 함수가 실제로 실행되는 대상이다.
test2는 실행 결과로 또 다른 함수를 돌려준다.그래서
let result1 = test2();를 실행하면
result1안에는 함수가 들어간다.
그래서 다시result1('둘리');처럼 호출할 수 있다.
마지막의
test2()('희동이');는 이 과정을 한 줄로 쓴 것이다.
test2()실행- 함수 하나를 돌려받음
- 그 함수를 바로 호출
즉, 함수도 값처럼 다룰 수 있다는 점이 여기서 분명하게 보인다.
9. 화살표 함수
문법화살표 함수는 함수를 더 짧게 쓰는 방식이다.
문법은 이렇게 생겼다.
const 함수이름 = (매개변수들) => { // 실행할 코드 };매개변수가 하나면 괄호를 생략할 수도 있고,
본문이 한 줄 표현식이면 중괄호와return을 줄여 쓸 수도 있다.
기본 형태const square = x => x * x; const sum = (x, y) => x + y; const printHello = () => { console.log('Hello, World!'); };이 부분 역시 기본 함수 정의를 익힌 뒤에 이어지는 확장 내용으로 보면 된다.
기존 함수와의 차이초보자 기준에서는 일단 이렇게만 잡으면 된다.
- 선언적 함수 / 익명 함수도 결국 함수다.
- 화살표 함수도 함수다.
- 다만 문법이 더 짧고 간단하다.
즉, 처음에는
“완전히 새로운 기능”으로 보기보다
함수를 더 간단하게 쓰는 방식으로 이해하면 된다.
코드 예시const square = x => x * x; console.log(square(5)); // 25 const sum = (x, y) => x + y; console.log(sum(10, 20)); // 30
헷갈리기 쉬운 부분화살표 함수는 짧아서 편하지만,
처음에는 너무 줄여 쓴 모양 때문에 오히려 낯설 수 있다.그래서 처음에는
- 매개변수가 어디인지
- 화살표 오른쪽이 실행 내용인지
- 값이 바로 리턴되는 형태인지
이 세 가지만 먼저 구분하면 된다.
지금 단계에서는
“짧게 쓴 함수 문법” 정도로 잡고 가면 충분하다.
3. 외부 JavaScript 파일 사용
개념
JavaScript코드를 꼭HTML파일 안에만 직접 써야 하는 것은 아니다.
자주 쓰는 함수나 여러 페이지에서 공통으로 사용할 코드는 별도의.js파일로 분리해서 불러올 수 있다.이 방식은 처음에는 단순히 파일을 나누는 것처럼 보일 수 있다.
하지만 실제로는 공통 코드를 한곳에 모아 두고 재사용하기 위한 방법이라는 점이 더 중요하다.예를 들어 제목을 출력하는 함수, 색상을 넣어 출력하는 함수, 줄을 그어 주는 함수처럼
여러 페이지에서 반복해서 쓸 기능이 있다면, 매번 같은 코드를 다시 쓰는 것보다
외부 파일에 한 번 정리해 두고 필요할 때 불러오는 편이 훨씬 깔끔하다.즉, 외부
JavaScript파일 사용은
코드를 정리하고, 중복을 줄이고, 재사용하기 쉽게 만드는 방식이라고 보면 된다.
핵심 특징외부
JavaScript파일을 사용할 때는script태그의src속성을 사용한다.<script src="파일명.js"></script>이렇게 작성하면 브라우저는 먼저 해당
JavaScript파일을 읽는다.
그 안에 정의된 함수나 변수는, 그 아래에서 이어지는script태그 안에서 사용할 수 있다.이번 예제에서는
util.js를 먼저 불러온 뒤,
아래script에서write,writeColor,hr,writeNewLine같은 함수를 호출하고 있다.즉, 이 예제의 핵심은 두 가지다.
- 공통 기능을
util.js로 분리했다.- 분리한 뒤 현재 페이지에서 다시 가져와서 사용했다.
여기서 중요한 순서도 있다.
외부 파일을 먼저 불러와야 그 안에 있는 함수를 아래쪽 코드에서 사용할 수 있다.즉,
script src="util.js"→ 함수 불러오기- 아래
script→ 불러온 함수 사용하기이 흐름으로 이해하면 된다.
코드 예제예제:
exam11<script src="util.js"></script> <script> write("테스트1", "h1"); write("테스트2", "h2"); write("테스트3", "h3"); hr(); writeColor("테스트칼라1", "h1", "red"); writeColor("테스트칼라2", "h2", "green"); writeColor("테스트칼라3", "h3", "blue"); hr(); writeNewLine("ㅋㅋㅋ"); writeNewLine("ㅎㅎㅎ"); writeNewLine("ㅍㅍㅍ"); // 출력결과 // 테스트1, 테스트2, 테스트3이 순서대로 출력된다. // 중간에 가로선이 들어가서 출력 구간이 나뉜다. // 테스트칼라1, 테스트칼라2, 테스트칼라3은 색상을 적용한 형태로 출력된다. // ㅋㅋㅋ, ㅎㅎㅎ, ㅍㅍㅍ은 줄바꿈되어 출력된다. </script>
코드 해설이 예제는 외부 파일에 있는 공통 함수를 현재 페이지에서 가져와 사용하는 흐름을 보여 준다.
맨 위의
<script src="util.js"></script>이 부분이 먼저 실행되면서
util.js를 읽어 온다.
그리고 그 아래script에서 그 안에 들어 있는 함수들을 호출한다.이때
write("테스트1", "h1")같은 코드는
문자열과 태그 이름을 전달해서 출력 형태를 정하는 함수로 사용되고 있다고 볼 수 있다.
즉,"테스트1"은 출력할 내용이고,"h1"은 어떤 형태로 보여 줄지를 뜻한다.
writeColor("테스트칼라1", "h1", "red")는
기본 출력에 색상 정보까지 하나 더 전달하는 형태로 사용되고 있다.
즉, 출력 내용, 태그 종류, 색상까지 함께 넘겨서 더 다양한 형태로 출력하고 있다.
hr()는 이름과 사용 위치를 보면
중간 구분선을 출력하는 용도로 쓰인다고 이해할 수 있다.
writeNewLine("ㅋㅋㅋ")는
문자열을 줄바꿈과 함께 출력하는 형태로 사용되고 있다고 볼 수 있다.여기서 정말 중요한 부분은
개별 함수의 내부 구현을 외우는 것이 아니라,
공통 기능을 외부 파일로 빼 두고 현재 페이지에서 다시 가져와 사용했다는 점이다.즉, 지금 이 페이지 안에는 함수 정의가 보이지 않지만,
util.js를 먼저 읽었기 때문에 아래에서 그 함수들을 바로 사용할 수 있는 것이다.
헷갈리기 쉬운 부분초보자가 가장 많이 헷갈리는 부분은
“현재 파일 안에 함수 정의가 안 보이는데 왜 호출이 되지?” 하는 점이다.그 이유는 함수가 없는 것이 아니라,
이미 위에서util.js를 통해 먼저 불러왔기 때문이다.또 하나 중요한 점은 순서다.
만약 아래처럼 함수를 사용하려는 코드가 먼저 오고,
util.js를 나중에 불러오면 문제가 생길 수 있다.그리고
src경로가 틀리면
브라우저가 외부 파일을 읽지 못하므로
아래쪽에서 함수 이름을 써도 찾지 못하게 된다.즉, 외부 파일에 있는 함수를 쓰려면
- 먼저
src로 불러오고- 그 다음에 호출해야 하며
- 경로도 정확해야 한다
는 점을 꼭 기억해야 한다.
지금 단계에서는 이렇게만 잡으면 충분하다.
- 공통 코드는 외부
.js파일로 분리할 수 있다.script src="파일명.js"로 불러온다.- 불러온 뒤에는 현재 페이지 안에서 함수처럼 호출해서 사용할 수 있다.
4. var, let, const와 스코프
1. var, let, const의 차이
개념
JavaScript에서 값을 저장할 때는 변수를 만든다.
이때 자주 쓰는 키워드가var,let,const다.셋 다 값을 담아 두는 이름을 만든다는 점은 같지만,
다시 값을 바꿀 수 있는지, 같은 이름으로 다시 선언할 수 있는지, 어디까지 사용할 수 있는지에서 차이가 난다.초보자 입장에서는 먼저 이렇게 잡으면 된다.
var: 예전부터 쓰던 방식let: 값을 바꿀 수 있는 변수const: 선언할 때 값을 정하고, 나중에 다른 값으로 바꾸지 않는 변수즉, 셋 다 변수처럼 보이지만
아무 상황에서나 똑같이 써도 되는 것은 아니다.
핵심 특징가장 먼저 구분해야 할 것은
재할당과재선언이다.
재할당: 이미 만든 변수에 다른 값을 다시 넣는 것재선언: 같은 이름으로 변수를 한 번 더 선언하는 것이 기준으로 보면:
var는 재할당이 가능하고, 같은 이름으로 재선언도 가능하다.let은 재할당은 가능하지만, 같은 이름으로 재선언은 안 된다.const는 선언과 동시에 값을 정해야 하고, 나중에 다른 값으로 바꿀 수 없다.즉,
const는 “값이 변하지 않게 둘 때”,
let은 “값이 바뀔 수 있지만 같은 이름을 다시 만들지는 않을 때”,
var는 “예전 방식”이라고 생각하면 된다.
코드 예제예제:
exam12var num1 = 100; let num2 = 200; const num3 = 300; // 다른 값으로 다시 바꾸지 않을 변수 writeColor('num1 : ' + num1, 'h1', 'red'); writeColor('num2 : ' + num2, 'h1', 'green'); writeColor('num3 : ' + num3, 'h1', 'blue'); hr(); num1 = '가'; num2 = '나'; // num3 = '다'; // const는 재할당 불가 writeColor('num1 : ' + num1, 'h2', 'red'); writeColor('num2 : ' + num2, 'h2', 'green'); writeColor('num3 : ' + num3, 'h2', 'blue'); hr(); var num1 = 3.5; // let num2 = 4.7; // let은 재선언 불가 // const num3 = 5.3; // const도 재선언 불가 writeColor('num1 : ' + num1, 'h3', 'red'); writeColor('num2 : ' + num2, 'h3', 'green'); writeColor('num3 : ' + num3, 'h3', 'blue'); hr(); function f1(n) { if (n == '올라프') { var name1 = '둘리'; let name2 = '고길동'; writeColor(name1 + ' : ' + name2, 'h1', 'red'); } writeColor(name1, 'h1', 'blue'); // writeColor(name2, 'h1', 'blue'); } f1('올라프'); hr(); writeColor('~~~~끝~~~~', 'h1', '#cc0066');코드 해설
이 예제는 세 키워드의 차이를 직접 보여 준다.
처음에는 숫자를 넣어 두었지만,
num1,num2에는 나중에 문자열을 다시 넣고 있다.
즉,var와let은 값을 다시 바꿀 수 있다는 뜻이다.
반면const인num3는 다시 값을 넣는 줄이 주석 처리되어 있다.
그 줄을 실제로 실행하면 오류가 난다.
즉,const는 한 번 정한 값을 다시 바꿀 수 없다.또 아래쪽에서
var num1 = 3.5;처럼 같은 이름으로 다시 선언하고 있는데,
이건var에서는 가능하다.
하지만let num2 = 4.7;,const num3 = 5.3;는 같은 이름을 또 선언하려는 것이므로 허용되지 않는다.그리고
f1()예제는 변수 종류 차이가 스코프 차이와도 연결된다는 점을 보여 준다.
if문 안에서 만든var name1은 아래쪽에서도 접근할 수 있지만,
let name2는 그 블록 밖에서 사용할 수 없다.
헷갈리기 쉬운 부분초보자가 자주 헷갈리는 부분은
let도 값이 바뀌니까var와 거의 같은 것 아니냐고 생각하는 것이다.하지만 둘은 다르다.
let은 값은 다시 넣을 수 있다.- 하지만 같은 이름으로 다시 선언할 수는 없다.
또
const는 “절대 안 바뀌는 마법 같은 값”처럼 보기보다,
지금 단계에서는 선언할 때 값을 정하고 다른 값을 다시 넣지 않는 변수라고 이해하면 충분하다.
2. 스코프란 무엇인가
개념
스코프는 변수를 사용할 수 있는 범위다.
즉, 어떤 변수가 어디에서 보이고, 어디에서는 안 보이는지를 정하는 기준이라고 보면 된다.변수를 만들었다고 해서 프로그램 어디에서나 다 쓸 수 있는 것은 아니다.
어떤 변수는 함수 안에서만 보이고, 어떤 변수는 블록 밖에서는 안 보인다.그래서
var,let,const를 제대로 이해하려면
단순히 값이 바뀌는지만 볼 것이 아니라 어디까지 살아 있는지도 같이 봐야 한다.
핵심 특징스코프는 크게 이렇게 나눠서 보면 된다.
전역 스코프: 함수 밖, 전체에서 볼 수 있는 범위지역 스코프: 함수 안에서만 볼 수 있는 범위블록 스코프:{ }블록 안에서만 볼 수 있는 범위여기서 중요한 차이가 나온다.
var는 기본적으로 함수 스코프를 따른다.let,const는 블록 스코프를 지원한다.즉,
if,for,{ }안에서 만든 변수라도
var는 블록 밖에서 보일 수 있고,
let은 그 블록을 벗어나면 더 이상 쓸 수 없다.
3. var의 함수 스코프
문법
var로 만든 변수는 보통 함수 기준으로 범위가 정해진다.즉, 함수 안에서 만들었으면 그 함수 안에서 사용할 수 있고,
함수 밖에서 만들었으면 전역에서 사용할 수 있다.중요한 점은
if문이나 단순{ }블록 안에서var를 만들었다고 해서
그 블록 안에만 갇히는 것은 아니라는 점이다.
코드 예제예제:
exam13var g_v = "전역변수"; function myFunc1() { var l_v = "myFunc1()의 지역 변수"; document.writeln("myFunc1() 함수의 스코프 영역"); document.writeln("l_v : " + l_v); document.writeln("g_v : " + g_v); } var myFunc2 = function (p) { var l_v = "myFunc2()의 지역 변수"; document.writeln("myFunc2() 함수의 스코프 영역"); document.writeln("l_v : " + l_v); document.writeln("g_v : " + g_v); if (p) { var l_b_v = "블럭스코프 지역변수"; document.writeln("l_b_v : " + l_b_v); } document.writeln("헉 : " + l_b_v); } myFunc1(); document.writeln(); myFunc2(true); document.writeln(); document.writeln("전역(글로벌) 스코프 영역"); document.writeln("g_v : " + g_v); document.writeln("l_v : " + l_v);이 예제는
var가 함수 기준으로 범위를 가진다는 점을 보여 준다.
g_v는 함수 밖에서 만들었으므로 전역 변수다.
그래서myFunc1(),myFunc2()안에서도 사용할 수 있다.반면
l_v는 각 함수 안에서 만든 변수다.
그래서 그 함수 안에서는 보이지만, 함수 밖에서는 사용할 수 없다.여기서 특히 중요한 줄은 이 부분이다.
if (p) { var l_b_v = "블럭스코프 지역변수"; } document.writeln("헉 : " + l_b_v);처음 보면
l_b_v가if문 안에서 만들어졌으니
밖에서는 못 쓸 것 같아 보인다.
하지만var는 블록 스코프가 아니라 함수 스코프를 따르므로,
같은 함수 안이라면if문 밖에서도 접근된다.즉,
var는
if문 안에 만들었다고 해서 그 블록 안에만 갇히는 것이 아니라는 점이 핵심이다.
헷갈리기 쉬운 부분이 예제에서
l_b_v라는 이름 때문에
마치 진짜 블록 스코프 변수처럼 느껴질 수 있다.
하지만 실제 동작은 그렇지 않다.
var는{ }블록보다 함수 전체 범위를 따른다.또 마지막의
document.writeln("l_v : " + l_v);이 코드는 전역에서
l_v를 찾으려는 것이므로 오류가 난다.
즉, 함수 안에서 만든var는 함수 밖으로 나가면 더 이상 보이지 않는다.
4. let의 블록 스코프
문법
let은var와 다르게 블록 스코프를 지원한다.즉,
if,for,{ }안에서 만든 변수는
그 블록을 벗어나면 더 이상 사용할 수 없다.이 점이
let을 이해할 때 가장 중요하다.
코드 예제예제:
exam14let g_v = "전역변수"; function myFunc1() { let l_v = "myFunc1()의 지역 변수"; document.writeln("myFunc1() 함수의 스코프 영역"); document.writeln("l_v : " + l_v); document.writeln("g_v : " + g_v); } var myFunc2 = function (p) { let l_v = "myFunc2()의 지역 변수"; document.writeln("myFunc2() 함수의 스코프 영역"); document.writeln("l_v : " + l_v); document.writeln("g_v : " + g_v); if (p) { let l_b_v = "블럭스코프 지역변수"; document.writeln("l_b_v : " + l_b_v); } document.writeln("^^ : " + l_b_v); // let은 블록 밖에서 접근할 수 없다. } myFunc1(); document.writeln(); myFunc2(true);이 예제는
let이 블록 스코프를 따른다는 점을 보여 준다.
g_v는 전역에서 만든 변수이므로 함수 안에서도 보인다.
l_v는 각 함수 안에서 만든 지역 변수이므로 함수 안에서만 보인다.그리고 핵심은 이 부분이다.
if (p) { let l_b_v = "블럭스코프 지역변수"; document.writeln("l_b_v : " + l_b_v); } document.writeln("^^ : " + l_b_v);
l_b_v는if블록 안에서let으로 만들었기 때문에
그 블록 안에서는 사용할 수 있다.
하지만 블록을 벗어난 뒤에는 더 이상 사용할 수 없다.그래서 마지막 줄처럼 블록 밖에서 접근하려고 하면 오류가 난다.
즉,
let은
만든 위치의{ }범위를 벗어나면 사용할 수 없다고 이해하면 된다.
헷갈리기 쉬운 부분
let도 함수 안에서 만들 수 있고,if문 안에서도 만들 수 있다.
그래서 처음에는 그냥var와 비슷해 보일 수 있다.하지만 결정적인 차이는
let은 블록 기준으로 더 좁게 범위를 나눈다는 점이다.즉, 같은 함수 안이라고 해도
if문 안에서 만든let변수는 밖에서 못 쓴다.
5. var와 let 비교
비교 기준
var와let은 둘 다 변수처럼 쓰이기 때문에
처음에는 거의 비슷해 보인다.
하지만 실제로는 범위를 나누는 기준이 다르다.비교 기준은 딱 이렇게 잡으면 된다.
var→ 함수 스코프let→ 블록 스코프
차이점 정리같은
if문 안에서 변수를 만든다고 해도:
var는 블록 밖에서도 같은 함수 안이면 접근 가능하다.let은 블록 밖으로 나가면 접근할 수 없다.또 선언 방식도 다르다.
var는 같은 이름으로 재선언 가능let은 같은 이름으로 재선언 불가즉,
let은 더 엄격하고 범위가 더 명확하다.
비교 예제예제
// var는 재선언 가능 var num1 = 100; var num1 = 3.5; // let은 재선언 불가 let num2 = 200; // let num2 = 4.7; // 오류 if (true) { var a = 10; let b = 20; } console.log(a); // 가능 // console.log(b); // 오류이 비교는 실제 예제들의 핵심만 한 번에 모아 본 것이다.
a는var로 만들었기 때문에 블록 밖에서도 보일 수 있다.
하지만b는let으로 만들었으므로 블록을 벗어나면 더 이상 사용할 수 없다.즉,
var와let의 가장 큰 차이는
변수가 살아 있는 범위를 어디까지로 보느냐에 있다고 보면 된다.
헷갈리기 쉬운 부분초보자는 종종
var도 되는데let은 왜 안 되지?
let도 변수인데 왜 블록 밖에서 못 쓰지?
라고 느낀다.그 이유는 문법이 이상한 것이 아니라,
두 키워드가 범위를 다르게 관리하기 때문이다.지금 단계에서는 이렇게 정리하면 충분하다.
var는 예전 방식이고 함수 범위로 움직인다.let은 더 명확하게 블록 범위로 움직인다.const는 값을 다시 넣지 않을 변수에 쓴다.
5. 배열
1. 배열이란 무엇인가
개념
배열은 값 여러 개를 한 번에 묶어서 저장하는 자료형이다.
지금까지는 변수 하나에 값 하나를 넣는 흐름을 많이 봤다.예를 들어
let num = 10; // 값 하나 저장이 코드는 값 하나만 저장한다.
그런데 어떤 값을 여러 개 함께 다뤄야 할 때도 많다.
예를 들어 학생 점수 여러 개, 과일 이름 여러 개, 숫자 목록 여러 개처럼
관련 있는 값들을 한 묶음으로 관리하고 싶을 수 있다.이럴 때 사용하는 것이 배열이다.
즉, 배열은
값 하나가 아니라 값 목록 전체를 저장하는 구조라고 보면 된다.
왜 필요한가배열이 없으면 값 여러 개를 다루기 위해
num1num2num3처럼 변수를 계속 따로 만들어야 한다.
이 방식은 값 개수가 적을 때는 괜찮아 보일 수 있다.
하지만 개수가 많아지면 관리하기가 금방 불편해진다.반면 배열을 사용하면 여러 값을 하나로 묶어 두고,
필요할 때 순서대로 꺼내서 처리할 수 있다.즉, 배열은
여러 값을 정리해서 저장하고 다루기 쉽게 만드는 도구다.
핵심 특징
- 배열은 값 여러 개를 저장할 수 있다.
- 각 값은 순서 번호를 가진다.
- 그 순서 번호로 값을 꺼낼 수 있다.
- 배열에는 숫자뿐 아니라 문자열 같은 다른 값도 들어갈 수 있다.
length로 배열 길이를 확인할 수 있다.즉, 배열은
“값이 여러 개 들어 있는 목록”이라고 이해하면 된다.
2. 배열 생성 방법
배열 리터럴배열을 만드는 가장 쉬운 방법은 대괄호
[]를 사용하는 것이다.var ary1 = []; // 빈 배열 var ary5 = [10]; // 값 하나가 들어 있는 배열 var ary6 = [10, 20, 30]; // 값 세 개가 들어 있는 배열이 방식은 짧고 읽기 쉬워서 가장 많이 쓴다.
[]→ 빈 배열[10]→ 값이 하나 들어 있는 배열[10, 20, 30]→ 값 세 개가 들어 있는 배열
new Array()배열은
Array생성자로도 만들 수 있다.var ary2 = new Array(); // 빈 배열 var ary3 = new Array(10); // 길이가 10인 배열 var ary4 = new Array(10, 20, 30); // 값 10, 20, 30이 들어 있는 배열여기서 주의할 점이 있다.
new Array()→ 빈 배열new Array(10)→ 값이 10개 들어 있는 배열이 아니라, 길이가 10인 배열new Array(10, 20, 30)→ 값 10, 20, 30이 들어 있는 배열즉,
new Array(10)은
[10]과 전혀 다르다.
[10]→ 값 하나new Array(10)→ 길이만 10이 차이를 꼭 구분해야 한다.
length
length는 배열의 길이를 보여 준다.document.writeln("ary6.length - " + ary6.length); // 배열 길이 확인즉, 배열을 만들고 나면
length로 현재 몇 칸을 가지고 있는지 확인할 수 있다.
코드 예제예제:
exam15var ary1 = []; // 빈 배열 var ary2 = new Array(); // 빈 배열 var ary3 = new Array(10); // 길이가 10인 배열 var ary4 = new Array(10, 20, 30); // 값 3개 저장 var ary5 = [10]; // 값 1개 저장 var ary6 = [10, 20, 30]; // 값 3개 저장 document.writeln("ary1.length - " + ary1.length); // 0 document.writeln("ary2.length - " + ary2.length); // 0 document.writeln("ary3.length - " + ary3.length); // 10 document.writeln("ary4.length - " + ary4.length); // 3 document.writeln("ary5.length - " + ary5.length); // 1 document.writeln("ary6.length - " + ary6.length); // 3 document.writeln(ary4); // 10,20,30이 예제는 배열을 만드는 여러 방법과
length차이를 한 번에 보여 준다.특히 눈여겨볼 부분은 이 두 줄이다.
var ary3 = new Array(10); // 길이가 10인 배열 var ary5 = [10]; // 값 10 하나가 들어 있는 배열겉보기에는 둘 다
10하나만 보이기 때문에 비슷해 보인다.
하지만 실제로는 완전히 다르다.
new Array(10)→ 길이가 10인 배열[10]→ 값 10 하나가 들어 있는 배열즉, 배열 생성 방식에 따라 결과가 달라질 수 있다는 점을 꼭 봐야 한다.
헷갈리기 쉬운 부분초보자가 가장 많이 헷갈리는 부분은
new Array(10)을 보고[10]과 같은 의미라고 생각하는 것이다.하지만 둘은 다르다.
[10]은 값 하나가 들어 있는 배열new Array(10)은 길이만 10인 배열즉, 대괄호 방식과
new Array()방식은
겉보기보다 동작 차이가 있을 수 있다는 점을 기억해야 한다.
3. 배열과 for...in, for...of
for...in
for...in은 객체나 배열을 반복해서 확인할 때 사용하는 문법이다.
하지만 가장 먼저 알아야 할 것은,for...in이 값 자체를 바로 꺼내는 반복문은 아니라는 점이다.
for...in이 꺼내는 것은 보통 이름표다.
- 객체에서는
key- 배열에서는
index즉, 객체를 돌리면
name,age같은 속성 이름이 나오고,
배열을 돌리면0,1,2같은 순서 번호가 나온다.
문법은 이렇게 생겼다.for (let 변수 in 객체또는배열) { // 반복할 코드 }여기서
변수자리에 들어가는 값은
객체라면 속성 이름, 배열이라면 인덱스라고 보면 된다.
for...in의 핵심 특징
for...in은 이름표를 먼저 꺼내고, 그 이름표로 값을 다시 찾는 흐름이다.그래서 객체와 잘 어울린다.
객체는 원래속성명 : 값형태로 정보를 묶어 두기 때문에,
속성 이름을 하나씩 꺼내서 확인하는 방식이 자연스럽다.배열에도 사용할 수는 있다.
하지만 배열에서는 실제 값이 아니라0,1,2같은 인덱스가 먼저 나오므로,
초반에는 배열 값 반복문으로 익히기보다 객체의 속성을 확인하는 문법으로 이해하는 것이 더 안전하다.
for...in 코드 예제예제:
ControlFlowForInconst student = { name: "둘리", age: 20, major: "Frontend", }; for (let key in student) { console.log(key); // 속성 이름 출력 console.log(student[key]); // key로 실제 값 찾기 } // 출력결과 // name // 둘리 // age // 20 // major // Frontend이 코드는 객체 안에 들어 있는 정보를 차례대로 확인하는 예제다.
여기서
key에는name,age,major같은 문자열이 들어간다.
그래서student[key]라고 써야 실제 값을 꺼낼 수 있다.
student.key라고 쓰면
문자 그대로key라는 이름의 속성을 찾게 된다.
지금은 변수 안에 들어 있는 값을 써야 하므로student[key]형태가 맞다.
for...in에서 헷갈리기 쉬운 부분초보자가 가장 많이 헷갈리는 부분은
for...in이 값을 꺼낸다고 생각하는 것이다.예를 들어 배열에서 이렇게 쓰면,
const fruits = ["사과", "바나나", "포도"]; for (let x in fruits) { console.log(x); // 인덱스 출력 } // 출력결과 // 0 // 1 // 2많은 사람이
"사과","바나나","포도"가 나올 것 같다고 생각한다.
하지만 실제로는 배열의 값이 아니라 인덱스가 나온다.즉,
for...in은
값 반복문이라기보다 이름표 반복문이라고 이해해야 한다.
for...of
for...of는 반복 가능한 값에서 실제 데이터를 하나씩 꺼내는 문법이다.
문법은 이렇게 생겼다.for (let 변수 of 반복가능한값) { // 반복할 코드 }여기서 중요한 것은
for...in과 다르게
for...of는 이름표가 아니라 값 자체를 꺼낸다는 점이다.즉,
- 배열이면 배열의 요소
- 문자열이면 한 글자씩
Set이면 저장된 값이렇게 실제 데이터가 바로 나온다.
for...of의 핵심 특징
for...of는 배열을 돌릴 때 특히 읽기 쉽다.
왜냐하면0,1,2같은 인덱스를 거치지 않고
바로"사과","바나나","포도"같은 값이 나오기 때문이다.처음 배우는 입장에서는
“배열의 값을 하나씩 꺼내고 싶다”라고 생각될 때
for...of를 먼저 떠올리는 것이 더 이해하기 쉽다.
for...of 코드 예제예제:
ControlFlowForOfconst fruits = ["사과", "바나나", "포도"]; for (let fruit of fruits) { console.log(fruit); // 값을 바로 출력 } // 출력결과 // 사과 // 바나나 // 포도이 코드는 배열 안에 들어 있는 값을 하나씩 바로 꺼내서 출력한다.
여기서
fruit에는 첫 번째 반복에서"사과",
두 번째 반복에서"바나나",
세 번째 반복에서"포도"가 들어간다.즉, 인덱스를 따로 꺼내서 다시 찾는 과정이 없다.
그래서 초보자가 읽을 때 더 직관적이다.
for...of에서 헷갈리기 쉬운 부분
for...of안의 변수는 값 자체다.
그래서 배열에서 돌릴 때fruit에는 이미"사과"같은 값이 들어 있다.이 상태에서 다시
fruits[fruit]처럼 쓰면 이상해질 수 있다.
왜냐하면fruit는 숫자 인덱스가 아니라 실제 값이기 때문이다.그리고
for...of는 배열이나 문자열처럼
반복 가능한 값에서 사용하는 문법이다.
그래서 일반 객체에 바로 쓰면 안 된다.const student = { name: "둘리", age: 20, }; for (let value of student) { console.log(value); }이 코드는 정상적으로 동작하지 않는다.
일반 객체는for...of로 바로 반복할 수 있는 대상이 아니기 때문이다.즉,
for...in→ 객체의 속성 이름이나 배열의 인덱스for...of→ 배열이나 문자열의 실제 값이 차이를 놓치면 뒤에서 계속 헷갈리게 된다.
for...in과 for...of의 차이둘은 모양이 비슷해서 처음 보면 거의 같은 문법처럼 느껴진다.
하지만 실제로는 무엇을 꺼내느냐가 다르다.비교 기준은 딱 하나로 잡으면 된다.
for...in은 이름표for...of는 실제 값이 한 줄만 정확하게 잡히면 대부분의 헷갈림이 정리된다.
차이점 정리배열을 예로 들면,
for...in은0,1,2for...of는"사과","바나나","포도"이렇게 나온다.
즉, 같은 배열을 돌려도
하나는 몇 번째 자리인지를 꺼내고,
다른 하나는 그 자리에 들어 있는 실제 내용을 꺼낸다.그래서 객체에서는
for...in이 자연스럽고,
배열의 값을 바로 쓰고 싶을 때는for...of가 더 직관적이다.
비교 예제예제:
ForInVsForOfconst colors = ["red", "green", "blue"]; for (let index in colors) { console.log(index, colors[index]); // 인덱스와 값 출력 } console.log("-----"); for (let color of colors) { console.log(color); // 값만 바로 출력 } // 출력결과 // 0 red // 1 green // 2 blue // ----- // red // green // blue이 예제는 둘의 차이를 가장 분명하게 보여 준다.
첫 번째 반복문은
먼저index를 꺼내고, 그다음colors[index]로 값을 찾는다.두 번째 반복문은
처음부터color에 값이 들어 있다.즉,
for...in은 한 번 더 찾아가야 하고,
for...of는 바로 쓸 수 있다.
차이에서 헷갈리기 쉬운 부분둘 중 무엇이 더 좋다고 단정하면 안 된다.
중요한 것은 상황에 맞게 고르는 것이다.초보자 기준에서는 이렇게 정리하면 된다.
- 객체의 속성을 확인할 때 →
for...in- 배열이나 문자열의 값을 꺼낼 때 →
for...of
코드 예제예제:
exam15_1var ary = [10, 20, 30]; for (let i = 0; i < ary.length; i++) writeColor(ary[i], "h3", "blue"); // 인덱스로 값 꺼내 출력 hr(); for (let i in ary) writeColor(ary[i], "h3", "red"); // 인덱스를 꺼낸 뒤 값 찾기 hr(); for (let e of ary) writeColor(e, "h3", "green"); // 값을 바로 꺼내 출력이 예제는 앞에서 정리한
for...in,for...of가 배열에서 실제로 어떻게 쓰이는지를 보여 준다.기본
for문은 인덱스를 직접 증가시키면서ary[i]로 값을 꺼낸다.
for...in은 인덱스를 꺼낸 뒤ary[i]로 실제 값을 찾는다.
for...of는 값 자체를 바로 꺼낸다.즉, 이 예제는
배열에서 세 방식이 어떻게 다르게 보이는지를 한 번에 확인하게 해 준다.
코드 해설여기서는
for...in,for...of문법을 새로 배우는 것이 아니라,
이미 정리한 반복문 문법이 배열에 적용될 때 어떻게 보이는지를 연결해서 보면 된다.배열에서는 보통 이렇게 이해하면 된다.
- 인덱스를 직접 써야 하면
for...in- 값을 바로 꺼내고 싶으면
for...of즉, 배열 파트에서는
문법 자체보다 배열에 적용했을 때 무엇이 꺼내지는지를 보는 것이 더 중요하다.
4. 배열 객체의 주요 메서드
sort()
sort()는 배열 안의 값을 정렬할 때 사용한다.문자열 배열에서는 비교적 직관적으로 정렬되지만,
숫자 배열에서는 그냥sort()만 쓰면 기대한 숫자 크기순이 아닐 수 있다.그래서 숫자 정렬에서는 비교 함수를 함께 넣는 경우가 많다.
push()
push()는 배열 맨 뒤에 값을 추가하는 메서드다.즉, 기존 배열 끝에 새로운 값을 하나씩 붙여 나갈 때 사용한다.
length 변화배열은 값이 추가되거나 중간에 큰 인덱스에 값을 넣으면
length도 함께 달라진다.즉, 배열 길이는 처음 정한 뒤 항상 고정되는 것이 아니라
상황에 따라 변할 수 있다.
코드 예제예제:
exam16var ary1 = ['둘리', '또치', '도우너', '희동이', '고길동']; document.write(ary1.length + "<br>"); // 길이 출력 document.write(ary1 + "<br>"); // 배열 내용 출력 var ary2 = ary1.sort(); // 문자열 정렬 document.write(ary1 + "<br>"); document.write(ary2 + "<br>"); document.write("<hr>"); var ary3 = [30, 11, 5, 27, 9]; document.write(ary3 + "<br>"); document.write(ary3.sort() + "<br>"); // 기본 정렬 document.write(ary3.sort(function(a, b){ return a - b;}) + "<br>"); // 오름차순 document.write(ary3.sort(function(a, b){ return b - a;}) + "<br>"); // 내림차순 document.write("<hr>"); var ary5 = []; ary5.push(100); // 값 추가 ary5.push(200); // 값 추가 ary5.push(500); // 값 추가 document.write(ary5 + "<br>"); ary5[7] = 1000; // 큰 인덱스에 값 대입 document.write(ary5 + "<br>"); document.write(ary5.length + "<br>"); // 길이 변화 확인이 예제는 배열 메서드와 길이 변화를 한 번에 보여 준다.
먼저 문자열 배열
ary1에서는sort()를 사용해 정렬하고 있다.
이 경우에는 이름 순서처럼 정렬된 결과를 바로 확인할 수 있다.그다음 숫자 배열
ary3에서는 중요한 차이가 나온다.document.write(ary3.sort() + "<br>"); document.write(ary3.sort(function(a, b){ return a - b;}) + "<br>"); document.write(ary3.sort(function(a, b){ return b - a;}) + "<br>");그냥
sort()만 쓰면 숫자가 기대한 크기순으로 정렬된다고 보기 어렵다.
그래서 비교 함수를 넣어서
a - b→ 오름차순b - a→ 내림차순형태로 정렬하고 있다.
마지막의
ary5는push()와length변화를 보여 준다.ary5.push(100); ary5.push(200); ary5.push(500);이렇게 하면 배열 끝에 값이 하나씩 붙는다.
그리고
ary5[7] = 1000;처럼 큰 인덱스에 값을 넣으면,
중간이 비어 있더라도 배열 길이는 그 위치에 맞춰 커진다.즉, 배열은 값 추가 방식에 따라
length가 달라질 수 있다는 점을 보여 준다.
헷갈리기 쉬운 부분초보자가 가장 많이 헷갈리는 부분은 숫자 배열 정렬이다.
ary3.sort()이 한 줄만 보고 “숫자가 자동으로 크기순 정렬되겠지”라고 생각하기 쉽다.
하지만 숫자 배열은 비교 함수를 같이 써야 원하는 정렬 결과에 가까워진다.또
ary5[7] = 1000;처럼 중간을 건너뛰고 값을 넣으면
배열 길이도 함께 커진다.
즉, 배열은 중간이 비어 있어도 마지막 인덱스 기준으로 길이가 달라질 수 있다는 점을 기억해야 한다.
filter()
filter()는 배열에서 조건에 맞는 요소만 골라서 새 배열로 만드는 메서드다.
배열을 처음부터 끝까지 하나씩 확인하면서, 조건식 결과가true인 요소만 결과 배열에 남긴다.
즉, 원본 배열을 직접 잘라내는 것이 아니라 조건을 통과한 요소들만 따로 모은 새 배열을 만든다고 이해하면 된다.예를 들어 사람 정보가 들어 있는 배열에서
점수가 80점을 넘는 사람만 다시 뽑고 싶을 수 있다.
이럴 때filter()를 사용하면 조건에 맞는 사람만 골라낼 수 있다.
map()
map()은 배열의 각 요소를 다른 형태로 바꿔서 새 배열로 만드는 메서드다.
배열을 한 칸씩 돌면서 각 요소를 변환하고, 그 변환 결과를 같은 순서대로 새 배열에 담는다.
즉, 일부만 골라내는 것이 아니라 각 요소 전체를 새로운 모습으로 바꾸는 것에 가깝다.예를 들어 사람 객체 배열이 있을 때
객체 자체를 그대로 쓰는 대신"이름(점수)"같은 문자열로 바꿔서 보고 싶을 수 있다.
즉,둘리객체 자체를 그대로 쓰는 대신"둘리(88)"처럼 보기 쉬운 문자열로 바꿔 담는 것이다.그리고 여기서 중요한 점은
원본 배열 자체를 바꾸는 것이 아니라, 바뀐 결과를 새 배열로 따로 만든다는 점이다.
filter()가 조건에 맞는 일부만 골라내는 쪽이라면,
map()은 보통 전체를 같은 순서로 바꿔서 새 배열을 만드는 쪽에 가깝다.
reduce()
reduce()는 배열의 여러 값을 차례대로 모아서 마지막에 하나의 결과값으로 만드는 메서드다.
앞 계산 결과를 다음 계산에 계속 넘겨 주면서, 마지막에 결과 하나만 남기는 방식이라고 생각하면 된다.
그래서 전체 합계, 최대값, 평균을 구하기 위한 총합처럼 여러 값을 하나로 모을 때 자주 사용한다.예를 들어
[10, 20, 30]이 있으면
처음값0에서 시작해서
0 + 10 = 1010 + 20 = 3030 + 30 = 60이런 식으로 앞의 계산 결과를 다음 계산에 계속 넘겨 주면서 마지막 결과 하나를 만든다.
forEach()
forEach()는 배열을 처음부터 끝까지 돌면서 각 요소를 하나씩 처리할 때 사용하는 메서드다.
map()처럼 새 배열을 만드는 것도 아니고,reduce()처럼 하나의 값으로 모으는 것도 아니다.
그 대신 배열을 순서대로 돌면서 출력하거나, 로그를 남기거나, 각 요소를 차례대로 확인하는 식의 반복 작업을 실행할 때 잘 어울린다.즉,
forEach()는
“배열을 돌면서 하나씩 처리한다”는 점에서는for문과 비슷하지만,
배열을 대상으로 반복할 때 인덱스를 직접 관리하지 않아도 되어 코드를 더 간단하게 쓸 수 있다는 점이 특징이다.그리고
forEach()는 보통 새 값을 따로 만들어 저장하기보다,
배열 요소마다 필요한 작업을 바로 실행하는 쪽에 가깝다.
코드 예제예제:
exam30_2,exam30_3function Person(name, point, city) { this.name = name; this.point = point; this.city = city; } Person.prototype.toString = function () { return this.name; }; const persons = [ new Person("둘리", 88, "서울"), new Person("또치", 92, "서울"), new Person("도우너", 76, "제주"), new Person("고길동", 78, "서울") ]; const pass = persons.filter(function (person) { return person.point > 80; }); // persons 배열을 앞에서부터 하나씩 확인한다. // 각 요소가 person 자리에 들어온다. // point가 80보다 크면 true이므로 결과 배열에 남는다. // 조건을 통과한 둘리, 또치만 pass에 저장된다. const plist = persons.map(function (p) { return `${p.name}(${p.point})`; }); // 각 Person 객체를 "이름(점수)" 형태의 문자열로 바꾼다. // 예를 들어 둘리 객체는 "둘리(88)" 문자열로 바뀐다. // 원본 배열 persons는 그대로 두고, 변환된 결과만 새 배열 plist에 저장한다. const points = [40, 100, 1, 5, 25, 10]; const sum = points.reduce(function (total, currentValue) { return total + currentValue; }, 0); // total은 지금까지 계산된 중간 결과다. // currentValue는 이번 차례에 새로 더할 값이다. // 처음에는 0에서 시작하고, 배열 값을 하나씩 더해 가며 최종 합계를 만든다. points.forEach(function (e, i) { writeColor((i + 1) + "번 데이터 " + e + "!!", "h3", "magenta"); }); // e는 현재 요소 값이다. // i는 현재 인덱스다. // 배열을 처음부터 끝까지 돌면서 각 값을 순서와 함께 출력한다. // forEach()는 새 배열을 만드는 것이 아니라, 각 요소마다 작업을 바로 실행한다. // 출력결과 // filter() 결과 : 둘리, 또치 // map() 결과 : 둘리(88), 또치(92), 도우너(76), 고길동(78) // reduce() 결과 : 181 // forEach() 결과 : 1번 데이터 40!! ~ 6번 데이터 10!! 형태로 순서대로 출력이 코드는 배열 메서드 네 가지가 어떻게 다른 역할을 하는지 한 번에 보여 준다.
모두 배열을 한 칸씩 확인한다는 공통점은 있지만, 무엇을 결과로 남기느냐가 다르다.
즉, 배열을 도는 과정은 비슷하지만 마지막에 만들어지는 결과물의 모양이 서로 다르다.
filter()→ 조건에 맞는 요소만 골라서 새 배열 생성map()→ 각 요소를 다른 형태로 바꿔서 새 배열 생성reduce()→ 여러 값을 누적해서 마지막에 하나의 값 생성forEach()→ 새 값을 만들기보다 각 요소를 순서대로 처리즉, 배열 메서드는 전부 비슷해 보이지만 실제 목적은 서로 다르다.
헷갈리기 쉬운 부분초보자가 가장 많이 헷갈리는 부분은
이 메서드들이 전부 배열을 반복해서 본다는 공통점 때문에
거의 같은 기능처럼 느껴진다는 점이다.하지만 결과를 보면 분명히 다르다.
filter()는 조건에 맞는 것만 남기므로 결과 개수가 줄어들 수 있다.map()은 보통 모든 요소를 바꾸기 때문에 원본과 결과 배열의 길이가 같은 경우가 많다.reduce()는 배열이 아니라 마지막에 값 하나가 나온다.forEach()는 반복 처리용이지, 새 배열을 만들기 위한 메서드가 아니다.즉,
filter()는 일부만 남기는 쪽에 가깝고,
map()은 전체를 같은 순서로 바꾸는 쪽에 가깝다.특히
forEach()를map()처럼 생각하면 헷갈리기 쉽다.
forEach()는 “돌면서 처리하는 것”에 가깝고,
map()은 “돌면서 바꾼 결과를 새 배열로 모으는 것”에 가깝다.정리하면 이렇게 외우면 된다.
- 골라내기 →
filter()- 바꾸기 →
map()- 모으기 →
reduce()- 반복 처리 →
forEach()
6. 배열 객체 생성과 사용
1. Date 객체
개념
Date객체는 날짜와 시간 정보를 다루기 위한 내장 객체다.
지금까지는 숫자, 문자열, 배열처럼 값을 직접 다뤘다면,
이번에는 현재 시각이나 특정 날짜를 객체로 만들어서 필요한 정보만 꺼내 쓰는 흐름을 보게 된다.예를 들어
- 지금 현재 연도는 몇 년인지
- 오늘이 몇 월 며칠인지
- 무슨 요일인지
- 현재 시간을 숫자 형태로 바꾸면 얼마인지
같은 정보를 알고 싶을 때
Date객체를 사용한다.즉,
Date객체는
날짜 전체를 하나로 만들고, 그 안에서 필요한 정보만 메서드로 꺼내 쓰는 객체라고 이해하면 된다.
핵심 특징
new Date()→ 현재 날짜와 시간을 기준으로 객체를 만든다.new Date(연, 월, 일)→ 원하는 날짜를 직접 지정해서 만들 수 있다.getFullYear()→ 연도getMonth()→ 월getDate()→ 일getDay()→ 요일getTime()→ 시간을 숫자 형태로 바꾼 값toLocaleString()→ 사람이 보기 쉬운 날짜/시간 문자열여기서 특히 조심해서 봐야 할 것은 두 가지다.
getMonth()는 월을 그대로 1부터 주는 것이 아니라 0부터 시작한다.getDay()는 일이 아니라 요일 번호를 준다.0은 일요일이다.즉,
- 날짜의 “일” →
getDate()- 요일 →
getDay()이 둘을 헷갈리지 않는 것이 중요하다.
코드 예제예제:
exam17var d = new Date(); // 현재 날짜와 시간 정보를 가진 Date 객체 생성 writeColor(d, "h2", "red"); // Date 객체 자체 출력 writeColor(d.toLocaleString(), "h2", "blue"); // 현재 날짜와 시간을 사람이 읽기 쉬운 문자열 형태로 출력 writeColor(d.getFullYear(), "h2", "green"); // 현재 연도 출력 writeColor(d.getMonth(), "h2", "magenta"); // 현재 월 출력 // 주의: 월은 0부터 시작하므로 10월이면 9가 나올 수 있다. writeColor(d.getDate(), "h2", "orange"); // 현재 날짜의 "일" 출력 writeColor(d.getDay(), "h2", "magenta"); // 현재 요일 출력 // 0은 일요일, 1은 월요일 ... 형태다. writeColor(d.getTime(), "h2", "brown"); // 현재 시각을 숫자 형태로 바꾼 값 출력 writeColor(Date.now(), "h2", "red"); // 현재 시각을 숫자 형태로 바로 구해서 출력 hr(); var d1 = new Date(2023, 9, 3); // 2023년 10월 3일 날짜 객체 생성 // month 자리는 0부터 시작하므로 9는 10월이다. writeColor(d1, "h2", "magenta"); writeColor(d1.getFullYear(), "h2", "green"); writeColor(d1.getDate(), "h2", "orange"); writeColor(d1.getDay(), "h2", "lime"); // 출력결과 // 현재 날짜 객체 정보가 순서대로 출력된다. // 현재 연도, 월, 일, 요일, 시간값이 각각 출력된다. // 이어서 2023년 10월 3일에 대한 날짜 정보가 출력된다.
코드 해설이 코드는
Date객체를 두 방식으로 만드는 예제다.첫 번째
d는 지금 이 순간의 날짜와 시간을 담고 있다.
그래서getFullYear(),getMonth(),getDate()같은 메서드를 호출하면
현재 기준 정보가 각각 출력된다.그리고
toLocaleString()은
날짜 객체를 사람이 읽기 쉬운 문자열로 바꿔서 보여 준다.
즉, 객체 자체를 그대로 보기보다 이해하기 쉬운 형태로 출력하는 용도라고 보면 된다.두 번째
d1은 현재 시각이 아니라
직접 지정한 날짜 객체다.
즉,new Date(2023, 9, 3)처럼 만들면
원하는 날짜를 기준으로 객체를 생성할 수 있다는 점을 보여 준다.이 예제에서 가장 중요한 포인트는
Date객체를 만들면 날짜 전체를 한 번에 다루고,
필요한 정보만 메서드로 나눠서 꺼낼 수 있다는 점이다.
헷갈리기 쉬운 부분초보자가 가장 많이 헷갈리는 것은
getMonth()와getDay()다.
getMonth()→ 월인데 0부터 시작getDay()→ 날짜가 아니라 요일즉,
3일을 구하고 싶을 때getDay()를 쓰면 안 되고,
getDate()를 써야 한다.또
new Date(2023, 9, 3)에서9를 보고
9월이라고 생각하기 쉽다.
하지만 월은 0부터 시작하므로 실제로는 10월이다.
2. Array 객체를 객체 형태로 다루기
개념배열은 앞에서 이미 값 여러 개를 저장하는 자료형으로 봤다.
이번 예제에서는 그 배열을 객체처럼 생성하고, 값 추가와 반복을 함께 확인하는 흐름으로 이어진다.즉, 여기서는 새로운 개념을 완전히 또 배우는 것이 아니라
배열도Array객체로 만들 수 있고,
그 안에 값을 넣고 길이가 변하고 반복문으로 꺼내 쓸 수 있다는 점을 다시 확인하는 구간이다.
핵심 특징
new Array()로 빈 배열을 만들 수 있다.push()를 사용하면 배열 끝에 값을 추가할 수 있다.- 값을 하나 넣을 때마다
length가 늘어난다.- 배열에는 숫자, 문자열, 불린값처럼 서로 다른 타입도 함께 들어갈 수 있다.
for...in으로 인덱스를 꺼낸 뒤 값을 확인할 수 있다.- 일반
for문으로 원하는 간격만 골라서 출력할 수도 있다.즉, 배열은
값을 저장하는 것에서 끝나는 것이 아니라, 추가하고 길이를 확인하고 반복해서 꺼내 쓰는 것까지 함께 보는 구조라고 이해하면 된다.
코드 예제예제:
exam17var list = new Array(); // [ ] // 빈 배열 생성 writeNewLine(list.length); // 현재 길이 출력 → 0 list.push(1); writeNewLine(list.length); // 값 1 추가 후 길이 출력 → 1 list.push('가나다'); writeNewLine(list.length); // 문자열 추가 후 길이 출력 → 2 list.push(100); writeNewLine(list.length); // 숫자 추가 후 길이 출력 → 3 list.push("abc"); writeNewLine(list.length); // 문자열 추가 후 길이 출력 → 4 list.push(true); writeNewLine(list.length); // 불린값 추가 후 길이 출력 → 5 writeNewLine("리스트의 모든 엘리먼트 : " + list); // 배열 전체 내용 출력 hr(); for (var i in list) { writeNewLine(list[i]); } // for...in으로 인덱스를 꺼낸 뒤 각 요소 출력 hr(); for (var i=0; i < list.length; i+=2) { writeNewLine(list[i]); } // 인덱스를 2씩 증가시키면서 0, 2, 4번째 요소만 출력 hr(); var list2 = [10, 20, 30, 40, 50] // 배열 리터럴로 새 배열 생성 for (var i in list2) { writeNewLine(list2[i]); } // list2의 모든 요소를 차례대로 출력 // 출력결과 // list 길이가 0 → 1 → 2 → 3 → 4 → 5로 증가한다. // list 전체 요소가 한 줄로 출력된다. // 그다음 각 요소가 하나씩 출력된다. // 이후에는 0, 2, 4번째 요소만 따로 출력된다. // 마지막에는 list2의 10, 20, 30, 40, 50이 순서대로 출력된다.
코드 해설이 코드는 배열에 값을 추가할 때
length가 어떻게 바뀌는지를 먼저 보여 준다.처음
list는 빈 배열이므로 길이가0이다.
그다음push()를 한 번 할 때마다 값이 뒤에 붙고,
배열 길이도1,2,3처럼 계속 늘어난다.그리고 이 예제에서는
배열에 숫자만 넣은 것이 아니라 문자열, 불린값도 함께 넣고 있다.
즉,JavaScript배열은 서로 다른 타입의 값도 함께 담을 수 있다는 점이 드러난다.그다음 반복문 구간에서는 같은 배열을 여러 방식으로 꺼내 보고 있다.
- 첫 번째
for...in→ 배열 전체 요소 출력- 두 번째
for문 → 인덱스를 2씩 건너뛰며 일부 요소만 출력- 마지막
list2→ 배열 리터럴로 만든 배열을 다시 반복 출력즉, 이 예제의 핵심은
배열을 만들고 → 값을 추가하고 → 길이 변화를 확인하고 → 반복문으로 꺼내 쓰는 흐름을 한 번에 확인하는 것이다.
헷갈리기 쉬운 부분초보자는
for...in이 배열의 값을 바로 꺼낸다고 생각하기 쉽다.
하지만for...in은 먼저 인덱스를 꺼내고,
그다음list[i]처럼 값을 다시 찾아가는 흐름이다.또
push()를 여러 번 했을 때
배열 안에 값만 늘어나는 것이 아니라length도 함께 바뀐다는 점을 같이 봐야 한다.그리고
list안에는 숫자만 들어가는 것이 아니라
문자열, 불린값도 함께 들어간다.
즉, 배열이라고 해서 반드시 같은 종류의 값만 들어가는 것은 아니라는 점도 함께 기억하면 된다.