
class 기본 문법 · 상속 · static
class Student {
// 생성자를 통해 인스턴스 생성 및 초기화
constructor(name) {
this.group = 1; // 고정 값
this.name = name; // 전달받은 값
}
// 프로토타입 메서드
introduce() {
console.log(`안녕 나는 ${this.group}반 학생 ${this.name}이야`);
}
}
const student = new Student('홍길동');
student.introduce();
console.log(typeof Student); // function
console.log(Student === Student.prototype.constructor); // true
console.log(Student.prototype.introduce); // 프로토타입 메서드
console.log(Object.getOwnPropertyNames(Student.prototype));
// ['constructor', 'introduce']
핵심 정리:
class Student { ... } 로 클래스를 선언new Student() 를 호출하면 인스턴스가 생성되고 constructor 가 자동 실행Student.prototype.introduce 로 접근 가능typeof Student === 'function'같은 역할을 생성자 함수로도 만들 수 있다.
function Teacher(name) {
this.group = 1;
this.name = name;
}
Teacher.prototype.introduce = function () {
console.log(`안녕 나는 ${this.group}반 교사 ${this.name}이야`);
};
const teacher = new Teacher('유관순');
teacher.introduce();
// Student(); // TypeError: Class constructor Student cannot be invoked without 'new'
Teacher(); // 에러 안 남 (단, this가 전역을 가리키게 되는 문제 있음)
클래스와 생성자 함수의 주요 차이:
new 없이 호출
new 없이 호출하면 무조건 에러메서드 열거 여부
enumerable: falsefor..in 순회에서 제외호이스팅 방식
자동 strict mode
use strict 적용→ 결론: 클래스 문법은 생성자 함수와 비슷하게 보이지만,
실제로는 다른 객체 생성 메커니즘이라고 보면 된다.
const Tutor = class {
teach() {
console.log('이해했지?');
}
};
new Tutor().teach();
const Tutee = class MyTutee {
learn() {
console.log('이제 알겠어');
console.log(MyTutee); // 이 이름은 클래스 내부에서만 사용 가능
}
};
new Tutee().learn();
// console.log(MyTutee); // ReferenceError
function makeTutee(message) {
return class {
feedback() {
console.log(message);
}
};
}
const SecondTutee = makeTutee('10점 만점에 10점');
new SecondTutee().feedback();
정리:
클래스도 일급 객체
class Product {
constructor(name, price) {
// setter가 여기서 호출됨
this.name = name;
this.price = price;
}
// getter
get name() {
console.log('get name 동작');
return this._name;
}
get price() {
console.log('get price 동작');
return this._price;
}
// setter
set name(value) {
console.log('set name 동작');
this._name = value;
}
set price(value) {
console.log('set price 동작');
if (value <= 0) {
console.log('가격은 0보다 작을 수 없다');
this._price = 0;
return;
}
this._price = value;
}
}
const phone = new Product('전화기', 23000);
console.log(phone.name + ', ' + phone.price);
const computer = new Product('컴퓨터', -1500000);
console.log(computer.name + ', ' + computer.price);
포인트:
get name() {…}, set name(value) {…} 같은 형태로 정의obj.name 처럼 일반 프로퍼티처럼 접근_name, _price 같은 내부 프로퍼티에 저장class Book {
// 클래스 필드
name = '모던 JavaScript';
// 초기값을 안 줄 수도 있음
price;
introduce() {
console.log(`${this.name} 재밌는 책이지`);
}
}
const book = new Book();
book.introduce();
console.log(book.name); // '모던 JavaScript'
console.log(Book.prototype.name); // undefined (프로토타입에는 없음)
console.log(book.price); // undefined
정리:
name = "값" 형태로 클래스 필드 정의Book.prototype 이 아니라 인스턴스 객체 자체에 저장prototype 쪽에 두는 걸 권장class Animal {
constructor(name, weight) {
this.name = name;
this.weight = weight;
}
eat(foodWeight) {
this.weight += foodWeight;
console.log(`${this.name} 은(는) ${foodWeight}kg 먹고 ${this.weight}kg 이 되었다`);
}
move(lostWeight) {
if (this.weight > lostWeight) {
this.weight -= lostWeight;
}
console.log(`${this.name} 은(는) 움직여서 ${lostWeight}kg 빠져서 ${this.weight}kg 이 되었다`);
}
}
const animal = new Animal('동물', 30);
animal.eat(1);
animal.move(0.5);
extends 로 상속하는 클래스:
class Human extends Animal {
develop(language) {
console.log(`${this.name} 은(는) ${language} 로 개발한다`);
}
}
const human = new Human('수강생', 70);
human.eat(2); // 부모 메서드 사용
human.move(1);
human.develop('JavaScript'); // 자식 메서드
동작 원리:
extends 는 내부적으로 프로토타입 기반 상속을 사용
Human.prototype.[[Prototype]] 이 Animal.prototype 을 가리키게 된다
Human 인스턴스에서 메서드를 찾을 때
Human.prototypeAnimal.prototype 순으로 검색 부모 메서드를 완전히 바꾸지 않고, 기능을 확장하고 싶을 때 오버라이딩을 사용한다.
class Tiger extends Animal {
attack(target) {
console.log(`${this.name} 이(가) ${target} 을(를) 공격한다`);
}
// Animal의 move를 확장
move(target) {
// 부모 메서드 호출
super.move(0.1);
// 추가 동작
this.attack(target);
}
}
const tiger = new Tiger('백두산 호랭이', 90);
tiger.move('슬픈 눈망울의 사슴');
포인트:
super.move(...) 로 부모 메서드 호출자식 클래스에 constructor 가 없으면, 자바스크립트가 자동으로 아래와 같은 생성자를 넣어 준다.
class Tiger extends Animal {
constructor(...args) {
super(...args);
}
}
직접 생성자를 작성하면 반드시 super(...) 를 호출해야 한다.
class Deer extends Animal {
constructor(name, weight, legLength) {
// super(name, weight) 보다 먼저 this에 접근하면 에러
// this.name = name; // 에러
super(name, weight); // 부모 생성자 호출
this.legLength = legLength;
}
hide(place) {
console.log(`${this.name} 은(는) ${place} 에 숨는다`);
}
}
const deer = new Deer('슬픈 눈망울의 사슴', 40, 1);
deer.hide('동굴 안');
왜 super 가 필수인지:
[[ConstructorKind]]: "derived" 로 구분된다super(...) 를 호출하지 않으면 this 자체가 준비되지 않아 에러가 발생한다 특정 인스턴스가 아니라 클래스 전체에 필요한 기능을 만들 때 사용한다.
class Student {
constructor(name, height) {
this.name = name;
this.height = height;
}
// 정적 메서드
static compare(studentA, studentB) {
return studentA.height - studentB.height;
}
}
const students = [
new Student('유관순', 165.5),
new Student('홍길동', 180.5),
new Student('선덕여왕', 159.5)
];
// 키 오름차순 정렬
students.sort(Student.compare);
console.log(students);
static compare 처럼 클래스 선언부 안에서 static 키워드 사용정적 메서드를 나중에 직접 프로퍼티로 붙여도 된다.
Student.staticMethod = function () {
console.log('이건 Student에 직접 붙인 정적 메서드');
};
Student.staticMethod();
정적 메서드로 객체 생성 로직을 감싸는 패턴도 자주 쓰인다.
class User {
constructor(id, registDate) {
this.id = id;
this.registDate = registDate;
}
// 팩토리 정적 메서드
static registUser(id) {
return new this(id, new Date());
}
}
const user01 = User.registUser('user01');
console.log(user01);
new User(...) 를 직접 쓰는 대신User.registUser('user01') 같은 형태로 생성new this(...) 를 호출해서,클래스 수준에서 공유할 데이터를 담을 수 있다.
class Animal {
// 정적 프로퍼티
static planet = '지구';
constructor(name, weight) {
this.name = name;
this.weight = weight;
}
eat(foodWeight) {
this.weight += foodWeight;
console.log(`${this.name} 이(가) ${foodWeight}kg 먹고 ${this.weight}kg 이 되었다`);
}
move(lostWeight) {
if (this.weight > lostWeight) {
this.weight -= lostWeight;
}
console.log(`${this.name} 이(가) 움직여서 ${lostWeight}kg 빠져서 ${this.weight}kg 이 되었다`);
}
// 정적 메서드
static compare(animalA, animalB) {
return animalA.weight - animalB.weight;
}
}
// 정적 프로퍼티를 나중에 추가하는 것도 가능
Animal.staticProperty =
'static으로 선언해도 결국 클래스 자체에 프로퍼티를 직접 할당하는 것과 같다';
console.log(Animal.planet);
console.log(Animal.staticProperty);
정리:
static planet = '지구' → 클래스 레벨에 붙는 값Animal.planet 으로 접근new Animal)에서는 접근 불가 (animal.planet X) 정적 메서드와 정적 프로퍼티도 상속된다.
class Human extends Animal {
develop(language) {
console.log(`${this.name} 이(가) ${language} 로 개발한다`);
}
}
const humans = [
new Human('홍길동', 70),
new Human('선덕여왕', 50),
new Human('신사임당', 60)
];
// 정적 메서드 상속 사용
humans.sort(Human.compare);
humans[0].develop('JavaScript');
// 정적 프로퍼티 상속
console.log(Human.planet);
console.log(Human.staticProperty);
// 프로토타입 체인 확인
console.log(Human.__proto__ === Animal); // true (정적 영역)
console.log(Human.prototype.__proto__ === Animal.prototype); // true (인스턴스 영역)
동작 방식:
class B extends AB.__proto__ === AB.prototype.__proto__ === A.prototype클래스 기본 문법
constructor 로 인스턴스 초기화new 없이 호출 불가, 자동 strict mode클래스 표현식
getter / setter, public field
상속과 super
extends 로 프로토타입 기반 상속 구성super 로 부모 메서드 호출this 사용 전에 super(...) 필수static 메서드·프로퍼티
extends 를 통해 정적 멤버도 그대로 상속된다