JavaScript 프로토타입: 객체 지향 프로그래밍 이해하기

작성일 :

JavaScript 프로토타입: 객체 지향 프로그래밍 이해하기

JavaScript는 유연하면서도 강력한 언어입니다. 특히, 객체지향 프로그래밍(OOP) 방식을 지원하면서 다른 언어와는 다르게 프로토타입 기반의 상속을 사용합니다. 이 글에서는 JavaScript의 프로토타입 기반 OOP를 이해하고 더 나아가 효율적으로 활용하는 방법에 대해 알아보겠습니다.

객체 생성 및 프로토타입 이해하기

JavaScript에서의 객체는 속성과 메서드의 집합입니다. 객체는 Object 생성자 함수나 객체 리터럴을 사용하여 생성할 수 있습니다. 예를 들어, 다음과 같습니다.

javascript
// 객체 리터럴을 사용한 객체 생성
const person = {
    name: 'John',
    age: 30,
    greet() {
        console.log(`Hello, my name is ${this.name}`);
    }
};

// 객체 사용하는 예
person.greet();  // Outputs: Hello, my name is John

프로토타입은 JavaScript에서 상속을 구현하는 핵심적인 개념입니다. 기본적으로 모든 객체는 __proto__라는 숨겨진 프로퍼티를 가지며, 이는 자신을 생성한 생성자의 prototype 프로퍼티를 참조합니다. 예를 들어, 위의 person 객체는 Object.prototype을 상속받습니다.

javascript
// 프로토타입 체인 확인
console.log(person.__proto__ === Object.prototype);  // true

생성자 함수와 프로토타입

생성자 함수는 새로운 객체를 만들기 위한 템플릿 역할을 합니다. 생성자 함수를 이용하면 여러 객체를 생성할 때 효율적으로 속성과 메서드를 공유할 수 있습니다.

javascript
// 생성자 함수
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 프로토타입 메서드 추가
Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name}`);
};

// 새로운 객체 생성
const john = new Person('John', 30);
const jane = new Person('Jane', 25);

// 메서드 호출
john.greet();  // Outputs: Hello, my name is John
jane.greet();  // Outputs: Hello, my name is Jane

위 예제에서 Person 생성자 함수는 nameage 속성을 초기화합니다. greet 메서드는 Person.prototype에 정의되어 있으며, 이를 통해 모든 인스턴스가 공유하게 됩니다. 따라서 johnjane은 각각 자신의 greet 메서드를 호출할 수 있습니다.

프로토타입 체인

프로토타입 체인은 객체 탐색 시 사용되는 메커니즘입니다. 특정 객체에서 프로퍼티를 찾지 못하면, 그 객체의 프로토타입 체인 상위 객체를 탐색합니다. 이 과정은 최종적으로 Object.prototype에 도달할 때까지 계속됩니다.

javascript
// 객체 생성
const obj = { a: 1 };

// 속성 탐색
console.log(obj.a);  // Outputs: 1
console.log(obj.toString());  // Outputs: [object Object]

위 예제에서 obj에는 toString 메서드가 없지만, Object.prototype에 정의된 toString을 사용합니다. 이처럼 객체는 프로토타입 체인을 통해 상속 관계를 형성합니다.

클래스와 프로토타입

ES6(ECMAScript 2015)에서 JavaScript는 class 문법을 도입하여 더 깔끔하고 직관적인 객체지향 프로그래밍 스타일을 제공합니다. class는 사실 함수이며 기존의 프로토타입 기반 상속을 여전히 사용합니다.

javascript
// 클래스 정의
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // 메서드 정의
    greet() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

// 객체 생성
const john = new Person('John', 30);
const jane = new Person('Jane', 25);

// 메서드 호출
john.greet();  // Outputs: Hello, my name is John
jane.greet();  // Outputs: Hello, my name is Jane

위 예제에서 Person 클래스는 생성자와 메서드를 정의하고 이를 통해 객체를 생성합니다. 클래스 내부에서 정의된 메서드 역시 프로토타입 체인에 의해 공유됩니다.

상속과 서브클래싱

JavaScript의 class 문법은 쉽게 상속을 구현할 수 있는 방법을 제공합니다. extends 키워드를 사용하여 서브클래스를 정의할 수 있습니다.

javascript
// 부모 클래스
class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

// 자식 클래스
class Dog extends Animal {
    constructor(name, breed) {
        super(name);  // 부모 클래스의 생성자를 호출
        this.breed = breed;
    }

    speak() {
        console.log(`${this.name} barks.`);
    }
}

// 객체 생성
const dog = new Dog('Rex', 'German Shepherd');

dog.speak();  // Outputs: Rex barks.

위 예제에서 Dog 클래스는 Animal 클래스를 상속받아 speak 메서드를 오버라이드합니다. super 키워드는 부모 클래스의 생성자를 호출하는 데 사용됩니다.

결론

JavaScript의 프로토타입 기반 객체지향 프로그래밍 방식은 매우 유연하며, 다양한 방법으로 활용할 수 있습니다. 프로토타입 체인을 통해 객체 간 상속 관계를 쉽게 구현할 수 있으며, 도시적 OOP 스타일의 class 문법도 사용할 수 있습니다. 이 글을 통해 JavaScript의 객체지향 프로그래밍 개념을 잘 이해하고 실제 프로젝트에 적용해보세요.