자바스크립트에서 객체 지향 프로그래밍을 구현하는 방법에는 크게 두 가지가 있다. 바로 클래스 기반과 프로토타입 기반이다. ES6에서 도입된 클래스 문법은 기존의 프로토타입 기반 상속을 더 직관적이고 명확하게 사용할 수 있도록 해준다. 하지만 자바스크립트의 근간을 이루는 것은 여전히 프로토타입 기반 상속이다.
클래스의 내부 동작
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return `Hello, ${this.name}!`;
}
}
console.log(Person.prototype.sayHello); // 메서드는 프로토타입에 존재
console.log(typeof Person); // 'function' 출력
첫번째 출력을 통해 sayHello 메서드는 Person.prototype에 존재함을 확인할 수 있다. 이는 모든 Person 인스턴스가 메서드를 공유하는 것을 의미한다. 두번째 출력을 통해 클래스가 사실은 함수라는 것을 보여주며 ES6의 클래스는 새로운 객체 타입을 만든 것이 아니라 기존 프로토타입 기반 상속에 대한 내용이다. 클래스는 프로토타입 상속을 더 쉽게 작성할 수 있게 해주는 문법적 설탕이며 내부적으로는 여전히 프로토타입 메커니즘을 사용한다.
클래스 선언은 내부적으로 다음 코드와 유사한 작업을 수행한다.
// 내부적으로 이렇게 변환됩니다
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
return `Hello, ${this.name}!`;
};
프로토타입 방식의 상속
// 프로토타입 방식
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
1. Animal.call(this, name)
- call 메서드를 사용하여 Animal 생성자를 Dog 컨텍스트에서 실행
- 이를 통해 name 속성이 Dog 인스턴스에 설정됨
2. Object.create(Animal.prototype)
- Animal.prototype을 [[Prototype]]으로 가지는 새로운 빈 객체 생성하고 그 객체를 Dog.prototype에 할당👉 Dog와 Animal의 프로토타입이 독립적으로 유지되어 Dog의 프로토타입을 수정해도 Animal의 프로토타입에 영향을 주지 않으면서 Dog인스턴스는 Animal의 메서드를 상속받아 사용할 수 있다.
- Animal.prototype을 프로토타입으로 가지는 새 객체 생성
- 이를 통해 프로토타입 체인 설정
3. Dog.prototype.constructor = Dog
- 프로토타입 체인 설정 과정에서 손실된 constructor 속성을 복구 👉 복구가 필요한 이유는 Object.create()는 완전히 새로운 객체를 만들고 그 객체의 [[Prototype]]을 지정된 객체(여기서는 Animal.prototype)로 설정하는데, 이 새 객체는 자체 constructor 속성을 가지고 있지 않기 때문에 프로토타입 체인을 통해 Animal.prototype의 constructor를 상속받는다. 따라서 Dog.prototype.constructor = Dog 을 통해 올바른 생성자 참조를 복구해주는 것이다.
⁉️빈 객체가 아닌 프로토타입을 바로 할당하면
Dog.prototype = Animal.prototype;
위와 같이 할당하면 Dog의 프로토타입에 Dog.prototype.bark = function() { }; 이러한 메서드를 추가했을 때 Animal.prototype도 수정이 되기 때문이다.
클래스 방식의 상속
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
}
1. constructor
- 클래스의 인스턴스가 생성될 때 자동으로 호출되는 특별한 메서드
- 인스턴스 초기화를 담당
2. extends
- 클래스 상속을 선언하는 키워드
- 내부적으로는 프로토타입 체인을 설정
3. super()
- 부모 클래스의 생성자를 호출
- 자식 클래스의 생성자에서는 반드시 부모 클래스의 생성자를 호출해야 함
⁉️ super()를 반드시 호출해야 하는 이유
👉 상속받은 부모 클래스의 초기화가 필요하기 때문
자식 클래스에서 constructor를 선언할 때는 반드시 super()를 호출해야 한다.
- this 객체가 처음부터 존재하는 것이 아님
- 부모 클래스의 constructor가 this 객체를 생성하고 초기화
- super() 호출을 통해 이 과정이 이루어짐
- 그 후에야 자식 클래스에서 this를 사용할 수 있음
class Animal {
constructor(name) {
// Animal의 constructor에서 빈 객체를 생성하고 초기화
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
// 이 시점에서는 this가 아직 생성되지 않음
// super()를 호출하면 Animal의 constructor가 this 객체를 생성
super(name);
// super() 호출 후에야 this 사용 가능
this.breed = breed;
}
}
이처럼 자바스크립트에서 객체 지향 프로그래밍은 프로토타입 기반과 클래스 기반이라는 두 가지 방식으로 구현할 수 있다. ES6에서 도입된 클래스는 기존의 프로토타입 기반 상속을 더 직관적이고 명확하게 사용할 수 있게 해주지만, 내부적으로는 여전히 프로토타입 메커니즘을 사용한다. 정리해보면, 프로토타입 방식에서는 Object.create()를 통해 프로토타입 체인을 설정하고, 이 과정에서 손실된 constructor 속성을 복구하는 등의 세세한 작업이 필요하다. 반면 클래스 방식에서는 extends 키워드와 super() 호출을 통해 이러한 복잡한 상속 구현을 간단하게 처리할 수 있다.
'개발공부 > Javascript' 카테고리의 다른 글
[JS] this (0) | 2024.05.12 |
---|---|
[JS] ESM, CJS 모듈 시스템 (0) | 2024.05.05 |
[JS]프로토타입 (1) | 2024.04.28 |
[JS] 이벤트 루프 (1) | 2024.04.18 |
[JS] 비동기 (0) | 2024.04.10 |