JavaScript · 6장 함수 심화
나머지 매개변수와 전개 구문: 같은 점 세 개, 반대의 일
점 세 개 ...는 앞 글들에서 배열 복사 · 객체 합치기 · 구조 분해에 여러 번 나왔어요. 사실 이 기호는 쓰는 자리에 따라 모으기와 펼치기, 정반대의 두 가지 일을 해요. 함수와 함께 쓰는 법을 중심으로 정리해요.
01. 나머지 매개변수: 인자를 몇 개든 받기
매개변수 이름 앞에 ...를 붙이면, 그 자리부터 넘어온 인자를 모두 모아 배열 하나로 받아요. 인자가 없으면 빈 배열이에요. 앞쪽에 보통 매개변수를 두고 "나머지"만 모을 수도 있어서, 나머지 매개변수는 늘 맨 마지막에 하나만 둘 수 있어요.
function sum(...nums) { // nums는 진짜 배열
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3), sum()); // 6 0
function log(level, ...messages) { // 첫 인자는 level, 나머지는 messages로
return `[${level}] ${messages.join(' ')}`;
}
console.log(log('INFO', '저장', '완료', 3)); // [INFO] 저장 완료 3
예전 코드에서는 같은 일을 함수 안의 arguments로 했어요. arguments는 진짜 배열이 아닌 유사 배열이라 map 같은 메서드가 없고(유사 배열), 화살표 함수 안에는 아예 없어요. 새 코드는 나머지 매개변수를 써요.
02. 전개 구문: 배열을 인자로 펼치기
반대로 함수를 부를 때 배열 앞에 ...를 붙이면, 배열의 항목이 낱개 인자로 펼쳐져요. 인자를 낱개로만 받는 함수(Math.max, push, new Date(y, m, d))에 배열을 넘길 때 써요.
const scores = [72, 95, 88];
console.log(Math.max(...scores)); // 95 — Math.max(72, 95, 88)과 같아요
console.log(Math.max(...[])); // -Infinity — 빈 배열이면?
const cart = ['연필'];
cart.push(...['공책', '지우개']); // 여러 개를 한 번에 넣기
console.log(cart); // ['연필', '공책', '지우개']
두 가지를 조심해요. 빈 배열을 펼친 Math.max()는 오류 대신 -Infinity를 돌려줘서, 점수가 없는 목록에서 "최고점 -Infinity점"이 화면에 찍힐 수 있어요. 그리고 인자 개수에는 한도가 있어서, 수십만 개짜리 배열을 펼치면 RangeError가 나요. 아주 긴 배열의 최댓값은 reduce로 구해요.
03. 배열과 객체를 만들 때 펼치기
대괄호 · 중괄호 안에서 펼치면 다른 배열 · 객체의 내용을 그 자리에 쏟아 넣어요. 합치기, 앞뒤에 더하기, 얕은 복사를 한 줄로 해요. 이터러블이면 무엇이든 배열 안에 펼칠 수 있어요(이터러블).
const a = [1, 2], b = [3, 4];
console.log([...a, ...b], [0, ...a, 9]); // [1, 2, 3, 4] [0, 1, 2, 9]
console.log([...'안녕'], [...new Set([1, 1, 2])]); // ['안', '녕'] [1, 2]
const defaults = { theme: 'light', size: 'md', animate: true };
console.log({ ...defaults, size: 'lg' }); // size만 lg로
console.log({ size: 'lg', ...defaults }); // size가 다시 md — 뒤에 쓴 값이 이겨요
console.log({ ...defaults, ...{ theme: undefined } }); // theme: undefined — 덮어써요
객체를 합칠 때는 뒤에 쓴 쪽이 이겨요. 그래서 기본 설정을 앞에, 사용자가 준 옵션을 뒤에 두는 { ...defaults, ...options }가 기본 모양이에요. 다만 옵션에 undefined가 들어 있으면 기본값까지 지워요. 빈칸으로 넘어올 수 있는 옵션이라면 구조 분해 기본값(매개변수 기본값)이 더 안전해요. 펼치기는 얕은 복사라는 것도 기억해요(얕은 복사).
04. 같은 점 세 개, 자리로 구분해요
헷갈리면 자리를 봐요. 값을 받는 자리(매개변수, 등호 왼쪽의 구조 분해)에 있으면 여러 개를 하나로 모으는 나머지, 값을 내놓는 자리(함수 호출의 인자, 배열 · 객체 리터럴 안)에 있으면 하나를 여러 개로 펼치는 전개예요.
function f(a, ...rest) {} // 받는 자리 → 나머지(모으기)
const [first, ...others] = arr; // 받는 자리 → 나머지(모으기)
const { id, ...fields } = obj; // 받는 자리 → 나머지(모으기)
f(...arr); // 내놓는 자리 → 전개(펼치기)
const copy = [...arr]; // 내놓는 자리 → 전개(펼치기)
const merged = { ...a, ...b }; // 내놓는 자리 → 전개(펼치기)
기본 설정에 옵션을 합쳐 차트 설정을 만드는 함수예요. 첫 번째 차트를 빨간색으로 만들었더니 두 번째 차트와 기본 설정까지 빨간색이 됐어요. 고쳐서 red blue 500 blue가 나오게 해 보세요.
const defaults = { color: 'blue', width: 300 };
function createChart(options) {
return Object.assign(defaults, options);
}
const first = createChart({ color: 'red' });
const second = createChart({ width: 500 });
console.log(first.color, second.color, second.width, defaults.color);
해답 보기
Object.assign(대상, …)은 첫 인자인 대상 객체를 고치고 그 객체를 돌려줘요. 그래서 defaults 자체에 빨간색이 들어갔고, first · second · defaults가 모두 같은 객체가 됐어요. 펼치기로 새 객체를 만들면 기본 설정은 그대로 남아요. Object.assign({}, defaults, options)처럼 빈 객체를 대상으로 줘도 같아요.
function createChart(options) {
return { ...defaults, ...options };
}
// red blue 500 blue
05. 실전 포인트
- 언제 쓰나요? 개수가 정해지지 않은 인자는 나머지 매개변수로 받고, 배열을 낱개 인자로 넘길 땐 전개해요. 배열 합치기 · 복사, 기본 설정과 옵션 합치기(
{ ...defaults, ...options })에 써요. - 대신 쓰는 것: 아주 긴 배열의 최댓값 · 합계는
reduce, 깊은 복사는structuredClone, 빈칸이 올 수 있는 옵션은 구조 분해 기본값을 써요. 인자가 많은 함수는 나머지 매개변수보다 옵션 객체 하나가 읽기 쉬워요. - 흔한 실수: 빈 배열을
Math.max(...arr)에 넣어-Infinity를 보여 주는 것, 기본값을 뒤에 펼쳐 옵션을 덮어쓰는 것,Object.assign(defaults, …)로 기본 설정을 망가뜨리는 것, 나머지 매개변수를 맨 끝이 아닌 곳에 두는 것이에요.
정리
- 받는 자리의
...는 나머지예요. 여러 값을 배열 · 객체 하나로 모아요. - 내놓는 자리의
...는 전개예요. 배열 · 객체 · 이터러블을 낱개로 펼쳐요. - 객체를 펼쳐 합치면 뒤에 쓴 값이 이겨요. 기본값은 앞, 옵션은 뒤에 둬요.
arguments대신 나머지 매개변수, 원본을 고치는Object.assign(대상, …)대신 펼치기를 써요.
function f(a, ...rest)를 f(1)로 부르면 rest는 무엇일까요?
빈 배열 []이에요. 나머지 매개변수는 남는 인자가 없어도 늘 배열이라, rest.length나 rest.map을 바로 써도 안전해요.
{ size: 'lg', ...defaults }에서 defaults에도 size가 있으면 결과의 size는 무엇일까요?
defaults의 size예요. 객체를 펼쳐 합칠 때는 뒤에 쓴 값이 앞의 값을 덮어써요. 옵션을 이기게 하려면 { ...defaults, size: 'lg' }처럼 뒤에 둬요.
점수 배열이 비어 있을 때 Math.max(...scores)의 결과와 대처법은?
-Infinity예요. 인자가 없는 Math.max()가 이 값을 돌려줘요. scores.length ? Math.max(...scores) : null처럼 비었는지 먼저 확인해요.