1. 프로토타입이란?
자바스크립트는 프로토타입 기반 언어로, 클래스 기반 언어와는 다른 방식으로 객체지향 프로그래밍을 구현한다.
프로토타입은 자바스크립트 객체가 다른 객체로부터 메서드와 속성을 상속받는 메커니즘이다.
모든 자바스크립트 객체는 프로토타입 객체에 대한 참조인 [[Prototype]]을 가지고 있다.
객체의 프로토타입은 Object.getPrototypeOf(obj) 또는 obj.__proto__로 접근할 수 있다.
자바스크립트에서 객체의 속성이나 메서드에 접근할 때, 해당 객체에 없으면 프로토타입 체인을 따라 올라가며 검색한다.
1.1. 객체지향 언어와 자바스크립트의 차이
전통적인 클래스 기반 언어(Java, C++ 등)에서는 클래스가 객체의 청사진 역할을 한다.
반면 자바스크립트는 클래스 개념 없이 객체가 다른 객체로부터 직접 상속받는 프로토타입 기반 언어이다.
클래스 기반 언어:
- 클래스를 정의하고 인스턴스를 생성
- 상속은 클래스 간에 발생
- 컴파일 타임에 클래스 구조가 고정됨
프로토타입 기반 언어:
- 객체를 직접 생성하고 확장
- 객체가 다른 객체로부터 상속
- 런타임에 객체 구조를 동적으로 변경 가능
ES6에서 class 키워드가 도입되었지만, 이것은 기존 프로토타입 상속에 대한 문법적 설탕(Syntactic sugar)일 뿐이다.
1.2. 프로토타입 체인
프로토타입 체인은 객체의 속성이나 메서드를 검색할 때 따라가는 참조의 사슬이다.
객체에서 속성을 찾지 못하면 해당 객체의 프로토타입에서 검색하고, 거기서도 찾지 못하면 프로토타입의 프로토타입에서 계속 검색한다.
이 과정은 프로토타입이 null인 객체(보통 Object.prototype)에 도달할 때까지 계속된다.
ex)
const animal = {
eat: function() {
return "먹는 중...";
}
};
const dog = Object.create(animal);
dog.bark = function() {
return "멍멍!";
};
console.log(dog.bark()); // "멍멍!" - dog 객체에서 직접 찾음
console.log(dog.eat()); // "먹는 중..." - 프로토타입 체인을 통해 animal에서 찾음
console.log(dog.fly); // undefined - 프로토타입 체인 어디에도 없음
이 예시에서 dog 객체는 animal 객체를 프로토타입으로 가진다.
2. 프로토타입 상속
2.1. 프로토타입을 통한 메서드 상속
자바스크립트에서 프로토타입을 이용한 상속은 여러 방법으로 구현할 수 있다.
1) Object.create() 사용
Object.create()는 지정된 프로토타입 객체와 속성을 가진 새 객체를 생성한다.
ex)
const parent = {
sayHello: function() {
return "안녕하세요!";
}
};
const child = Object.create(parent);
child.sayGoodbye = function() {
return "안녕히 가세요!";
};
console.log(child.sayHello()); // "안녕하세요!" - 부모에서 상속
console.log(child.sayGoodbye()); // "안녕히 가세요!" - 자식의 메서드
2) 프로토타입 직접 설정
객체의 __proto__ 속성이나 Object.setPrototypeOf()를 사용하여 프로토타입을 직접 설정할 수 있다.
ex)
const parent = {
sayHello: function() {
return "안녕하세요!";
}
};
const child = {
sayGoodbye: function() {
return "안녕히 가세요!";
}
};
// 프로토타입 설정
Object.setPrototypeOf(child, parent);
console.log(child.sayHello()); // "안녕하세요!"
2.2. 생성자 함수와 프로토타입
자바스크립트에서 함수는 prototype 속성을 가지며, 이 속성은 new 키워드로 생성된 객체의 프로토타입이 된다.
ex)
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입에 메서드 추가
Person.prototype.sayHello = function() {
return `안녕하세요, ${this.name}입니다.`;
};
// 인스턴스 생성
const person1 = new Person("김무무");
const person2 = new Person("김경민");
console.log(person1.sayHello()); // "안녕하세요, 김무무입니다."
console.log(person2.sayHello()); // "안녕하세요, 김경민입니다."
// 모든 인스턴스가 동일한 프로토타입을 공유
console.log(person1.__proto__ === Person.prototype); // true
console.log(person2.__proto__ === Person.prototype); // true
생성자 함수의 프로토타입에 메서드를 추가하면 해당 생성자로 만든 모든 인스턴스가 그 메서드를 공유한다.
3. Class
3.1. class 키워드의 내부 작동 방식
ES6에서 도입된 class 문법은 기존 프로토타입 상속을 더 직관적으로 작성할 수 있게 해준다.
그러나 내부적으로는 여전히 프로토타입 메커니즘을 사용한다.
ex)
// ES6 클래스 정의
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return `안녕하세요, ${this.name}입니다.`;
}
}
const person = new Person("무무");
console.log(person.sayHello()); // "안녕하세요, 무무입니다."
// 클래스 메서드는 프로토타입에 추가됨
console.log(person.sayHello === Person.prototype.sayHello); // true
위 코드에서 sayHello 메서드는 Person.prototype에 추가된다.
constructor 메서드는 생성자 함수와 동일한 역할을 한다.
3.2. 클래스 vs 프로토타입 작성 비교
1) ES6 클래스를 사용한 상속
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name}가 소리를 냅니다.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 부모 클래스의 constructor 호출
this.breed = breed;
}
speak() {
return `${this.name}가 멍멍 짖습니다.`;
}
describeSelf() {
return `${this.name}는 ${this.breed} 입니다.`;
}
}
const dog = new Dog("무무", "강아지");
console.log(dog.speak()); // "무무가 멍멍 짖습니다."
console.log(dog.describeSelf()); // "무무는 강아지입니다."
2) 프로토타입 방식
// 생성자 함수
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name}가 소리를 냅니다.`;
};
function Dog(name, breed) {
// 부모 생성자 호출
Animal.call(this, name);
this.breed = breed;
}
// 프로토타입 상속 설정
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // constructor 복구
// 메서드 오버라이드
Dog.prototype.speak = function() {
return `${this.name}가 멍멍 짖습니다.`;
};
// 새 메서드 추가
Dog.prototype.describeSelf = function() {
return `${this.name}는 ${this.breed} 입니다.`;
};
const dog = new Dog("무무", "강아지");
console.log(dog.speak()); // "무무가 멍멍 짖습니다."
console.log(dog.describeSelf()); // "무무는 강아지입니다."
클래스 문법은 상당히 간결하고 직관적인 반면, 전통적인 프로토타입 방식은 더 복잡하고 실수하기 쉽다.
그러나 두 방식 모두 내부적으로는 동일한 프로토타입 체인을 생성한다.
4. 주의사항
1) 내장 객체 프로토타입 수정 자제
내장 객체(String, Array 등)의 프로토타입을 수정하는 것은 위험할 수 있다.
- 다른 라이브러리와 충돌 가능성
- 향후 자바스크립트 버전에서 동일한 이름의 메서드가 추가될 경우 문제 발생
- 코드의 예측 가능성 감소
2) 프로토타입 체인의 깊이
프로토타입 체인이 너무 깊으면 속성 검색 시 성능이 저하될 수 있다.
// 프로토타입 체인이 너무 깊은 경우
const level1 = { prop1: 'value1' };
const level2 = Object.create(level1);
const level3 = Object.create(level2);
const level4 = Object.create(level3);
const level5 = Object.create(level4);
// level5.prop1에 접근할 때 5단계를 거쳐야 함
console.log(level5.prop1); // "value1" (하지만 접근 속도가 느림)
3) 프로토타입 체인의 동적 수정
이미 생성된 객체의 프로토타입을 변경하면 성능에 악영향을 미칠 수 있다.
가능하면 객체 생성 시 프로토타입을 설정하고 이후에는 변경하지 않는 것이 좋다.
'학습 > JavaScript' 카테고리의 다른 글
JavaScript 비동기 3 : async/await (0) | 2025.04.11 |
---|---|
JavaScript 비동기 2 : 콜백, Promise (0) | 2025.04.09 |
JavaScript 비동기 1 : 이벤트 기반 (0) | 2025.04.09 |
var, let, const (0) | 2025.04.07 |
Javascript와 C++ (0) | 2024.12.15 |