본문으로 건너뛰기
김신건의 로그

[Javascript] Spread / Rest

· 수정 · 📖 약 1분 · 393자/단어 #javascript #spread #rest #syntax
JS Spread, JS Rest, JS Spread / Rest, ...연산자, 전개 연산자

정의

같은 문법 ... 가 두 가지 의미.

  • Spread : iterable 을 펼침 (right-hand)
  • Rest : 나머지를 모음 (left-hand 또는 함수 매개변수)

Spread (펼침)

배열

const a = [1, 2];
const b = [...a, 3, 4];        // [1, 2, 3, 4]
const c = [0, ...a, ...a];     // [0, 1, 2, 1, 2]
const copy = [...a];            // 얕은 복사

// 함수 인자
Math.max(...[1, 2, 3]);         // 3
console.log(...['a', 'b']);     // a b

// string 도 iterable
[...'abc']                       // ['a', 'b', 'c']

객체 (ES2018+)

const a = { x: 1, y: 2 };
const b = { ...a, z: 3 };       // { x: 1, y: 2, z: 3 }
const copy = { ...a };           // 얕은 복사

// 병합 + override
const merged = { ...defaults, ...userInput };
// userInput 이 defaults 를 덮어씀

Rest (모음)

함수 매개변수

function sum(...nums) {
    return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);            // 10

// 일부 인자 + rest
function logger(level, ...messages) {
    console.log(`[${level}]`, messages.join(' '));
}
logger('INFO', 'a', 'b', 'c');

배열 destructuring

const [first, ...others] = [1, 2, 3, 4];
// first = 1, others = [2, 3, 4]

const [a, b, ...rest] = arr;

객체 destructuring

const { name, ...rest } = user;
// name = user.name, rest = 나머지 모든 속성

arguments vs rest

// 옛 스타일
function foo() {
    console.log(arguments);    // 유사 배열 (array-like)
    Array.from(arguments).map(...);   // 변환 필요
}

// 모던
function foo(...args) {
    console.log(args);          // 진짜 배열
    args.map(...);              // 바로 메서드
}

arguments 는 화살표 함수에서 안 됨, rest 는 됨.

자주 쓰는 패턴

배열 복사

const original = [1, 2, 3];
const copy = [...original];     // 얕은 복사
// 또는: Array.from(original), original.slice()

객체 복사

const copy = { ...original };   // 얕은 복사
// 깊은 복사는 structuredClone(original)

객체 병합

const config = { ...defaults, ...userOptions };

객체에서 특정 키 제외

const { password, ...safe } = user;     // safe 는 password 없는 user

배열 끝/처음 추가

const newArr = [...arr, newItem];        // 끝
const newArr2 = [newItem, ...arr];       // 처음

immutable 갱신.

Math 함수 + 배열

const max = Math.max(...arr);
const min = Math.min(...arr);

함정

1. 얕은 복사

const a = [{ x: 1 }];
const b = [...a];
b[0].x = 99;
a[0].x      // 99 (참조 공유)

깊은 복사는 structuredClone(obj) (ES2022+) 또는 JSON.parse(JSON.stringify(obj)) (제한적).

2. 객체 spread 의 순서

const a = { x: 1 };
const b = { x: 99, ...a };       // { x: 1 }  (뒤가 우선)
const c = { ...a, x: 99 };       // { x: 99 } (뒤가 우선)

같은 키는 뒤에 오는 값이 우선.

3. iterable 만 spread

[...{ a: 1 }]      // ❌ TypeError (객체는 iterable 아님)
{ ...[1, 2, 3] }   // { 0: 1, 1: 2, 2: 3 } (배열을 객체로)

4. rest 의 위치

function foo(...args, last) {}    // ❌ SyntaxError (rest 는 마지막)
function foo(first, ...rest) {}    // ✓

5. Symbol.iterator 가 없으면

const obj = { length: 3 };
[...obj]    // ❌ TypeError

Symbol.iterator 가 있는 객체만 array spread 가능.

Object.assign 과의 비교

const a = { x: 1, y: 2 };
const b = { z: 3 };

// Object.assign: target 을 직접 변경
Object.assign(a, b);              // a = { x: 1, y: 2, z: 3 }  (a 변경됨)
const m1 = Object.assign({}, a, b);  // 새 객체

// Spread: 항상 새 객체
const m2 = { ...a, ...b };       // a 변경 안 됨

주요 차이:

특성Object.assignSpread {...}
target 변경첫 인자 변경항상 새 객체
getter 처리getter 호출 후 값 복사동일
prototype복사 안 함복사 안 함
성능유사유사
가독성낮음높음

React / Redux 에서는 immutable 갱신 때문에 spread 가 표준.

Array.concat 과의 비교

const a = [1, 2];
const b = [3, 4];

// concat
const c1 = a.concat(b);           // [1, 2, 3, 4]
const c2 = a.concat(5, 6);        // [1, 2, 5, 6]

// Spread (더 유연, 중간 삽입 가능)
const c3 = [...a, ...b];           // [1, 2, 3, 4]
const c4 = [0, ...a, 99, ...b];    // [0, 1, 2, 99, 3, 4]

TypeScript 와의 타입 추론

const a = { x: 1, y: 2 } as const;
const b = { z: 3 };
const merged = { ...a, ...b };
// type: { readonly x: 1; readonly y: 2; z: number }

// 가변 인자 rest 타입
function sum(...nums: number[]): number {
    return nums.reduce((acc, n) => acc + n, 0);
}

// 튜플 rest
function head<T>([first, ..._rest]: T[]): T {
    return first;
}

// 객체 rest 로 타입 제외
function withoutId<T extends { id: unknown }>({ id: _id, ...rest }: T) {
    return rest;
}

사용 맥락 시각화

flowchart LR
    A["Spread:배열 [...arr]"] --> B["복사/병합"]
    C["Spread: 객체 {...obj}"] --> D["복사/병합/override"]
    E["Spread: fn(...arr)"] --> F["가변 인자 전달"]
    G["Rest: fn(...rest)"] --> H["가변 인자 수집"]
    I["Rest: [a, ...rest]"] --> J["배열 나머지 수집"]
    K["Rest: {x, ...rest}"] --> L["객체 나머지 수집"]

성능

  • Spread 복사 = 얕은 복사, O(n). 중첩 객체는 참조만 복사.
  • 대용량 배열 spread 를 함수 인자로 넘길 때 배열 원소 수십만 개면 콜 스택 초과 가능.
const arr = new Array(200_000).fill(1);

// 위험
Math.max(...arr);    // RangeError 가능

// 안전
arr.reduce((a, b) => Math.max(a, b), -Infinity);
  • 객체 spread = Object.assign({}, ...) 과 성능 거의 동일.
  • Immutable 갱신 패턴 을 반복적으로 중첩하면 GC 압박. 깊은 중첩은 immer 검토.

참고

이 글의 용어 (5개)
[Javascript] 타입 변환 / 강제 변환javascript
정의 JavaScript 의 암묵적 형변환 (coercion). 연산자가 양쪽 피연산자의 타입을 자동으로 맞춘다. 명시적 변환과 구분. 3 가지 강제 변환 1. ToString …
[Javascript] Arrayjavascript
정의 JavaScript 의 는 순서 있는 객체. 정수 키 + . 풍부한 메서드 (map/filter/reduce/...) 가 함수형 스타일을 지원. 생성 핵심 메서드 (반복) …
[Javascript] Destructuringjavascript
정의 Destructuring 은 배열 또는 객체의 일부를 변수로 풀어내는 ES6 문법. 코드를 짧고 명시적으로. 객체 배열 함수 매개변수 자주 쓰는 idiom API 응답 분해…
[Javascript] Map / Set / WeakMap / WeakSetjavascript
정의 ES6 의 4 가지 컬렉션. 객체 / 배열보다 적합한 경우. | 컬렉션 | 키 | 값 | 강참조 | |:---|:---|:---|:---:| | | 모든 값 | 모든 값 |…
[Javascript] Objectjavascript
정의 JavaScript 의 는 key-value 쌍의 컬렉션. 거의 모든 non-primitive 값이 객체. 함수, 배열, 정규식도 객체. 생성 접근 수정 / 삭제 Objec…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기