배열 구조 분해, 객체 배열 CRUD 패턴, 중첩 데이터 처리, 얕은·깊은 복사 차이, JSON 변환까지 실전 복합 데이터 구조를 완전히 다룹니다.
🎯 학습 목표
이 강을 마치면 다음을 할 수 있습니다.
- 배열 구조 분해 할당과 나머지 패턴을 활용할 수 있습니다.
- 객체 배열에서 CRUD(추가·읽기·수정·삭제) 패턴을 구현할 수 있습니다.
- 얕은 복사와 깊은 복사의 차이를 이해하고 상황에 맞게 사용할 수 있습니다.
JSON.stringify/JSON.parse로 데이터를 직렬화하고 역직렬화할 수 있습니다.
🔧 배열 구조 분해 할당
이 섹션에서는 14강에서 배운 객체 구조 분해에 이어, 배열에서도 값을 바로 변수로 꺼내는 배열 구조 분해를 살펴보겠습니다.
배열 구조 분해 할당은 객체와 달리 키 이름이 아닌 순서(위치)를 기준으로 값을 추출합니다. 대괄호 []를 사용합니다.
const rgb = [255, 128, 0];
// 기존 방식
const r1 = rgb[0];
const g1 = rgb[1];
const b1 = rgb[2];
// 배열 구조 분해
const [r, g, b] = rgb;
console.log(r, g, b); // 255 128 0
// 특정 요소 건너뛰기 (쉼표로)
const [first, , third] = ["a", "b", "c"];
console.log(first, third); // "a" "c"
// 나머지 패턴 — rest
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// 기본값 설정
const [x = 0, y = 0, z = 0] = [10, 20];
console.log(x, y, z); // 10 20 0
// 두 변수 값 교환 — 구조 분해의 우아한 활용
let a = 1, b2 = 2;
[a, b2] = [b2, a];
console.log(a, b2); // 2 1
// 함수에서 여러 값 반환 패턴
function getMinMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = getMinMax([3, 1, 7, 2, 9]);
console.log(min, max); // 1 9
배열 구조 분해에서 특히 두 변수 값 교환 패턴은 매우 우아합니다. 기존에는 임시 변수가 필요했지만, 구조 분해로 한 줄에 처리할 수 있습니다. 함수가 배열을 반환할 때 이를 바로 구조 분해로 받는 패턴도 실무에서 자주 사용됩니다. Math.min(...arr)처럼 스프레드 연산자로 배열을 개별 인수로 펼치는 것도 11강에서 배운 개념입니다.
🗃️ 객체 배열 CRUD 패턴
이 섹션에서는 실무에서 가장 자주 다루는 데이터 구조인 객체 배열에서 항목을 추가·수정·삭제하는 표준 패턴을 살펴보겠습니다.
실제 애플리케이션의 데이터는 대부분 객체 배열(Object Array) 형태입니다. 할 일 목록, 상품 목록, 사용자 목록처럼 같은 구조의 여러 항목을 배열로 관리합니다. 이 데이터를 조작하는 CRUD 패턴을 익혀두면 어떤 기능도 구현할 수 있습니다. 특히 불변성을 유지하는 방식으로 작성하면 React 같은 프레임워크에서도 그대로 사용할 수 있습니다.
let todos = [
{ id: 1, text: "자바스크립트 공부", done: true },
{ id: 2, text: "운동하기", done: false },
{ id: 3, text: "독서 30분", done: false }
];
// C — Create: 새 항목 추가
function addTodo(text) {
const newId = todos.length ? Math.max(...todos.map(t => t.id)) + 1 : 1;
const newTodo = { id: newId, text, done: false }; // 단축 속성명 활용
todos = [...todos, newTodo]; // 스프레드로 불변 추가
}
addTodo("리액트 공부");
console.log(todos.length); // 4
// R — Read: 조건으로 읽기
const doneTodos = todos.filter(t => t.done);
console.log(doneTodos.map(t => t.text)); // ["자바스크립트 공부"]
const todo = todos.find(t => t.id === 2);
console.log(todo.text); // "운동하기"
// U — Update: 특정 항목 수정
function toggleTodo(id) {
todos = todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t // 해당 id만 done 토글
);
}
toggleTodo(2);
console.log(todos.find(t => t.id === 2).done); // true
// D — Delete: 특정 항목 삭제
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
}
deleteTodo(3);
console.log(todos.length); // 3
이 패턴의 핵심은 불변성(Immutability)입니다. 직접 수정하는 대신 항상 새로운 배열이나 객체를 만들어 변수에 재할당합니다. 수정 패턴에서 t.id === id ? { ...t, done: !t.done } : t는 해당 항목만 새 객체로 교체하고 나머지는 원본 참조를 유지하는 효율적인 방식입니다. 삭제는 filter로 해당 항목만 빼고 새 배열을 만들기 때문에 원본에 접근조차 하지 않습니다. 이 세 가지 패턴은 앞으로 어떤 프레임워크를 사용하든 핵심 데이터 조작 방법입니다.
🏗️ 중첩 데이터 구조 처리
이 섹션에서는 객체 안에 배열, 배열 안에 객체처럼 중첩된 복잡한 데이터 구조를 다루는 방법을 살펴보겠습니다.
실제 API 데이터는 여러 단계로 중첩된 복잡한 구조를 가집니다. 예를 들어 회사 → 부서 배열 → 직원 배열처럼 계층적으로 구성됩니다. 이를 올바르게 읽고 처리하는 방법을 익혀보겠습니다.
const company = {
name: "테크 코리아",
departments: [
{
name: "개발팀",
employees: [
{ name: "홍길동", salary: 5000000 },
{ name: "김철수", salary: 4500000 }
]
},
{
name: "디자인팀",
employees: [
{ name: "이영희", salary: 4800000 },
{ name: "박민준", salary: 4200000 }
]
}
]
};
// 중첩 접근
console.log(company.departments[0].name); // "개발팀"
console.log(company.departments[1].employees[0].name); // "이영희"
// 전체 직원 수
const totalCount = company.departments.reduce(
(acc, dept) => acc + dept.employees.length, 0
);
console.log(totalCount); // 4
// 모든 직원 평균 급여
const allSalaries = company.departments
.flatMap(dept => dept.employees) // 중첩 배열 펼치기 — 4명의 배열
.map(emp => emp.salary); // 급여만 추출
const avgSalary = allSalaries.reduce((a, b) => a + b, 0) / allSalaries.length;
console.log(`평균 급여: ${avgSalary.toLocaleString()}원`); // 4,625,000원
// 옵셔널 체이닝 — 중첩 접근 시 안전하게
const user2 = null;
console.log(user2?.address?.city); // undefined (TypeError 없음)
flatMap은 map을 적용한 후 한 단계 펼치는(flat) 동작을 동시에 수행합니다. departments.map(d => d.employees)의 결과는 배열 안의 배열이지만, flatMap을 쓰면 하나의 배열로 펼쳐집니다. 중첩 배열에서 개별 요소 전체를 한 번에 처리할 때 매우 유용합니다. 옵셔널 체이닝(Optional Chaining) 연산자 ?.는 중첩 데이터 접근 시 중간 값이 null이나 undefined이면 오류 대신 undefined를 반환하므로, API 응답처럼 존재하지 않을 수 있는 데이터에 안전하게 접근할 수 있습니다.
📋 얕은 복사 vs 깊은 복사
이 섹션에서는 중첩 객체·배열에서 복사의 한계와 깊은 복사 방법을 살펴보겠습니다.
11강과 14강에서 스프레드 연산자로 복사하는 방법을 배웠습니다. 그런데 이 방법에는 중요한 한계가 있습니다. 얕은 복사(Shallow Copy)는 1단계 속성만 새로 만들고, 중첩된 객체·배열은 여전히 같은 참조를 공유합니다. 이 차이를 이해하지 못하면 예상치 못한 버그가 생깁니다.
const original = {
name: "홍길동",
scores: [90, 85, 92], // 중첩 배열
address: { city: "서울" } // 중첩 객체
};
// 얕은 복사 — 스프레드
const shallow = { ...original };
shallow.name = "김철수"; // 기본값 — 독립적 변경 OK
shallow.scores.push(88); // 중첩 배열 수정
shallow.address.city = "부산"; // 중첩 객체 수정
console.log(original.name); // "홍길동" (기본값은 독립)
console.log(original.scores); // [90, 85, 92, 88] 원본도 변함!
console.log(original.address.city); // "부산" 원본도 변함!
// 깊은 복사 방법 1: JSON 직렬화
const deep1 = JSON.parse(JSON.stringify(original));
deep1.scores.push(77);
console.log(original.scores); // [90, 85, 92, 88] 원본 유지
// 주의: 함수, undefined, Symbol, Date 객체는 올바르게 복사되지 않음
// 깊은 복사 방법 2: structuredClone (ES2022 표준, 권장)
const deep2 = structuredClone(original);
deep2.address.city = "대구";
console.log(original.address.city); // "부산" 원본 유지
// 더 많은 타입을 지원하며 현대 브라우저와 Node.js 17+에서 사용 가능
얕은 복사의 한계를 이해하는 것은 매우 중요합니다. 대부분의 실무 상황에서는 스프레드 연산자의 얕은 복사로 충분합니다. CRUD 패턴에서 { ...t, done: !t.done }처럼 사용할 때, 수정하는 속성이 기본값(문자열, 숫자, 불리언)이라면 얕은 복사가 안전합니다. 중첩 객체·배열을 독립적으로 수정해야 할 때만 깊은 복사를 사용하세요. structuredClone()이 없는 구형 환경에서는 JSON 방식을 사용하되, 함수나 날짜 객체가 있을 때는 각별히 주의해야 합니다.
🔄 JSON — 데이터 직렬화
이 섹션에서는 자바스크립트 객체를 서버와 주고받을 때 사용하는 JSON 형식과 변환 방법을 살펴보겠습니다.
JSON(JavaScript Object Notation)은 자바스크립트 객체 리터럴에서 파생된 데이터 교환 형식입니다. 서버 API와 데이터를 주고받을 때 사실상 표준으로 사용됩니다. 자바스크립트 객체와 매우 비슷하게 생겼지만, 차이점이 있습니다. JSON에서는 모든 키가 큰따옴표로 감싸져야 하고, 함수·undefined·Symbol은 표현할 수 없습니다.
const user = {
name: "홍길동",
age: 28,
hobbies: ["독서", "코딩"]
};
// 객체 → JSON 문자열 (직렬화)
const jsonStr = JSON.stringify(user);
console.log(jsonStr);
// '{"name":"홍길동","age":28,"hobbies":["독서","코딩"]}'
console.log(typeof jsonStr); // "string" — 문자열!
// 보기 좋게 들여쓰기 (디버깅·로그 확인에 유용)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
// "name": "홍길동",
// "age": 28,
// "hobbies": ["독서", "코딩"]
// }
// JSON 문자열 → 객체 (역직렬화)
const parsed = JSON.parse(jsonStr);
console.log(parsed.name); // "홍길동"
console.log(typeof parsed); // "object"
// JSON의 제한 확인
const withSpecial = {
name: "홍길동",
greet: function() { return "안녕!"; }, // 함수는 직렬화 시 사라짐
value: undefined, // undefined도 사라짐
createdAt: new Date() // Date는 ISO 문자열로 변환
};
console.log(JSON.stringify(withSpecial));
// '{"name":"홍길동","createdAt":"2024-01-01T00:00:00.000Z"}'
// greet, value 속성은 결과에 포함되지 않음
JSON 직렬화는 실무에서 매우 자주 사용됩니다. 26강에서 fetch로 서버 API를 호출할 때, 요청 body를 JSON.stringify()로 변환하고 응답을 response.json()으로 역직렬화하는 과정이 핵심입니다. 또한 localStorage는 문자열만 저장할 수 있어 객체를 저장할 때 JSON 직렬화가 필수입니다. 이 내용은 27강에서 자세히 다룹니다.
📝 핵심 요약
15강에서 배운 내용을 정리해 보겠습니다.
| 개념 | 핵심 내용 |
|---|---|
| 배열 구조 분해 | 위치 기준 추출, 나머지 패턴(...rest), 기본값, 값 교환 |
| 객체 배열 추가 | [...arr, newItem] — 스프레드로 불변 추가 |
| 객체 배열 수정 | map + { ...item, field: value } |
| 객체 배열 삭제 | filter(item => item.id !== targetId) |
| flatMap | map + flat 한 번에, 중첩 배열을 펼치며 변환 |
| 얕은 복사 | 스프레드/slice — 1단계만 복사, 중첩은 참조 공유 |
| 깊은 복사 | structuredClone()(권장) 또는 JSON.parse(JSON.stringify()) |
| JSON 변환 | JSON.stringify(obj) ↔ JSON.parse(str) |
이로써 2단계(배열·객체, 11~15강)가 완성되었습니다! 다음 강인 16강 — DOM이란부터는 3단계가 시작됩니다. 지금까지 배운 모든 자바스크립트 기초가 실제 웹 페이지와 연결되는 순간입니다. DOM을 통해 HTML 요소를 선택하고 조작하는 방법을 배우면 진짜 인터랙티브 웹 페이지를 만들 수 있게 됩니다!
관련 주제
- 배열 구조 분해
- 나머지 패턴
- 객체 배열 CRUD
- 얕은 복사 깊은 복사
- JSON.stringify parse
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 자바스크립트 기초 30강 — 웹을 움직이는 언어
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
자바스크립트 기초 30강 — 웹을 움직이는 언어
이 강의가 속한 시리즈는 총 22강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
