구조분해와 스프레드
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**의 정체를 밝히고, 프로토타입과 클래스를 본다.