자바스크립트 중급 #1 클래스
자바스크립트 중급 시리즈 첫 글입니다. 기초 7편을 마쳤다면 이미 작은 도구나 스크립트는 자신 있게 짤 수 있을 텐데, 중급은 그 위에 모던 자바스크립트의 표현력을 얹는 단계입니다.
총 7편으로 구성됩니다.
- #1 클래스 ← 이번 글
- #2 비동기 입문 — Promise, async/await
- #3 이터레이터와 제너레이터
- #4 디스트럭처링/spread/rest 깊이
- #5 옵셔널 체이닝과 nullish 병합
- #6 fetch API와 에러 처리
- #7 JSON 다루기와 직렬화
이번 글에서는 — 자바스크립트의 클래스 문법을 처음부터 끝까지 정리합니다.
클래스 기본 #
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`안녕, ${this.name} (${this.age})`);
}
}
const u = new User('커티스', 30);
u.greet(); // 안녕, 커티스 (30)
class 키워드로 시작하고, **constructor**가 인스턴스를 만들 때 호출되는 초기화 함수입니다. 본문 안의 this는 새로 만들어지는 인스턴스를 가리킵니다.
new 없이 호출하면 에러가 나요 — 클래스는 항상 new와 함께 씁니다.
User('커티스', 30); // ✗ TypeError
new User('커티스', 30); // OK
메서드와 this
#
메서드 안의 this는 호출하는 인스턴스를 가리키는 게 기본입니다. 다만 함수를 떼어 호출하면 this가 사라질 수 있습니다.
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`안녕, ${this.name}`);
}
}
const u = new User('커티스');
u.greet(); // 안녕, 커티스
const fn = u.greet; // 떼어내기
fn(); // 안녕, undefined ← this 사라짐
이게 자바스크립트 클래스의 가장 흔한 함정입니다. 콜백으로 메서드를 넘기거나 이벤트 핸들러로 등록할 때 자주 만납니다. 해결법은 다음 두 가지.
1) 화살표 함수로 묶기 #
button.addEventListener('click', () => u.greet());2) bind 또는 클래스 필드로 화살표 함수
#
class User {
constructor(name) {
this.name = name;
}
greet = () => {
console.log(`안녕, ${this.name}`);
};
}
const u = new User('커티스');
const fn = u.greet;
fn(); // 안녕, 커티스 — 떼어내도 this 살아 있음
이 패턴은 리액트 콜백에서 자주 봤을 것입니다. 다만 메서드가 인스턴스마다 새로 만들어지는 비용이 있어 무거운 클래스에는 권장되지 않습니다.
getter / setter — 속성처럼 보이는 메서드 #
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
get fahrenheit() {
return this._celsius * 9 / 5 + 32;
}
set fahrenheit(value) {
this._celsius = (value - 32) * 5 / 9;
}
}
const t = new Temperature(25);
console.log(t.fahrenheit); // 77 — 메서드 호출이 아니라 속성처럼 접근
t.fahrenheit = 100;
console.log(t._celsius); // 약 37.7
get / set 키워드로 정의하면 호출 시 괄호 없이 속성처럼 읽고 쓸 수 있습니다. 계산해서 반환하거나 설정 시 검증 하고 싶을 때 적합합니다.
정적 멤버 — static
#
인스턴스가 아니라 클래스 자체에 붙는 메서드나 속성입니다.
class MathUtil {
static PI = 3.14159;
static square(n) {
return n * n;
}
}
MathUtil.PI; // 3.14159
MathUtil.square(5); // 25
const m = new MathUtil();
m.square(5); // ✗ 인스턴스에는 없음
유틸 함수 모음, 또는 인스턴스 생성을 돕는 팩토리 메서드(예: User.fromJSON(...))에 적합합니다.
static 블록 — ES2022
#
정적 멤버를 만들 때 복잡한 초기화 로직이 필요하면 static { ... } 블록을 쓸 수 있습니다.
class Config {
static defaults = {};
static {
Config.defaults = JSON.parse(loadConfigFile());
Config.defaults.timestamp = Date.now();
}
}흔히 쓰진 않지만, 라이브러리 초기화에서 가끔 만나요.
Private 필드 — #
#
ES2022에 추가된 정식 private 문법. 외부에서 접근 자체가 불가능 해집니다.
class BankAccount {
#balance = 0;
deposit(amount) {
this.#balance = this.#balance + amount;
}
get balance() {
return this.#balance;
}
}
const acc = new BankAccount();
acc.deposit(100);
console.log(acc.balance); // 100
console.log(acc.#balance); // ✗ SyntaxError — 외부 접근 불가
이름 앞에 #을 붙입니다. 컨벤션이 아니라 언어 차원에서 강제 되는 진짜 private 입니다. 옛 자바스크립트의 “관습적 private (_balance)” 와 달리, 외부에서 접근 자체가 막힙니다.
_언더스코어 와의 차이
#
옛 코드에 자주 보이는 _balance 같은 표기는 약속일 뿐입니다.
class OldStyle {
constructor() {
this._balance = 0; // 컨벤션: 접근하지 마세요
}
}
const o = new OldStyle();
o._balance = 999; // 사실 접근 가능
새 코드에는 #을 쓰는 게 안전합니다.
상속 — extends와 super
#
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} 가(이) 소리를 냅니다`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 부모 constructor 호출
this.breed = breed;
}
speak() {
super.speak(); // 부모 메서드 호출
console.log(`${this.name} 가(이) 짖습니다 (멍멍)`);
}
}
const d = new Dog('보리', '시바');
d.speak();
// 보리 가(이) 소리를 냅니다
// 보리 가(이) 짖습니다 (멍멍)
extends로 부모 클래스를 지정하고, 자식 constructor 안에서는 super(...)를 먼저 부른 다음에야 this를 쓸 수 있습니다. 이걸 까먹으면 ReferenceError가 납니다.
super.method()로 부모의 메서드를 호출할 수 있고, 자식이 같은 이름의 메서드를 정의하면 부모 것을 가립니다(오버라이드).
상속 — 깊게 쓸 가치가 있나? #
상속은 자바스크립트뿐 아니라 OOP 전반에서 남용하면 코드가 빠르게 깨지는 도구입니다. 모던 자바스크립트는 다음을 권합니다.
- 단일 클래스로 충분한 경우에 일부러 상속 만들지 말기
- 상속 대신 합성(composition) — 객체에 다른 객체를 들고 있게 함
- 인터페이스/공통 시그니처가 필요하면 함수와 객체로도 충분
리액트가 클래스 컴포넌트 → 함수 컴포넌트로 옮긴 것도 비슷한 맥락입니다. 클래스가 필수인 경우는 의외로 많지 않습니다.
instanceof — 클래스 체크
#
const d = new Dog('보리', '시바');
d instanceof Dog; // true
d instanceof Animal; // true (부모도 true)
d instanceof Object; // true
d instanceof User; // false
상속 체인을 따라 검사합니다. 클래스로 만든 인스턴스인지 확인할 때 자주 써요.
클래스 vs 객체 리터럴 #
같은 일을 하려고 할 때, 클래스와 그냥 객체 중 어느 쪽이 적합한지 가이드.
| 상황 | 적합한 쪽 |
|---|---|
| 같은 모양의 인스턴스 여러 개 만들기 | 클래스 |
| 행동(메서드)이 데이터에 강하게 묶여 있음 | 클래스 |
| 단일 인스턴스, 설정 객체 같은 경우 | 객체 리터럴 |
| 함수형으로 변환만 거듭하는 경우 | 그냥 함수 + 객체 |
| 상속이 필요할 만큼 계층이 깊음 | 클래스 (또는 합성 재고려) |
처음에는 클래스를 안 쓰고도 충분한 경우가 많습니다. 정말 클래스가 적합한 상황을 만났을 때 꺼내 쓴다고 생각하면 됩니다.
마무리 #
이번 글에서 정리한 내용:
class와new,constructor안에서this초기화- 메서드를 떼어내면
this가 사라짐 — 화살표 함수 또는 필드 화살표 get/set으로 속성처럼 보이는 메서드static으로 클래스 자체에 붙는 멤버,static {}블록 (ES2022)#필드로 진짜 private (_언더스코어는 약속일 뿐)extends로 상속,super(...)먼저 호출- 상속 남용 주의 — 모던 컨벤션은 합성 우선
- 클래스가 적합한 경우 vs 객체 리터럴
다음 글(#2 비동기 입문)에서는 자바스크립트의 가장 큰 특징 중 하나인 비동기 — Promise와 async/await를 처음부터 정리합니다.