구조 분해 할당과 스프레드 연산자
배열·객체에서 필요한 값을 구조 분해하고 스프레드로 복사·결합·함수 인수 전달을 간결하게 처리합니다.
우리는 8장을 통해 let, const로 변수 선언을 개선하고, 화살표 함수로 코드 작성 방식을 더 간결하고 예측 가능하게 만들었습니다.
이제 데이터 처리 방식 자체를 크게 바꿔준 다음 핵심 기능으로 넘어갑니다.
구조 분해 할당(Destructuring Assignment)과 스프레드 연산자(Spread Operator)는 배열이나 객체에서 데이터를 추출하거나, 배열이나 객체를 복사/결합할 때 사용되는 문법입니다.
이 두 기능은 코드를 훨씬 간결하고 가독성 높게 만들어주며, 데이터를 다루는 다양한 상황에서 개발자의 편의성을 극대화합니다.
이번 장에서는 구조 분해 할당으로 값 추출하기, 스프레드 연산자로 배열/객체 다루기를 중심으로 핵심 패턴을 정리합니다.
구조 분해 할당
먼저 객체와 배열의 참조 공유를 떠올려 보세요. 값을 다른 이름에 연결하는 일과 새 바깥 객체를 만드는 일은 다릅니다. 아래 코드 블록은 별개의 예제이므로 같은 이름의 const 선언을 한 파일에 모두 붙이지 않습니다.
구조 분해 할당 (Destructuring Assignment)은 배열이나 객체의 속성(property)을 해체하여 그 값을 개별 변수에 할당할 수 있게 하는 자바스크립트 표현식입니다.
복잡한 데이터 구조에서 필요한 값만 깔끔하게 추출할 때 매우 유용합니다.
배열 구조 분해 (Array Destructuring)
배열의 각 요소를 해체하여 변수에 할당할 수 있습니다.
const colors = ["red", "green", "blue"];
// 배열 구조 분해 할당
const [firstColor, secondColor, thirdColor] = colors;
console.log(firstColor); // 결과: "red"
console.log(secondColor); // 결과: "green"
console.log(thirdColor); // 결과: "blue"-
일부 값만 추출: 필요한 요소만 선택적으로 가져올 수 있습니다. 건너뛰고 싶은 요소는 쉼표로 비워둡니다.
const numbers = [10, 20, 30, 40, 50]; const [a, , c, , e] = numbers; // 두 번째와 네 번째 요소 건너뛰기 console.log(a); // 결과: 10 console.log(c); // 결과: 30 console.log(e); // 결과: 50 -
기본값 설정: 할당할 값이
undefined일 경우에 대비하여 기본값을 설정할 수 있습니다.const fruits = ["apple"]; const [myFruit, otherFruit = "orange"] = fruits; console.log(myFruit); // 결과: "apple" console.log(otherFruit); // 결과: "orange" (fruits 배열에 두 번째 요소가 없으므로 기본값 사용) const [item1, item2 = "default"] = []; console.log(item1); // 결과: undefined console.log(item2); // 결과: "default" -
나머지 요소 가져오기 (
...rest): 배열의 나머지 요소들을 새로운 배열로 묶을 수 있습니다. (스프레드 연산자와 동일한 문법이지만 여기서는 rest 요소 역할)const scores = [80, 90, 70, 60, 95]; const [math, english, ...restScores] = scores; console.log(math); // 결과: 80 console.log(english); // 결과: 90 console.log(restScores); // 결과: [70, 60, 95] (새로운 배열) -
변수 값 교환: 임시 변수 없이 두 변수의 값을 쉽게 교환할 수 있습니다.
let x = 1; let y = 2; [x, y] = [y, x]; // x와 y의 값을 교환 console.log(x); // 결과: 2 console.log(y); // 결과: 1
객체 구조 분해 (Object Destructuring)
객체의 프로퍼티를 해체하여 변수에 할당할 수 있습니다.
축약형에서는 프로퍼티 이름을 변수 이름으로 쓰고, key: localName 형태로 다른 변수 이름을 지정할 수도 있습니다.
const person = {
name: "Alice",
age: 30,
city: "New York"
};
// 객체 구조 분해 할당
const { name, age } = person;
console.log(name); // 결과: "Alice"
console.log(age); // 결과: 30
// console.log(city); // ReferenceError: city is not defined (city는 할당하지 않음)-
다른 이름으로 할당: 객체의 프로퍼티 이름과 다른 이름으로 변수를 선언하고 싶을 때 사용합니다.
const { name: userName, age: userAge } = person; console.log(userName); // 결과: "Alice" console.log(userAge); // 결과: 30 -
기본값 설정: 할당할 프로퍼티가
undefined일 경우에 대비하여 기본값을 설정할 수 있습니다.const { country = "Korea", age } = person; // person 객체에 country 프로퍼티가 없으므로 기본값 사용 console.log(country); // 결과: "Korea" console.log(age); // 결과: 30 -
나머지 프로퍼티 가져오기 (
...rest): 객체의 나머지 프로퍼티들을 새로운 객체로 묶을 수 있습니다.const { name, ...restInfo } = person; console.log(name); // 결과: "Alice" console.log(restInfo); // 결과: { age: 30, city: "New York" } (새로운 객체) -
중첩된 객체 구조 분해: 중첩된 객체에서 깊숙이 있는 프로퍼티를 한 번에 추출할 수 있습니다.
const userProfile = { id: 1, userInfo: { fullName: "Bob Johnson", email: "[email protected]" }, preferences: { theme: "dark" } }; const { userInfo: { fullName, email }, // userInfo 객체를 분해하여 fullName, email 추출 preferences: { theme: userTheme } // preferences 객체를 분해하여 theme를 userTheme으로 추출 } = userProfile; console.log(fullName); // 결과: "Bob Johnson" console.log(email); // 결과: "[email protected]" console.log(userTheme); // 결과: "dark" -
함수 매개변수에서의 구조 분해: 함수의 매개변수에서 객체나 배열을 직접 구조 분해하여 사용할 수 있어 코드가 더욱 간결해집니다.
// 객체 매개변수 구조 분해 function displayUser({ name, age }) { console.log(`이름: ${name}, 나이: ${age}`); } const myUser = { name: "철수", age: 25 }; displayUser(myUser); // 결과: 이름: 철수, 나이: 25 // 배열 매개변수 구조 분해 function printCoordinates([x, y]) { console.log(`X: ${x}, Y: ${y}`); } const point = [10, 20]; printCoordinates(point); // 결과: X: 10, Y: 20
스프레드 연산자 ...
배열 리터럴과 함수 인수의 스프레드는 이터러블의 순회로 얻은 원소를 펼칩니다. 객체 리터럴의 스프레드는 대상의 열거 가능한 자체 프로퍼티 값을 새 객체에 넣습니다. 모양은 같아도 일반 객체를 배열처럼 순회하는 규칙은 아닙니다.
배열, 객체, 함수의 인자 등 다양한 곳에서 활용됩니다.
배열에서의 스프레드 연산자
-
배열 결합 (Concatenation): 여러 배열을 쉽게 결합할 수 있습니다.
const arr1 = [1, 2]; const arr2 = [3, 4]; const combinedArr = [...arr1, ...arr2]; console.log(combinedArr); // 결과: [1, 2, 3, 4] const newArr = [0, ...arr1, 5, ...arr2]; console.log(newArr); // 결과: [0, 1, 2, 5, 3, 4] -
배열 복사 (Shallow Copy): 원본 배열을 변경하지 않고 새로운 배열의 사본을 만들 수 있습니다. (얕은 복사임에 유의)
const original = [1, 2, 3]; const copy = [...original]; console.log(copy); // 결과: [1, 2, 3] console.log(original === copy); // 결과: false (다른 메모리 주소) copy.push(4); console.log(original); // 결과: [1, 2, 3] (원본 불변) -
배열 요소 추가/삽입: 배열의 특정 위치에 요소를 쉽게 추가하거나 삽입할 수 있습니다.
const baseArray = [1, 2, 3]; const newArrayAtStart = [0, ...baseArray]; // 맨 앞에 추가 console.log(newArrayAtStart); // 결과: [0, 1, 2, 3] const newArrayAtMiddle = [1, ...baseArray.slice(1, 2), 99, ...baseArray.slice(2)]; // 중간에 삽입 (복잡) console.log(newArrayAtMiddle); // 결과: [1, 2, 99, 3] // 실용적으로는 splice나 map, filter를 조합하는 경우가 많음 -
이터러블을 배열로 변환: 이터러블(예: Set, String)을 배열로 쉽게 변환할 수 있습니다. length와 숫자 키만 있는 유사 배열 객체는 iterable이 아니므로, 그 경우에는 Array.from() 같은 변환이 필요합니다.
const myString = "hello"; const charArray = [...myString]; console.log(charArray); // 결과: ["h", "e", "l", "l", "o"] const mySet = new Set([1, 2, 3]); const setToArray = [...mySet]; console.log(setToArray); // 결과: [1, 2, 3]
객체에서의 스프레드 연산자 (ES2018부터)
-
객체 병합 (Object Merging): 여러 객체를 병합하여 새로운 객체를 만들 수 있습니다.
const user = { name: "John", age: 30 }; const address = { city: "Seoul", zip: "12345" }; const details = { job: "Developer" }; const fullProfile = { ...user, ...address, ...details }; console.log(fullProfile); // 결과: { name: 'John', age: 30, city: 'Seoul', zip: '12345', job: 'Developer' }이때 동일한 키가 있으면 뒤에 오는 객체의 값으로 앞의 값을 덮어씁니다.
const objA = { a: 1, b: 2 }; const objB = { b: 3, c: 4 }; const mergedObj = { ...objA, ...objB }; console.log(mergedObj); // 결과: { a: 1, b: 3, c: 4 } (b가 덮어써짐) -
객체 복사 (Shallow Copy): 원본 객체를 변경하지 않고 새로운 객체의 사본을 만들 수 있습니다. (얕은 복사임에 유의)
const originalObj = { x: 1, y: 2 }; const copyObj = { ...originalObj }; console.log(copyObj); // 결과: { x: 1, y: 2 } console.log(originalObj === copyObj); // 결과: false copyObj.x = 10; console.log(originalObj.x); // 결과: 1 (원본 불변) -
객체 프로퍼티 추가/업데이트: 기존 객체에 새로운 프로퍼티를 추가하거나, 특정 프로퍼티 값을 업데이트할 때 유용합니다.
const product = { id: 1, name: "Laptop", price: 1200 }; const updatedProduct = { ...product, price: 1100, category: "Electronics" }; console.log(updatedProduct); // 결과: { id: 1, name: 'Laptop', price: 1100, category: 'Electronics' }
함수 호출 시 스프레드 연산자 (...args)
함수에 배열의 요소들을 개별적인 인자로 전달할 때 사용합니다.
(이때는 나머지 파라미터와는 다름)
function sum(a, b, c) {
return a + b + c;
}
const numbersToSum = [1, 2, 3];
const result = sum(...numbersToSum); // 배열 요소를 개별 인자로 펼쳐서 전달
console.log(result); // 결과: 6위 원시 값 배열·객체 예제에서는 새 바깥 구조를 수정해도 원본 값이 그대로입니다. 그러나 ===는 물리적 메모리 주소 대신 객체의 동일성을 비교하며, 내부 객체 참조를 공유하면 그 내부 변경은 원본에서도 보입니다.
구조 분해와 spread의 복사 경계 — 상태 추적
원본/구조 분해/rest/spread가 같은 settings를 가리켜 내부 theme 변경을 공유한다. 별개의 연산을 직렬이라고 주장하지 않고, 준비된 참조 전체에서 변경 전후 관찰값을 비교하는 trace가 유용하다.
REFERENCE GRAPH
구조 분해와 spread의 복사 경계
구조 분해는 값을 이름에 연결한다. rest와 spread는 새 바깥 객체·배열을 만들지만, 그 안의 객체 참조는 공유한다.
const {name} = source
새 container 만들지 않음
nested identity name 값이 object면 같은 nested reference
const {skip, ...rest} = source
새 container 새 object outer
nested identity nested object는 원본과 공유
const copy = {...source}
새 container 새 object outer
nested identity enumerable own property를 얕게 복사하며 내부 객체 참조는 공유
const copy = [...source]
새 container 새 array outer
nested identity element object identity는 공유
- 연산별 참조 공유 비교
- rest·spread의 새 바깥 구조
새 바깥 구조 · 같은 내부 객체
rest와 spread의 결과는 원본과 다른 객체·배열이다. 내부 객체를 변경하면 공유된 참조를 통해 원본에서도 그 변경이 보일 수 있다.
destructuring은 container 복사 문법이 아니며, rest와 spread도 기본적으로 outer만 새로 만든다. nested graph까지 독립시키지는 않는다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | 새 container: 만들지 않음 |
| 원도표 설명 | nested identity: name 값이 object이면 같은 nested reference |
| 원도표 설명 | 새 container: 새 object outer |
| 원도표 설명 | nested identity: nested object는 원본과 공유 |
| 원도표 설명 | nested identity: enumerable own property를 얕게 복사하며 내부 객체 참조는 공유 |
| 원도표 설명 | 새 container: 새 array outer |
| 원도표 설명 | nested identity: element object identity는 공유 |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 공유 입력 | outer: 서로 다른 결과 가능 / settings: 같은 참조 | source와 독립 복사 결과의 settings는 같은 내부 객체다 | rest/spread는 내부 객체의 깊은 복사가 아니다 |
| 내부 변경 | settings.theme: dark / 독립 outer: 그대로 | 한 경로에서 settings.theme을 바꾼다 | 공유 참조를 통한 변경이다 |
| 공유 결과 | 관찰: 모든 공유 settings dark / 생성 동작: 서로 독립적인 사례 | 다른 경로도 theme:dark를 읽는다 | 여러 복사 문법을 반드시 순차 실행해야 한다는 뜻은 아니다 |
구조 분해와 spread의 복사 경계 — 상태 추적
원본/구조 분해/rest/spread가 같은 settings를 가리켜 내부 theme 변경을 공유한다. 별개의 연산을 직렬이라고 주장하지 않고, 준비된 참조 전체에서 변경 전후 관찰값을 비교하는 trace가 유용하다.
REFERENCE GRAPH
구조 분해와 spread의 복사 경계
구조 분해는 값을 이름에 연결한다. rest와 spread는 새 바깥 객체·배열을 만들지만, 그 안의 객체 참조는 공유한다.
const {name} = source
새 container 만들지 않음
nested identity name 값이 object면 같은 nested reference
const {skip, ...rest} = source
새 container 새 object outer
nested identity nested object는 원본과 공유
const copy = {...source}
새 container 새 object outer
nested identity enumerable own property를 얕게 복사하며 내부 객체 참조는 공유
const copy = [...source]
새 container 새 array outer
nested identity element object identity는 공유
- 연산별 참조 공유 비교
- rest·spread의 새 바깥 구조
새 바깥 구조 · 같은 내부 객체
rest와 spread의 결과는 원본과 다른 객체·배열이다. 내부 객체를 변경하면 공유된 참조를 통해 원본에서도 그 변경이 보일 수 있다.
destructuring은 container 복사 문법이 아니며, rest와 spread도 기본적으로 outer만 새로 만든다. nested graph까지 독립시키지는 않는다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | 새 container: 만들지 않음 |
| 원도표 설명 | nested identity: name 값이 object이면 같은 nested reference |
| 원도표 설명 | 새 container: 새 object outer |
| 원도표 설명 | nested identity: nested object는 원본과 공유 |
| 원도표 설명 | nested identity: enumerable own property를 얕게 복사하며 내부 객체 참조는 공유 |
| 원도표 설명 | 새 container: 새 array outer |
| 원도표 설명 | nested identity: element object identity는 공유 |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 공유 입력 | outer: 서로 다른 결과 가능 / settings: 같은 참조 | source와 독립 복사 결과의 settings는 같은 내부 객체다 | rest/spread는 내부 객체의 깊은 복사가 아니다 |
| 내부 변경 | settings.theme: dark / 독립 outer: 그대로 | 한 경로에서 settings.theme을 바꾼다 | 공유 참조를 통한 변경이다 |
| 공유 결과 | 관찰: 모든 공유 settings dark / 생성 동작: 서로 독립적인 사례 | 다른 경로도 theme:dark를 읽는다 | 여러 복사 문법을 반드시 순차 실행해야 한다는 뜻은 아니다 |
기본값이 적용되는 조건
기본값은 값이 없거나 undefined일 때만 적용됩니다. null은 그대로 남으며, 분해할 대상 자체가 null 또는 undefined이면 프로퍼티를 읽을 수 없어 TypeError가 발생합니다. 중첩 패턴도 중간 객체가 존재해야 합니다.
const { theme: first = "light" } = { theme: undefined };
const { theme: second = "light" } = { theme: null };
console.log(first, second);출력은 light null입니다. null도 기본값으로 바꿔야 하는 요구라면 구조 분해의 =만으로 해결되는지 먼저 검토해야 합니다.
예시 바깥 객체와 내부 설정 객체의 동일성을 따로 확인해 봅시다.
const originalSettings = { name: "원본", settings: { theme: "dark" } };
const { settings } = originalSettings;
const copiedSettings = { ...originalSettings, name: "복사본" };
copiedSettings.settings.theme = "light";
console.log(originalSettings === copiedSettings);
console.log(settings === copiedSettings.settings);
console.log(originalSettings.name, originalSettings.settings.theme);출력은 false, true, 원본 light입니다. 스프레드는 새 바깥 객체를 만들지만 settings의 값은 같은 내부 객체에 대한 참조입니다. 구조 분해 역시 그 내부 객체를 복제하지 않습니다.
스스로 확인하기
복사 선언을 const copiedSettings = { ...originalSettings, settings: { ...originalSettings.settings }, name: "복사본" };로 바꾼 뒤 처음부터 실행하면 세 출력은 어떻게 달라지나요? 이 변경이 모든 깊이의 복사를 보장하는지도 생각해 보세요.
풀이 확인
false, false, 원본 dark입니다. 이 예제의 설정 객체까지 새로 만들었으므로 theme 변경은 복사본에만 적용됩니다. 더 깊은 객체가 있다면 그 참조는 다시 공유할 수 있어, 전체 깊은 복사라고 일반화하지 않습니다.
기본값과 대상 조건은 MDN 구조 분해 설명, 펼치는 위치별 계약은 MDN 스프레드 설명에서 확인할 수 있습니다.
구조 분해와 스프레드 정리
이번 장에서는 ES2015(ES6)에서 도입된 매우 강력하고 유용한 문법인 구조 분해 할당(Destructuring Assignment)과 스프레드 연산자(Spread Operator)에 대해 심도 있게 학습했습니다.
구조 분해 할당은 배열과 객체에서 필요한 값만 꺼낼 때 사용합니다.
스프레드 연산자는 배열 결합, 얕은 복사, 객체 병합, 함수 인자 전달처럼 값을 펼쳐 전달해야 하는 상황에서 자주 쓰입니다.
이 두 기능은 데이터를 꺼내고 합치는 코드를 짧게 만들지만, 원본 변경 여부와 얕은 복사 범위는 항상 함께 확인해야 합니다.
예제를 직접 바꿔 보며 배열과 객체에서 어떤 값이 새로 만들어지는지 확인해 보세요.
구조 분해와 스프레드는 객체와 배열을 직접 바꾸지 않고 필요한 값만 꺼내거나 새 값으로 조합하는 데 자주 쓰입니다.
마지막으로 상태나 응답 데이터를 다룰 때는 어떤 값이 추출되고 어떤 값이 새 객체로 복사되는지 한 흐름으로 확인합니다.