객체 다루기 — Object.keys, entries, assign, freeze
자바스크립트에서 객체는 가장 기본적인 데이터 구조입니다.
Object의 정적 메서드들을 제대로 알고 있으면, 데이터 변환이나 불변성 관리에서 훨씬 깔끔한 코드를 작성할 수 있습니다.
Object.keys, values, entries
객체의 키, 값, 키-값 쌍을 배열로 추출하는 메서드입니다.
const user = { name: "정훈", age: 25, job: "개발자" };
// 키 배열
Object.keys(user); // ["name", "age", "job"]
// 값 배열
Object.values(user); // ["정훈", 25, "개발자"]
// [키, 값] 쌍 배열
Object.entries(user); // [["name", "정훈"], ["age", 25], ["job", "개발자"]]
entries의 활용 — 객체 순회
const prices = { apple: 1000, banana: 2000, cherry: 3000 };
// 구조 분해와 함께 사용
for (const [fruit, price] of Object.entries(prices)) {
console.log(`${fruit}: ${price}원`);
}
// 객체 → 변환 → 다시 객체
const discounted = Object.fromEntries(
Object.entries(prices).map(([key, value]) => [key, value * 0.9])
);
console.log(discounted); // { apple: 900, banana: 1800, cherry: 2700 }
Object.fromEntries()는 Object.entries()의 역변환입니다. 이 두 개를 조합하면 객체를 map처럼 변환할 수 있습니다.
Object.assign — 얕은 병합
하나 이상의 소스 객체의 프로퍼티를 타깃 객체에 복사합니다.
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
const result = Object.assign(target, source1, source2);
console.log(result); // { a: 1, b: 2, c: 3 }
console.log(target); // { a: 1, b: 2, c: 3 } — target이 변경됨!
**주의 **: Object.assign은 타깃 객체를 ** 직접 수정 **합니다. 원본을 보존하려면 빈 객체를 타깃으로 사용합니다.
// 원본을 보존하는 복사
const copy = Object.assign({}, original);
// 스프레드 연산자로 더 간결하게 (동일한 얕은 복사)
const copy2 = { ...original };
얕은 복사의 한계
const original = {
name: "정훈",
address: { city: "서울" },
};
const copy = { ...original };
copy.address.city = "부산";
console.log(original.address.city); // "부산" — 중첩 객체는 공유됨
Object.freeze — 객체 동결
객체의 프로퍼티를 추가, 수정, 삭제할 수 없게 만듭니다.
const config = Object.freeze({
API_URL: "https://api.example.com",
TIMEOUT: 5000,
});
config.API_URL = "https://hacked.com"; // 무시됨 (strict mode에서는 TypeError)
config.newProp = "test"; // 무시됨
delete config.TIMEOUT; // 무시됨
console.log(config.API_URL); // "https://api.example.com"
얕은 동결의 한계
const settings = Object.freeze({
theme: "dark",
nested: { fontSize: 14 },
});
settings.theme = "light"; // 무시됨
settings.nested.fontSize = 20; // 변경됨! — 얕은 동결
console.log(settings.nested.fontSize); // 20
깊은 동결이 필요하면 재귀적으로 적용해야 합니다.
function deepFreeze(obj) {
Object.freeze(obj);
Object.values(obj).forEach((value) => {
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
deepFreeze(value);
}
});
return obj;
}
Object.seal — 밀봉
기존 프로퍼티의 값은 변경할 수 있지만, 추가/삭제는 막습니다.
const user = Object.seal({ name: "정훈", age: 25 });
user.name = "길동"; // OK — 값 변경은 가능
user.email = "a@b.c"; // 무시됨 — 추가 불가
delete user.age; // 무시됨 — 삭제 불가
freeze vs seal vs preventExtensions 비교
| 기능 | freeze | seal | preventExtensions |
|---|---|---|---|
| 프로퍼티 추가 | X | X | X |
| 프로퍼티 삭제 | X | X | O |
| 프로퍼티 값 변경 | X | O | O |
| 프로퍼티 설정 변경 | X | X | O |
Object.hasOwn — 안전한 프로퍼티 확인
const obj = { name: "정훈" };
// 기존 방식 — prototype chain 문제 가능
obj.hasOwnProperty("name"); // true
// Object.create(null)로 만든 객체에서는 실패
const bare = Object.create(null);
bare.name = "test";
// bare.hasOwnProperty("name"); // TypeError!
// 안전한 방식 (ES2022)
Object.hasOwn(bare, "name"); // true
프로퍼티 디스크립터
const user = {};
Object.defineProperty(user, "id", {
value: 1,
writable: false, // 값 변경 불가
enumerable: false, // for...in에 나타나지 않음
configurable: false, // 삭제/재정의 불가
});
user.id = 999; // 무시됨
console.log(user.id); // 1
console.log(Object.keys(user)); // [] — enumerable: false
실전 활용 패턴
// 객체에서 특정 키만 추출 (pick)
function pick(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => keys.includes(key))
);
}
const user = { name: "정훈", age: 25, password: "1234" };
const safe = pick(user, ["name", "age"]);
console.log(safe); // { name: "정훈", age: 25 }
// 객체에서 특정 키 제외 (omit)
function omit(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keys.includes(key))
);
}
const public_ = omit(user, ["password"]);
console.log(public_); // { name: "정훈", age: 25 }
** 기억하기 **:
Object.keys/values/entries로 객체를 배열처럼 다루고,Object.freeze로 불변성을 확보합니다. 다만 freeze는 얕은 동결이라는 점을 면접에서 꼭 언급해야 합니다.
댓글 로딩 중...