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 등 명시적
핵심 포인트
- 클래스 = 생성자 함수의 문법적 설탕 (내부 동작은 동일)
- public: 기본값, 어디서든 접근 가능
- private (#): 클래스 내부에서만, 자식도 불가
- static: 클래스 이름으로 직접 접근, 인스턴스 불필요
- 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
'javascript' 카테고리의 다른 글
| null === null, undefined === undefined, NaN === NaN (0) | 2026.01.24 |
|---|---|
| 클로저 vs 클래스 private: 메모리 효율 차이 (0) | 2026.01.24 |
| JavaScript 원시값의 비밀: undefined와 null은 메모리를 차지할까? (0) | 2026.01.23 |
| 이벤트 캡처링, 버블링 그리고 disabled vs readonly (1) | 2026.01.22 |
| 브라우저 vs Node.js: JavaScript 실행 환경의 차이 (1) | 2026.01.21 |
댓글