본문으로 건너뛰기

배열 바꾸기와 복사

자바스크립트 목차배열레슨 34 / 76

목차

push·pop·shift·unshift는 새 배열을 만들지 않고 그 배열을 제자리에서 바꾸는 배열 메서드입니다. 펼치기 [...a]는 칸 값이 같은 새 배열(사본)을 만들고, 함수에 넘긴 배열을 함수 안에서 바꾸면 밖에서도 보입니다.

문자열 메서드는 원래 문자열을 두고 새 문자열을 돌려줬습니다. 이 네 메서드는 거꾸로 그 배열을 바꾸고, 바뀐 배열 대신 새 길이나 뺀 값을 돌려줍니다. 둘을 섞으면 돌려받은 값에 이름을 다시 붙였다가 배열을 잃거나, 사본이라 믿은 배열이 함께 바뀝니다. 이 레슨에서는 이름표 규칙(규칙 3)으로 바꾸기와 복사를 가릅니다.

13단계 중 1단계. 배열을 바꾸는 메서드와 펼치기를 쓰는 코드입니다.

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:8

2번 줄에서 이름 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
2

today는 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);을 더합니다. 마지막 줄에 무엇이 찍힐지 먼저 적고, 바꿔 돌려서 맞춰 봅니다. 저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로