728x90
반응형
SMALL

클로저 vs 클래스 private: 메모리 효율 차이
생성자 함수에서 클로저로 private을 구현하면 메모리가 비효율적이라고 했는데, 정확히 무엇이 문제일까?
핵심: 변수가 아니라 메서드가 문제!
변수는 둘 다 똑같다
// 클로저 방식
function Person(name) {
let _name = name; // 인스턴스마다 새 변수
}
// 클래스 방식
class Person {
#name; // 인스턴스마다 새 변수
constructor(name) {
this.#name = name;
}
}
변수 할당:
- 클로저든 클래스든 인스턴스마다 새 변수 생성
- 각 인스턴스의 데이터니까 당연함
- 메모리 사용량 동일 ✅
진짜 차이: 메서드의 위치
클로저 방식 - 메서드도 복사됨 ❌
function Person(name) {
let _name = name;
// ❌ 메서드가 생성자 안에 있음
// 인스턴스마다 새로 생성됨!
this.getName = function() {
return _name;
};
this.setName = function(newName) {
_name = newName;
};
}
const p1 = new Person('Kim');
const p2 = new Person('Lee');
const p3 = new Person('Park');
// 메서드가 각각 다른 함수!
console.log(p1.getName === p2.getName); // false
console.log(p2.getName === p3.getName); // false
// 3개 인스턴스 = getName 3개 + setName 3개 = 총 6개 함수
메모리 구조:
p1: { _name: 'Kim', getName: fn1, setName: fn2 }
p2: { _name: 'Lee', getName: fn3, setName: fn4 }
p3: { _name: 'Park', getName: fn5, setName: fn6 }
→ 6개 함수 생성 (비효율!)
클래스 방식 - 메서드는 공유됨 ✅
class Person {
#name;
constructor(name) {
this.#name = name;
}
// ✅ 메서드가 prototype에 있음
// 모든 인스턴스가 공유!
getName() {
return this.#name;
}
setName(newName) {
this.#name = newName;
}
}
const p1 = new Person('Kim');
const p2 = new Person('Lee');
const p3 = new Person('Park');
// 메서드가 같은 함수!
console.log(p1.getName === p2.getName); // true
console.log(p2.getName === p3.getName); // true
// 3개 인스턴스 = getName 1개 + setName 1개 = 총 2개 함수
메모리 구조:
Person.prototype: { getName: fn1, setName: fn2 } ← 1개만!
↑ ↑
│ │
p1: { #name: 'Kim' } ──┘ │
p2: { #name: 'Lee' } ───────────────┘
p3: { #name: 'Park' } ───────────────┘
→ 2개 함수만 생성 (효율적!)
왜 클로저는 메서드를 복사할까?
클로저가 필요하기 때문
function Person(name) {
let _name = name; // 생성자의 지역 변수
// 이 함수는 _name에 접근해야 함
// → 클로저 형성 필요
// → 생성자 안에 있어야 함
this.getName = function() {
return _name; // 클로저로 접근
};
}
// prototype에 두면 안 됨
Person.prototype.getName = function() {
return _name; // ❌ _name을 찾을 수 없음!
};
이유:
- _name은 생성자 함수의 지역 변수
- prototype 메서드는 생성자 밖에 있음
- 클로저를 만들려면 메서드가 생성자 안에 있어야 함
메모리 사용량 비교
1000개 인스턴스 생성 시
클로저 방식
function Person(name) {
let _name = name;
this.getName = function() { return _name; };
this.setName = function(newName) { _name = newName; };
this.greet = function() { return `Hello, ${_name}`; };
}
const people = [];
for (let i = 0; i < 1000; i++) {
people.push(new Person(`Person${i}`));
}
메모리:
변수: 1000개 (_name × 1000)
메서드: 3000개 (getName, setName, greet × 1000)
────────────────────────────
총: 4000개 항목
클래스 방식
class Person {
#name;
constructor(name) { this.#name = name; }
getName() { return this.#name; }
setName(newName) { this.#name = newName; }
greet() { return `Hello, ${this.#name}`; }
}
const people = [];
for (let i = 0; i < 1000; i++) {
people.push(new Person(`Person${i}`));
}
메모리:
변수: 1000개 (#name × 1000)
메서드: 3개 (getName, setName, greet, prototype에서 공유)
────────────────────────────
총: 1003개 항목
시각화
클로저 방식 (비효율)
인스턴스 1:
┌─────────────────┐
│ _name: 'Kim' │
│ getName: fn1 │ ← 함수 복사본
│ setName: fn2 │ ← 함수 복사본
└─────────────────┘
인스턴스 2:
┌─────────────────┐
│ _name: 'Lee' │
│ getName: fn3 │ ← 함수 복사본
│ setName: fn4 │ ← 함수 복사본
└─────────────────┘
인스턴스 3:
┌─────────────────┐
│ _name: 'Park' │
│ getName: fn5 │ ← 함수 복사본
│ setName: fn6 │ ← 함수 복사본
└─────────────────┘
→ 6개 함수 생성
클래스 방식 (효율적)
Person.prototype:
┌─────────────────┐
│ getName: fn1 │ ← 1개만!
│ setName: fn2 │ ← 1개만!
└─────────────────┘
↑ ↑ ↑
│ │ └────────┐
│ └────────┐ │
└────────┐ │ │
인스턴스 1: │ │ │
┌──────────┐│ │ │
│#name:'Kim'││ │ │
└──────────┘│ │ │
│ │ │
인스턴스 2: │ │ │
┌──────────┐│ │ │
│#name:'Lee'││ │ │
└──────────┘│ │ │
│ │ │
인스턴스 3: │ │ │
┌───────────┐ │ │
│#name:'Park'│ │ │
└───────────┘ │ │
→ 2개 함수만 생성
클래스는 어떻게 둘 다 할까?
private + prototype 동시 사용
class Person {
#name; // private 필드
getName() { // prototype 메서드
return this.#name; // private 접근 가능!
}
}
내부 메커니즘 (개념적):
// private 필드는 내부 슬롯에 저장
Person 인스턴스: {
[[PrivateField_name]]: 'Kim'
}
// 메서드는 prototype에 저장
Person.prototype: {
getName: function() {
// JavaScript 엔진이 특별한 메커니즘으로
// 내부 슬롯에 접근
return this.[[PrivateField_name]];
}
}
정리
메모리 비효율의 진짜 이유
❌ 변수 할당 때문이 아님
클로저든 클래스든 인스턴스마다 변수 생성 (당연함)
✅ 메서드 복사 때문
클로저: 메서드가 인스턴스마다 생성됨
클래스: 메서드가 prototype에서 공유됨
비교표
| 항복 | 클로저 | 클래스 (#) |
| private 변수 | 인스턴스마다 생성 | 인스턴스마다 생성 |
| 메서드 위치 | 인스턴스 내부 | prototype |
| 메서드 공유 | ❌ 복사됨 | ✅ 공유됨 |
| private 보호 | ✅ 진짜 | ✅ 진짜 |
| 메모리 효율 | ❌ 비효율 | ✅ 효율적 |
결론
인스턴스 100개 생성 시:
클로저:
- 변수: 100개
- 메서드: 300개 (메서드 3개 × 100)
→ 총 400개
클래스:
- 변수: 100개
- 메서드: 3개 (prototype 공유)
→ 총 103개
클래스가 약 4배 더 효율적!
권장: 새 코드는 클래스 #private 사용 (ES2022+)
그럼 생성자함수에서 클로저방식을 안쓰고 (프라이빗을 안하고) 기존 정적메서드(스태틱) 인스턴스메서드(그냥 메서드)를 쓰면 이런 비효율은 없는건가?
클로저 안 쓰면 비효율 없어요!
생성자 함수 + prototype (효율적 ✅)
// 생성자 함수
function Person(name, age) {
// public 필드만
this.name = name;
this.age = age;
}
// 정적 메서드 - 함수에 직접 추가
Person.isAdult = function(age) {
return age >= 18;
};
// 인스턴스 메서드 - prototype에 추가
Person.prototype.greet = function() {
return `Hello, ${this.name}`;
};
Person.prototype.getAge = function() {
return this.age;
};
// 사용
const p1 = new Person('Kim', 25);
const p2 = new Person('Lee', 30);
const p3 = new Person('Park', 20);
// ✅ 메서드 공유됨!
console.log(p1.greet === p2.greet); // true
console.log(p2.greet === p3.greet); // true
```
**메모리:**
```
Person.prototype: { greet: fn1, getAge: fn2 } ← 1개만!
↑ ↑
│ │
p1: { name: 'Kim', age: 25 } ───────┘
p2: { name: 'Lee', age: 30 } ───────┘
p3: { name: 'Park', age: 20 } ───────┘
→ 메서드 2개만 (효율적!)
클래스와 거의 동일!
생성자 함수 + prototype
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.isAdult = function(age) {
return age >= 18;
};
Person.prototype.greet = function() {
return `Hello, ${this.name}`;
};
클래스 (위와 동일한 결과)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static isAdult(age) {
return age >= 18;
}
greet() {
return `Hello, ${this.name}`;
}
}
```
**결과:**
```
typeof Person; // "function" (둘 다)
Person.prototype.greet; // function (둘 다)
p1.greet === p2.greet; // true (둘 다 공유)
```
---
## 정리 비교표
| 방식 | private | 메서드 위치 | 메서드 공유 | 메모리 효율 |
|------|---------|-----------|-----------|-----------|
| **클로저** | ✅ | 생성자 내부 | ❌ 복사됨 | ❌ 비효율 |
| **prototype** | ❌ | prototype | ✅ 공유됨 | ✅ 효율적 |
| **클래스 (public)** | ❌ | prototype | ✅ 공유됨 | ✅ 효율적 |
| **클래스 (#private)** | ✅ | prototype | ✅ 공유됨 | ✅ 효율적 |
---
## 핵심 정리
```
메모리 비효율의 원인:
❌ 생성자 함수 자체가 문제 아님
✅ 클로저 사용이 문제
→ 메서드가 생성자 안에 있어야 함
→ 인스턴스마다 복사됨
→ 클로저로 지정한 값을 메서드가 기억하고 있어야하니까, 프로토타입 메서드로 못만들기 때문
해결책:
1. private 필요 없으면:
→ prototype 사용 (효율적)
2. private 필요하면:
→ 클래스 # 사용 (효율적 + private)
3. ES5 환경에서 private 필요하면:
→ 클로저 사용 (비효율적이지만 어쩔 수 없음)
최종 답변
zzimzzim님 질문
"생성자 함수에서 클로저 방식을 안 쓰고 (private을 안 하고) 기존 정적 메서드, 인스턴스 메서드를 쓰면 이런 비효율은 없는 건가?"
답: 맞아요! ✅
// ✅ 효율적 (클로저 없음)
function Person(name) {
this.name = name; // public
}
Person.prototype.greet = function() {
return `Hello, ${this.name}`;
};
// ❌ 비효율적 (클로저 사용)
function Person(name) {
let _name = name; // private (클로저)
this.greet = function() {
return `Hello, ${_name}`;
};
}
```
---
### 핵심
```
생성자 함수 자체는 문제 없음!
비효율의 진짜 원인:
→ 클로저를 사용해서 메서드가 생성자 안에 있을 때
해결:
→ private 필요 없으면 prototype 사용
→ private 필요하면 클래스 # 사용
728x90
반응형
LIST
'javascript' 카테고리의 다른 글
| 바인딩 vs 할당 vs 참조 (0) | 2026.01.25 |
|---|---|
| null === null, undefined === undefined, NaN === NaN (0) | 2026.01.24 |
| JavaScript 클래스: 생성자 함수 vs 클래스(static,public,private) 문법 (0) | 2026.01.24 |
| JavaScript 원시값의 비밀: undefined와 null은 메모리를 차지할까? (0) | 2026.01.23 |
| 이벤트 캡처링, 버블링 그리고 disabled vs readonly (1) | 2026.01.22 |
댓글