JavaScript에서 객체를 다루다 보면 Object.keys(), Object.entries(), Object.assign() 같은 메서드들을 마주친다. 다만 대부분의 개발자들은 이 메서드들의 정확한 동작 원리와 언제 어떤 상황에서 써야 하는지 모른 채 인터넷에서 가져온 코드를 그냥 복사해 붙인다. 이번에는 자주 쓰이는 Object 정적 메서드들이 정확히 뭔지, 왜 필요한지, 어떻게 실무에서 활용하는지 완벽하게 정리해서 소개하겠다.
Object는 JavaScript의 최상위 객체이고, Object 뒤에 붙는 메서드들(Object.keys 같은)은 객체 인스턴스가 아니라 Object 생성자 함수 자체에 달린 '정적 메서드(Static Method)'다. 즉, new Object()로 만든 객체에는 쓸 수 없고, Object.메서드() 형태로만 호출할 수 있다.
객체를 다룰 때 가장 자주 필요한 세 가지 상황이 있다. 첫째, 객체의 모든 키를 배열로 얻고 싶을 때(Object.keys). 둘째, 객체의 키와 값을 한 번에 다루고 싶을 때(Object.entries). 셋째, 여러 객체를 합치거나 속성을 복사하고 싶을 때(Object.assign)다.
Object.keys()는 전달받은 객체의 모든 자신의 열거 가능한 속성 이름을 배열로 반환한다. 헷갈리기 쉬운 부분은 프로토타입 체인에 있는 속성은 포함하지 않는다는 점이다.
// ✗ 잘못된 예: for...in 루프로 객체 순회 후 수동으로 key 배열 생성
const user = { name: 'John', age: 30, email: 'john@example.com' };
const keys = [];
for (let key in user) {
keys.push(key);
}
console.log(keys); // ['name', 'age', 'email']
// 문제: 프로토타입 체인의 열거 가능한 속성도 포함될 수 있음
// ✓ 올바른 예: Object.keys() 사용
const user = { name: 'John', age: 30, email: 'john@example.com' };
const keys = Object.keys(user);
console.log(keys); // ['name', 'age', 'email']
// Object.keys()는 자신의 속성만 가져오므로 안전함
Object.keys()의 실무 활용은 주로 객체를 순회할 때다. 특히 응답 데이터의 모든 필드를 검증하거나, 동적으로 테이블을 만들 때 컬럼 헤더를 자동 생성할 때 유용하다.
// 실전 예제: API 응답 필드 검증
function validateUserFields(userData, requiredFields) {
const existingKeys = Object.keys(userData);
const missingFields = requiredFields.filter(
field => !existingKeys.includes(field)
);
return missingFields.length === 0 ? true : missingFields;
}
const apiResponse = { name: 'John', age: 30 };
const required = ['name', 'age', 'email'];
console.log(validateUserFields(apiResponse, required)); // ['email']
Object.entries()는 Object.keys()보다 강력하다. 각 key-value 쌍을 [key, value] 배열로 담은 배열을 반환해서, 키와 값을 동시에 다룰 수 있다. 요즘 코드에서 가장 자주 본 패턴이다.
// ✗ 잘못된 예: Object.keys()로 키를 가져온 뒤 다시 접근
const user = { name: 'John', age: 30, email: 'john@example.com' };
const keys = Object.keys(user);
const pairs = [];
for (let key of keys) {
pairs.push([key, user[key]]);
}
console.log(pairs);
// 두 번 순회하므로 불필요한 연산
// ✓ 올바른 예: Object.entries() 직접 사용
const user = { name: 'John', age: 30, email: 'john@example.com' };
const pairs = Object.entries(user);
console.log(pairs);
// [['name', 'John'], ['age', 30], ['email', 'john@example.com']]
// for...of로 깔끔하게 순회
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
Object.entries()의 장점은 ES2017 표준이므로 모던 JavaScript에서 문제없이 쓸 수 있다는 점이다. 특히 객체를 다른 형식으로 변환할 때 매우 유용하다.
// 실전 예제: 객체를 URL Query String으로 변환
function objectToQueryString(obj) {
return Object.entries(obj)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}
const filters = { category: 'electronics', price: 100, inStock: true };
console.log(objectToQueryString(filters));
// 'category=electronics&price=100&inStock=true'
Object.assign()은 하나 이상의 소스 객체의 모든 열거 가능한 자신의 속성을 대상 객체로 복사한다. 첫 번째 인자가 수정되고 반환되므로, 얕은 복사(Shallow Copy)다.
// ✗ 잘못된 예: 수동으로 속성 복사
const defaults = { theme: 'light', language: 'en' };
const userPrefs = { theme: 'dark' };
const finalConfig = {};
for (let key of Object.keys(defaults)) {
finalConfig[key] = defaults[key];
}
for (let key of Object.keys(userPrefs)) {
finalConfig[key] = userPrefs[key];
}
console.log(finalConfig); // { theme: 'dark', language: 'en' }
// 반복적이고 실수하기 쉬움
// ✓ 올바른 예: Object.assign()으로 병합
const defaults = { theme: 'light', language: 'en' };
const userPrefs = { theme: 'dark' };
const finalConfig = Object.assign({}, defaults, userPrefs);
console.log(finalConfig); // { theme: 'dark', language: 'en' }
// 주의: 첫 번째 인자가 변경됨
const target = defaults;
Object.assign(target, userPrefs);
console.log(target); // 원본 defaults가 수정됨!
Object.assign()을 쓸 때 가장 흔한 실수는 원본 객체가 수정된다는 점을 무시하는 것이다. 반드시 새 빈 객체 {}를 첫 번째 인자로 전달해서 원본을 보호해야 한다.
// 실전 예제: Redux 스타일 상태 업데이트
const initialState = {
user: { name: 'John', role: 'user' },
loading: false,
error: null
};
function updateUser(state, newUserData) {
return Object.assign({}, state, {
user: Object.assign({}, state.user, newUserData),
loading: false
});
}
const newState = updateUser(initialState, { role: 'admin' });
console.log(newState);
// { user: { name: 'John', role: 'admin' }, loading: false, error: null }
console.log(initialState === newState); // false (새로운 객체)
| 메서드 | 반환값 | 사용 목적 | 원본 수정 |
|---|---|---|---|
| Object.keys() | 속성명 배열 | 키만 필요할 때, 객체 순회 | 아니오 |
| Object.entries() | [key, value] 쌍 배열 | 키와 값을 동시에 다룰 때 | 아니오 |
| Object.assign() | 병합된 객체 | 객체 복사, 병합, 기본값 설정 | 첫 번째 인자만 |
요즘에는 Object.assign() 대신 스프레드 연산자를 쓰는 경우가 많다. 두 가지 모두 얕은 복사를 하지만, 스프레드 연산자가 더 간결하고 읽기 쉽다.
// Object.assign() 방식
const merged1 = Object.assign({}, obj1, obj2);
// 스프레드 연산자 방식 (모던 JavaScript)
const merged2 = { ...obj1, ...obj2 };
// 둘 다 같은 결과지만, 스프레드가 가독성이 좋음
다만 Object.assign()은 여전히 폴리필이 필요 없는 안정적인 방법이고, 대상 객체에 직접 속성을 추가하는 동작이 필요할 때는 Object.assign()이 더 명확하다.
✗ Object.keys/entries를 원본 객체 수정으로 착각. 이 둘은 읽기만 한다.
✓ Object.assign은 첫 인자가 변경되므로, 새 객체를 만들고 싶으면 {}를 첫 인자로 전달하자.
✗ 객체의 모든 속성을 깊은 복사(Deep Copy)한다고 믿기. Object.assign()은 얕은 복사이므로, 중첩된 객체는 참조만 복사된다.
// 얕은 복사의 위험성
const original = { user: { name: 'John' } };
const copy = Object.assign({}, original);
copy.user.name = 'Jane';
console.log(original.user.name); // 'Jane' (원본이 변경됨!)
✓ 깊은 복사가 필요하면 JSON 방식이나 라이브러리(lodash cloneDeep)를 써야 한다.
Object.keys(), Object.entries(), Object.assign()은 JavaScript에서 객체를 다룰 때 가장 자주 쓰이는 도구들이다. 작은 메서드지만 정확히 이해하고 쓰는 것이 깔끔한 코드의 출발점이다. 이 글의 실전 예제들을 참고해 내 프로젝트에서 반복되는 객체 처리 로직을 리팩토링해보면, 코드 가독성이 한 단계 업그레이드될 것이다.