JavaScript에서 객체를 다루다 보면 Object.keys(), Object.entries(), Object.assign() 같은 메서드들을 마주친다. 다만 대부분의 개발자들은 이 메서드들의 정확한 동작 원리와 언제 어떤 상황에서 써야 하는지 모른 채 인터넷에서 가져온 코드를 그냥 복사해 붙인다. 이번에는 자주 쓰이는 Object 정적 메서드들이 정확히 뭔지, 왜 필요한지, 어떻게 실무에서 활용하는지 완벽하게 정리해서 소개하겠다.

 

기초 개념: Object 정적 메서드란

Object는 JavaScript의 최상위 객체이고, Object 뒤에 붙는 메서드들(Object.keys 같은)은 객체 인스턴스가 아니라 Object 생성자 함수 자체에 달린 '정적 메서드(Static Method)'다. 즉, new Object()로 만든 객체에는 쓸 수 없고, Object.메서드() 형태로만 호출할 수 있다.

객체를 다룰 때 가장 자주 필요한 세 가지 상황이 있다. 첫째, 객체의 모든 키를 배열로 얻고 싶을 때(Object.keys). 둘째, 객체의 키와 값을 한 번에 다루고 싶을 때(Object.entries). 셋째, 여러 객체를 합치거나 속성을 복사하고 싶을 때(Object.assign)다.

 

Object.keys() - 객체의 모든 키를 배열로

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.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() - 객체 병합과 속성 복사

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에서 객체를 다룰 때 가장 자주 쓰이는 도구들이다. 작은 메서드지만 정확히 이해하고 쓰는 것이 깔끔한 코드의 출발점이다. 이 글의 실전 예제들을 참고해 내 프로젝트에서 반복되는 객체 처리 로직을 리팩토링해보면, 코드 가독성이 한 단계 업그레이드될 것이다.