JavaScript 클래스: 생성자 함수 vs 클래스(static,public,private) 문법

    728x90
    반응형
    SMALL

    JavaScript 클래스: 생성자 함수 vs 클래스(static,public,private) 문법

    ES6 이전에는 생성자 함수로 객체를 만들었지만, ES6부터는 클래스 문법을 사용할 수 있게 되었다.

    Date, Number, String 같은 빌트인 객체들은 여전히 생성자 함수 방식으로 구현되어 있다.

     

    생성자 함수 vs 클래스

    생성자 함수 방식 (ES5)

    // 생성자 함수
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    
    // 정적 메서드 - 함수에 직접 추가
    Person.isAdult = function(age) {
      return age >= 18;
    };
    
    // 인스턴스 메서드 - prototype에 추가
    Person.prototype.greet = function() {
      return `Hello, ${this.name}`;
    };
    
    // 사용
    const person = new Person('Kim', 25);
    person.greet();          // "Hello, Kim"
    Person.isAdult(25);      // true
    

     

     

    클래스 방식 (ES6+)

    // 클래스 (내부적으로는 생성자 함수와 동일)
    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
      
      // 정적 메서드
      static isAdult(age) {
        return age >= 18;
      }
      
      // 인스턴스 메서드
      greet() {
        return `Hello, ${this.name}`;
      }
    }
    
    // 사용 (완전히 동일)
    const person = new Person('Kim', 25);
    person.greet();          // "Hello, Kim"
    Person.isAdult(25);      // true
    

    핵심: 클래스는 생성자 함수의 문법적 설탕(Syntactic Sugar)이다.

     

    Date의 실제 구현

    Date는 생성자 함수로 구현됨

    // JavaScript 엔진 내부 (개념적 표현)
    // 실제로는 C++로 작성됨
    
    function Date() {
      // 네이티브 코드로 구현
    }
    
    // 정적 메서드 - 함수 객체에 직접 추가
    Date.now = function() {
      // 현재 타임스탬프 반환
    };
    
    Date.parse = function(dateString) {
      // 문자열 파싱
    };
    
    Date.UTC = function(year, month, day) {
      // UTC 타임스탬프 생성
    };
    
    // 인스턴스 메서드 - prototype에 추가
    Date.prototype.getFullYear = function() {
      // 연도 반환
    };
    
    Date.prototype.getMonth = function() {
      // 월 반환 (0-11)
    };
    
    Date.prototype.toISOString = function() {
      // ISO 8601 형식 문자열 반환
    };
    

     

     

    Date의 구조

    Date (생성자 함수 = 함수 + 객체)
    │
    ├─ 함수로서의 역할
    │  ├─ Date() → 문자열 반환
    │  └─ new Date() → Date 객체 생성
    │
    ├─ 객체로서의 역할 (정적 메서드)
    │  ├─ Date.now()
    │  ├─ Date.parse()
    │  └─ Date.UTC()
    │
    └─ prototype (인스턴스 메서드)
       ├─ Date.prototype.getFullYear()
       ├─ Date.prototype.getMonth()
       └─ Date.prototype.toISOString()
    

     

    사용 예시

    // 1. 생성자로 사용
    const date = new Date();
    
    // 2. 인스턴스 메서드 (prototype 체인)
    date.getFullYear();        // 2026
    date.getMonth();           // 0 (1월)
    date.toISOString();        // "2026-01-25T12:34:56.789Z"
    
    // 3. 정적 메서드 (객체 프로퍼티)
    Date.now();                // 1737801296789
    Date.parse('2026-01-25');  // 타임스탬프
    

     

     

     

    클래스로 만든다면?

    ES6 클래스 문법으로 Date 재구현

    class Date {
      // private 필드
      #timeValue;
      
      constructor(year, month, day) {
        if (year === undefined) {
          this.#timeValue = performance.now();
        } else {
          this.#timeValue = this.#calculateTime(year, month, day);
        }
      }
      
      // 정적 메서드 - static 키워드
      static now() {
        return performance.now();
      }
      
      static parse(dateString) {
        return new Date(dateString).#timeValue;
      }
      
      static UTC(year, month, day) {
        return new Date(year, month, day).#timeValue;
      }
      
      // 인스턴스 메서드 (public)
      getFullYear() {
        return this.#extractYear(this.#timeValue);
      }
      
      getMonth() {
        return this.#extractMonth(this.#timeValue);
      }
      
      toISOString() {
        return this.#formatISO(this.#timeValue);
      }
      
      // private 메서드
      #calculateTime(year, month, day) {
        // 내부 계산 로직
      }
      
      #extractYear(timestamp) {
        // 연도 추출 로직
      }
      
      #extractMonth(timestamp) {
        // 월 추출 로직
      }
      
      #formatISO(timestamp) {
        // ISO 형식 변환
      }
    }
    
    // 사용 (동일)
    const date = new Date(2026, 0, 25);
    date.getFullYear();  // 2026
    Date.now();          // 타임스탬프
    
    // date.#timeValue;  // ❌ SyntaxError (private)
    

     

     

    비교표

    항복 생성자 함수 클래스
    생성자 function Date() {} constructor() {}
    정적 메서드 Date.now = function() {} static now() {}
    인스턴스 메서드 Date.prototype.method = function() {} method() {}
    private 없음 (또는 클로저) #field, #method()
    결과 동일 동일

     

    접근 제어자

    1. public (기본값)

    "어디서든 접근 가능"

    class Person {
      // public 필드 (기본)
      name = 'Kim';
      age = 25;
      
      // public 메서드 (기본)
      greet() {
        return `Hello, ${this.name}`;
      }
    }
    
    const person = new Person();
    person.name;       // ✅ "Kim"
    person.age;        // ✅ 25
    person.greet();    // ✅ "Hello, Kim"
    

     

     

    2. private (#)

    "클래스 내부에서만 접근 가능"

    class BankAccount {
      // private 필드
      #balance = 0;
      #password = '1234';
      
      // public 필드
      owner = 'Kim';
      
      constructor(initialBalance) {
        this.#balance = initialBalance;
      }
      
      // public 메서드
      deposit(amount) {
        this.#balance += amount;  // ✅ 클래스 내부
        return this.#balance;
      }
      
      withdraw(amount, password) {
        if (password !== this.#password) {  // ✅ 클래스 내부
          throw new Error('Wrong password');
        }
        this.#balance -= amount;
        return this.#balance;
      }
      
      getBalance() {
        return this.#balance;  // ✅ 클래스 내부
      }
      
      // private 메서드
      #validateAmount(amount) {
        return amount > 0;
      }
    }
    
    const account = new BankAccount(1000);
    
    // ✅ public 접근 가능
    account.owner;             // "Kim"
    account.deposit(500);      // 1500
    account.getBalance();      // 1500
    
    // ❌ private 접근 불가
    // account.#balance;       // SyntaxError!
    // account.#password;      // SyntaxError!
    // account.#validateAmount(); // SyntaxError!
    

    왜 private을 쓸까?

    • 중요한 데이터 보호 (비밀번호, 잔액 등)
    • 내부 구현 숨김 (캡슐화)
    • 안전한 인터페이스만 제공

     

    3. static

    "인스턴스 없이 클래스로 직접 접근"

    class MathHelper {
      // static 필드
      static PI = 3.14159;
      static VERSION = '1.0.0';
      
      // 일반 필드 (인스턴스 필드)
      instanceValue = 100;
      
      // static 메서드
      static add(a, b) {
        return a + b;
      }
      
      static getCircleArea(radius) {
        return this.PI * radius * radius;
      }
      
      // 일반 메서드 (인스턴스 메서드)
      double(num) {
        return num * 2;
      }
    }
    
    // ✅ static은 클래스 이름으로 직접 접근
    MathHelper.PI;                      // 3.14159
    MathHelper.add(5, 3);               // 8
    MathHelper.getCircleArea(10);       // 314.159
    
    // ❌ 인스턴스로는 static 접근 불가
    const helper = new MathHelper();
    helper.PI;           // undefined
    helper.add(1, 2);    // TypeError
    
    // ✅ 일반 메서드는 인스턴스로 접근
    helper.double(5);    // 10
    helper.instanceValue; // 100
    
    // ❌ 클래스로는 일반 메서드/필드 접근 불가
    MathHelper.double(5);      // TypeError
    MathHelper.instanceValue;  // undefined
    

    static을 언제 쓸까?

    • 유틸리티 함수 (Math.max, Array.isArray)
    • 팩토리 메서드 (Date.now, Array.from)
    • 상수 값 (Math.PI)

     

    접근 범위 정리

    class Example {
      publicField = 1;
      #privateField = 2;
      static staticField = 3;
      
      publicMethod() {
        this.publicField;        // ✅ 접근 가능
        this.#privateField;      // ✅ 클래스 내부
        Example.staticField;     // ✅ 접근 가능
      }
      
      #privateMethod() {
        // private 메서드
      }
      
      static staticMethod() {
        // this.publicField;     // ❌ static은 인스턴스 접근 불가
        // this.#privateField;   // ❌
        this.staticField;        // ✅ static끼리 접근 가능
      }
    }
    
    const ex = new Example();
    
    // 외부 접근
    ex.publicField;           // ✅ public
    ex.publicMethod();        // ✅ public
    // ex.#privateField;      // ❌ private
    // ex.#privateMethod();   // ❌ private
    Example.staticField;      // ✅ static (클래스 이름으로)
    Example.staticMethod();   // ✅ static (클래스 이름으로)
    // ex.staticField;        // ❌ static (인스턴스로 불가)
    

     

     

    상속과 접근 제어

    private은 자식도 접근 불가

    class Parent {
      // private 필드
      #privateField = 'private';
      
      // public 필드
      publicField = 'public';
      
      // private 메서드
      #privateMethod() {
        return 'Private method';
      }
      
      // public 메서드
      publicMethod() {
        // ✅ 부모 클래스에서는 private 접근 가능
        console.log(this.#privateField);
        this.#privateMethod();
      }
    }
    
    class Child extends Parent {
      childMethod() {
        // ✅ public은 접근 가능
        console.log(this.publicField);  // "public"
        this.publicMethod();             // ✅ 가능
        
        // ❌ private은 접근 불가
        // console.log(this.#privateField);  // SyntaxError!
        // this.#privateMethod();            // SyntaxError!
      }
    }
    
    const child = new Child();
    child.childMethod();
    // "public" 출력
    // publicMethod 실행됨
    

     

     

    자식이 부모 기능 사용하려면

    class Animal {
      // private
      #energy = 100;
      
      // public - 안전한 인터페이스 제공
      getEnergy() {
        return this.#energy;
      }
      
      consumeEnergy(amount) {
        if (this.#energy >= amount) {
          this.#energy -= amount;
          return true;
        }
        return false;
      }
    }
    
    class Dog extends Animal {
      bark() {
        // ❌ private 직접 접근 불가
        // this.#energy -= 10;
        
        // ✅ public 메서드 사용
        if (this.consumeEnergy(10)) {
          console.log('Woof!');
        }
      }
      
      run() {
        // ✅ public 메서드로 간접 접근
        const energy = this.getEnergy();
        if (energy > 20) {
          this.consumeEnergy(20);
          console.log('Running!');
        }
      }
    }
    
    const dog = new Dog();
    dog.bark();  // "Woof!"
    dog.run();   // "Running!"
    

     

     

    접근 가능 범위 표

    위치 public private(#)
    정의한 클래스 내부
    자식 클래스 내부
    외부 (인스턴스)

    왜 자식도 private 접근 불가?

    • private은 외부에 노출하지 않을 내부 구현
    • 자식 클래스도 "외부"로 간주
    • 캡슐화와 보안 유지

     

    정리

    생성자 함수 vs 클래스

    생성자 함수 (ES5):
    - function으로 정의
    - 정적 메서드: 함수에 직접 추가
    - 인스턴스 메서드: prototype에 추가
    - private 없음
    
    클래스 (ES6+):
    - class 키워드 사용
    - 정적 메서드: static 키워드
    - 인스턴스 메서드: 그냥 메서드로 작성
    - private: # 사용
    
    결과: 내부적으로 동일하게 동작
    

     

     

    접근 제어자

    public (기본값):
    - 어디서든 접근 가능
    - 명시 불필요
    
    private (#):
    - 클래스 내부에서만 접근 가능
    - 자식 클래스도 접근 불가
    - 외부 접근 완전 차단
    
    static:
    - 인스턴스 없이 클래스로 직접 접근
    - 유틸리티 함수, 팩토리 메서드에 사용
    

     

     

    Date와 같은 빌트인 객체

    Date, Number, String, Array 등:
    - 생성자 함수로 구현됨 (ES5 이전부터 존재)
    - JavaScript 엔진이 C++로 구현
    - 클래스 문법 이전에 만들어짐
    - 동작은 클래스와 유사
    
    새로운 코드 작성:
    - class 문법 권장
    - 더 읽기 쉽고 명확함
    - private, static 등 명시적
    

     

     

    핵심 포인트

    1. 클래스 = 생성자 함수의 문법적 설탕 (내부 동작은 동일)
    2. public: 기본값, 어디서든 접근 가능
    3. private (#): 클래스 내부에서만, 자식도 불가
    4. static: 클래스 이름으로 직접 접근, 인스턴스 불필요
    5. Date는 생성자 함수, 클래스로 만들면 더 명확한 구조

     

     

     

     

     

     

    총 정리!!!

    접근 제한자 접근 범위
    public 어디서나 접근 가능
    protected 선언한 클래스 + 상속받은 자식 클래스
    private 선언한 클래스에서만
    static 인스턴스 없이 클래스에서 바로 접근

    코드로 보면

    class Parent {
      public a = 1;      // 어디서나
      protected b = 2;   // 나 + 자식만
      private c = 3;     // 나만
    }
    
    class Child extends Parent {
      test() {
        console.log(this.a); // ✅ public
        console.log(this.b); // ✅ protected (자식이니까)
        console.log(this.c); // ❌ private (부모만 접근 가능)
      }
    }
    
    const parent = new Parent();
    console.log(parent.a); // ✅ public
    console.log(parent.b); // ❌ protected (외부 접근 불가)
    console.log(parent.c); // ❌ private
    

     

    시각화

    접근 범위 (좁음 → 넓음)
    
    private < protected < public
       │          │          │
       │          │          └─ 외부에서도 OK
       │          └─ 자식까지만 OK
       └─ 나만 OK
    

     

    참고: static은 별개

    static은 접근 제한이 아니라 소속의 개념이에요.

    class Example {
      static publicStatic = 1;       // 클래스에서 바로, 어디서나
      private static privateStatic = 2; // 클래스에서 바로, 클래스 내부만
    }
    
    Example.publicStatic;  // ✅
    Example.privateStatic; // ❌ private이라서
    

    static + 접근 제한자 조합도 가능합니다.

    728x90
    반응형
    LIST

    댓글