JavaScript 객체의 속성을 순회하거나 검사할 때, 그 속성이 객체 자신의 것인지 프로토타입 체인을 통해 상속받은 것인지 구분하는 것은 매우 중요합니다. hasOwnProperty 메서드는 이러한 구분을 가능하게 하는 핵심 도구입니다. 프로토타입 시스템이 있는 JavaScript에서는 객체가 직접 정의한 속성과 상속받은 속성이 혼재되어 있어, 이를 명확히 구분하지 않으면 의도하지 않은 동작이나 버그가 발생할 수 있습니다. 특히 for…in 루프로 객체를 순회할 때나, 객체의 고유 데이터만 다루어야 하는 상황에서 hasOwnProperty는 필수적인 안전장치 역할을 합니다.
🔍 핵심 문제점
- 프로토타입 속성 혼입: for…in 루프는 프로토타입 체인의 열거 가능한 속성까지 모두 순회하여 예상치 못한 결과 발생
- 데이터 무결성 문제: 객체 자신의 데이터만 처리해야 하는 로직에서 상속 속성까지 포함되어 오류 유발
- 프로토타입 오염 취약성: Object.prototype에 추가된 속성이 모든 객체 순회에 영향을 미치는 보안 위험
- 직렬화 오류: JSON.stringify와 같은 직렬화 작업에서 의도하지 않은 속성이 포함될 수 있음
- 성능 저하: 불필요한 프로토타입 속성까지 처리하여 연산 비용 증가
💡 실무에서의 영향
실무 개발에서는 객체를 다루는 거의 모든 상황에서 hasOwnProperty의 필요성이 드러납니다. 레거시 코드나 외부 라이브러리가 Object.prototype을 확장한 경우, 이를 인지하지 못하면 애플리케이션 전체에서 예상치 못한 버그가 발생할 수 있습니다. 특히 폴리필(polyfill)이나 레거시 브라우저 지원을 위해 프로토타입을 확장하는 코드가 많았던 과거에는 더욱 심각한 문제였습니다. 현대의 프레임워크와 라이브러리들도 객체 속성을 안전하게 다루기 위해 hasOwnProperty를 내부적으로 광범위하게 사용합니다. 또한 사용자 입력이나 외부 데이터를 객체로 변환할 때, hasOwnProperty 검사는 보안 취약점을 방지하는 중요한 방어 메커니즘이 됩니다. ES6 이후 Object.keys()나 Object.entries() 같은 대안이 등장했지만, hasOwnProperty의 동작 원리를 이해하는 것은 여전히 JavaScript 프로토타입 시스템을 제대로 다루기 위한 필수 지식입니다.
핵심 개념
Own vs Inherited Properties
입문
JavaScript 객체는 자신이 직접 가진 물건과 부모에게 물려받은 물건이 섞여 있어요. hasOwnProperty는 이 둘을 구분해주는 도구입니다!
📦 자신의 물건과 물려받은 물건 여러분의 방을 생각해보세요. 책상 위에 있는 연필은 여러분이 직접 산 것(자신의 속성)이고, 집에 있는 냉장고는 부모님 것을 같이 쓰는 것(상속 속성)이에요. JavaScript 객체도 마찬가지로 자신이 직접 가진 데이터와 프로토타입에서 물려받은 메서드가 섞여 있습니다.
🔍 왜 구분이 필요한가요? 만약 “네 물건만 가방에 넣어”라고 하면 자신의 연필만 넣어야지, 집 냉장고를 가방에 넣을 수는 없죠! 마찬가지로 객체의 데이터만 처리하거나 저장할 때는 자신의 속성만 골라내야 합니다. hasOwnProperty는 “이게 정말 네 거니?”라고 확인해주는 역할이에요.
⚠️ 구분하지 않으면 어떻게 되나요? 만약 부모님 물건까지 다 세면 실제보다 훨씬 많이 가진 것처럼 보이겠죠? JavaScript도 마찬가지로 상속 속성까지 포함하면 예상보다 많은 데이터가 나와서 프로그램이 이상하게 동작할 수 있어요.
✅ 실제 예시: 학생 명부 만들기 반 학생들의 이름과 나이를 기록한다고 해볼까요? 각 학생 객체에는 name과 age가 있고, 모든 학생이 공통으로 쓰는 메서드(예: sayHello)는 프로토타입에 있어요. 명부에는 이름과 나이만 적어야 하는데, hasOwnProperty 없이 모든 속성을 다 적으면 sayHello 메서드까지 명부에 들어가서 이상해져요!
중급
JavaScript 객체는 자신이 직접 정의한 속성(own properties)과 프로토타입 체인을 통해 상속받은 속성(inherited properties)을 모두 가질 수 있습니다. hasOwnProperty 메서드는 특정 속성이 객체 자신의 것인지 여부를 확인하는 데 사용됩니다.
Own Properties vs Inherited Properties
- Own properties: 객체에 직접 정의되거나 할당된 속성
- Inherited properties: 프로토타입 체인 상위 객체로부터 상속받은 속성
이 구분은 객체 데이터를 정확하게 처리하고, 의도하지 않은 프로토타입 속성을 제외하기 위해 필수적입니다.
const parent = { inherited: 'parent property' };
const child = Object.create(parent);
child.own = 'child property';
console.log(child.own); // 'child property'
console.log(child.inherited); // 'parent property' (상속)
console.log(child.hasOwnProperty('own')); // true
console.log(child.hasOwnProperty('inherited')); // false
왜 구분이 필요한가?
- 데이터 직렬화: JSON.stringify나 데이터베이스 저장 시 객체 자신의 데이터만 필요
- 객체 복사: 상속 속성을 제외하고 순수 데이터만 복사
- 속성 존재 확인: in 연산자는 상속 속성도 포함하지만, hasOwnProperty는 자신의 속성만 확인
const user = {
name: 'Alice',
age: 25
};
// 사용자 데이터만 서버로 전송
const userData = {};
for (let key in user) {
if (user.hasOwnProperty(key)) {
userData[key] = user[key];
}
}
console.log(userData); // { name: 'Alice', age: 25 }
심화
JavaScript의 속성 소유권(property ownership) 모델은 ECMAScript 명세의 속성 기술자(Property Descriptor)와 내부 슬롯([[OwnPropertyKeys]])을 통해 정의됩니다. hasOwnProperty는 이 소유권을 런타임에 검증하는 핵심 메커니즘입니다.
ECMAScript 명세 기반 속성 소유권 모델 ECMAScript 2024, Section 20.1.3.5 (Object.prototype.hasOwnProperty)에 따르면, hasOwnProperty(V)는 다음과 같이 동작합니다:
- P = ToPropertyKey(V)로 속성 키 변환
- O = ToObject(this value)로 객체 변환
- HasOwnProperty(O, P) 추상 연산 호출
- HasOwnProperty는 O.[GetOwnProperty]가 undefined가 아닌지 확인
이는 프로토타입 체인을 거치지 않고 객체의 내부 슬롯만 검사하여 O(1) 성능을 보장합니다.
속성 소유권 구분의 구현 수준 의미 JavaScript 엔진에서 객체는 두 가지 속성 저장소를 유지합니다:
- Own Properties Map: 객체 자신의 속성을 저장하는 해시 맵 (V8의 경우 Properties 배열)
- Prototype Link: [[Prototype]] 내부 슬롯을 통한 프로토타입 참조
hasOwnProperty는 Own Properties Map만 검색하므로, 프로토타입 체인 탐색 비용이 없습니다. 반면 in 연산자나 속성 접근(obj.prop)은 체인을 따라 올라가며 O(n) 복잡도를 가집니다 (n = 체인 깊이).
프로토타입 오염 방어 메커니즘 hasOwnProperty는 Prototype Pollution 공격 방어의 핵심입니다:
// 공격 시나리오: Object.prototype 오염
Object.prototype.polluted = 'malicious';
const obj = { safe: 'data' };
// 취약한 코드: 프로토타입 속성까지 처리
for (let key in obj) {
processData(key, obj[key]); // 'polluted' 포함됨
}
// 안전한 코드: 자신의 속성만 처리
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
processData(key, obj[key]); // 'polluted' 제외됨
}
}
엔진 최적화와 Hidden Class V8 엔진에서 hasOwnProperty는 Hidden Class(내부적으로는 Map)를 활용하여 최적화됩니다:
- 동일한 구조의 객체들은 같은 Hidden Class를 공유
- hasOwnProperty 호출 시 Hidden Class의 속성 테이블을 직접 조회
- Inline Cache를 통해 반복 호출 시 O(1)에 가까운 성능 달성
Modern JavaScript (ES2022+)에서는 Object.hasOwn() 정적 메서드가 도입되어 더욱 안전하고 간결한 API를 제공하지만, hasOwnProperty의 동작 원리를 이해하는 것은 프로토타입 시스템의 본질을 파악하는 데 여전히 중요합니다.
for…in Loop Safety
입문
for…in 루프로 객체를 돌 때 hasOwnProperty가 없으면 예상치 못한 것들이 튀어나와요!
🔄 for…in 루프는 뭘 하나요? for…in은 객체의 모든 속성을 하나씩 꺼내서 확인하는 도구예요. 마치 서랍장의 모든 칸을 열어보는 것과 같아요. 그런데 문제는 자기 서랍뿐만 아니라 부모님 서랍까지 다 열어본다는 거예요!
⚠️ 무엇이 문제인가요? 예를 들어 학생 목록을 돌면서 이름을 출력한다고 해봐요. 본인이 만든 {name: ‘Alice’, age: 25} 같은 객체만 보고 싶은데, JavaScript가 기본으로 제공하는 toString, valueOf 같은 메서드까지 나올 수 있어요. 마치 학생 명부를 보다가 갑자기 “학교 규칙”까지 같이 나오는 것처럼 이상하죠?
🛡️ hasOwnProperty가 어떻게 막아주나요? hasOwnProperty는 “이건 진짜 네 거니?”라고 물어봐서, 학생 객체가 직접 가진 name, age만 통과시키고 상속받은 메서드들은 걸러내요. 마치 보안 요원이 “학생증 있어요?”라고 확인하는 것과 같아요!
✨ 실생활 비유 여러분 방에서 물건을 세는데, 방문이 열려있어서 거실 물건까지 같이 셀 수 있다고 생각해보세요. 정확히 세려면 “이게 내 방 안에 있는 거니?”라고 확인해야겠죠? hasOwnProperty가 바로 이 확인 역할을 합니다!
중급
for…in 루프는 객체의 열거 가능한(enumerable) 속성을 순회하는데, 프로토타입 체인의 속성까지 포함합니다. 이는 의도하지 않은 동작을 일으킬 수 있어, hasOwnProperty로 필터링하는 것이 안전한 패턴입니다.
for…in의 동작 방식
- 객체 자신의 열거 가능한 속성
- 프로토타입 체인의 열거 가능한 속성
- 순서는 구현에 따라 다를 수 있음 (일반적으로 추가 순서)
// Object.prototype에 속성 추가 (나쁜 예시)
Object.prototype.injected = 'unexpected';
const user = {
name: 'Bob',
age: 30
};
// hasOwnProperty 없이 순회
console.log('Without hasOwnProperty:');
for (let key in user) {
console.log(key); // 'name', 'age', 'injected' 모두 출력
}
// hasOwnProperty로 필터링
console.log('With hasOwnProperty:');
for (let key in user) {
if (user.hasOwnProperty(key)) {
console.log(key); // 'name', 'age'만 출력
}
}
delete Object.prototype.injected; // 정리
실무 패턴 레거시 코드나 폴리필이 Object.prototype을 확장한 환경에서는 hasOwnProperty 검사가 필수입니다. 특히 다음 상황에서 중요합니다:
- 객체를 딕셔너리(dictionary)로 사용할 때
- 외부 라이브러리와 함께 사용할 때
- 오래된 브라우저를 지원할 때
const config = {
host: 'localhost',
port: 3000,
debug: true
};
// 안전한 순회
for (let key in config) {
if (config.hasOwnProperty(key)) {
console.log(`${key}: ${config[key]}`);
}
}
// 또는 Object.keys() 사용 (자동으로 own properties만)
Object.keys(config).forEach(key => {
console.log(`${key}: ${config[key]}`);
});
심화
for…in 루프의 열거 동작은 ECMAScript 명세의 EnumerateObjectProperties 추상 연산과 [[Enumerate]] 내부 메서드에 의해 정의되며, 프로토타입 체인 전체를 순회하는 특성으로 인해 성능과 보안 이슈를 야기합니다.
ECMAScript 명세 기반 for…in 열거 메커니즘 ECMAScript 2024, Section 14.7.5.9 (EnumerateObjectProperties)에 따르면:
- 객체 O와 그 프로토타입 체인의 모든 객체를 순회
- 각 객체에서 [[OwnPropertyKeys]]를 호출하여 속성 키 획득
- 각 속성에 대해 [[GetOwnProperty]]로 Property Descriptor 확인
- enumerable이 true인 속성만 Iterator에 포함
- 이미 방문한 키는 제외 (중복 방지)
이 과정은 O(n * m) 복잡도를 가집니다 (n = 프로토타입 체인 길이, m = 각 객체의 속성 개수).
프로토타입 체인 순회의 성능 특성 V8 엔진에서 for…in 최적화:
// 케이스 1: 짧은 체인, 적은 속성 (빠름)
const obj1 = { a: 1, b: 2 };
for (let key in obj1) { /* O(1) InlineCache 히트 */ }
// 케이스 2: 긴 체인, 많은 속성 (느림)
const proto3 = { z1: 1, z2: 2, z3: 3 };
const proto2 = Object.create(proto3);
proto2.y1 = 1; proto2.y2 = 2;
const proto1 = Object.create(proto2);
proto1.x1 = 1; proto1.x2 = 2;
const obj2 = Object.create(proto1);
obj2.a = 1; obj2.b = 2;
// 프로토타입 체인 4단계 * 각 2-3개 속성 = 약 10회 조회
for (let key in obj2) {
if (obj2.hasOwnProperty(key)) { /* hasOwnProperty 추가 비용 */ }
}
보안 취약점: Prototype Pollution과 for…in hasOwnProperty 누락은 실제 CVE를 유발한 보안 취약점입니다:
// 공격 벡터: __proto__ 오염
const maliciousInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
const user = { name: 'attacker' };
Object.assign(user, maliciousInput);
// 취약한 권한 검사
function checkAdmin(user) {
for (let key in user) {
if (key === 'isAdmin' && user[key]) {
return true; // 프로토타입 체인의 isAdmin을 잘못 검출
}
}
return false;
}
// 안전한 검사
function checkAdminSafe(user) {
for (let key in user) {
if (user.hasOwnProperty(key) && key === 'isAdmin' && user[key]) {
return true;
}
}
return false;
}
Modern JavaScript 대안과 성능 비교 ES6+ 환경에서는 다음 대안들이 더 안전하고 빠릅니다:
const obj = { a: 1, b: 2, c: 3 };
// for...in + hasOwnProperty: ~10,000 ops/sec (체인 길이에 따라 가변)
for (let key in obj) {
if (obj.hasOwnProperty(key)) { process(obj[key]); }
}
// Object.keys(): ~50,000 ops/sec (내부에서 own properties만 수집)
Object.keys(obj).forEach(key => { process(obj[key]); });
// for...of + Object.keys(): ~45,000 ops/sec
for (const key of Object.keys(obj)) { process(obj[key]); }
// Object.entries(): ~40,000 ops/sec (키와 값 동시 추출)
for (const [key, value] of Object.entries(obj)) { process(value); }
V8 TurboFan 컴파일러는 Object.keys()에 대해 특별한 최적화 경로를 제공하므로, 가능하면 for…in 대신 Object.keys()를 사용하는 것이 권장됩니다.
Safe Property Access Pattern
입문
hasOwnProperty를 안전하게 쓰는 특별한 방법이 있어요. 왜냐하면 hasOwnProperty 자체도 가짜로 바꿀 수 있거든요!
🎭 메서드도 속일 수 있다고요? 네! JavaScript에서는 hasOwnProperty 메서드 자체를 다른 것으로 바꿔버릴 수 있어요. 마치 경비원 배지를 가짜로 만드는 것처럼요. 그래서 정말 진짜 hasOwnProperty를 써야 안전해요!
🔒 어떻게 진짜를 쓰나요?
진짜 hasOwnProperty는 Object.prototype에 있는 원본이에요. 그래서 Object.prototype.hasOwnProperty.call(obj, 'prop')라는 긴 코드를 씁니다. 이건 “Object.prototype에 있는 진짜 hasOwnProperty를 빌려서 obj에 적용해줘”라는 뜻이에요!
⚠️ 왜 이렇게 복잡하게 해야 하나요?
만약 누군가 나쁜 의도로 여러분의 객체에 가짜 hasOwnProperty를 심어놨다면, obj.hasOwnProperty()는 가짜를 실행하게 돼요. 하지만 Object.prototype.hasOwnProperty.call(obj, ...)는 항상 진짜 원본을 쓰기 때문에 안전해요!
✨ 실생활 비유 학교 출입증을 확인할 때, 학생이 가진 출입증을 보는 게 아니라 선생님실에 보관된 원본 명단과 비교하는 거예요. 학생이 출입증을 위조했어도 원본 명단은 속일 수 없으니까요!
🆕 더 쉬운 새로운 방법
최근에는 Object.hasOwn(obj, 'prop')라는 짧고 안전한 방법이 생겼어요. 이건 자동으로 진짜 방법을 쓰기 때문에 더 간단하고 안전해요!
중급
hasOwnProperty를 안전하게 사용하려면 Object.prototype.hasOwnProperty.call() 패턴을 사용해야 합니다. 객체가 hasOwnProperty 메서드를 오버라이드했거나, null 프로토타입 객체인 경우를 대비하기 위함입니다.
안전하지 않은 패턴
obj.hasOwnProperty(key); // 취약점 가능
문제점:
- obj가 hasOwnProperty 속성을 직접 가지고 있으면 메서드가 아닐 수 있음
- obj가 null 프로토타입 객체면 hasOwnProperty가 없음
// 문제 1: hasOwnProperty 오버라이드
const obj1 = {
name: 'Alice',
hasOwnProperty: 'not a function'
};
// obj1.hasOwnProperty('name'); // TypeError: obj1.hasOwnProperty is not a function
// 문제 2: null 프로토타입
const obj2 = Object.create(null);
obj2.name = 'Bob';
// obj2.hasOwnProperty('name'); // TypeError: obj2.hasOwnProperty is not a function
안전한 패턴 Object.prototype에서 직접 hasOwnProperty 메서드를 가져와 call()로 호출합니다.
const hasOwn = Object.prototype.hasOwnProperty;
// 패턴 1: 변수에 저장
function safeCheck1(obj, key) {
return hasOwn.call(obj, key);
}
// 패턴 2: 직접 호출
function safeCheck2(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
// 패턴 3: ES2022+ Object.hasOwn()
function safeCheck3(obj, key) {
return Object.hasOwn(obj, key);
}
// 모든 경우에 안전
console.log(safeCheck1(obj1, 'name')); // true
console.log(safeCheck2(obj2, 'name')); // true
console.log(safeCheck3(obj1, 'name')); // true
ES2022 Object.hasOwn() Object.hasOwn()은 Object.prototype.hasOwnProperty.call()의 간결하고 안전한 대안으로, 다음 장점이 있습니다:
- 더 읽기 쉬운 구문
- null 프로토타입 객체 지원
- 오버라이드 문제 없음
const obj = Object.create(null);
obj.prop = 'value';
// 구식 안전 패턴
if (Object.prototype.hasOwnProperty.call(obj, 'prop')) {
console.log('Found');
}
// 현대적 패턴
if (Object.hasOwn(obj, 'prop')) {
console.log('Found');
}
심화
hasOwnProperty의 안전한 사용 패턴은 JavaScript의 메서드 바인딩 메커니즘과 프로토타입 체인의 동적 특성으로 인해 발생하는 취약점을 방어하기 위한 필수 기법입니다.
ECMAScript 명세 기반 메서드 조회 메커니즘 ECMAScript 2024, Section 7.1.1 (GetValue)에 따르면, obj.hasOwnProperty(key) 호출 시:
- GetValue(obj.hasOwnProperty) 수행
- 객체 obj에서 ‘hasOwnProperty’ 속성 검색 (자신의 속성 먼저)
- 없으면 프로토타입 체인을 따라 검색
- Object.prototype.hasOwnProperty 발견 시 반환
이 과정에서 obj가 ‘hasOwnProperty’라는 이름의 own property를 가지면 메서드 조회가 중단되고, 그 값이 반환됩니다 (Property Shadowing).
메서드 셰도잉(Method Shadowing) 취약점 Property Shadowing은 프로토타입 체인의 메서드를 의도적으로 차단하는 공격 벡터가 될 수 있습니다:
// 공격 시나리오: hasOwnProperty 셰도잉
const maliciousData = JSON.parse('{"hasOwnProperty": null, "isAdmin": true}');
function checkPermission(user) {
// 취약한 코드
if (user.hasOwnProperty('isAdmin')) { // TypeError
return user.isAdmin;
}
return false;
}
// 안전한 코드
function checkPermissionSafe(user) {
if (Object.prototype.hasOwnProperty.call(user, 'isAdmin')) {
return user.isAdmin;
}
return false;
}
Function.prototype.call()의 동작 원리 Object.prototype.hasOwnProperty.call(obj, key)의 실행 과정:
- Object.prototype.hasOwnProperty 함수 객체 참조 (항상 원본 메서드)
- Function.prototype.call(thisArg, …args) 호출
- thisArg로 obj 바인딩
- hasOwnProperty 내부에서 ToObject(this)로 obj 변환
- HasOwnProperty(obj, key) 추상 연산 수행
이는 obj의 속성을 거치지 않고 직접 Object.prototype의 메서드를 실행하므로 셰도잉을 우회합니다.
ES2022 Object.hasOwn() 명세 Object.hasOwn(O, P)는 ECMAScript 2022에 추가된 정적 메서드로, 다음과 같이 동작합니다:
// 폴리필 (명세 의사 코드)
if (!Object.hasOwn) {
Object.hasOwn = function(object, property) {
if (object == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
return Object.prototype.hasOwnProperty.call(object, property);
};
}
성능 특성 비교 (V8 벤치마크)
const obj = { a: 1, b: 2, c: 3 };
const iterations = 1000000;
// 1. obj.hasOwnProperty('a')
// - Inline Cache 히트 시: ~0.02ns/op
// - 프로토타입 조회: ~0.5ns/op
// 2. Object.prototype.hasOwnProperty.call(obj, 'a')
// - Function.call() 오버헤드: ~1.0ns/op
// - 하지만 항상 안전
// 3. Object.hasOwn(obj, 'a')
// - 정적 메서드 호출: ~0.8ns/op
// - TurboFan 최적화 적용 시 2번과 동일한 코드로 인라인
// 실제 성능 차이는 미미 (<10%) but 안전성이 훨씬 중요
Null Prototype 객체 처리 Object.create(null)로 생성된 객체는 프로토타입 체인이 없으므로 특별한 처리가 필요합니다:
const nullProtoObj = Object.create(null);
nullProtoObj.data = 'value';
// obj.hasOwnProperty() - TypeError (메서드 없음)
// Object.prototype.hasOwnProperty.call(nullProtoObj, 'data') - 안전
// Object.hasOwn(nullProtoObj, 'data') - 안전 (권장)
권장 사항
- ES2022+ 환경: Object.hasOwn() 사용 (가독성, 안전성, 성능 모두 우수)
- 레거시 환경: Object.prototype.hasOwnProperty.call() 패턴 사용
- 고성능 반복문: Object.keys() 사용 (hasOwnProperty 내부적으로 처리됨)
Modern Alternatives
입문
요즘에는 hasOwnProperty보다 더 쉽고 안전한 방법들이 많이 생겼어요!
🆕 Object.hasOwn() - 간단하고 안전한 방법
Object.hasOwn(obj, 'prop')는 hasOwnProperty를 더 쉽게 쓸 수 있도록 만든 새로운 방법이에요. 긴 코드를 쓸 필요 없이 짧고 안전하게 “이 속성이 네 거니?”를 확인할 수 있어요!
📦 Object.keys() - 목록으로 받기
Object.keys(obj)는 객체가 가진 모든 자신의 속성 이름을 배열로 만들어줘요. for…in처럼 일일이 확인할 필요 없이, 처음부터 자신의 것만 깨끗하게 정리된 목록을 받을 수 있어요!
📋 Object.entries() - 이름과 값을 함께
Object.entries(obj)는 속성 이름과 값을 쌍으로 묶어서 배열로 만들어줘요. [[‘name’, ‘Alice’], [‘age’, 25]] 이런 식으로요. 이름과 값을 동시에 쓸 때 편해요!
🎯 어떤 걸 써야 하나요?
- 속성이 있는지만 확인:
Object.hasOwn(obj, 'prop') - 속성 이름 목록 필요:
Object.keys(obj) - 이름과 값 둘 다 필요:
Object.entries(obj) - 값만 필요:
Object.values(obj)
✨ 왜 이 방법들이 좋나요? 이 방법들은 모두 자동으로 자신의 속성만 골라내요. 부모에게 물려받은 것은 처음부터 제외되기 때문에, hasOwnProperty로 일일이 확인할 필요가 없어요. 더 간단하고 실수할 일도 없죠!
중급
ES6 이후 도입된 여러 메서드들은 hasOwnProperty의 필요성을 줄이고 더 간결한 코드를 작성할 수 있게 합니다. 이들은 내부적으로 own properties만 다루므로 더 안전합니다.
Modern API Overview
- Object.keys(obj): own property 이름 배열 반환
- Object.values(obj): own property 값 배열 반환
- Object.entries(obj): [key, value] 쌍 배열 반환
- Object.hasOwn(obj, prop): ES2022 hasOwnProperty 대체
- Object.getOwnPropertyNames(obj): 열거 불가능한 속성 포함
const user = {
name: 'Charlie',
age: 28,
email: 'charlie@example.com'
};
// for...in + hasOwnProperty 대신
Object.keys(user).forEach(key => {
console.log(`${key}: ${user[key]}`);
});
// 또는 for...of
for (const key of Object.keys(user)) {
console.log(`${key}: ${user[key]}`);
}
Object.entries()로 키-값 동시 처리 Object.entries()는 키와 값을 동시에 다룰 때 매우 유용합니다.
const config = {
host: 'localhost',
port: 3000,
ssl: true
};
// 배열 디스트럭처링으로 깔끔하게
for (const [key, value] of Object.entries(config)) {
console.log(`${key}: ${value}`);
}
// 또는 forEach
Object.entries(config).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// 객체 변환 예시
const upperConfig = Object.fromEntries(
Object.entries(config).map(([key, value]) => [key.toUpperCase(), value])
);
Object.hasOwn() - ES2022 Object.hasOwn()은 hasOwnProperty의 현대적 대체재로, 더 읽기 쉽고 안전합니다.
const obj = { prop: 'value' };
// 구식 패턴
if (Object.prototype.hasOwnProperty.call(obj, 'prop')) {
console.log('Found');
}
// 현대 패턴
if (Object.hasOwn(obj, 'prop')) {
console.log('Found');
}
// 폴리필 (레거시 환경)
if (!Object.hasOwn) {
Object.hasOwn = function(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
};
}
선택 가이드
- 단순 존재 확인: Object.hasOwn() 또는 in 연산자
- 속성 순회: Object.keys(), Object.entries()
- 성능 중요: 사전에 Object.keys()로 배열 생성 후 반복
심화
Modern JavaScript의 대안 API들은 ECMAScript 명세의 다양한 추상 연산을 활용하여 own properties를 효율적으로 추출하며, 엔진 레벨에서 최적화된 경로를 제공합니다.
ECMAScript 명세 기반 API 동작 원리 각 메서드는 다음과 같은 명세 알고리즘을 따릅니다:
-
Object.keys(O) (Section 20.1.2.17):
- EnumerableOwnPropertyNames(O, “key”) 호출
- [[OwnPropertyKeys]]로 키 목록 수집
- [[GetOwnProperty]]로 각 속성의 enumerable 확인
- enumerable이 true인 키만 배열로 반환
-
Object.entries(O) (Section 20.1.2.5):
- EnumerableOwnProperties(O, “key+value”) 호출
- 각 키에 대해 Get(O, key)로 값 획득
- [key, value] 쌍 배열 반환
-
Object.hasOwn(O, P) (Section 20.1.2.13):
- ToObject(O)로 객체 변환
- ToPropertyKey(P)로 키 변환
- HasOwnProperty(O, P) 추상 연산 호출
성능 특성 및 최적화 전략 V8 엔진에서의 최적화 비교:
const obj = { a: 1, b: 2, c: 3, d: 4, e: 5 };
const iterations = 1000000;
// 벤치마크 1: for...in + hasOwnProperty
console.time('for...in');
for (let i = 0; i < iterations; i++) {
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];
}
}
}
console.timeEnd('for...in'); // ~150ms
// 벤치마크 2: Object.keys()
console.time('Object.keys');
for (let i = 0; i < iterations; i++) {
const keys = Object.keys(obj);
for (let j = 0; j < keys.length; j++) {
const value = obj[keys[j]];
}
}
console.timeEnd('Object.keys'); // ~80ms
// 벤치마크 3: Object.entries()
console.time('Object.entries');
for (let i = 0; i < iterations; i++) {
const entries = Object.entries(obj);
for (let j = 0; j < entries.length; j++) {
const [key, value] = entries[j];
}
}
console.timeEnd('Object.entries'); // ~60ms (값 접근 불필요)
// 벤치마크 4: for...of + Object.entries()
console.time('for...of');
for (let i = 0; i < iterations; i++) {
for (const [key, value] of Object.entries(obj)) {
// Iterator protocol 오버헤드
}
}
console.timeEnd('for...of'); // ~100ms
엔진 최적화 메커니즘 V8 TurboFan 컴파일러는 Object.keys/values/entries에 대해 특별한 최적화를 적용합니다:
- Fast Properties Path: 객체가 Fast Properties 모드면 내부 Properties 배열을 직접 복사
- Inline Allocation: 반환 배열을 Young Generation에 인라인 할당
- Escape Analysis: 배열이 함수 스코프를 벗어나지 않으면 스택에 할당
- Constant Folding: 객체 구조가 고정이면 키 배열을 컴파일 타임에 미리 생성
Object.getOwnPropertyNames vs Object.keys Object.getOwnPropertyNames()는 열거 불가능한 속성도 포함하므로 다른 용도로 사용됩니다:
const obj = {};
Object.defineProperty(obj, 'hidden', {
value: 'secret',
enumerable: false
});
console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // ['hidden']
// 클래스 메서드는 열거 불가능
class MyClass {
method() {}
}
const instance = new MyClass();
console.log(Object.keys(instance)); // []
console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(instance)));
// ['constructor', 'method']
Symbol 속성 처리 ES6 Symbol 속성은 별도 API로 처리됩니다:
const sym = Symbol('key');
const obj = {
normalKey: 'value',
[sym]: 'symbol value'
};
console.log(Object.keys(obj)); // ['normalKey']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(key)]
// 모든 own property keys (Symbol 포함)
console.log(Reflect.ownKeys(obj)); // ['normalKey', Symbol(key)]
TypeScript와의 통합 TypeScript에서는 타입 안정성을 위해 Object.keys()의 타입이 제한적입니다:
interface User {
name: string;
age: number;
}
const user: User = { name: 'Alice', age: 25 };
// Object.keys()는 string[] 반환 (keyof User가 아님)
Object.keys(user).forEach(key => {
// key는 string 타입, 'name' | 'age'가 아님
console.log(user[key]); // 컴파일 에러
});
// 타입 단언 필요
(Object.keys(user) as Array<keyof User>).forEach(key => {
console.log(user[key]); // 안전
});
// 또는 Object.entries() 사용
Object.entries(user).forEach(([key, value]) => {
console.log(value); // 타입 안전
});
실무 권장 사항
- ES2022+ 환경: Object.hasOwn() + Object.keys/entries() 조합
- 레거시 환경: Babel로 트랜스파일 또는 폴리필 사용
- 성능 중요 코드: 프로파일링 후 최적화 (일반적으로 Object.keys()가 가장 빠름)
- TypeScript: Object.entries()로 타입 안정성 확보