push·pop·shift·unshift는 새 배열을 만들지 않고 그 배열을 제자리에서 바꾸는 배열 메서드입니다. 펼치기 [...a]는 칸 값이 같은 새 배열(사본)을 만들고, 함수에 넘긴 배열을 함수 안에서 바꾸면 밖에서도 보입니다.
문자열 메서드는 원래 문자열을 두고 새 문자열을 돌려줬습니다. 이 네 메서드는 거꾸로 그 배열을 바꾸고, 바뀐 배열 대신 새 길이나 뺀 값을 돌려줍니다. 둘을 섞으면 돌려받은 값에 이름을 다시 붙였다가 배열을 잃거나, 사본이라 믿은 배열이 함께 바뀝니다. 이 레슨에서는 이름표 규칙(규칙 3)으로 바꾸기와 복사를 가릅니다.
1제자리에서 바꾸기와 사본
그림 첫 장면의 코드입니다.
const orders = ['커피'];
orders.push('라테');
const backup = [...orders];
orders.pop();
console.log(orders);
console.log(backup);
[ '커피' ]
[ '커피', '라테' ]2번 줄 push는 배열 끝에 '라테'를 더하고, 4번 줄 pop은 끝 칸을 빼서 돌려줍니다. 두 줄 모두 orders가 가리키는 배열 ①을 제자리에서 바꿉니다. 칸이 늘고 빠지는 동안 이름 orders는 ①에 그대로 붙어 있습니다.
3번 줄의 ... 기호는 펼치기(spread)입니다. [...orders]는 ①의 칸 값을 꺼내 대괄호 안에 펼쳐 새 배열 ②를 만듭니다. 자료에 따라 전개 구문이라고도 부릅니다.
이렇게 같은 값을 담아 새로 만든 배열을 사본(copy)이라고 합니다. backup은 사본 ②에 붙었으므로, 4번 줄이 ①에서 '라테'를 빼도 ②는 그대로입니다(규칙 3).
2메서드가 돌려주는 값
push도 메서드라 부르면 값을 하나 돌려줍니다. 아래 코드는 무엇을 찍을지 답을 정한 뒤 펼쳐 보세요.
const prices = [4500, 900];
const n = prices.push(2000);
console.log(n);
console.log(prices);
실행 결과 보기
3
[ 4500, 900, 2000 ]예상과 다르면 그곳이 배울 곳입니다. push는 끝에 값을 더한 뒤, 바뀐 배열이 아니라 새 길이(칸 수)를 돌려줍니다. 그래서 n은 3이고, 2000은 prices가 가리키는 배열에 더해져 있습니다.
네 메서드가 돌려주는 값은 이렇습니다.
| 메서드 | 하는 일 | 돌려주는 값 |
|---|---|---|
push(값) | 끝에 더한다 | 새 길이 |
pop() | 끝 칸을 뺀다 | 뺀 값 |
unshift(값) | 앞에 더한다 | 새 길이 |
shift() | 첫 칸을 뺀다 | 뺀 값 |
넷 모두 같은 배열을 바꿀 뿐, 새 배열을 돌려주지 않습니다.
돌려받은 값을 배열로 믿고 이름을 다시 붙이면 배열을 잃습니다.
let prices = [4500, 900];
prices = prices.push(2000);
console.log(prices);
prices.push(3000);
3main.js:4prices.push(3000); ^ TypeError: prices.push is not a function at main.js:4:82번 줄에서 이름 prices가 배열을 떠나 돌려받은 수 3에 붙었습니다. 수 3에는 push라는 메서드가 없어 부를 수 없는 것을 부른 셈이라, 4번 줄은 TypeError로 멈춥니다.
push·pop·shift·unshift는 prices.push(2000);처럼 부르기만 하고, 돌려받은 값에 배열의 이름을 다시 붙이지 않습니다.
3함수에 넘긴 배열
그림 둘째 장면의 코드 끝에 찍는 줄 하나를 더했습니다. 배열을 함수에 넘겨 함수 안에서 바꿉니다.
function add(items) {
items.push('쿠키');
}
const order = ['커피'];
add(order);
console.log(order);
[ '커피', '쿠키' ]6번 줄에서 add를 부르면 새 칸이 생기고, 매개변수 items가 그 칸에 생깁니다(규칙 5). items는 넘긴 값에 붙으므로 order와 같은 배열 ①을 가리킵니다. 사본은 만들어지지 않습니다(규칙 3). 2번 줄은 items가 가리키는 ①을 제자리에서 바꿉니다. add의 칸이 사라져도 ①은 order가 그대로 가리키므로, 7번 줄에 '쿠키'가 보입니다.
그러면 함수 안에서 매개변수에 새 배열을 대입하면 어떻게 될까요? 아래 코드는 무엇을 찍을지 답을 정한 뒤 펼쳐 보세요.
function reset(items) {
items = [];
}
const order = ['커피'];
reset(order);
console.log(order);
실행 결과 보기
[ '커피' ]예상과 다르면 그곳이 배울 곳입니다. 2번 줄은 새 빈 배열을 만들고 이름 items만 그 배열로 옮겨 붙입니다. order는 처음 배열에 그대로 붙어 있어서 바뀐 것이 없습니다(규칙 3).
items.push(…)는 items가 가리키는 배열을 바꾸고, items = []는 이름 items를 옮길 뿐입니다.
예제
줄 선 손님 차례로 부르기
줄 끝에 손님을 더하고, 맨 앞 손님부터 부릅니다.
const line = ['민지', '철수'];
line.push('영희');
const first = line.shift();
console.log(first);
console.log(line);
민지
[ '철수', '영희' ]shift는 첫 칸을 빼서 돌려주므로 first가 '민지'에 붙습니다. 첫 칸이 빠지면 남은 칸이 한 칸씩 앞으로 당겨져, '철수'의 자리 번호가 0이 됩니다.
과목 끝의 할 일 앱에서도 새 할 일은 push로 할 일 배열을 제자리에서 바꿔 더하고, 돌려받은 길이에 이름을 다시 붙이지 않습니다.
자주 틀리는 곳
틀린 생각: 배열을 함수에 넘기면 함수는 그 배열의 사본을 받는다.
함수 안의 이름은 함수가 끝나면 사라지니, 함수 안에서 바꾼 것도 함께 사라질 것 같습니다. 그러나 3절에서 add 안의 push가 밖의 order에도 보였습니다.
맞는 규칙: 매개변수는 넘긴 배열 그 자체에 붙습니다. 함수 안에서 그 배열을 바꾸면 밖에서도 보입니다(규칙 3). 원래 배열을 지키려면 add([...order])처럼 사본을 넘깁니다.
틀린 생각: push는 값을 더한 배열을 돌려준다.
문자열 메서드는 바뀐 모양의 새 문자열을 돌려줬으니, 배열 메서드도 바뀐 배열을 돌려줄 것 같습니다.
맞는 규칙: push와 unshift는 새 길이를, pop과 shift는 뺀 값을 돌려줍니다. 배열은 제자리에서 이미 바뀌었으니 a = a.push(3)처럼 이름을 다시 붙이지 않습니다(2절).
틀린 생각: 함수 안에서 items = []를 하면 넘긴 배열이 빈다.
items.push(…)가 밖의 배열을 바꿨으니, items에 하는 일은 모두 밖의 배열에 닿을 것 같습니다.
맞는 규칙: 매개변수에 대입하면 이름 items만 새 배열로 옮겨 갑니다. 밖의 이름은 처음 배열에 그대로 붙어 있습니다(3절, 규칙 3).
틀린 생각: [...a] 사본은 칸에 든 배열까지 모두 새로 만든다.
1절에서 사본이 원래 배열과 따로 바뀌었으니, 칸에 든 배열도 따로 바뀔 것 같습니다.
const a = [[1], [2]];
const b = [...a];
b[0].push(9);
console.log(a);
[ [ 1, 9 ], [ 2 ] ]맞는 규칙: 펼치기는 바깥 배열 한 겹만 새로 만듭니다. a와 b의 첫 칸은 같은 배열 [1]을 가리켜서, b[0]으로 더한 9가 a에서도 보입니다. 8단원 객체에서 다시 봅니다.
확인 문제
1. 무엇이 찍힐까요? 세 줄을 글자 그대로 쓰세요.
const line = ['철수'];
const n = line.unshift('민지');
const first = line.shift();
console.log(n);
console.log(first);
console.log(line);
힌트
2번 줄 뒤 배열의 칸 수를 세고, 3번 줄이 어느 쪽 칸을 빼는지 봅니다.
정답
2
민지
[ '철수' ]2번 줄은 앞에 '민지'를 더해 칸이 둘이 되고, 새 길이 2를 돌려줍니다. 3번 줄은 첫 칸 '민지'를 빼서 돌려주므로 배열에는 '철수'만 남습니다.
흔한 오답은 첫 줄을 [ '민지', '철수' ]처럼 배열로 쓴 것입니다. unshift가 바뀐 배열을 돌려준다고 본 것입니다. push·pop·shift·unshift는 새 길이나 뺀 값을 돌려줍니다.
2. 무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
const orders = ['커피', '라테'];
const today = orders;
const backup = [...orders];
today.pop();
console.log(orders.length);
console.log(backup.length);
힌트
배열이 몇 개 만들어졌는지, 4번 줄이 어느 배열을 바꾸는지 봅니다.
정답
1
2today는 orders와 같은 배열에 붙은 이름이라, 4번 줄은 orders의 배열에서 '라테'를 뺍니다. backup은 3번 줄의 펼치기가 만든 새 배열이라 칸 둘이 그대로입니다.
흔한 오답은 2와 2입니다. 2번 줄이 사본을 만든다고 본 것입니다(복사된다는 생각). 1과 1을 썼다면 펼치기도 같은 배열에 이름만 하나 더 붙인다고 본 것입니다.
3. 함수 안에서 넘겨받은 배열을 바꾼 뒤 매개변수를 다시 붙입니다. 무엇이 찍힐까요?
function edit(items) {
items.push('쿠키');
items = ['물'];
items.push('빵');
}
const orders = ['커피'];
edit(orders);
console.log(orders);
힌트
줄마다 items가 가리키는 배열을 따로 적어 봅니다. 3번 줄이 옮기는 이름은 하나입니다.
정답
[ '커피', '쿠키' ]2번 줄까지 items는 orders와 같은 배열을 가리켜, '쿠키'가 그 배열에 더해집니다. 3번 줄은 새 배열 ['물']을 만들고 items만 옮겨 붙이므로, 4번 줄의 '빵'은 새 배열에 더해집니다.
흔한 오답은 [ '커피' ]입니다. 함수가 배열의 사본을 받는다고 본 것입니다(복사된다는 생각). [ '물', '빵' ]을 썼다면 items를 다시 붙일 때 orders도 따라 옮겨 간다고 본 것입니다(따라 옮겨 간다는 생각).
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
1절 그림 코드를 main.js에 저장해 돌린 뒤, 2번 줄을 const n = orders.push('라테');로 바꾸고 끝에 console.log(n);을 더합니다. 마지막 줄에 무엇이 찍힐지 먼저 적고, 바꿔 돌려서 맞춰 봅니다.
저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.