🧬 프로토타입이란?
📖 정의
**프로토타입(Prototype)**은 JavaScript 객체가 다른 객체로부터 속성과 메서드를 상속받을 수 있게 해주는 메커니즘입니다. 모든 JavaScript 객체는 다른 객체를 참조하는 내부 링크를 가지고 있으며, 이를 통해 속성과 메서드를 공유합니다.
🎯 비유로 이해하기
유전자 비유
프로토타입을 유전자에 비유하면:
부모 (Prototype)
├─ 눈 색깔
├─ 키
└─ 혈액형
자식 (Object)
├─ 부모의 특징 상속
├─ 자신만의 특징 추가
└─ 부모 특징 덮어쓰기 가능
과정:
1. 자식은 부모의 유전자를 물려받음
2. 자식에게 없는 특징은 부모에게 물어봄
3. 부모에게도 없으면 할아버지에게 물어봄
4. 최상위 조상까지 거슬러 올라감
JavaScript도 동일:
const child = Object.create(parent);
child.name // 자식에게 없으면 parent 확인
도서관 시스템 비유
중앙 도서관 (Object.prototype)
├─ 모든 책의 기 본 규칙
├─ 대출 시스템
└─ 반납 시스템
지역 도서관 (Array.prototype)
├─ 중앙 도서관 규칙 상속
├─ 배열 전용 기능 추가
└─ push, pop, map 등
나의 책장 (배열 인스턴스)
├─ 지역 도서관 기능 사용
├─ 나만의 책 보관
└─ 필요한 기능은 상위에서 빌려옴
예시:
const myBooks = ['책1', '책2'];
myBooks.push('책3'); // Array.prototype에서
myBooks.toString(); // Object.prototype에서
myBooks.myMethod(); // 본인 것
프로토타입 체인:
myBooks → Array.prototype → Object.prototype → null
회사 조직도 비유
CEO (Object.prototype)
├─ 기본 규칙: toString, valueOf 등
└─ 모든 직원이 사용 가능
부서장 (특정 Prototype)
├─ CEO 규칙 + 부서별 규칙
├─ Array: 배열 메서드
├─ Function: 함수 메서드
└─ Date: 날짜 메서드
직원 (인스턴스)
├─ 부서 규칙 사용
├─ 개인 업무 수행
└─ 필요시 상급자에게 질의
예시:
직원: "이 일 어떻게 하죠?"
└─> 부서장: "우리 매뉴얼에 있어"
└─> CEO: "그럼 회사 규정 확인해봐"
└─> null: "최상위까지 없으면 undefined"
코드로:
employee.task()
→ 직원에게 없으면
→ 부서장 확인
→ CEO 확인
→ 없으면 undefined
⚙️ 작동 원리
1. 프로토타입 기본 개념
// 모든 객체는 프로토타입을 가짐
// 객체 생성
const person = {
name: '철수',
age: 25
};
// person의 프로토타입 확인
console.log(Object.getPrototypeOf(person));
// Object.prototype
// person은 Object.prototype의 메서드 사용 가능
console.log(person.toString()); // Object.prototype에서
console.log(person.hasOwnProperty('name')); // true
// 프로토타입 체인
console.log(person.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null (최상위)
// 체인:
// person → Object.prototype → null
2. 생성자 함수와 프로토타입
// 생성자 함수
function Person(name, age) {
// 인스턴스 속성
this.name = name;
this.age = age;
}
// 프로토타입에 메서드 추가
Person.prototype.greet = function() {
console.log(`안녕, 나는 ${this.name}이야!`);
};
Person.prototype.getAge = function() {
return this.age;
};
// 인스턴스 생성
const person1 = new Person('철수', 25);
const person2 = new Person('영희', 23);
// 메서드 호출
person1.greet(); // 안녕, 나는 철수야!
person2.greet(); // 안녕, 나는 영희야!
// 메서드는 프로토타입에 한 번만 존재
console.log(person1.greet === person2.greet); // true (같은 함수)
// ❌ 메서드를 생성자에 넣으면?
function BadPerson(name) {
this.name = name;
this.greet = function() { // 매번 새로 생성!
console.log(`안녕, ${this.name}`);
};
}
const bad1 = new BadPerson('철수');
const bad2 = new BadPerson('영희');
console.log(bad1.greet === bad2.greet); // false (다른 함수)
// 메모리 낭비! 인스턴스마다 함수 복사
// ✅ 프로토타입 사용 (메모리 효율)
// 모든 인스턴스가 하나의 메서드 공유
3. 프로토타입 체인
// 프로토타입 체인: 상속 구조
// 1단계: Animal (부모)
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name}이(가) 먹고 있어요`);
};
Animal.prototype.sleep = function() {
console.log(`${this.name}이(가) 자고 있어요`);
};
// 2단계: Dog (자식)
function Dog(name, breed) {
Animal.call(this, name); // 부모 생성자 호출
this.breed = breed;
}
// Dog의 프로토타입을 Animal의 인스턴스로 설정
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // constructor 복원
// Dog만의 메서드 추가
Dog.prototype.bark = function() {
console.log(`${this.name}: 멍멍!`);
};
// 인스턴스 생성
const myDog = new Dog('바둑이', '진돗개');
// 프로토타입 체인 탐색
myDog.bark(); // Dog.prototype에서
myDog.eat(); // Animal.prototype에서
myDog.toString(); // Object.prototype에서
// 체인 구조:
// myDog → Dog.prototype → Animal.prototype → Object.prototype → null
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
console.log(myDog instanceof Object); // true
// 프로토타입 확인
console.log(Object.getPrototypeOf(myDog) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
4. Object.create()
// Object.create(): 프로토타입을 지정하여 객체 생성
// 프로토타입 객체
const personPrototype = {
greet() {
console.log(`안녕, 나는 ${this.name}이야`);
},
introduce() {
console.log(`나이는 ${this.age}살이야`);
}
};
// personPrototype을 프로토타입으로 하는 객체 생성
const person1 = Object.create(personPrototype);
person1.name = '철수';
person1.age = 25;
const person2 = Object.create(personPrototype);
person2.name = '영희';
person2.age = 23;
person1.greet(); // 안녕, 나는 철수야
person2.introduce(); // 나이는 23살이야
// 프로토타입 체인 확인
console.log(Object.getPrototypeOf(person1) === personPrototype); // true
// null 프로토타입 객체 (순수 객체)
const pureObject = Object.create(null);
console.log(pureObject.toString); // undefined
console.log(Object.getPrototypeOf(pureObject)); // null
// 활용: 진짜 딕셔너리
const dictionary = Object.create(null);
dictionary.toString = '문자열'; // 충돌 없음!
dictionary.hasOwnProperty = '속성'; // 안전!
5. 프로토타입 vs Class
// ES6 Class는 프로토타 입의 문법적 설탕(Syntactic Sugar)
// 프로토타입 방식
function PersonProto(name, age) {
this.name = name;
this.age = age;
}
PersonProto.prototype.greet = function() {
console.log(`안녕, ${this.name}야`);
};
// Class 방식 (내부적으로는 프로토타입)
class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`안녕, ${this.name}야`);
}
}
// 두 방식은 동일하게 작동
const proto = new PersonProto('철수', 25);
const classInstance = new PersonClass('영희', 23);
proto.greet();
classInstance.greet();
// Class도 프로토타입 사용
console.log(typeof PersonClass); // function
console.log(PersonClass.prototype.greet); // function
// 차이점:
// 1. Class는 호이스팅 안 됨
// 2. Class는 strict mode
// 3. Class 문법이 더 깔끔
// 4. 상속이 더 쉬움
// 상속 비교
// ❌ 프로토타입 방식 (복잡)
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log('먹는 중');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('멍멍');
};
// ✅ Class 방식 (간단)
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log('먹는 중');
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log('멍멍');
}
}
const dog = new Dog('바둑이', '진돗개');
dog.eat(); // Animal에서 상속
dog.bark(); // Dog의 메서드
💡 실제 예시
프로토타입 상속 구현
// 실전 프로토타입 상속 패턴
// 1. 기본 클래스
function Shape(x, y) {
this.x = x;
this.y = y;
}
Shape.prototype.move = function(dx, dy) {
this.x += dx;
this.y += dy;
console.log(`위치: (${this.x}, ${this.y})`);
};
Shape.prototype.getPosition = function() {
return { x: this.x, y: this.y };
};
// 2. Circle 상속
function Circle(x, y, radius) {
Shape.call(this, x, y); // 부모 생성자 호출
this.radius = radius;
}
// 프로토타입 체인 설정
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
// Circle 메서드 추가
Circle.prototype.getArea = function() {
return Math.PI * this.radius * this.radius;
};
Circle.prototype.getCircumference = function() {
return 2 * Math.PI * this.radius;
};
// 3. Rectangle 상속
function Rectangle(x, y, width, height) {
Shape.call(this, x, y);
this.width = width;
this.height = height;
}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype.getArea = function() {
return this.width * this.height;
};
Rectangle.prototype.getPerimeter = function() {
return 2 * (this.width + this.height);
};
// 사용
const circle = new Circle(0, 0, 10);
console.log(circle.getArea()); // 314.159...
circle.move(5, 5); // 위치: (5, 5)
const rect = new Rectangle(0, 0, 10, 20);
console.log(rect.getArea()); // 200
rect.move(3, 3); // 위치: (3, 3)
// instanceof 확인
console.log(circle instanceof Circle); // true
console.log(circle instanceof Shape); // true
console.log(circle instanceof Rectangle); // false
// 프로토타입 체인
console.log(Object.getPrototypeOf(circle) === Circle.prototype); // true
console.log(Object.getPrototypeOf(Circle.prototype) === Shape.prototype); // true