들어가며
Java를 배운 개발자가 JavaScript를 처음 접하면 혼란스러워합니다.
// Java의 클래스
class Animal {
void makeSound() {
System.out.println("Some sound");
}
}
// JavaScript의... 프로토타입?
function Animal() {}
Animal.prototype.makeSound = function() {
console.log("Some sound");
};왜 JavaScript는 클래스 대신 프로토타입을 선택했을까요?
JavaScript 탄생의 배경 (1995년)
브렌던 아이크의 10일
- 1995년 5월, Netscape의 Brendan Eich는 단 10일 만에 JavaScript의 첫 버전을 만들었습니다
- 초기 이름은 Mocha → LiveScript → JavaScript
- Java의 인기에 편승한 마케팅 전략으로 이름만 JavaScript
설계 요구사항
- 간단해야 한다: 비프로그래머도 사용할 수 있어야 함
- 빨리 만들어야 한다: 10일의 시간 제약
- Java와 다르면서도 비슷해야 한다: 마케팅 전략
- 브라우저에서 실행: 가벼워야 함
프로토타입 선택의 이유
1. Self 언어의 영향
Brendan Eich는 Self 언어에서 큰 영감을 받았습니다.
Self는:
- Sun Microsystems에서 개발한 프로토타입 기반 언어
- 클래스 없이도 객체 지향 프로그래밍 가능
- 더 간단하고 유연한 객체 생성 방식
// Self 스타일의 객체 생성 (JavaScript로 표현)
const animal = {
sound: 'generic',
makeSound() {
console.log(this.sound);
}
};
// 프로토타입 체인으로 상속
const dog = Object.create(animal);
dog.sound = 'woof';
dog.makeSound(); // 'woof'2. 클래스의 복잡성 회피
당시 Java의 클래스 시스템은:
- 컴파일 필요
- 타입 시스템 복잡
- 초보자에게 어려움
- 무거운 실행 환경
프로토타입의 장점:
- 컴파일 불필요
- 동적으로 객체 생성
- 학습 곡선이 낮음 (처음에는…)
- 가벼운 실행
3. 동적 언어의 철학
JavaScript는 동적 타입 언어로 설계되었습니다.
// 클래스는 정적 구조를 강제
class Dog {
bark() {} // 미리 정의해야 함
}
// 프로토타입은 동적 확장 가능
function Dog() {}
Dog.prototype.bark = function() {};
// 런타임에 메서드 추가 가능
Dog.prototype.wagTail = function() {}; // 자유롭게 확장!4. 메모리 효율성
프로토타입 체인은 메모리를 절약합니다.
// 프로토타입: 메서드는 한 곳에만 존재
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
const dog1 = new Dog('Max');
const dog2 = new Dog('Buddy');
// bark 메서드는 prototype에 하나만 존재
// dog1과 dog2는 이를 공유
console.log(dog1.bark === dog2.bark); // true
// 만약 각 인스턴스마다 메서드를 복사한다면?
function BadDog(name) {
this.name = name;
this.bark = function() { // 메모리 낭비!
console.log(`${this.name} barks!`);
};
}
const bad1 = new BadDog('Max');
const bad2 = new BadDog('Buddy');
console.log(bad1.bark === bad2.bark); // false (별도 인스턴스)프로토타입의 작동 원리
프로토타입 체인
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};
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(`${this.name} barks!`);
};
const myDog = new Dog('Max', 'Labrador');
// 프로토타입 체인:
// myDog → Dog.prototype → Animal.prototype → Object.prototype → null
myDog.bark(); // Dog.prototype에서 찾음
myDog.eat(); // Animal.prototype에서 찾음 (위로 올라감)Prototype vs prototype
헷갈리는 두 개념:
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {};
const myDog = new Dog('Max');
// Dog.prototype: 생성자 함수의 프로퍼티 (템플릿)
// myDog.[[Prototype]]: 실제 인스턴스의 내부 링크 (__proto__로 접근 가능)
console.log(Dog.prototype); // 객체 (템플릿)
console.log(myDog.__proto__); // Dog.prototype과 같음
console.log(myDog.__proto__ === Dog.prototype); // true프로토타입 vs 클래스 (ES6+)
ES6 Class는 문법 설탕
// ES6 Class
class Dog {
constructor(name) {
this.name = name;
}
bark() {
console.log(`${this.name} barks!`);
}
}
// 위 코드는 본질적으로 이것과 동일:
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
// 증명
console.log(typeof Dog); // 'function'
console.log(Dog.prototype.bark); // function왜 ES6에서 Class를 추가했을까?
- 가독성: 다른 언어 사용자에게 친숙
- 편의성: 상속이 더 간단
- 표준화: 일관된 객체 지향 패턴
// ES6 Class로 상속
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name} barks!`);
}
}
// 훨씬 간결하고 명확!프로토타입의 장점과 단점
장점
✅ 유연성: 런타임에 동적으로 확장 가능
Dog.prototype.newMethod = function() {}; // 이미 생성된 인스턴스에도 적용!✅ 메모리 효율: 메서드 공유 ✅ 간단한 구조: (처음에는) 이해하기 쉬움 ✅ Duck Typing: 타입보다는 행동에 집중
단점
❌ 혼란스러운 문법: prototype, __proto__, [[Prototype]]
❌ 상속 복잡성: 프로토타입 체인 설정이 번거로움
❌ 디버깅 어려움: 체인을 따라가며 추적
❌ 성능: 프로토타입 체인을 거슬러 올라가는 비용
현대 JavaScript에서의 선택
언제 프로토타입을 직접 사용할까?
// 라이브러리/프레임워크 개발
// 네이티브 객체 확장 (권장하지 않지만)
Array.prototype.customMethod = function() {
// 모든 배열에서 사용 가능
};
// 성능이 중요한 경우
// 메모리 사용을 최소화해야 하는 경우대부분의 경우 Class 사용 권장
// 현대적인 코드
class UserService {
constructor(api) {
this.api = api;
}
async getUser(id) {
return await this.api.get(`/users/${id}`);
}
}
// 명확하고, 읽기 쉽고, 유지보수 용이결론
왜 프로토타입을 선택했나?
- 역사적 맥락: 10일의 시간 제약, Self 언어의 영향
- 설계 철학: 간단함, 유연성, 동적 특성
- 기술적 요구: 가벼움, 메모리 효율
현재의 선택
- ES6+ Class 사용 권장: 가독성과 유지보수성
- 프로토타입 이해 필수: JavaScript의 근본 원리
- 두 가지 모두 같은 메커니즘: Class도 내부적으로는 프로토타입
JavaScript의 프로토타입은 단점도 있지만, 언어의 유연성과 동적 특성의 근간입니다. 이를 이해하는 것은 JavaScript를 깊이 있게 사용하는 첫 걸음입니다.
참고 자료
- 자바스크립트는 왜 프로토타입을 선택했을까
- You Don’t Know JS: this & Object Prototypes
- MDN: Inheritance and the prototype chain