배열(array)은 값 여럿을 차례로 담은 값입니다. 자리 번호로 값을 꺼내고 push로 끝에 값을 더하며, 이름 둘이 한 배열을 가리키면 한쪽 이름으로 바꾼 것이 다른 쪽 이름으로도 보입니다.
주문이 몇 개일지 미리 모르면 주문마다 이름을 지어 둘 수 없습니다. 배열 하나에 주문을 모두 담고 이름 하나를 붙이면 됩니다. 그런데 배열은 수나 문자열과 달리 만든 뒤에도 바뀌는 값입니다. 이것을 모르면 이름 둘이 한 배열을 가리킬 때, 손대지 않은 이름의 배열이 저절로 바뀐 것처럼 보입니다. 이 레슨에서는 이름표 규칙(규칙 3)을 배열에서 다시 봅니다.
이 단원(배열)을 이미 안다면 단원 연습 문제부터 풀고 틀린 레슨만 보세요. 건너뛸지 볼 때는 힌트를 펴지 않고 풉니다.
파이썬을 배웠다면 — 이 단원에서 다른 곳
파이썬을 배웠다면 이 단원에서 다른 곳: append가 없어 orders.append('쿠키')는 is not a function 오류로 멈추고, 끝에 더할 때는 push(이 레슨) · for...in은 값이 아니라 자리 번호를 글자로 꺼냄(for...of로 배열 돌기) · max가 없고 Math.max(prices)는 NaN(가장 큰 값 찾기) · 기본 sort는 수도 글자 차례로 정렬함(정렬: sort와 toSorted).
1배열을 만들고 꺼내기
대괄호 안에 값을 쉼표로 나열하면 배열이 하나 만들어집니다.
const orders = ['커피', '라테'];
console.log(orders);
console.log(orders[0]);
console.log(orders.at(-1));
console.log(orders.length);
[ '커피', '라테' ]
커피
라테
2배열을 통째로 찍으면 Node는 대괄호 안쪽에 빈칸을 하나씩 두고 문자열을 작은따옴표로 감싸 찍습니다. 결과를 쓸 때도 이 꼴 그대로 씁니다. 칸 하나를 꺼내 찍으면 따옴표 없이 그 값만 찍힙니다.
배열의 칸에도 문자열의 글자처럼 0부터 자리 번호가 붙고, 꺼내는 법도 인덱스와 자르기와 같습니다. orders.at(-1)은 마지막 칸이고, orders.length는 문자열의 .length처럼 칸 수입니다.
배열에 든 값을 자료에 따라 요소(element)라고도 부릅니다.
칸이 하나도 없는 빈 배열은 []로 씁니다. 빈 배열도 참 같은 값이라, 비었는지는 orders.length === 0처럼 칸 수로 묻습니다(참 같은 값과 ??).
2이름 둘이 한 배열을 가리킨다
그림의 코드입니다. push는 배열 끝에 값 하나를 더하는 메서드입니다.
const orders = ['커피', '라테'];
const today = orders;
today.push('쿠키');
console.log(orders);
[ '커피', '라테', '쿠키' ]2번 줄 const today = orders;는 오른쪽 orders를 계산해 orders가 가리키는 배열을 얻고, 새 이름 today를 바로 그 배열에 붙입니다(규칙 2). 새 배열은 만들지 않습니다. 그래서 이름은 둘이고 배열은 하나입니다(규칙 3).
3번 줄은 today가 가리키는 배열 끝에 '쿠키'를 더합니다. 배열을 새로 만들지 않고 그 배열을 제자리에서(in place) 바꿉니다.
4번 줄의 orders도 같은 배열을 가리키므로 칸 셋이 다 찍힙니다. 이름표 다시 붙이기와 const에서 미뤄 둔 장면이 이것입니다. 이름이 값을 가리키는 것을 자료에 따라 참조(reference)라고도 부릅니다.
그림은 배열을 이름 칸 밖에 따로 그리고, 머리에 종류 이름 Array와 꼬리표 ①을 붙입니다. 꼬리표가 같으면 같은 배열입니다.
칸 안의 '커피' 같은 값은 칸에 바로 적었습니다. 정확히는 칸도 값을 가리키지만, 수와 문자열은 바꿀 수 없는 값이라 이렇게 줄여 적어도 결과가 같습니다.
=== 연산자로 배열을 견주면 같은 배열 하나인지를 묻습니다. 그래서 orders === today는 true이고, 담긴 값이 같아도 따로 만든 두 배열을 견주면 false입니다(같음과 비교).
3const로 만든 이름과 배열
2절 코드의 두 이름은 모두 const로 만들었는데, 3번 줄에서 배열이 바뀌었습니다. 그러면 const는 무엇을 막을까요? 아래 코드는 무엇을 찍을지 답을 정한 뒤 펼쳐 보세요.
const orders = ['커피', '라테'];
orders.push('쿠키');
console.log(orders);
orders = [];
console.log(orders);
실행 결과 보기
[ '커피', '라테', '쿠키' ]main.js:4orders = []; ^ TypeError: Assignment to constant variable. at main.js:4:8예상과 다르면 그곳이 배울 곳입니다. 2번 줄은 orders가 가리키는 배열을 제자리에서 바꿀 뿐, 이름 orders는 같은 배열에 그대로 붙어 있습니다. 그래서 const 이름이어도 됩니다. 4번 줄은 새 빈 배열을 만들어 이름 orders를 다시 붙이려 해 TypeError로 멈추고, 5번 줄은 실행되지 않습니다.
| 줄 | 하는 일 | const 이름이면 |
|---|---|---|
orders.push('쿠키'); | 같은 배열을 제자리에서 바꾼다 | 된다 |
orders = []; | 새 배열에 이름을 다시 붙인다 | TypeError |
const는 이름을 다시 붙이지 못하게 할 뿐, 그 이름이 가리키는 배열을 얼리지 않습니다(규칙 3). let으로 만든 이름이면 4번 줄도 되지만, 옮겨 가는 것은 그 이름 하나뿐입니다. 같은 배열을 가리키던 다른 이름은 처음 배열에 그대로 붙어 있습니다.
예제
마지막 주문 꺼내기
빈 배열에서 시작해 주문을 둘 더한 뒤, 마지막 주문을 꺼내려고 칸 수 n을 자리 번호로 썼습니다.
const orders = [];
orders.push('커피');
orders.push('라테');
const n = orders.length;
console.log(orders[n]);
console.log(orders[n - 1]);
undefined
라테5번 줄은 오류 없이 undefined를 찍습니다. 칸 수는 2이지만 자리 번호는 0부터라 0과 1뿐이고, 없는 자리를 꺼내면 문자열에서처럼 undefined가 나옵니다. 오류로 멈추지 않으니, 찍힌 undefined를 보면 자리 번호를 먼저 의심합니다.
마지막 칸은 orders[n - 1], 더 짧게는 orders.at(-1)로 꺼냅니다.
과목 끝의 할 일 앱에서도 할 일 목록은 배열 하나이고, 새 할 일은 그 배열 끝에 더합니다.
자주 틀리는 곳
틀린 생각: const today = orders;를 하면 today는 orders가 가리키는 배열의 사본을 받는다.
수에서는 b = a 뒤에 a를 다시 붙여도 b가 그대로였습니다. 그래서 사본을 받은 것처럼 보이지만, 2절에서 today로 더한 '쿠키'가 orders로도 보였습니다.
맞는 규칙: 이름을 대입하면 새 배열을 만들지 않고, 같은 배열에 이름을 하나 더 붙입니다. 그래서 한 이름으로 배열을 바꾸면 다른 이름으로도 보입니다(규칙 3). 사본은 다음 레슨 배열 바꾸기와 복사에서 만듭니다.
틀린 생각: const로 만든 배열은 바꿀 수 없다.
const 이름에 다시 대입하면 TypeError가 나니, 그 이름이 가리키는 배열도 얼어 있을 것 같습니다. 그러나 3절에서 push는 되고 orders = []만 멈췄습니다.
맞는 규칙: const는 그 이름을 다른 값에 다시 붙이지 못하게 할 뿐, 값을 얼리지 않습니다. push처럼 배열을 제자리에서 바꾸는 일은 const 이름으로도 됩니다(규칙 3).
틀린 생각: 배열을 찍으면 코드에 쓴 꼴 그대로 ['커피', '라테']가 찍힌다.
코드와 찍힌 결과는 대괄호와 따옴표가 같아서, 빈칸 차이를 놓치기 쉽습니다.
맞는 규칙: Node는 배열을 [ '커피', '라테' ]처럼 대괄호 안쪽에 빈칸을 하나씩 두고 찍습니다. 결과를 쓸 때는 찍힌 꼴 그대로 씁니다. 브라우저 콘솔은 같은 배열을 다른 꼴로 보여 주니, 이 과목의 결과는 Node 꼴로 봅니다.
틀린 생각: typeof로 물으면 배열은 'array'가 나온다.
typeof는 수에 'number', 문자열에 'string'을 돌려주니(수·글자·불리언과 typeof), 배열에도 제 이름이 나올 것 같습니다.
const a = [1, 2];
console.log(typeof a);
console.log(Array.isArray(a));
object
true배열은 8단원에서 배울 객체의 한 종류라 typeof는 'object'를 돌려줍니다.
맞는 규칙: 배열인지 물을 때는 Array.isArray(값)을 씁니다. 배열이면 true, 아니면 false를 돌려줍니다.
확인 문제
1. 2절 코드의 4번 줄을 console.log(orders.at(-1));로 바꿨습니다. 무엇이 찍힐까요?
const orders = ['커피', '라테'];
const today = orders;
today.push('쿠키');
console.log(orders.at(-1));
힌트
3번 줄의 '쿠키'가 어느 배열 끝에 더해졌는지 봅니다.
정답
쿠키today와 orders는 같은 배열을 가리키므로, today로 더한 '쿠키'는 orders로 봐도 마지막 칸입니다.
흔한 오답은 라테입니다. 2번 줄이 배열의 사본을 만든다고 본 것입니다(복사된다는 생각). 2번 줄은 같은 배열에 이름을 하나 더 붙일 뿐입니다(규칙 3).
2. 무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
const orders = ['커피'];
const today = orders;
const mine = ['커피'];
console.log(orders === today);
console.log(orders === mine);
힌트
배열이 몇 개 만들어졌는지 셉니다. 대괄호로 배열을 쓸 때마다 새 배열이 하나 생깁니다.
정답
true
false1번 줄과 3번 줄이 배열을 하나씩, 모두 둘 만듭니다. today는 orders와 같은 배열이라 true이고, mine은 담긴 값만 같은 다른 배열이라 false입니다.
흔한 오답은 true 두 줄입니다. === 연산자가 담긴 값을 견준다고 본 것입니다. false 두 줄을 썼다면 2번 줄이 사본을 만든다고 본 것입니다(복사된다는 생각).
3. 장보기 목록을 두 이름으로 다루는 코드입니다. 무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
let cart = ['우유', '빵'];
const mine = cart;
cart = ['사과'];
mine.push('달걀');
console.log(cart);
console.log(mine);
힌트
줄마다 cart와 mine이 가리키는 배열을 따로 적어 봅니다. 3번 줄이 옮기는 이름은 하나입니다.
정답
[ '사과' ]
[ '우유', '빵', '달걀' ]3번 줄은 새 배열 ['사과']를 만들고 이름 cart만 그 배열로 옮겨 붙입니다. mine은 처음 배열에 그대로 붙어 있어서, 4번 줄의 '달걀'은 처음 배열 끝에 더해집니다.
흔한 오답은 두 줄 모두 [ '사과', '달걀' ]입니다. cart를 다시 붙이면 mine도 따라 옮겨 간다고 본 것입니다(따라 옮겨 간다는 생각). 다시 붙이기는 그 이름 하나만 옮깁니다(규칙 3).
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
2절 그림 코드를 main.js에 저장해 돌리면 칸 셋이 찍힙니다. 그다음 2번 줄을 const today = [...orders];로 바꾸면 무엇이 찍힐지 먼저 적고, 바꿔 돌려서 맞춰 봅니다. [...orders]는 다음 레슨에서 배웁니다.
저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.