JavaScript Classes | ES6 Class Syntax Explained
이 글의 핵심
From constructor functions to modern class syntax: encapsulation, inheritance, static factories, private fields, and game/e‑commerce examples you can reuse.
Introduction
What is a class?
A class is a template for creating objects. ES6 (ES2015) introduced class syntax.
Before classes (ES5):
// Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`안녕하세요, ${this.name}입니다.`);
};
const person = new Person("홍길동", 25);
person.greet(); // 안녕하세요, 홍길동입니다.
With classes (ES6+):
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`안녕하세요, ${this.name}입니다.`);
}
}
const person = new Person("홍길동", 25);
person.greet(); // 안녕하세요, 홍길동입니다.
1. Class basics
Defining a class
class Rectangle {
// Constructor: runs when an instance is created
constructor(width, height) {
this.width = width;
this.height = height;
}
// Methods
getArea() {
return this.width * this.height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
// Calling other methods
describe() {
return `넓이: ${this.getArea()}, 둘레: ${this.getPerimeter()}`;
}
}
// Create an instance
const rect = new Rectangle(10, 5);
console.log(rect.getArea()); // 50
console.log(rect.getPerimeter()); // 30
console.log(rect.describe()); // 넓이: 50, 둘레: 30
Class expressions
// Anonymous class
const Person = class {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
};
// Named class expression
const Person = class PersonClass {
constructor(name) {
this.name = name;
}
};
const person = new Person("홍길동");
person.greet();
2. Getters and setters
Defining getters/setters
class Circle {
constructor(radius) {
this._radius = radius; // “private by convention” (_prefix)
}
// Getter: read like a property
get radius() {
return this._radius;
}
// Setter: assign like a property
set radius(value) {
if (value < 0) {
throw new Error("반지름은 양수여야 합니다");
}
this._radius = value;
}
// Computed properties
get area() {
return Math.PI * this._radius ** 2;
}
get diameter() {
return this._radius * 2;
}
set diameter(value) {
this._radius = value / 2;
}
}
// Usage
const circle = new Circle(5);
console.log(circle.radius); // 5 (getter)
console.log(circle.area); // 78.53981633974483
circle.radius = 10; // setter
console.log(circle.area); // 314.1592653589793
circle.diameter = 20; // diameter setter
console.log(circle.radius); // 10
// circle.radius = -5; // Error: 반지름은 양수여야 합니다
3. Static methods and properties
The static keyword
class MathUtils {
// Static property
static PI = 3.14159;
// Static method: callable without an instance
static add(a, b) {
return a + b;
}
static max(...numbers) {
return Math.max(...numbers);
}
// Factory-style helper
static createCircle(radius) {
return new Circle(radius);
}
}
// Call static members on the class
console.log(MathUtils.add(10, 20)); // 30
console.log(MathUtils.max(1, 5, 3)); // 5
console.log(MathUtils.PI); // 3.14159
// Not available on instances
// const util = new MathUtils();
// util.add(1, 2); // TypeError
Practical example: factory pattern
class User {
constructor(name, email, role) {
this.name = name;
this.email = email;
this.role = role;
}
// Static factories
static createAdmin(name, email) {
return new User(name, email, "admin");
}
static createGuest(name) {
return new User(name, `${name}@guest.com`, "guest");
}
hasPermission(permission) {
const permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};
return permissions[this.role].includes(permission);
}
}
// Usage
const admin = User.createAdmin("관리자", "[email protected]");
const guest = User.createGuest("손님");
console.log(admin.hasPermission("delete")); // true
console.log(guest.hasPermission("write")); // false
4. Inheritance
extends
// Base class
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}이(가) 소리를 냅니다.`);
}
move() {
console.log(`${this.name}이(가) 움직입니다.`);
}
}
// Derived classes
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor (required!)
this.breed = breed;
}
// Override
speak() {
console.log(`${this.name}: 멍멍!`);
}
fetch() {
console.log(`${this.name}이(가) 공을 가져옵니다.`);
}
}
class Cat extends Animal {
speak() {
console.log(`${this.name}: 야옹~`);
}
}
// Usage
const dog = new Dog("바둑이", "진돗개");
dog.speak(); // 바둑이: 멍멍!
dog.move(); // 바둑이이(가) 움직입니다. (inherited)
dog.fetch(); // 바둑이이(가) 공을 가져옵니다.
const cat = new Cat("나비");
cat.speak(); // 나비: 야옹~
// instanceof
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Cat); // false
The super keyword
class Employee {
constructor(name, salary) {
this.name = name;
this.salary = salary;
}
getInfo() {
return `${this.name} - ${this.salary.toLocaleString()}원`;
}
work() {
return `${this.name}이(가) 일합니다.`;
}
}
class Manager extends Employee {
constructor(name, salary, teamSize) {
super(name, salary);
this.teamSize = teamSize;
}
// Extend parent behavior
getInfo() {
const baseInfo = super.getInfo();
return `${baseInfo} (팀원: ${this.teamSize}명)`;
}
manageTeam() {
return `${this.name}이(가) ${this.teamSize}명을 관리합니다.`;
}
}
const manager = new Manager("김팀장", 5000000, 5);
console.log(manager.getInfo()); // 김팀장 - 5,000,000원 (팀원: 5명)
console.log(manager.work()); // 김팀장이(가) 일합니다. (inherited)
console.log(manager.manageTeam()); // 김팀장이(가) 5명을 관리합니다.
5. Private fields (ES2022+)
# prefix
class BankAccount {
// Private fields
#balance;
constructor(owner, balance) {
this.owner = owner;
this.#balance = balance;
}
// Public methods
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
// Private method
#log(message) {
console.log(`[${this.owner}] ${message}`);
}
}
const account = new BankAccount("홍길동", 10000);
console.log(account.owner); // 홍길동
// console.log(account.#balance); // SyntaxError: Private field
account.deposit(5000);
console.log(account.getBalance()); // 15000
6. Practical examples
Example 1: game characters
class Character {
constructor(name, hp, attack) {
this.name = name;
this.hp = hp;
this.maxHp = hp;
this.attack = attack;
}
takeDamage(damage) {
this.hp = Math.max(0, this.hp - damage);
console.log(`${this.name} HP: ${this.hp}/${this.maxHp}`);
return this.hp;
}
heal(amount) {
this.hp = Math.min(this.maxHp, this.hp + amount);
console.log(`${this.name} 회복! HP: ${this.hp}/${this.maxHp}`);
}
isAlive() {
return this.hp > 0;
}
basicAttack(target) {
console.log(`${this.name}의 공격!`);
return target.takeDamage(this.attack);
}
}
class Warrior extends Character {
constructor(name, hp, attack, defense) {
super(name, hp, attack);
this.defense = defense;
}
takeDamage(damage) {
const reduced = Math.max(0, damage - this.defense);
console.log(`${this.name}이(가) 방어력 ${this.defense}로 데미지 감소!`);
return super.takeDamage(reduced);
}
shieldBash(target) {
console.log(`${this.name}의 방패 강타!`);
return target.takeDamage(this.attack * 1.5);
}
}
class Mage extends Character {
constructor(name, hp, attack, mana) {
super(name, hp, attack);
this.mana = mana;
this.maxMana = mana;
}
fireball(target) {
if (this.mana < 20) {
console.log("마나 부족!");
return 0;
}
this.mana -= 20;
console.log(`${this.name}의 파이어볼! (마나: ${this.mana}/${this.maxMana})`);
return target.takeDamage(this.attack * 2);
}
}
// Simple battle
const warrior = new Warrior("전사", 150, 20, 5);
const mage = new Mage("마법사", 100, 30, 50);
console.log("=== 전투 시작 ===");
mage.basicAttack(warrior);
warrior.shieldBash(mage);
mage.fireball(warrior);
console.log("\n=== 전투 결과 ===");
console.log(`${warrior.name}: ${warrior.isAlive() ? "생존" : "사망"}`);
console.log(`${mage.name}: ${mage.isAlive() ? "생존" : "사망"}`);
Example 2: shopping cart
class Product {
constructor(id, name, price, stock) {
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
}
isAvailable(quantity = 1) {
return this.stock >= quantity;
}
decreaseStock(quantity) {
if (!this.isAvailable(quantity)) {
throw new Error("재고 부족");
}
this.stock -= quantity;
}
toString() {
return `${this.name} - ${this.price.toLocaleString()}원 (재고: ${this.stock})`;
}
}
class Cart {
constructor() {
this.items = [];
}
addItem(product, quantity = 1) {
if (!product.isAvailable(quantity)) {
console.log(`${product.name} 재고 부족`);
return false;
}
const existing = this.items.find(item => item.product.id === product.id);
if (existing) {
existing.quantity += quantity;
} else {
this.items.push({ product, quantity });
}
console.log(`${product.name} ${quantity}개 추가`);
return true;
}
removeItem(productId) {
this.items = this.items.filter(item => item.product.id !== productId);
}
getTotal() {
return this.items.reduce((total, item) => {
return total + item.product.price * item.quantity;
}, 0);
}
checkout() {
this.items.forEach(item => {
item.product.decreaseStock(item.quantity);
});
const total = this.getTotal();
this.items = [];
return total;
}
printCart() {
console.log("=== 장바구니 ===");
this.items.forEach(item => {
console.log(`${item.product.name} × ${item.quantity} = ${(item.product.price * item.quantity).toLocaleString()}원`);
});
console.log(`총액: ${this.getTotal().toLocaleString()}원`);
}
}
// Usage
const laptop = new Product(1, "노트북", 1200000, 5);
const mouse = new Product(2, "마우스", 30000, 10);
const cart = new Cart();
cart.addItem(laptop, 1);
cart.addItem(mouse, 2);
cart.printCart();
const total = cart.checkout();
console.log(`결제 완료: ${total.toLocaleString()}원`);
7. Common mistakes
Mistake 1: forgetting super()
// ❌ Wrong
class Child extends Parent {
constructor(name, age) {
// Missing super()!
this.age = age; // ReferenceError
}
}
// ✅ Correct
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
}
Mistake 2: arrow functions as methods (sometimes)
// ⚠️ Arrow as field (different this semantics; often intentional for callbacks)
class Person {
constructor(name) {
this.name = name;
}
greet = () => {
console.log(`Hello, ${this.name}!`);
}
}
// ✅ Ordinary methods (prototype methods; usual choice)
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
Mistake 3: calling without new
class Person {
constructor(name) {
this.name = name;
}
}
// ❌ No new
// const person = Person("홍길동"); // TypeError
// ✅ Use new
const person = new Person("홍길동");
8. Practice problems
Problem 1: Stack class
class Stack {
constructor() {
this.items = [];
}
push(item) {
this.items.push(item);
}
pop() {
if (this.isEmpty()) {
throw new Error("Stack is empty");
}
return this.items.pop();
}
peek() {
if (this.isEmpty()) {
return null;
}
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
get size() {
return this.items.length;
}
}
// Tests
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.peek()); // 3
console.log(stack.pop()); // 3
console.log(stack.size); // 2
Problem 2: Timer class
class Timer {
constructor() {
this.startTime = null;
this.elapsed = 0;
this.running = false;
}
start() {
if (this.running) return;
this.running = true;
this.startTime = Date.now() - this.elapsed;
}
stop() {
if (!this.running) return;
this.running = false;
this.elapsed = Date.now() - this.startTime;
}
reset() {
this.startTime = null;
this.elapsed = 0;
this.running = false;
}
getTime() {
if (this.running) {
return Date.now() - this.startTime;
}
return this.elapsed;
}
toString() {
const ms = this.getTime();
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}
}
// Test
const timer = new Timer();
timer.start();
setTimeout(() => {
console.log(timer.toString()); // 0:02
timer.stop();
}, 2000);
Summary
Key takeaways
- Basics:
class,constructor, methods - Getters/setters: property-like access with validation
static: class-level helpers and factories- Inheritance:
extends,super(), overrides - Private fields (ES2022+):
#for true encapsulation
Best practices
- ✅ Initialize in
constructor - ✅ Use getters/setters for controlled access
- ✅ Always call
super()in derived constructors - ✅ Use
staticfor utilities and factories - ✅ Use
#fields to hide internal state
Next steps
- JavaScript 모듈
- JavaScript 에러 처리
- JavaScript 디자인 패턴
Related posts
- Python 클래스 | 객체지향 프로그래밍(OOP) 완벽 정리
- C++ 클래스와 객체
- Java 클래스와 객체 | OOP, 상속, 인터페이스
- Kotlin 클래스와 객체 | 클래스, 상속, 인터페이스
- C++ struct vs class