객체 — 이름으로 값을 찾는다
Article
배열은 값을 순서대로 담았다. 사용자 한 명을 배열로 표현하면 이렇게 된다.
const user = ["지원", 27, "seoul@example.com"];
console.log(user[1]);
user[1]이 나이라는 걸 어떻게 알까. 외워야 한다. 순서가 바뀌면 전부 깨진다.
값마다 이름을 붙여 담는 그릇이 객체다.
만들고 꺼낸다
중괄호 안에 키: 값 짝을 쉼표로 나열한다.
const user = {
name: "지원",
age: 27,
email: "seoul@example.com",
};
console.log(user.name);
console.log(user["age"]);
console.log(user.phone);
- '지원'
- 27
- undefined
없는 키를 꺼내면 에러가 아니라 undefined다. 4강에서 본 "아직 값이 정해지지 않았다"는 그것이다.
꺼내는 방법이 둘이다.
user.name
점 표기법
- 키를 직접 쓸 때
- 짧고 읽기 좋다
- 키에 공백이나 하이픈이 있으면 못 쓴다
- 평소에는 이걸 쓴다
user[key]
대괄호 표기법
- 키가 변수에 담겨 있을 때
- 키를 동적으로 정할 때
user["my-key"]처럼 특수문자도 가능- 안에 들어가는 건 문자열이다
차이가 결정적인 순간은 이때다.
const user = { name: "지원", age: 27 };
const key = "name";
console.log(user[key]);
console.log(user.key);
- '지원'
- undefined
user.key는 "key"라는 이름의 키를 찾는다. 그런 건 없다. 변수에 담긴 키로 접근하려면 반드시 대괄호를 쓴다.
추가하고 바꾸고 지운다
const user = { name: "지원" };
user.age = 27;
user["city"] = "서울";
user.name = "민수";
delete user.city;
console.log(user);
- { name: '민수', age: 27 }
없는 키에 값을 넣으면 추가되고, 있는 키에 넣으면 덮어쓴다. delete로 지운다.
const인데 다 된다. 15강의 배열과 똑같은 이유다.
이름과 객체의 연결만 잠겼다. 객체 내용은 열려 있다
- user
- 0x3c4d
- { name: '민수', age: 27 }
user = {} 라고 다른 객체를 넣는 건 막히지만, 안을 고치는 건 막지 않는다.
중첩과 옵셔널 체이닝
객체 안에 객체를, 배열을 담을 수 있다.
const user = {
name: "지원",
address: {
city: "서울",
zip: "04524",
},
tags: ["개발", "디자인"],
};
console.log(user.address.city);
console.log(user.tags[0]);
- '서울'
- '개발'
문제는 중간이 비어 있을 때다.
const user = { name: "지원" };
console.log(user.address);
console.log(user.address.city);
- undefined
- TypeError: Cannot read properties of undefined (reading 'city')
user.address가 undefined인데 거기서 .city를 또 꺼내려니 터진다. undefined에서는 아무것도 꺼낼 수 없다.
이걸 막는 문법이 옵셔널 체이닝 ?.이다.
const user = { name: "지원" };
console.log(user.address?.city);
console.log(user.address?.city ?? "주소 없음");
- undefined
- '주소 없음'
?.는 앞이 null이나 undefined면 거기서 멈추고 undefined를 준다. 에러 대신 조용히 물러난다.
6강의 ??와 짝을 이룬다. 없으면 기본값을 채운다.
메서드 — 객체 안의 함수
값 자리에 함수를 넣으면 메서드라 부른다. 11강에서 함수가 값이라고 했으니 당연한 일이다.
const counter = {
count: 0,
increase() {
this.count++;
return this.count;
},
};
console.log(counter.increase());
console.log(counter.increase());
- 1
- 2
this가 등장했다. 여기서는 "이 메서드를 호출한 객체", 즉 counter를 가리킨다. 정확한 규칙은 19강에서 다룬다.
키를 순회한다
객체는 배열이 아니라서 map이나 filter를 쓸 수 없다. 대신 키·값을 배열로 뽑아내는 함수가 있다.
const scores = { 수학: 90, 영어: 85, 과학: 95 };
console.log(Object.keys(scores));
console.log(Object.values(scores));
console.log(Object.entries(scores));
- ['수학', '영어', '과학']
- [90, 85, 95]
- [['수학', 90], ['영어', 85], ['과학', 95]]
배열로 바뀌었으니 16강의 메서드를 전부 쓸 수 있다.
const scores = { 수학: 90, 영어: 85, 과학: 95 };
const total = Object.values(scores).reduce((acc, n) => acc + n, 0);
console.log(total);
const high = Object.entries(scores).filter(([, score]) => score >= 90);
console.log(high);
- 270
- [['수학', 90], ['과학', 95]]
([, score])는 다음 강의에서 배울 구조분해다. 첫 요소를 건너뛰고 두 번째만 꺼냈다.
for...in으로 키를 직접 돌 수도 있다. 9강에서 예고한 그것이다.
const scores = { 수학: 90, 영어: 85 };
for (const subject in scores) {
console.log(subject, scores[subject]);
}
- 수학 90
- 영어 85
값이 있는지 확인한다
const user = { name: "지원", age: 0, nickname: undefined };
console.log(user.age ? "있음" : "없음");
console.log("age" in user);
console.log(Object.hasOwn(user, "nickname"));
console.log(user.nickname !== undefined);
- '없음'
- true
- true
- false
첫 줄이 함정이다. age가 0이라 falsy로 판정됐다. 7강의 그 함정이다. 키가 있는지 묻고 싶다면 in이나 Object.hasOwn()을 쓴다.
객체도 참조 타입이다
4강에서 본 이야기를 다시 확인한다.
const a = { count: 1 };
const b = a;
b.count = 99;
console.log(a.count);
console.log(a === b);
const c = { count: 1 };
console.log(a === c);
- 99
- true
- false
b를 바꿨는데 a가 바뀌었다. 주소를 공유하기 때문이다.
마지막 줄이 중요하다. a와 c는 내용이 똑같은데도 다르다. ===는 내용이 아니라 같은 객체를 가리키는지를 본다.
- ===
- 원시 타입
- 값이 같으면 true
- 객체 타입
- 같은 주소를 가리켜야 true
- 원시 타입
정리하면
- 객체는 키와 값의 짝을 담는다. 없는 키를 꺼내면
undefined다. - 키가 변수에 담겨 있다면 반드시 대괄호로 접근한다.
user.key는"key"를 찾는다. const객체도 내용은 바뀐다. 이름과 객체의 연결만 잠긴다.- 중간이 비었을 수 있다면 **
?.**를 쓴다.??와 짝지어 기본값을 채운다. Object.keys·values·entries로 배열로 바꾸면 16강의 메서드를 쓸 수 있다.- 키의 존재는
in이나Object.hasOwn()으로 확인한다. 값으로 판단하면0에 걸린다. - 객체 비교는 내용이 아니라 주소를 본다.
다음 강의는 구조분해와 스프레드다. 객체와 배열을 훨씬 편하게 다루는 문법이고, 4강과 15강에서 미뤄둔 복사 문제를 여기서 끝낸다.