객체와 배열 기초
객체의 속성·메서드와 배열의 순서 있는 요소를 생성·조회·수정·삭제하며 복합 데이터를 구조화합니다.
앞선 절들에서는 숫자, 문자열, 불리언 같은 기본 데이터 타입과 변수 저장 방식을 배웠습니다.
하지만 실제 웹 애플리케이션에서는 사용자 정보, 상품 목록, 게시글처럼 연관된 데이터를 묶어 관리해야 하는 경우가 훨씬 많습니다.
이런 복합 데이터를 효율적으로 관리하기 위해 자바스크립트는 객체(Object)와 배열(Array)을 제공합니다.
이번 장에서는 객체/배열의 생성, 데이터 추가, 접근, 조작의 기본 흐름을 정리합니다.
객체·배열: 참조 공유와 복사 범위 — 상태 추적
alias·얕은 복사·structuredClone의 바깥/profile 동일성이 이후 속성 변경에 영향을 준다. 객체를 고정한 채 변경의 공유 범위를 보여 줄 가치가 있다.
DATA-FLOW
객체·배열: 참조 공유와 복사 범위
대입은 같은 객체를 공유하고, 얕은 복사는 바깥 객체만 새로 만든다. 중첩 객체까지 따로 변경하려면 그 참조도 복사해야 한다. 아래 번호는 일반 객체 예시의 동일성을 구분한다.
alias = source
바깥 객체: 같은 #1
중첩·변경 영향: 같은 #9 · mutation이 원본에 즉시 보임
{...source} / [...source]
바깥 객체: 새 #2
중첩·변경 영향: nested #9는 공유 · nested mutation은 원본에도 보임
map/filter/spread
바깥 객체: 새 배열 outer
중첩·변경 영향: map/filter의 결과는 새 배열이지만 element 객체는 공유될 수 있음
push/splice/property set
바깥 객체: 기존 outer 유지
중첩·변경 영향: 기존 graph를 직접 변경
structuredClone · 일반 객체 예시
바깥 객체: 새 #3
중첩·변경 영향: nested도 새 #10 · 비용과 지원 타입 확인
새 바깥 객체가 모든 중첩 객체의 독립을 뜻하지는 않는다
위 얕은 복사의 nested #9는 원본과 같다. 배열도 push/splice는 원본을 바꾸고, map/filter/spread는 새 바깥 배열을 만들지만 객체 요소가 공유될 수 있다. map의 콜백 자체는 원본을 변경할 수도 있다. structuredClone의 지원 타입·비용을 확인하며 함수는 복제할 수 없다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 원본과 별칭 | 원본: #1 → #9 / 별칭: #1 → #9 | 원본 outer#1/nested#9를 별칭이 공유한다 | 대입은 객체를 복제하지 않는다 |
| 얕은 복사 | 원본: #1 → #9 / shallow: #2 → #9 | 새 outer#2도 nested#9를 가리킨다 | 바깥 참조만 새로 생긴다 |
| 깊은 복사와 변경 | deep: #3 → #10 / 공유 nested: #9 변경 반영 | deep outer#3/nested#10은 독립하고 nested#9 변경은 공유 쪽에 보인다 | 복사 깊이가 변경 전파 범위를 정한다 |
객체·배열: 참조 공유와 복사 범위 — 상태 추적
alias·얕은 복사·structuredClone의 바깥/profile 동일성이 이후 속성 변경에 영향을 준다. 객체를 고정한 채 변경의 공유 범위를 보여 줄 가치가 있다.
DATA-FLOW
객체·배열: 참조 공유와 복사 범위
대입은 같은 객체를 공유하고, 얕은 복사는 바깥 객체만 새로 만든다. 중첩 객체까지 따로 변경하려면 그 참조도 복사해야 한다. 아래 번호는 일반 객체 예시의 동일성을 구분한다.
alias = source
바깥 객체: 같은 #1
중첩·변경 영향: 같은 #9 · mutation이 원본에 즉시 보임
{...source} / [...source]
바깥 객체: 새 #2
중첩·변경 영향: nested #9는 공유 · nested mutation은 원본에도 보임
map/filter/spread
바깥 객체: 새 배열 outer
중첩·변경 영향: map/filter의 결과는 새 배열이지만 element 객체는 공유될 수 있음
push/splice/property set
바깥 객체: 기존 outer 유지
중첩·변경 영향: 기존 graph를 직접 변경
structuredClone · 일반 객체 예시
바깥 객체: 새 #3
중첩·변경 영향: nested도 새 #10 · 비용과 지원 타입 확인
새 바깥 객체가 모든 중첩 객체의 독립을 뜻하지는 않는다
위 얕은 복사의 nested #9는 원본과 같다. 배열도 push/splice는 원본을 바꾸고, map/filter/spread는 새 바깥 배열을 만들지만 객체 요소가 공유될 수 있다. map의 콜백 자체는 원본을 변경할 수도 있다. structuredClone의 지원 타입·비용을 확인하며 함수는 복제할 수 없다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 원본과 별칭 | 원본: #1 → #9 / 별칭: #1 → #9 | 원본 outer#1/nested#9를 별칭이 공유한다 | 대입은 객체를 복제하지 않는다 |
| 얕은 복사 | 원본: #1 → #9 / shallow: #2 → #9 | 새 outer#2도 nested#9를 가리킨다 | 바깥 참조만 새로 생긴다 |
| 깊은 복사와 변경 | deep: #3 → #10 / 공유 nested: #9 변경 반영 | deep outer#3/nested#10은 독립하고 nested#9 변경은 공유 쪽에 보인다 | 복사 깊이가 변경 전파 범위를 정한다 |
객체: 연관된 데이터를 묶는 컨테이너
앞 절의 변수·함수·반복을 사용해 관련 값을 묶습니다. 사용자 한 명의 이름·나이처럼 이름으로 찾는 정보는 객체, 여러 사용자를 순서대로 처리하는 목록은 배열로 표현할 수 있습니다. 객체를 다른 변수에 대입하는 것과 새 객체로 복사하는 것은 다릅니다.
객체는 여러 개의 속성(property)을 담는 컨테이너라고 할 수 있습니다.
각 속성은 이름(key)과 값(value)의 쌍으로 이루어져 있습니다.
마치 사전에서 단어(key)와 그 의미(value)가 짝을 이루는 것과 같습니다.
객체는 특정한 것에 대한 정보를 구조화하여 표현할 때 매우 유용합니다.
객체 생성하기
가장 일반적인 객체 생성 방법은 중괄호 {}를 사용하는 객체 리터럴(Object Literal) 방식입니다.
// 사용자 정보를 담는 객체
let user = {
name: "김민준", // 'name'은 속성 이름(키), "김민준"은 속성 값
age: 28, // 'age'는 숫자 타입 값
isStudent: false, // 'isStudent'는 불리언 타입 값
email: "[email protected]",
"favorite-color": "blue" // 속성 이름에 하이픈이 포함되면 따옴표로 묶어야 합니다.
};
console.log(user);
/*
결과:
{
name: '김민준',
age: 28,
isStudent: false,
email: '[email protected]',
'favorite-color': 'blue'
}
*/각 속성 이름(키)과 값은 콜론(:)으로 구분하며, 각 속성 쌍은 쉼표(,)로 구분합니다. 일반 객체의 속성 키는 문자열 또는 Symbol입니다. 위 예제는 모두 문자열 키이며, 숫자 모양의 키도 문자열로 처리됩니다.
객체 속성 접근하기
객체에 저장된 속성 값에 접근하는 방법은 두 가지가 있습니다.
점 표기법 (Dot Notation): 가장 흔하게 사용되는 방법으로, 객체이름.속성_이름 형식으로 접근합니다.
속성 이름이 유효한 자바스크립트 식별자(변수 이름 규칙과 유사)일 때 사용합니다.
console.log(user.name); // 결과: 김민준
console.log(user.age); // 결과: 28
console.log(user.email); // 결과: [email protected]대괄호 표기법 (Bracket Notation): 객체이름['속성_이름'] 형식으로 접근합니다.
속성 이름이 변수에 저장되어 있거나, 하이픈(-)과 같이 유효하지 않은 식별자 문자를 포함할 때 사용합니다.
console.log(user['name']); // 결과: 김민준 (점 표기법과 동일한 결과)
console.log(user['favorite-color']); // 결과: blue (하이픈 때문에 대괄호 필수)
let keyName = "age";
console.log(user[keyName]); // 결과: 28 (변수를 통해 속성 이름에 접근)user[keyName]은 변수 keyName의 값인 "age"를 키로 사용합니다. user.keyName은 글자 그대로 "keyName"이라는 속성을 찾으므로 이 객체에서는 undefined입니다. 위 속성 조회는 생성한 user를 같은 예제 안에서 이어 사용합니다.
객체 속성 추가 및 변경하기
객체에 새로운 속성을 추가하거나 기존 속성의 값을 변경할 수 있습니다.
let product = {
name: "노트북",
price: 1200000
};
// 속성 추가
product.manufacturer = "XYZ전자"; // 새로운 manufacturer 속성 추가
product['release-date'] = "2024-05-01"; // 대괄호 표기법으로 속성 추가
console.log(product);
/*
결과:
{
name: '노트북',
price: 1200000,
manufacturer: 'XYZ전자',
'release-date': '2024-05-01'
}
*/
// 속성 값 변경
product.price = 1150000; // 기존 price 속성 값 변경
console.log(product.price); // 결과: 1150000객체 속성 삭제하기
delete 연산자를 사용하여 객체의 특정 속성을 제거할 수 있습니다.
let student = {
id: 101,
name: "박지성",
grade: 3
};
console.log(student); // { id: 101, name: '박지성', grade: 3 }
delete student.grade; // grade 속성 삭제
console.log(student); // { id: 101, name: '박지성' }
// delete student['id']; // 대괄호 표기법으로도 삭제 가능객체 내의 함수 (메서드)
객체의 속성 값으로는 일반적인 데이터뿐만 아니라 함수도 올 수 있습니다.
객체 내부에 정의된 함수를 메서드(Method)라고 부릅니다.
메서드는 해당 객체의 데이터를 조작하거나 객체와 관련된 동작을 수행할 때 사용됩니다.
let person = {
name: "이수진",
age: 22,
// sayHello라는 메서드 정의
sayHello: function() {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
// this는 현재 객체(여기서는 person)를 가리킵니다.
},
// ES6에서 도입된 간결한 메서드 정의 문법
introduce() {
console.log(`저는 ${this.age}살 ${this.name}입니다.`);
}
};
person.sayHello(); // 결과: 안녕하세요, 제 이름은 이수진입니다.
person.introduce(); // 결과: 저는 22살 이수진입니다.위의 person.sayHello()처럼 일반 함수를 객체.메서드() 형식으로 호출하면 this는 그 호출의 객체를 가리킵니다. 함수를 따로 변수에 꺼내 sayHello()로 호출하면 자동으로 person을 가리키지 않습니다. 화살표 함수도 자기 this를 만들지 않으므로 같은 방식의 메서드로 무조건 교체하면 안 됩니다.
이는 객체 내부의 다른 속성 값에 접근할 때 매우 유용합니다.
배열: 순서가 있는 데이터 목록
배열(Array)은 여러 개의 값을 순서대로 나열하여 저장하는 특별한 형태의 객체입니다.
객체가 키-값 쌍으로 데이터를 저장하는 반면, 배열은 순서(index)를 기반으로 데이터를 관리합니다.
같은 종류의 데이터를 여러 개 모아둘 때 주로 사용됩니다.
배열 생성하기
배열은 대괄호 []를 사용하여 생성하며, 각 요소는 쉼표(,)로 구분합니다.
// 숫자 배열
let numbers = [10, 20, 30, 40, 50];
// 문자열 배열
let fruits = ["사과", "바나나", "오렌지", "포도"];
// 다양한 타입의 요소를 포함하는 배열 (권장하지는 않음)
let mixedArray = ["Hello", 123, true, { id: 1 }];
// 빈 배열
let emptyArray = [];
console.log(numbers); // 결과: [ 10, 20, 30, 40, 50 ]
console.log(fruits); // 결과: [ '사과', '바나나', '오렌지', '포도' ]배열 요소 접근하기
배열의 각 요소는 고유한 인덱스(index)를 가지고 있습니다.
인덱스는 0부터 시작하는 정수입니다.
배열이름[인덱스] 형식으로 요소에 접근합니다.
let colors = ["red", "green", "blue"];
console.log(colors[0]); // 결과: red (첫 번째 요소)
console.log(colors[1]); // 결과: green (두 번째 요소)
console.log(colors[2]); // 결과: blue (세 번째 요소)
console.log(colors[3]); // 결과: undefined (인덱스를 벗어나면 undefined)length 속성)
아래처럼 인덱스가 0부터 빈자리 없이 채워진 배열에서는 length가 요소 개수입니다. 일반적으로는 마지막 인덱스 다음의 범위를 나타냅니다. 예를 들어 빈 배열에 arr[3] = "x"만 대입하면 length는 4지만 실제로 채운 요소는 하나입니다.
console.log(fruits.length); // 결과: 4빈자리 없이 채운 배열의 마지막 요소는 배열[배열.length - 1]로 읽습니다. 빈 배열에서는 마지막 요소가 없으므로 먼저 길이가 0인지 확인해야 합니다.
배열 요소 추가 및 변경하기
배열의 특정 인덱스에 접근하여 값을 변경하거나, 새로운 요소를 추가할 수 있습니다.
let tasks = ["운동하기", "공부하기", "요리하기"];
console.log(tasks); // 결과: [ '운동하기', '공부하기', '요리하기' ]
// 요소 변경 (인덱스를 통해)
tasks[0] = "산책하기";
console.log(tasks); // 결과: [ '산책하기', '공부하기', '요리하기' ]
// 요소 추가 (length 속성을 이용)
tasks[tasks.length] = "책 읽기"; // 배열의 마지막에 추가
console.log(tasks); // 결과: [ '산책하기', '공부하기', '요리하기', '책 읽기' ]
// `push()` 메서드를 이용한 추가 (더 권장되는 방법)
tasks.push("청소하기"); // 배열의 마지막에 요소 추가
console.log(tasks); // 결과: [ '산책하기', '공부하기', '요리하기', '책 읽기', '청소하기' ]
// `unshift()` 메서드를 이용한 추가 (배열의 맨 앞에 추가)
tasks.unshift("명상하기"); // 배열의 맨 앞에 요소 추가
console.log(tasks); // 결과: [ '명상하기', '산책하기', '공부하기', '요리하기', '책 읽기', '청소하기' ]배열 요소 삭제하기
배열에서 요소를 삭제하는 여러 방법이 있습니다.
let items = ["A", "B", "C", "D"];
console.log(items); // 결과: [ 'A', 'B', 'C', 'D' ]
// `pop()` 메서드: 배열의 마지막 요소를 제거하고 반환합니다.
let lastItem = items.pop();
console.log(items); // 결과: [ 'A', 'B', 'C' ]
console.log(lastItem); // 결과: D
// `shift()` 메서드: 배열의 첫 번째 요소를 제거하고 반환합니다.
let firstItem = items.shift();
console.log(items); // 결과: [ 'B', 'C' ]
console.log(firstItem); // 결과: A
// `splice()` 메서드: 특정 인덱스부터 원하는 개수만큼 요소를 제거할 수 있습니다.
// items.splice(시작_인덱스, 제거할_개수);
items.splice(0, 1); // 인덱스 0부터 1개 제거 (B 제거)
console.log(items); // 결과: [ 'C' ]
items.push("D", "E"); // 다시 추가
console.log(items); // 결과: [ 'C', 'D', 'E' ]
items.splice(1, 2); // 인덱스 1부터 2개 제거 (D, E 제거)
console.log(items); // 결과: [ 'C' ]위 코드는 같은 items를 차례로 바꿉니다. 길이는 4 → 3 → 2 → 1 → 3 → 1로 변합니다. 각 블록을 따로 실행해 얻은 결과를 한 흐름의 결과와 혼동하지 마세요. 배열에서 delete items[0]을 쓰면 빈자리만 만들고 뒤 요소를 당기거나 length를 줄이지 않으므로, 순서를 유지하며 제거하려면 splice() 등을 사용합니다.
splice() 메서드는 요소를 추가하거나 교체하는 데도 사용될 수 있습니다.
(추가적인 활용은 심화 학습에서 다루겠습니다.)
배열 순회하기 (반복문과 함께)
배열의 모든 요소에 하나씩 접근하여 어떤 작업을 수행해야 할 때 반복문을 사용합니다.
let students = ["민수", "지영", "태호"];
// 1. for 문을 이용한 순회 (가장 기본적인 방법)
for (let i = 0; i < students.length; i++) {
console.log(`${i + 1}번째 학생: ${students[i]}`);
}
/*
결과:
1번째 학생: 민수
2번째 학생: 지영
3번째 학생: 태호
*/
// 2. for...of 문을 이용한 순회 (ES6, 배열 요소에 직접 접근)
for (let student of students) {
console.log(`학생 이름: ${student}`);
}
/*
결과:
학생 이름: 민수
학생 이름: 지영
학생 이름: 태호
*/
// 3. forEach() 메서드를 이용한 순회 (배열 메서드, 함수 활용)
// forEach(콜백함수)
students.forEach(function(student, index) { // 각 요소와 인덱스를 인자로 받습니다.
console.log(`${index + 1}. ${student}`);
});
/*
결과:
1. 민수
2. 지영
3. 태호
*/
// 화살표 함수로 더 간결하게 표현 가능
students.forEach((student, index) => {
console.log(`${index + 1}. ${student}`);
});위 forEach는 각 요소에 호출할 함수를 전달하는 콜백의 예입니다. for...of에서는 break로 순회를 끝낼 수 있지만, forEach 콜백에 일반 반복문의 break를 쓸 수는 없습니다. 조기 종료가 필요하면 그 요구에 맞는 반복문을 선택하세요.
객체와 배열은 모두 데이터를 묶지만, 읽는 방식과 변경 패턴이 다릅니다.
이 절의 통합 다이어그램은 이름으로 찾는 객체와 순서대로 처리하는 배열이 접근·변경·복사에서 어떻게 달라지는지 보여줍니다.
예시
그림의 바깥 객체와 중첩 객체를 다음 코드로 구분할 수 있습니다. 각 줄을 실행한 직후 해당 속성값을 확인하세요.
const source = { name: "민수", profile: { score: 3 } };
const alias = source;
const shallow = { ...source };
const separate = { ...source, profile: { ...source.profile } };
shallow.name = "영희";
alias.profile.score = 4;
console.log(source.name, shallow.name); // 민수 영희
console.log(source.profile.score, shallow.profile.score); // 4 4
console.log(separate.profile.score); // 3alias는 객체 자체를 공유합니다. shallow는 바깥 객체만 새로 만들므로 name 변경은 독립적이지만 profile은 공유합니다. separate는 이 예제에서 변경할 profile도 새로 만들어 점수가 3으로 남습니다. 모든 타입과 중첩 깊이에 통용되는 완전한 복사 함수는 아닙니다. 배열의 [...배열]도 바깥 배열만 새로 만들며 객체 요소는 공유할 수 있습니다.
직접 풀어보기
빈자리 없는 배열 const cart = [{ price: 2000, quantity: 3 }, { price: 1500, quantity: 2 }];의 상품액을 반복문으로 합산하세요. 그런 뒤 const copiedCart = [...cart];를 만들고 copiedCart[0].quantity = 1을 실행했을 때 원본의 수량과 다시 계산한 총액을 예상하세요. 배송비·할인은 이번 문제에 없습니다.
합산과 공유 참조 풀이
const cart = [{ price: 2000, quantity: 3 }, { price: 1500, quantity: 2 }];
function cartTotal(lines) {
let total = 0;
for (const line of lines) total += line.price * line.quantity;
return total;
}
console.log(cartTotal(cart)); // 9000
const copiedCart = [...cart];
copiedCart[0].quantity = 1;
console.log(cart[0].quantity); // 1
console.log(cartTotal(cart)); // 5000처음에는 2000 × 3 + 1500 × 2 = 9000입니다. 두 배열의 첫 요소는 같은 객체이므로 복사 배열을 통해 수량을 1로 바꾸면 원본에서도 1입니다. 이후 상품액은 2000 × 1 + 1500 × 2 = 5000입니다. 원본과 수량을 독립적으로 바꾸려면 해당 상품 객체도 별도로 복사해야 합니다.
규칙 확인: 배열 length와 빈자리, 일반 함수의 this.
객체와 배열 기본 정리
이번 장에서는 자바스크립트에서 여러 데이터를 효과적으로 관리하기 위한 핵심 데이터 구조인 객체와 배열의 기초를 학습했습니다.
객체는 이름(키)과 값으로 이루어진 속성을 묶어 연관된 데이터를 구조화할 때 사용됩니다.
점 표기법과 대괄호 표기법으로 속성에 접근하고, 속성을 추가/변경/삭제하는 흐름을 확인했습니다.
객체 내부에 정의된 함수인 메서드의 개념도 함께 다뤘습니다.
배열은 순서(인덱스)를 기반으로 여러 값을 목록 형태로 저장하는 데 사용됩니다.
배열의 요소에 접근하고, push(), pop(), shift(), unshift(), splice()와 같은 다양한 메서드를 사용하여 요소를 추가하거나 제거하는 방법을 배웠습니다.
그리고 for, for...of, forEach()를 이용한 배열 순회 방법도 알아보았습니다.
이러한 객체와 배열은 실제 웹 개발에서 수많은 데이터를 다루는 데 있어 없어서는 안 될 도구입니다.
복잡한 데이터를 논리적으로 구성하고 처리하는 능력을 기르기 위해, 다양한 상황에서 객체와 배열을 활용해보는 연습을 꾸준히 수행해 데이터 처리 기준을 고정하는 것이 좋습니다.