class는 함수다
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return this.name + " barks!";
}
}
typeof Dog; // "function"class는 새로운 타입이 아닙니다. 함수입니다. 이전 글의 생성자 함수 + prototype 패턴의 문법적 설탕 (syntactic sugar) 입니다.
// class 문법
class Dog {
constructor(name) { this.name = name; }
bark() { return this.name + " barks!"; }
}
// 동등한 생성자 함수 패턴
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() {
return this.name + " barks!";
};두 코드는 거의 동일한 결과를 만듭니다.
"거의" 동일한 차이
class는 몇 가지 차이가 있습니다.
1. new 없이 호출 불가
function OldDog() {}
OldDog(); // 동작함 (좋지 않지만)
class NewDog {}
NewDog(); // TypeError: Class constructor NewDog
// cannot be invoked without 'new'2. 엄격 모드 강제
class Dog {
bark() {
// 여기는 자동으로 strict mode
undeclaredVar = 1; // ReferenceError
}
}3. 열거 불가능
class Dog {
bark() {}
}
// class 메서드는 열거되지 않음
Object.keys(Dog.prototype); // [], bark가 안 보임
// 생성자 함수 방식은 열거됨
function OldDog() {}
OldDog.prototype.bark = function() {};
Object.keys(OldDog.prototype); // ["bark"]4. 호이스팅 차이
함수 선언은 완전히 호이스팅되지만, class 선언은 let/const처럼 TDZ(Temporal Dead Zone)에 놓여 선언 전에 사용할 수 없습니다.
// 함수 선언, 호이스팅됨
const dog = new OldDog(); // 동작함
function OldDog() {}
// class 선언, TDZ
const dog2 = new NewDog(); // ReferenceError!
class NewDog {}extends, 상속
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return this.name + " speaks";
}
}
class Dog extends Animal {
bark() {
return this.name + " barks";
}
}
const d = new Dog("Rex");
d.bark(); // "Rex barks" , Dog.prototype
d.speak(); // "Rex speaks", Animal.prototypeextends가 설정하는 두 개의 링크
extends는 프로토타입 링크를 두 개 설정합니다.
// 1. 인스턴스 메서드 상속
Object.getPrototypeOf(Dog.prototype) === Animal.prototype; // true
// d.speak()를 찾을 때: d → Dog.prototype → Animal.prototype
// 2. 정적 메서드 상속
Object.getPrototypeOf(Dog) === Animal; // true
// Dog.create()를 찾을 때: Dog → Animal이전 글의 생성자 함수로 이것을 수동 구현하면:
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() {
return this.name + " speaks";
};
function Dog(name) { Animal.call(this, name); }
// extends가 하는 일
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Object.setPrototypeOf(Dog, Animal);
Dog.prototype.bark = function() {
return this.name + " barks";
};class extends가 이 모든 과정을 한 줄로 처리합니다.
super
super는 두 가지 용도로 쓰입니다.
super(), 부모 생성자 호출
class Dog extends Animal {
constructor(name, breed) {
super(name); // Animal의 constructor 호출
this.breed = breed;
}
}extends를 사용하면 자식 클래스의 constructor에서 반드시 super()를 먼저 호출해야 합니다. this를 사용하기 전에 부모가 초기화해야 하기 때문입니다.
class Dog extends Animal {
constructor(name) {
this.breed = "unknown"; // ReferenceError!
super(name); // super() 전에 this 사용 불가
}
}super.method(), 부모 메서드 호출
class Dog extends Animal {
speak() {
const parentResult = super.speak();
return parentResult + " and barks";
}
}
const d = new Dog("Rex");
d.speak(); // "Rex speaks and barks"super.speak()는 Animal.prototype.speak를 this를 바인딩하여 호출합니다.
static, 정적 메서드
class Dog {
constructor(name) {
this.name = name;
}
// 인스턴스 메서드, Dog.prototype에 저장
bark() {
return this.name + " barks";
}
// 정적 메서드, Dog 자체에 저장
static create(name) {
return new Dog(name);
}
}
const d = Dog.create("Rex"); // Dog.create(), OK
d.bark(); // d.bark(), OK
d.create("Buddy"); // TypeError, 인스턴스에서는 접근 불가정적 메서드는 Dog.prototype이 아닌 Dog 함수 자체에 저장됩니다. 인스턴스에서는 접근할 수 없습니다.
private 필드
class Dog {
#name; // private 필드
constructor(name) {
this.#name = name;
}
get name() {
return this.#name;
}
}
const d = new Dog("Rex");
d.name; // "Rex" (getter)
d.#name; // SyntaxError, 외부 접근 불가# 접두사로 선언한 필드는 클래스 바깥에서 접근할 수 없습니다. 프로토타입이 아닌 각 인스턴스에 직접 저장됩니다.
class 문법은 언제 쓰는가
| 상황 | 추천 |
|---|---|
| 상태 + 메서드가 있는 객체를 여러 개 만들 때 | class |
| 상속 관계가 필요할 때 | class extends |
| 단순 유틸리티 함수 모음 | 일반 함수 / 모듈 |
| 설정 객체, 옵션 | 객체 리터럴 |
class는 프로토타입을 더 읽기 쉽고 안전하게 사용하는 문법입니다. 하지만 내부에서는 여전히 프로토타입 체인이 동작합니다.
다음 단계
class의 실체를 이해했습니다. 마지막 글에서는 실전에서 쓰이는 상속 패턴, 프로토타입 상속, 조합 (composition), 믹스인을 비교하고, 언제 무엇을 선택해야 하는지 정리합니다.