Menu

자바스크립트 Rest / Spread 연산자(...) 완벽 정리

자바스크립트의 ... 연산자가 어떻게 동작하는지 한 번에 정리했습니다. rest 파라미터로 인자를 모으고, spread로 배열과 객체를 펼치는 방법, 그리고 언제 어떤 걸 써야 하는지 알려드립니다.

같은 문법, 정반대의 두 가지 역할

요즘 자바스크립트 코드를 보면 점 세 개(...)가 참 자주 등장합니다. 그런데 이 ... 연산자는 어디에 쓰이냐에 따라 정반대의 일을 합니다. 패턴만 잡으면 모든 ... 사용법은 결국 다음 두 가지 중 하나입니다:

  • Rest(나머지 파라미터): _받는 쪽_에 쓰는 ...name. 여러 값을 하나의 배열이나 객체로 모아 담습니다.
  • Spread(스프레드 연산자): _주는 쪽_에 쓰는 ...value. 배열이나 객체를 개별 요소로 펼쳐 줍니다.

머릿속에 이 구분만 넣어두면 끝입니다. 이 문서의 나머지는 각 용법을 보여주는 예제와, 실무에서 자주 쓰게 되는 패턴들이에요.

Rest 파라미터: 인자를 하나로 모으기

함수 정의에서 rest 파라미터를 쓰면, 넘어오는 인자가 몇 개든 실제 배열 하나로 모아서 받을 수 있습니다:

index.js
Output
Click Run to see the output here.

nums는 평범한 배열입니다. .map도 되고, .filter도 되고, .length로 길이도 확인할 수 있고, 다른 함수에 인자로 넘기는 것도 됩니다. 배열이 할 수 있는 건 전부 가능하죠.

rest 파라미터는 일반 파라미터와 함께 쓸 수 있지만, 반드시 맨 뒤에 와야 합니다:

index.js
Output
Click Run to see the output here.

label은 첫 번째 인자를 가져가고, 그 뒤로 들어오는 값은 전부 items에 담깁니다. rest 파라미터는 반드시 마지막에 와야 하며, 다른 위치에 쓰면 문법 에러가 납니다.

rest 파라미터 vs 옛날 arguments 객체

예전 자바스크립트 코드에서는 일반 함수 안에서 arguments라는 특수한 변수를 사용했습니다. 겉보기엔 배열 같지만 실제 배열은 아니라서, 배열 메서드를 바로 쓸 수 없다는 불편함이 있었죠. rest 파라미터를 쓰면 이 문제를 깔끔하게 해결할 수 있습니다.

index.js
Output
Click Run to see the output here.

애초에 화살표 함수에는 arguments 객체 자체가 없습니다. 그래서 가변 인자를 받으려면 rest 파라미터를 쓰는 방법밖에 없죠. 새로 작성하는 코드라면 ...args를 기본으로 쓰는 걸 권장합니다.

함수 호출에서 쓰는 spread 연산자

spread는 rest와 정반대로 동작합니다. 배열을 받아서 호출 지점에서 개별 인자로 풀어 넘겨주죠.

index.js
Output
Click Run to see the output here.

Math.max는 배열이 아니라 개별 숫자들을 인자로 받습니다. spread 문법이 나오기 전에는 Math.max.apply(null, nums)라고 써야 했죠. 이제는 ... 하나로 끝입니다.

재미있는 점은 똑같이 생긴 ...이 함수 정의 에서는 rest 파라미터로, 함수 호출 에서는 spread 연산자로 동작한다는 겁니다. 즉, 어디에 쓰였는지(위치)가 둘을 구분하는 기준이에요.

배열 리터럴에서 쓰는 spread 연산자

배열 리터럴 안에 spread를 쓰면 배열을 복사하거나 여러 배열을 합칠 수 있습니다:

index.js
Output
Click Run to see the output here.

[...a]를 쓰면 동일한 원소를 가진 새 배열이 만들어져요. 원본은 그대로 두고 정렬하거나 값을 바꾸고 싶을 때 딱입니다:

index.js
Output
Click Run to see the output here.

scores는 그대로 남아 있습니다. .sort가 복사본 위에서 동작했기 때문이죠. 사소한 습관이지만, 예기치 못한 사이드 이펙트를 피해야 하는 코드를 짤 때 효과가 큽니다.

객체 리터럴에서의 spread 연산자

spread 문법은 일반 객체에도 그대로 쓸 수 있어서, 여러 객체의 프로퍼티를 새 객체로 합칠 때 유용합니다:

index.js
Output
Click Run to see the output here.

뒤에 오는 키가 이깁니다. updates.ageuser.age를 덮어쓰고, city는 덤으로 딸려옵니다. 전개 순서가 최종 결과를 결정하니, 기본값과 덮어쓸 값을 쌓아 올릴 때 이 점을 꼭 기억하세요:

index.js
Output
Click Run to see the output here.

기본값을 먼저 깔고, 사용자 설정을 덮어쓰는 패턴이죠. fontSize는 사용자가 지정한 값이 적용되고, theme은 기본값을 그대로 물려받습니다.

spread는 얕은 복사라는 함정

spread 연산자는 딱 한 단계만 복사합니다. 그래서 중첩된 객체나 배열은 원본과 복사본이 여전히 같은 참조를 공유하게 되죠.

index.js
Output
Click Run to see the output here.

두 배열 모두 새로 추가된 태그가 보이는 이유는 copy.tagsoriginal.tags가 사실상 같은 배열이기 때문입니다. spread는 중첩된 배열까지 복제하는 게 아니라, 참조만 그대로 복사할 뿐이거든요.

순수한 데이터를 제대로 깊은 복사(deep copy)하고 싶다면 structuredClone을 쓰면 됩니다.

index.js
Output
Click Run to see the output here.

이렇게 하면 두 배열이 완전히 독립적인 객체가 됩니다. structuredClone은 최신 브라우저와 Node에 기본 내장되어 있고 중첩 구조까지 알아서 처리해 주니, 얕은 복사로는 부족한 상황이라면 이 방법이 정답입니다.

구조 분해 할당에서 쓰는 rest

rest 문법은 구조 분해 할당에서도 유용하게 쓰입니다. 나머지 요소나 남은 프로퍼티를 한 번에 모아주는 역할을 하죠:

index.js
Output
Click Run to see the output here.

몇 개 필드만 따로 빼고 나머지는 하나의 객체로 묶어두는 패턴은 props를 그대로 넘기거나, 민감한 필드를 제거하거나, 데이터를 부분적으로 수정한 버전을 만들 때 자주 쓰는 방식입니다:

index.js
Output
Click Run to see the output here.

password는 꺼내서 버려지고, 나머지는 전부 safe에 담깁니다. 원본을 건드리지도, 직접 복사할 필요도 없습니다.

핵심 정리

  • 파라미터 목록이나 구조 분해 패턴에 쓰인 ...namerest입니다. 값을 모아 담죠.
  • 함수 호출이나 배열 리터럴, 객체 리터럴에 쓰인 ...valuespread입니다. 값을 펼쳐주죠.
  • spread로 만든 복사본은 얕은 복사입니다. 중첩된 구조는 여전히 원본과 참조를 공유하니, 깊은 복사가 필요하다면 structuredClone을 쓰세요.
  • rest 파라미터는 진짜 배열입니다. arguments 대신 이걸 쓰세요.
  • 객체 리터럴에서는 뒤에 펼친 값이 앞의 값을 덮어씁니다. "기본값 + 오버라이드" 패턴이 이렇게 만들어집니다.

다음 주제: 클로저

자바스크립트의 함수는 단순히 인자를 받아 결과를 돌려주는 데서 그치지 않습니다. 자기가 정의된 스코프까지 기억하죠. 이 기억을 클로저라고 부르는데, 콜백이나 팩토리 함수, 그리고 다음 페이지에서 만나게 될 여러 패턴의 바탕이 되는 원리입니다.

자주 묻는 질문

자바스크립트에서 rest와 spread는 어떻게 다른가요?

문법은 똑같이 ...을 쓰지만 하는 일은 정반대입니다. rest는 여러 값을 하나의 배열로 모으는 역할을 하고, 주로 함수 파라미터나 구조 분해 할당에서 등장합니다. 반면 spread는 iterable이나 객체를 개별 요소로 펼치는 역할을 하고, 함수 호출이나 배열/객체 리터럴 안에서 쓰입니다. 쉽게 기억하려면 ...이 값을 받는 쪽에 있으면 rest, 값을 내보내는 쪽에 있으면 spread라고 보면 됩니다.

함수에서 rest 파라미터는 어떻게 동작하나요?

function sum(...nums)처럼 선언하면, 함수에 전달된 모든 인자가 nums라는 진짜 배열에 담깁니다. 단, rest 파라미터는 반드시 파라미터 목록의 맨 마지막에 와야 합니다. 예전부터 쓰이던 arguments 객체와 달리 rest 파라미터는 진짜 Array이기 때문에 .map, .filter, .reduce 같은 메서드를 바로 호출할 수 있어서 훨씬 편합니다.

spread 연산자로 깊은 복사(deep copy)가 되나요?

안 됩니다. spread는 한 단계만 복사하는 **얕은 복사(shallow copy)**입니다. { ...user }를 하면 최상위 키를 가진 새 객체가 만들어지지만, 그 안의 중첩된 객체나 배열은 여전히 원본과 같은 참조를 공유합니다. 깊은 복사가 필요하다면 structuredClone(value)를 쓰거나, 단순 데이터라면 JSON.parse(JSON.stringify(obj))로 직렬화하는 방법을 쓰면 됩니다.

Coddy로 코딩 배우기

시작하기