Object

Object (객체)

Object 타입은 JavaScript의 데이터 타입 중 하나를 나타냅니다. 다양한 키 기반 컬렉션과 더 복잡한 개체를 저장하는 데 사용됩니다. 객체는 Object() 생성자 또는 객체 이니셜라이저 / 리터럴 문법으로 만들 수 있습니다. 원문은 MDN의 JavaScript 내장 객체 참고 문서입니다.

출처: Object - JavaScript | MDN

본문

Object 타입은 JavaScript의 데이터 타입 중 하나를 나타냅니다. 다양한 키 기반 컬렉션(keyed collections)과 더 복잡한 개체(entity)를 저장하는 데 사용됩니다. 객체는 Object() 생성자 또는 객체 이니셜라이저 / 리터럴 문법으로 만들 수 있습니다.

설명 (Description)

JavaScript의 거의 모든 객체는 Object의 인스턴스입니다. 전형적인 객체는 Object.prototype에서 속성(메서드 포함)을 상속하지만, 이러한 속성은 가려질(shadowed, 일명 override) 수 있습니다. Object.prototype을 상속하지 않는 유일한 객체는 null 프로토타입을 가진 객체이거나 다른 null 프로토타입 객체에서 파생된 객체입니다.

Object.prototype 객체에 대한 변경은 프로토타입 체인을 통해 모든 객체에 보입니다. 단, 프로토타입 체인에서 더 아래쪽에 있는 객체가 해당 변경의 대상이 되는 속성과 메서드를 override하지 않는 한 그렇습니다. 이는 객체 동작을 override하거나 확장하는 강력하지만 잠재적으로 위험한 메커니즘을 제공합니다. 더 안전하게 만들기 위해 Object.prototype은 핵심 JavaScript 언어에서 불변 프로토타입(immutable prototype)을 가진 유일한 객체입니다 — Object.prototype의 프로토타입은 항상 null이며 변경할 수 없습니다.

Object 프로토타입 속성. 인스턴스에서 Object.prototype 메서드를 직접 호출하는 것은 피해야 합니다. 특히 다형적(polymorphic)으로 의도되지 않은 것들(즉 최초 동작만 의미가 있고 하위 객체가 의미 있게 override할 수 없는 것) 말입니다. Object.prototype에서 파생된 모든 객체는 같은 이름이지만 예상과 완전히 다른 의미를 가진 고유(own) 속성을 정의할 수 있습니다. 또한 이러한 속성은 null 프로토타입 객체에는 상속되지 않습니다. 객체를 다루는 모든 현대 JavaScript 유틸리티는 정적(static)입니다. 구체적으로:

  • valueOf(), toString(), toLocaleString()은 다형적으로 존재하도록 만들어졌으며, 객체가 합리적인 동작으로 자신의 구현을 정의할 것이라 기대할 수 있으므로 인스턴스 메서드로 호출할 수 있습니다. 하지만 valueOf()toString()은 보통 타입 변환을 통해 암묵적으로 호출되므로 코드에서 직접 호출할 필요는 없습니다.
  • __defineGetter__(), __defineSetter__(), __lookupGetter__(), __lookupSetter__()는 deprecated이며 사용해선 안 됩니다. 대신 정적 대안인 Object.defineProperty()Object.getOwnPropertyDescriptor()를 사용하세요.
  • __proto__ 속성은 deprecated이며 사용해선 안 됩니다. 대안인 Object.getPrototypeOf()Object.setPrototypeOf()는 정적 메서드입니다.
  • propertyIsEnumerable()hasOwnProperty() 메서드는 각각 Object.getOwnPropertyDescriptor()Object.hasOwn() 정적 메서드로 대체할 수 있습니다.
  • isPrototypeOf() 메서드는 보통 생성자의 prototype 속성을 확인하는 경우 instanceof로 대체할 수 있습니다.
  • 의미적으로 동등한 정적 메서드가 존재하지 않거나 정말로 Object.prototype 메서드를 사용하고 싶다면, override 속성으로 인한 예상치 못한 결과를 막기 위해 대상 객체에 Object.prototype 메서드를 직접 call()해야 합니다.
const obj = {
  foo: 1,
  // 자신의 객체에 이런 메서드를 정의하면 안 되지만,
  // 외부 입력에서 객체를 받는다면 이를 막지 못할 수도 있음
  propertyIsEnumerable() {
    return false;
  },
};
obj.propertyIsEnumerable("foo"); // false; 예상 밖의 결과
Object.prototype.propertyIsEnumerable.call(obj, "foo"); // true; 예상된 결과

객체에서 속성 삭제하기. Object 자체에는 자신의 속성을 삭제하는 메서드가 없습니다(Map.prototype.delete() 같은 것). 그러려면 delete 연산자를 사용해야 합니다.

null 프로토타입 객체(null-prototype objects). JavaScript의 거의 모든 객체는 궁극적으로 Object.prototype을 상속합니다. 그러나 Object.create(null)이나 __proto__: null을 가진 객체 이니셜라이저 문법으로 null 프로토타입 객체를 만들 수 있습니다(참고: 객체 리터럴의 __proto__ 키는 deprecated된 Object.prototype.__proto__ 속성과 다릅니다). 또한 Object.setPrototypeOf(obj, null)을 호출해 기존 객체의 프로토타입을 null로 바꿀 수도 있습니다.

const obj = Object.create(null);
const obj2 = { __proto__: null };

null 프로토타입 객체는 Object.prototype에서 어떤 객체 메서드도 상속하지 않으므로 예상 밖으로 동작할 수 있습니다. 특히 디버깅할 때 흔한 객체 속성 변환/탐지 유틸리티 함수가 오류를 내거나 정보를 잃을 수 있어(r특히 오류를 무시하는 조용한 오류 트랩을 쓰는 경우) 곤란합니다. 예를 들어 Object.prototype.toString()이 없으면 디버깅이 어려워지는 경우가 많습니다.

const normalObj = {};                          // 일반 객체 생성
const nullProtoObj = Object.create(null);      // "null" 프로토타입 객체 생성
console.log(`normalObj is: ${normalObj}`);     // "normalObj is: [object Object]"
console.log(`nullProtoObj is: ${nullProtoObj}`); // 오류 발생: Cannot convert object to primitive value
nullProtoObj.valueOf();                        // 오류: nullProtoObj.valueOf is not a function
nullProtoObj.hasOwnProperty("p");              // 오류: nullProtoObj.hasOwnProperty is not a function
nullProtoObj.constructor;                      // "undefined"

toString 메서드를 null 프로토타입 객체에 할당해 되살릴 수 있습니다.

nullProtoObj.toString = Object.prototype.toString; // 새 객체에는 toString이 없으므로 원래의 일반 버전을 되살림
console.log(nullProtoObj.toString()); // "[object Object]"

일반 객체에서는 toString()이 객체의 프로토타입에 있지만, 여기서의 toString()nullProtoObj의 고유 속성입니다. nullProtoObj가 (null) 프로토타입이 없기 때문입니다. Object.setPrototypeOf(nullProtoObj, Object.prototype)으로 null 프로토타입 객체를 다시 일반 객체로 되돌릴 수도 있습니다.

실무에서 null 프로토타입 객체는 보통 map의 저렴한 대체물로 사용됩니다. Object.prototype 속성이 존재하면 일부 버그가 발생합니다.

const ages = { alice: 18, bob: 27 };
function hasPerson(name) { return name in ages; }
function getAge(name) { return ages[name]; }
hasPerson("hasOwnProperty"); // true
getAge("toString");          // [Function: toString]

null 프로토타입 객체를 사용하면 hasPersongetAge 함수에 너무 많은 복잡성을 더하지 않고도 이 위험을 제거합니다.

const ages = Object.create(null, {
  alice: { value: 18, enumerable: true },
  bob: { value: 27, enumerable: true },
});
hasPerson("hasOwnProperty"); // false
getAge("toString");          // undefined

이런 경우 메서드 추가는 신중히 해야 하는데, 데이터로 저장된 다른 키-값 쌍과 혼동될 수 있기 때문입니다. 또한 객체가 Object.prototype을 상속하지 않게 만들면 프로토타입 오염(prototype pollution) 공격도 막을 수 있습니다. 악성 스크립트가 Object.prototype에 속성을 추가하면 null 프로토타입 객체를 제외한 프로그램의 모든 객체에서 그 속성에 접근할 수 있게 되기 때문입니다.

const user = {};
// 악성 스크립트:
Object.prototype.authenticated = true;
// 인증되지 않은 사용자가 지나가는 것을 예기치 않게 허용
if (user.authenticated) { /* 기밀 데이터 접근 */ }

JavaScript에는 null 프로토타입 객체를 만드는 내장 API도 있습니다. 특히 객체를 임시(ad hoc) 키-값 컬렉션으로 사용하는 것들입니다. 예: Object.groupBy()의 반환 값, RegExp.prototype.exec()의 결과의 groupsindices.groups 속성, Array.prototype[Symbol.unscopables], import.meta, import * as ns from "module" 또는 import()로 얻은 모듈 네임스페이스 객체 등. "null 프로토타입 객체"라는 용어는 프로토타입 체인에 Object.prototype이 없는 어떤 객체든 포함하며, 클래스를 쓸 때 extends null로도 만들 수 있습니다.

객체 강제 변환(Object coercion). 객체를 기대하는 많은 내장 연산은 먼저 인자를 객체로 강제 변환합니다. 그 과정은 다음과 같이 요약됩니다.

  • 객체는 그대로 반환됩니다.
  • undefinednullTypeError를 던집니다.
  • Number, String, Boolean, Symbol, BigInt 기본형은 대응하는 객체 래퍼로 감싸집니다.

JavaScript에서 거의 같은 효과를 얻는 두 가지 방법이 있습니다. Object.prototype.valueOf()Object.prototype.valueOf.call(x)x를 변환하는 위의 객체 강제 변환 단계를 정확히 수행합니다. 그리고 Object() 함수 — Object(x)도 같은 알고리즘으로 x를 변환하지만, undefinednullTypeError를 던지지 않고 평범한 객체를 반환합니다.

객체 강제 변환을 사용하는 곳: for...in 루프의 object 매개변수, Array 메서드의 this 값, Object.keys() 같은 Object 메서드의 매개변수, 기본형에는 속성이 없으므로 기본형 값에 속성을 접근할 때의 자동 박싱(auto-boxing), 비-strict 함수를 호출할 때의 this 값(기본형은 박싱되고 nullundefined는 전역 객체로 대체됨). 객체로의 기본형 변환과 달리 객체 강제 변환 과정 자체는 toString이나 valueOf 같은 사용자 코드를 호출하지 않으므로 어떤 방식으로도 관찰할 수 없습니다.

생성자 (Constructor)

  • Object() — 입력을 객체로 바꿉니다.

정적 메서드 (Static methods)

  • Object.assign() — 하나 이상의 소스 객체에서 모든 열거 가능한 고유 속성의 값을 대상 객체로 복사합니다.
  • Object.create() — 지정된 프로토타입 객체와 속성을 가진 새 객체를 만듭니다.
  • Object.defineProperties() / Object.defineProperty() — 주어진 설명자(descriptor)가 가리키는 (이름 있는) 속성을 객체에 추가합니다.
  • Object.entries() — 주어진 객체의 고유 열거 가능한 문자열 속성의 모든 [key, value] 쌍을 담은 배열을 반환합니다.
  • Object.freeze() — 객체를 동결합니다. 다른 코드가 그 속성을 삭제하거나 변경할 수 없습니다.
  • Object.fromEntries() — [key, value] 쌍의 iterable로 새 객체를 반환합니다(Object.entries의 역).
  • Object.getOwnPropertyDescriptor() / Object.getOwnPropertyDescriptors() — 객체의 (이름 있는) 고유 속성 설명자(들)를 반환합니다.
  • Object.getOwnPropertyNames() — 주어진 객체의 고유 열거 가능/비열거 가능 속성 이름을 모두 담은 배열을 반환합니다.
  • Object.getOwnPropertySymbols() — 주어진 객체에서 직접 찾은 모든 symbol 속성의 배열을 반환합니다.
  • Object.getPrototypeOf() — 지정된 객체의 프로토타입(내부 [[Prototype]] 속성)을 반환합니다.
  • Object.groupBy() — 제공된 콜백 함수가 반환하는 문자열 값에 따라 주어진 iterable의 요소를 그룹화합니다. 반환 객체는 각 그룹별로 별도 속성을 가지며, 그 그룹의 요소를 담은 배열을 포함합니다.
  • Object.hasOwn() — 지정된 객체가 해당 속성을 고유 속성으로 가지면 true, 상속되었거나 존재하지 않으면 false를 반환합니다.
  • Object.is() — 두 값이 같은 값인지 비교합니다. 모든 NaN 값을 동일하게 취급합니다(==가 쓰는 IsLooselyEqual이나 ===가 쓰는 IsStrictlyEqual과 다름).
  • Object.isExtensible() / Object.isFrozen() / Object.isSealed() — 객체 확장 허용 여부, 동결 여부, 밀봉 여부를 판단합니다.
  • Object.keys() — 주어진 객체의 고유 열거 가능한 문자열 속성 이름을 모두 담은 배열을 반환합니다.
  • Object.preventExtensions() — 객체의 어떤 확장도 막습니다.
  • Object.seal() — 다른 코드가 객체의 속성을 삭제하지 못하게 합니다.
  • Object.setPrototypeOf() — 객체의 프로토타입(내부 [[Prototype]] 속성)을 설정합니다.
  • Object.values() — 주어진 객체의 고유 열거 가능한 문자열 속성에 대응하는 값들을 담은 배열을 반환합니다.

인스턴스 속성 및 메서드 (Instance properties and methods)

인스턴스 속성은 Object.prototype에 정의되어 모든 Object 인스턴스가 공유합니다. __proto__는 객체가 인스턴스화될 때 프로토타입으로 사용된 객체를 가리키고, constructor는 인스턴스 객체를 만든 생성자 함수를 가리킵니다(일반 Object 인스턴스의 초기값은 Object 생성자). 인스턴스 메서드로는 __defineGetter__(), __defineSetter__(), __lookupGetter__(), __lookupSetter__(), constructor, hasOwnProperty(), isPrototypeOf(), propertyIsEnumerable(), toLocaleString(), toString(), valueOf() 등이 있습니다(이 중 deprecated 메서드는 정적 대안 사용을 권장).

사양 및 호환성

Object는 ECMAScript 사양에서 정의되며, Baseline "Widely available"로 분류되어 2015년 7월부터 브라우저 전반에서 사용 가능했습니다. 기능의 일부 부분은 지원 수준이 각각 다를 수 있습니다.

더 알아보기