자바스크립트의 `Object` 클래스
이 글은 자바스크립트의 Object 클래스에 대해 설명합니다.
이 글은 실제 예제를 포함하여 자바스크립트의 Object 클래스에 대해 설명합니다.
YouTube Video
자바스크립트의 Object 클래스
Object는 모든 자바스크립트 객체의 기반이 되는 내장 객체입니다. Object의 동작을 통해 속성 관리, 상속(프로토타입 체인), 열거, 복제, 동결과 같은 언어의 핵심 기능 대부분이 제공됩니다.
Object.create
객체를 생성하는 방법에는 여러 가지가 있으며, 목적에 따라 적절하게 사용해야 합니다.
객체 리터럴(가장 일반적인 방법)
아래 코드는 객체를 생성하는 가장 간단하고 가독성이 좋은 방법을 보여줍니다.
1// Create an object using object literal
2const user = {
3 name: "Alice",
4 age: 30,
5 greet() {
6 return `Hello, I'm ${this.name}`;
7 }
8};
9
10console.log(user.greet()); // "Hello, I'm Alice"
- 이 예제에서 속성과 메서드는 리터럴을 사용하여 정의됩니다. 간단하며 일반적으로 우수한 성능을 제공합니다.
new Object() 생성자
Object 생성자는 거의 사용되지 않지만, 그 동작을 이해하는 것이 도움이 됩니다.
1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
new Object()는 빈 객체를 반환하지만, 리터럴{}이 더 짧고 흔히 사용됩니다.
Object.create로 프로토타입 명시하기
Object.create는 프로토타입을 지정하여 객체를 생성할 때 사용됩니다.
1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
Object.create는 상속 기반 설계에 적합하며, 프로토타입 체인을 정밀하게 제어할 수 있습니다.
프로퍼티 속성과 디스크립터
프로퍼티는 'value', 'writable', 'enumerable', 'configurable' 등과 같은 속성을 가지며, Object.defineProperty로 세밀하게 제어할 수 있습니다.
defineProperty 기본 예제
다음은 defineProperty를 사용하여 열거할 수 없고 읽기 전용 속성을 정의하는 예제입니다.
1// Define a non-enumerable read-only property
2const person = { name: "Carol" };
3
4Object.defineProperty(person, "id", {
5 value: 12345,
6 writable: false,
7 enumerable: false,
8 configurable: false
9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
defineProperty를 사용하면 속성의 열거, 수정, 삭제와 같은 동작을 정밀하게 제어할 수 있습니다.
접근자 프로퍼티(getter / setter)
접근자를 이용해 프로퍼티의 읽기 및 쓰기에 로직을 삽입할 수 있습니다.
1// Use getter and setter to manage internal state
2const data = {
3 _value: 1,
4 get value() {
5 return this._value;
6 },
7 set value(v) {
8 if (typeof v === "number" && v > 0) {
9 this._value = v;
10 } else {
11 throw new Error("value must be a positive number");
12 }
13 }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
getter와setter를 사용하면 프로퍼티 접근을 외부 API처럼 다루고, 검증이나 부수효과를 추가할 수 있습니다.
프로토타입과 상속(prototype / __proto__ / Object.getPrototypeOf)
자바스크립트의 상속은 클래스가 아니라 프로토타입 체인을 기반으로 합니다. 객체는 다른 객체를 자신의 프로토타입으로 참조할 수 있습니다.
Object.getPrototypeOf와 Object.setPrototypeOf
다음 예제는 프로토타입을 확인하고 설정하는 방법을 보여줍니다.
1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
Object.getPrototypeOf는 객체의 프로토타입을 반환합니다.Object.setPrototypeOf는 기존 객체의 프로토타입을 변경하지만, 성능에 영향을 줄 수 있으므로 신중히 사용해야 합니다.
중요한 내장 메서드
Object.prototype이 제공하는 인스턴스 메서드와 Object가 소유한 정적 메서드 중에서 자주 사용되고 중요한 메서드를 명확하게 설명하겠습니다.
hasOwnProperty, isPrototypeOf, toString, valueOf
hasOwnProperty, isPrototypeOf, toString, valueOf는 객체의 기본 동작을 정의합니다.
1// Demonstrate prototype methods
2const base = { greet() { return "hello"; } };
3const child = Object.create(base);
4const a = { x: 1 };
5
6console.log(a.hasOwnProperty("x")); // true
7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
8
9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
hasOwnProperty는 프로퍼티가 해당 객체에 직접 정의되어 있는지 확인하는 필수 메서드입니다.isPrototypeOf는 대상 객체가 자신을 프로토타입으로 가지고 있는지 확인합니다.
Object.keys, Object.values, Object.entries
Object.keys, Object.values, Object.entries는 객체 자신의 열거 가능한 프로퍼티 목록을 반환합니다. 이들은 반복 및 변환 작업에 유용합니다.
1// Keys, values and entries
2const item = { id: 1, name: "Widget", price: 9.99 };
3
4// ["id", "name", "price"]
5console.log(Object.keys(item));
6
7// [1, "Widget", 9.99]
8console.log(Object.values(item));
9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));- 이들은 객체를 반복하거나 변환할 때 자주 사용됩니다.
Object.assign
Object.assign은 얕은 복사 및 병합에 사용됩니다. 프로토타입과 접근자 프로퍼티는 복사되지 않으니 주의하세요.
1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
- 중첩된 객체의 경우 참조만 복사되므로, 깊은 복사는 별도의 구현이 필요합니다.
Object.freeze, Object.seal, Object.preventExtensions
Object.freeze, Object.seal, Object.preventExtensions는 객체의 변경 가능성을 제어합니다.
1// Freeze vs seal vs preventExtensions
2const obj = { a: 1 };
3Object.freeze(obj);
4obj.a = 2; // fails silently or throws in strict mode
5delete obj.a; // fails
6
7const obj2 = { b: 2 };
8Object.seal(obj2);
9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
freeze가 가장 엄격하며, 객체의 프로퍼티 변경을 모두 막습니다.seal은 속성의 추가나 삭제를 방지하지만, 기존 속성 값의 변경은 허용합니다.preventExtensions는 새 속성 추가만 막고, 기존 속성의 변경이나 삭제는 여전히 가능합니다.
객체의 열거성, 순서, for...in / for...of
for...in은 열거 가능한 프로퍼티 이름을 열거하지만, 프로토타입 체인의 프로퍼티도 포함하므로 hasOwnProperty와 함께 사용하는 경우가 많습니다. Object.keys()와 for...of를 조합해서 사용하는 것이 더 안전하며, 의도가 더 명확해집니다.
1// Safe enumeration
2const obj = Object.create({ inherited: true });
3obj.own = 1;
4
5for (const key in obj) {
6 if (obj.hasOwnProperty(key)) {
7 console.log("own prop:", key);
8 } else {
9 console.log("inherited prop:", key);
10 }
11}
12
13for (const key of Object.keys(obj)) {
14 console.log("key via Object.keys:", key);
15}- 프로퍼티 열거 규칙은 ECMAScript 명세에 정의되어 있으며, 순서가 보장되는 경우와 그렇지 않은 경우가 있습니다. 일반적으로 숫자로 해석되는 키는 오름차순으로 정렬되고, 그 외의 키는 삽입 순서를 따릅니다.
복제와 깊은 복사
객체 복사는 두 가지 유형이 있습니다. 얕은 복사는 Object.assign이나 전개 문법(spread syntax)를 사용하고, 깊은 복사는 재귀적인 방법을 통해 수행합니다. 상황에 따라 적절히 사용하는 것이 중요합니다.
얕은 복사(전개 구문 / Object.assign)
1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
- 얕은 복사는 중첩 객체의 참조를 공유하므로, 원본 객체의 변경이 복사본에 영향을 줄 수 있습니다.
간단한 깊은 복사(주의사항 있음)
JSON 기법을 사용하면 깊은 복사를 빠르게 할 수 있지만, 함수, Date, 순환 참조, undefined 값이 손실되는 등의 단점이 있습니다. 진정한 깊은 복사를 위해서는 전용 라이브러리를 사용하는 것이 필요합니다.
1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
- JSON 기반 메서드는 단순한 데이터를 빠르게 처리할 때 편리하지만, 일반적인 경우에 동작이 올바르지 않을 수 있습니다.
믹스인과 객체 합성
다중 상속 대신, 믹스인을 이용한 동작 합성 패턴이 자주 사용됩니다.
1// Simple mixin function
2const canEat = {
3 eat() { return `${this.name} eats`; }
4};
5const canWalk = {
6 walk() { return `${this.name} walks`; }
7};
8
9function createPerson(name) {
10 const person = { name };
11 return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
- 믹스인은 유연하지만, 프로퍼티 이름 충돌과 테스트 가능성에 주의해야 합니다.
자주 발생하는 오류와 모범 사례
몇 가지 흔한 오류와 모범 사례를 소개합니다.
-
변경 가능성 객체는 기본적으로 변경 가능합니다. 상태 관리를 사용하는 애플리케이션에서는
Object.freeze나 불변성 라이브러리를 사용하여 불변 데이터 구조를 고려하세요. -
프로토타입 오염
Object.assign이나 루프를 써서 외부 데이터를 직접 객체에 병합하면,__proto__나constructor와 같은 특수 프로퍼티로 인해 예기치 않은 부작용이나 보안 취약점이 발생할 수 있습니다. 직접 병합하기 전에 사용자 입력을 필터링하세요. -
for...in의 함정for...in은 프로토타입 프로퍼티도 열거하므로 반드시hasOwnProperty로 확인하세요.Object.keys를 사용하는 것이 더 명확합니다. -
얕은 복사의 오용 중첩 객체 변경이 원본 객체에 영향을 주지 않게 하려면 깊은 복사가 필요한지 고려하세요.
실전 예제: 불변 객체 업데이트 패턴
상태를 직접 수정하지 않고 새 객체를 반환하는 패턴은 React 등 유사한 라이브러리에서도 자주 사용됩니다.
1// Immutable update example
2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
3
4// Toggle todo done immutably
5function toggleTodo(state, todoId) {
6 return {
7 ...state,
8 todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
9 };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
- 이 코드는 원래의
state객체를 직접 수정하지 않고 새로운 상태 객체를 만드는 예제입니다.toggleTodo함수는todos배열을 복사하고, 대상 요소만 변경된 새로운 객체를 반환하므로 원래의state는 변경되지 않습니다. - 불변 업데이트는 부작용을 줄이고 상태 관리를 쉽게 합니다.
실전 예제: 안전한 병합(프로토타입 오염에 주의)
외부 JSON을 병합할 때는 __proto__를 무시하여 프로토타입 오염을 방지하세요.
1// Safe merge ignoring __proto__ keys
2function safeMerge(target, source) {
3 for (const key of Object.keys(source)) {
4 if (key === "__proto__" || key === "constructor") continue;
5 target[key] = source[key];
6 }
7 return target;
8}
9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
- 이런 보호는 라이브러리나 프레임워크에서도 매우 중요합니다.
성능 고려사항
성능과 관련해 다음을 고려하세요:.
Object.setPrototypeOf를 사용한 잦은 프로토타입 변경이나 프로퍼티의 동적 추가/삭제는 엔진 최적화를 방해하므로 피하세요.- 많은 작은 객체를 생성할 때, 동일한 속성 집합을 가진 일정한 구조의 객체를 사용하면 최적화가 더욱 효과적으로 이루어집니다.
- 깊은 복사는 비용이 많이 듭니다. 사용을 최소화하거나, 차분 기반 업데이트를 고려하세요.
요약
Object는 자바스크립트의 핵심으로, 객체 생성, 프로퍼티 제어, 상속, 복사, 변경 가능성 관리 등 다양한 기능을 제공합니다. Object.defineProperty, Object.assign, Object.freeze와 같은 API를 이해하고, 프로토타입 오염이나 얕은 복사와 같은 함정을 피하기 위해 신중하게 설계하는 것이 중요합니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.