함수형만 쓰던 내가 클래스를 이해하게 된 과정

jinew·2025년 10월 29일

🍎 Javascript

목록 보기
22/22

최근 Three.js를 공부하면서 강의 코드를 따라 치다 보니 class 문법이 계속 등장했다. 나는 주로 함수형으로 코드를 작성해왔기 때문에 클래스 문법이 낯설고 어색했다. "이거 꼭 클래스로 써야 하나?" 싶기도 했지만, Three.js 커뮤니티에서 클래스를 많이 사용하는 걸 보고 제대로 공부해보기로 했다. 오늘은 클래스 문법을 처음부터 다시 공부하면서 정리한 내용을 공유하고자 한다.


🤔 클래스가 뭐길래?

클래스를 이해하는 가장 쉬운 방법은 붕어빵 틀에 비유하는 것이다.

  • 붕어빵 틀(Class): 붕어빵을 만드는 설계도
  • 실제 붕어빵(Instance): 틀로 찍어낸 개별 붕어빵들
// 붕어빵 틀 만들기 (Class 정의)
class Bungeoppang {
  constructor(filling) {
    this.filling = filling;
    this.temperature = 'hot';
  }
}

// 실제 붕어빵 만들기 (Instance 생성)
const redBeanBread = new Bungeoppang('팥');
const creamBread = new Bungeoppang('슈크림');

console.log(redBeanBread.filling); // '팥'
console.log(creamBread.filling); // '슈크림'

같은 틀(Class)을 사용하지만, 각각 다른 속재료(데이터)를 가진 붕어빵(Instance)이 만들어진다. 이게 클래스의 핵심 개념이다 😎


🏗️ 클래스의 구성 요소

1. Constructor (생성자)

constructor는 붕어빵이 만들어질 때 초기 설정을 해주는 특별한 함수다. 인스턴스가 생성되는 순간 자동으로 실행된다.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const person1 = new Person('철수', 25);
console.log(person1.name); // '철수'

this는 뭘까?
this는 "지금 만들어지고 있는 이 인스턴스"를 가리킨다. this.name은 "이 사람의 이름"이라는 뜻이다.

2. Method (메서드)

메서드는 인스턴스가 할 수 있는 행동이다. 클래스 안에 정의된 함수라고 생각하면 된다.

class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  bark() {
    console.log(`${this.name}이(가) 멍멍!`);
  }
  
  getAge() {
    return `${this.age}`;
  }
}

const myDog = new Dog('바둑이', 3);
myDog.bark(); // '바둑이이(가) 멍멍!'
console.log(myDog.getAge()); // '3살'

메서드 안에서도 this를 사용하면 인스턴스의 속성에 접근할 수 있다.


⚖️ 함수형 vs 클래스, 뭐가 다를까?

"굳이 클래스를 써야 하나?"라는 의문이 들 수 있다. 실제로 같은 기능을 함수로도 구현할 수 있다.

함수형 방식

function createCar(brand, color) {
  return {
    brand: brand,
    color: color,
    drive: function() {
      console.log(`${brand} 자동차가 달립니다!`);
    },
    honk: function() {
      console.log('빵빵!');
    }
  };
}

const car1 = createCar('현대', '흰색');
const car2 = createCar('기아', '검정');

car1.drive(); // '현대 자동차가 달립니다!'
car2.honk();  // '빵빵!'

클래스 방식

class Car {
  constructor(brand, color) {
    this.brand = brand;
    this.color = color;
  }
  
  drive() {
    console.log(`${this.brand} 자동차가 달립니다!`);
  }
  
  honk() {
    console.log('빵빵!');
  }
}

const car1 = new Car('현대', '흰색');
const car2 = new Car('기아', '검정');

car1.drive(); // '현대 자동차가 달립니다!'
car2.honk();  // '빵빵!'

결과는 똑같다! 그렇다면 왜 클래스를 쓸까?


🌟 클래스를 쓰는 이유

1. 코드 구조가 명확해진다

클래스는 관련된 데이터(속성)와 기능(메서드)을 하나로 묶어준다. 코드를 읽는 사람이 "아, 이건 Car에 관련된 모든 것들이구나"라고 바로 이해할 수 있다.

// 함수형: 어디까지가 자동차 관련 코드인지 불명확
const car = createCar('현대', '흰색');
function repairCar(car) { /* ... */ }
function washCar(car) { /* ... */ }

// 클래스: Car 클래스만 보면 자동차의 모든 것을 알 수 있음
class Car {
  constructor(brand, color) {
    this.brand = brand;
    this.color = color;
  }
  
  repair() { /* ... */ }
  wash() { /* ... */ }
}

2. 상태 관리가 쉬워진다

복잡한 상태를 가진 객체를 다룰 때 클래스가 훨씬 편하다.

class BankAccount {
  constructor(owner, balance) {
    this.owner = owner;
    this.balance = balance;
    this.transactions = [];
  }
  
  deposit(amount) {
    this.balance += amount;
    this.transactions.push({ type: 'deposit', amount, date: new Date() });
  }
  
  withdraw(amount) {
    if (this.balance >= amount) {
      this.balance -= amount;
      this.transactions.push({ type: 'withdraw', amount, date: new Date() });
      return true;
    }
    return false;
  }
  
  getBalance() {
    return this.balance;
  }
  
  getHistory() {
    return this.transactions;
  }
}

const account = new BankAccount('철수', 10000);
account.deposit(5000);
account.withdraw(3000);
console.log(account.getBalance()); // 12000
console.log(account.getHistory()); // 거래 내역 배열

이렇게 여러 메서드가 같은 상태(balance, transactions)를 공유하고 수정할 때 클래스가 빛을 발한다!

3. 확장성이 좋아진다

나중에 기능을 추가하거나 수정할 때 클래스 안에서만 작업하면 되니까 관리가 편하다.

class Calculator {
  constructor() {
    this.result = 0;
  }
  
  add(number) {
    this.result += number;
    return this;
  }
  
  subtract(number) {
    this.result -= number;
    return this;
  }
  
  multiply(number) {
    this.result *= number;
    return this;
  }
  
  // 나중에 기능 추가하기 쉬움
  divide(number) {
    if (number !== 0) {
      this.result /= number;
    }
    return this;
  }
  
  getResult() {
    return this.result;
  }
  
  clear() {
    this.result = 0;
    return this;
  }
}

// 메서드 체이닝도 가능!
const calc = new Calculator();
calc.add(10).multiply(2).subtract(5).divide(3);
console.log(calc.getResult()); // 5

4. 재사용성이 높아진다

같은 패턴의 객체가 여러 개 필요할 때 클래스로 찍어내면 된다.

class TodoItem {
  constructor(text) {
    this.text = text;
    this.completed = false;
    this.createdAt = new Date();
  }
  
  toggle() {
    this.completed = !this.completed;
  }
  
  edit(newText) {
    this.text = newText;
  }
  
  getStatus() {
    return this.completed ? '완료' : '미완료';
  }
}

// 여러 개의 할 일 생성
const todos = [
  new TodoItem('클래스 문법 공부하기'),
  new TodoItem('블로그 글 쓰기'),
  new TodoItem('Three.js 강의 보기')
];

todos[0].toggle();
console.log(todos[0].getStatus()); // '완료'

💡 실전 예제: 쇼핑 카트 만들기

이론만 보면 와닿지 않을 수 있으니, 실제로 사용할 법한 예제를 만들어보자.

class ShoppingCart {
  constructor() {
    this.items = [];
  }
  
  addItem(name, price, quantity = 1) {
    const existingItem = this.items.find(item => item.name === name);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      this.items.push({ name, price, quantity });
    }
  }
  
  removeItem(name) {
    this.items = this.items.filter(item => item.name !== name);
  }
  
  updateQuantity(name, quantity) {
    const item = this.items.find(item => item.name === name);
    if (item) {
      item.quantity = quantity;
    }
  }
  
  getTotal() {
    return this.items.reduce((total, item) => {
      return total + (item.price * item.quantity);
    }, 0);
  }
  
  getItemCount() {
    return this.items.reduce((count, item) => count + item.quantity, 0);
  }
  
  clear() {
    this.items = [];
  }
  
  getItems() {
    return this.items;
  }
}

// 사용 예시
const cart = new ShoppingCart();

cart.addItem('사과', 2000, 3);
cart.addItem('바나나', 3000, 2);
cart.addItem('사과', 2000, 1); // 기존 사과 수량 증가

console.log(cart.getItems());
// [
//   { name: '사과', price: 2000, quantity: 4 },
//   { name: '바나나', price: 3000, quantity: 2 }
// ]

console.log(cart.getTotal()); // 14000
console.log(cart.getItemCount()); // 6

cart.removeItem('바나나');
console.log(cart.getTotal()); // 8000

이렇게 장바구니의 모든 기능이 ShoppingCart 클래스 안에 깔끔하게 정리되어 있다. 나중에 할인 기능을 추가하거나, 배송비를 계산하는 메서드를 추가하기도 쉽다!


🎨 심화: Private 필드와 Getter/Setter

ES2022부터는 #을 사용해서 외부에서 접근할 수 없는 private 필드를 만들 수 있다.

class User {
  #password; // private 필드
  
  constructor(username, password) {
    this.username = username;
    this.#password = password;
  }
  
  // Getter
  get username() {
    return this._username;
  }
  
  // Setter
  set username(value) {
    if (value.length < 3) {
      console.log('사용자명은 3글자 이상이어야 합니다.');
      return;
    }
    this._username = value;
  }
  
  checkPassword(input) {
    return this.#password === input;
  }
  
  changePassword(oldPassword, newPassword) {
    if (this.checkPassword(oldPassword)) {
      this.#password = newPassword;
      return true;
    }
    return false;
  }
}

const user = new User('철수', 'secret123');
console.log(user.username); // '철수'
// console.log(user.#password); // 오류! private 필드는 외부에서 접근 불가

console.log(user.checkPassword('secret123')); // true
user.changePassword('secret123', 'newPassword456');

이렇게 하면 민감한 정보를 보호하고, 데이터를 안전하게 관리할 수 있다.


🎯 Three.js에서 클래스를 쓰는 이유

Three.js 강의를 보다 보면 클래스 문법이 자주 등장한다. 그 이유는 간단하다:

1. Three.js 자체가 클래스 기반이다

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera();
const renderer = new THREE.WebGLRenderer();

2. 3D 씬은 상태 관리가 복잡하다
카메라, 조명, 오브젝트, 애니메이션 등 여러 요소를 관리해야 하는데, 클래스로 묶으면 훨씬 깔끔하다.

class MyScene {
  constructor() {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera();
    this.renderer = new THREE.WebGLRenderer();
    this.cube = null;
  }
  
  init() {
    this.createCube();
    this.setupLights();
    this.setupCamera();
  }
  
  createCube() {
    // 큐브 생성
  }
  
  animate() {
    // 애니메이션 루프
  }
}

3. 재사용 가능한 3D 오브젝트를 만들기 좋다

class CustomCube {
  constructor(size, color) {
    this.size = size;
    this.color = color;
    this.mesh = this.create();
  }
  
  create() {
    const geometry = new THREE.BoxGeometry(this.size, this.size, this.size);
    const material = new THREE.MeshBasicMaterial({ color: this.color });
    return new THREE.Mesh(geometry, material);
  }
  
  rotate(x, y) {
    this.mesh.rotation.x += x;
    this.mesh.rotation.y += y;
  }
}


🌟 회고

클래스 문법을 다시 공부하면서 "왜 쓰는지"에 대한 답을 찾을 수 있었다. 단순히 문법을 외우는 게 아니라, 언제 클래스가 유용한지 이해하니 코드를 볼 때 훨씬 편해졌다.

특히 Three.js 강의 코드를 보면서 "왜 이렇게 작성했을까?"를 이해할 수 있게 되었고, 복잡한 상태를 관리할 때는 클래스가 정말 유용하다는 걸 체감했다.

함수형으로 작성하는 게 익숙하더라도, 클래스 문법을 알아두면 남의 코드를 읽을 때나 협업할 때 큰 도움이 된다. 앞으로는 상황에 맞게 함수형과 클래스를 적절히 섞어 쓸 수 있을 것 같다!

"이거 클래스로 짜는 게 나을까, 함수로 짜는 게 나을까?" 고민하는 시간이 줄어들고, 자신있게 선택할 수 있게 되었다는 게 이번 공부의 가장 큰 수확이다 💪

profile
멈추지만 않으면 도착해 🛫

0개의 댓글