본문으로 건너뛰기 JavaScript Classes | ES6 Class Syntax Explained

JavaScript Classes | ES6 Class Syntax Explained

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

  1. Basics: class, constructor, methods
  2. Getters/setters: property-like access with validation
  3. static: class-level helpers and factories
  4. Inheritance: extends, super(), overrides
  5. Private fields (ES2022+): # for true encapsulation

Best practices

  1. ✅ Initialize in constructor
  2. ✅ Use getters/setters for controlled access
  3. ✅ Always call super() in derived constructors
  4. ✅ Use static for utilities and factories
  5. ✅ Use # fields to hide internal state

Next steps



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.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers JavaScript, Class, OOP, ES6, Inheritance, Prototype.