학습 자료

구조분해와 스프레드


Article

객체에서 값을 꺼낼 때마다 이렇게 쓰면 지겹다.

const user = { name: "지원", age: 27, city: "서울" };

const name = user.name;
const age = user.age;

한 줄로 끝낼 수 있다. 구조분해 할당이다.

객체 구조분해

const user = { name: "지원", age: 27, city: "서울" };

const { name, age } = user;

console.log(name, age);
콘솔
  • 지원 27

왼쪽 중괄호 안의 이름이 키 이름과 같아야 한다. 순서는 상관없다. 필요한 것만 꺼내면 된다.

없는 키를 꺼내면 undefined다. 기본값을 줄 수 있다.

const user = { name: "지원" };

const { name, age = 20, city = "미상" } = user;

console.log(name, age, city);
콘솔
  • 지원 20 미상

기본값은 값이 undefined일 때만 쓰인다. 10강의 함수 기본값과 같은 규칙이다.

이름을 바꿔 받을 수도 있다. 변수명이 겹칠 때 유용하다.

const user = { name: "지원" };

const { name: userName } = user;

console.log(userName);
console.log(typeof name);
콘솔
  • 지원
  • 'undefined'

name이라는 변수는 만들어지지 않았다. userName으로 받았기 때문이다.

배열 구조분해

배열은 이름이 없으니 순서로 꺼낸다.

const colors = ["빨강", "파랑", "초록"];

const [first, second] = colors;
const [, , third] = colors;

console.log(first, second, third);
콘솔
  • 빨강 파랑 초록

쉼표만 남기면 그 자리를 건너뛴다. 17강 마지막에 나온 ([, score])가 이것이다.

값 교환도 한 줄이다.

let a = 1;
let b = 2;

[a, b] = [b, a];

console.log(a, b);
콘솔
  • 2 1

임시 변수가 필요 없다.

함수에서 쓴다

가장 많이 만나는 자리다. 매개변수에서 바로 분해한다.

function printUser({ name, age = 0 }) {
  console.log(`${name} (${age}세)`);
}

printUser({ name: "지원", age: 27 });
printUser({ name: "민수" });
콘솔
  • 지원 (27세)
  • 민수 (0세)

10강에서 봤던 "인자 순서가 바뀌면 뜻이 무너지는" 문제가 사라진다. 이름으로 넘기니 순서가 상관없다.

10강에서 미뤄둔 "여러 값 반환"도 이제 가능하다.

function getMinMax(numbers) {
  return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax([3, 1, 8, 5]);
console.log(min, max);
콘솔
  • 1 8

...numbers가 다음 주제다.

스프레드 — 펼친다

...을 붙이면 배열이나 객체가 낱개로 펼쳐진다.

const numbers = [3, 1, 8];

console.log(Math.max(numbers));
console.log(Math.max(...numbers));
console.log(Math.max(3, 1, 8));
콘솔
  • NaN
  • 8
  • 8

Math.max는 인자를 낱개로 받는다. 배열째 넘기면 계산하지 못해 NaN이 나온다. ...으로 펼쳐야 한다.

배열을 합치고 복사할 때 쓴다.

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

console.log([...a, ...b]);
console.log([0, ...a, 99]);

const copy = [...a];
copy.push(999);
console.log(a, copy);
콘솔
  • [1, 2, 3, 4]
  • [0, 1, 2, 99]
  • [1, 2] [1, 2, 999]

객체도 된다. 뒤에 오는 것이 앞을 덮어쓴다.

const base = { name: "지원", age: 27 };

const updated = { ...base, age: 28 };
const merged = { ...base, city: "서울" };

console.log(updated);
console.log(merged);
console.log(base);
콘솔
  • { name: '지원', age: 28 }
  • { name: '지원', age: 27, city: '서울' }
  • { name: '지원', age: 27 }

원본을 건드리지 않고 바뀐 새 객체를 만들었다. React 같은 라이브러리에서 상태를 갱신하는 기본 방식이다.

rest — 나머지를 모은다

같은 ...인데 자리가 다르면 뜻이 반대다.

같은 점 셋, 다른 역할

스프레드

오른쪽 — 펼친다

  • [...arr], { ...obj }
  • 하나를 여러 개로
  • 복사·병합·인자 전달

rest

왼쪽 — 모은다

  • const [a, ...rest] = arr
  • 여러 개를 하나로
  • 남은 것을 배열·객체로 묶는다
const [first, ...others] = [1, 2, 3, 4];
console.log(first, others);

const { name, ...info } = { name: "지원", age: 27, city: "서울" };
console.log(name, info);
콘솔
  • 1 [2, 3, 4]
  • 지원 { age: 27, city: '서울' }

함수에서도 쓴다. 인자를 몇 개 받을지 모를 때다.

function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}

console.log(sum(1, 2));
console.log(sum(1, 2, 3, 4, 5));
콘솔
  • 3
  • 15

넘어온 인자들이 numbers 배열로 묶인다. rest항상 마지막 자리에 와야 한다.

얕은 복사와 깊은 복사

4강과 15강에서 미뤄둔 문제를 끝낸다.

const original = {
  name: "지원",
  address: { city: "서울" },
};

const copy = { ...original };

copy.name = "민수";
copy.address.city = "부산";

console.log(original.name);
console.log(original.address.city);
콘솔
  • '지원'
  • '부산'

name은 지켜졌는데 address.city는 원본까지 바뀌었다.

스프레드는 한 겹만 복사한다. name은 문자열(원시 타입)이라 값이 복사됐지만, address는 객체라 주소가 복사됐다. 원본과 복사본이 같은 address를 가리킨다.

얕은 복사 후

address 는 여전히 하나다

original.address
0x9f1a
{ city: '부산' }
copy.address
0x9f1a
같은 객체를 가리킨다

전부 복사하려면 깊은 복사를 한다.

const original = { name: "지원", address: { city: "서울" } };

const deep = structuredClone(original);
deep.address.city = "부산";

console.log(original.address.city);
console.log(deep.address.city);
콘솔
  • '서울'
  • '부산'

structuredClone()은 브라우저와 Node.js가 제공하는 내장 함수다. 중첩된 것까지 전부 새로 만든다.

방법깊이함수 복사비고
{ ...obj }한 겹가능가장 흔하게 쓴다
Object.assign({}, obj)한 겹가능스프레드 이전 방식
structuredClone(obj)전부불가능함수가 있으면 에러
JSON.parse(JSON.stringify(obj))전부불가능undefined·Date 가 깨진다

깊은 복사는 비싸다. 대부분의 경우는 얕은 복사로 충분하도록 구조를 얕게 유지하는 편이 낫다.

정리하면

  • 구조분해는 필요한 값만 꺼내 변수로 만든다. 객체는 이름으로, 배열은 순서로 꺼낸다.
  • 기본값은 값이 undefined일 때만 쓰인다. { key: newName }은 이름을 바꿔 받는 문법이다.
  • 함수 매개변수를 구조분해하면 인자 순서에서 자유로워진다.
  • **스프레드 ...**는 펼치고, 같은 기호가 왼쪽에 오면 rest로 모은다.
  • { ...obj }얕은 복사다. 중첩된 객체는 여전히 공유된다.
  • 전부 복사하려면 structuredClone()을 쓴다. JSON을 거치는 방법은 값을 잃는다.

Part 5가 끝났다. 다음 강의부터 마지막 Part다. 17강에서 흘려 넘긴 **this**의 정체를 밝히고, 프로토타입과 클래스를 본다.

구조분해와 스프레드 — 디코드랩(DCODELAB)