JavaScript Classes | ES6 Class Syntax Explained
이 글의 핵심
JavaScript ES6 classes: constructors, getters/setters, static methods, inheritance with extends and super, private fields (#), and common mistakes—OOP patterns for the web.
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("관리자", "admin@test.com");
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
Related posts
- Python 클래스 | 객체지향 프로그래밍(OOP) 완벽 정리
- C++ 클래스와 객체
- Java 클래스와 객체 | OOP, 상속, 인터페이스
- Kotlin 클래스와 객체 | 클래스, 상속, 인터페이스
- C++ struct vs class
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. JavaScript ES6 classes: constructors, getters/setters, static methods, inheritance with extends and super, private field…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the JavaScript series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- JavaScript Functions | Declarations· Arrows
- JavaScript Modules | ES Modules and CommonJS Explained
- JavaScript 클래스 | ES6 Class 문법 완벽 정리
Keywords Covered in This Article (Related Search Terms)
This article covers JavaScript, Class, OOP, ES6, Inheritance, Prototype.