📌 IIFE의 본질
- 함수 정의와 동시에 즉시 실행되는 함수
(function() {
console.log("바로 실행됨!");
})();
- 주 목적: 스코프를 한정해서 전역 오염(global pollution)을 막는 것.
- ES6 이전에는 let, const, class 같은 블록 스코프 문법이 없었기 때문에, IIFE로 캡슐화 비슷한 효과를 만들었음.
📌 OOP와의 관계
- OOP의 핵심: 캡슐화, 상속, 다형성
- IIFE는 그중에서 캡슐화(encapsulation)와 비슷한 역할을 합니다.
- 외부에서 접근하지 못하는 private 변수/메서드를 흉내낼 수 있었음.
const Counter = (function() {
let count = 0; // private 변수
return {
increment() { count++; },
getCount() { return count; }
};
})();
Counter.increment();
console.log(Counter.getCount()); // 1
➡️ count는 외부에서 직접 접근할 수 없고, 오직 공개된 메서드로만 다룰 수 있음 → OOP의 정보 은닉과 유사.
📌 지금 기준에서는?
- ES6 이후에는 class, module, let/const로 대부분 대체 가능.
- 그래서 요즘은 OOP 구조를 짜기 위해 IIFE를 굳이 쓰지 않고, 모듈 시스템(import/export) 이 표준이 됨.
- 다만 레거시 코드나 라이브러리에서는 여전히 볼 수 있음.
✅ 결론:
IIFE는 OOP 자체가 아니라, JavaScript에서 OOP적 개념(특히 캡슐화)을 흉내내기 위해 사용되던 패턴이에요. 지금은 class나 module 문법이 그 역할을 더 명확하게 대신하고 있죠.
둘을 같은 코드로 비교해서 보여줘
1) IIFE + 모듈 패턴 (클로저로 private 흉내)
// counter.js (전통 IIFE 패턴, 번들러 없이도 즉시 동작)
const Counter = (function () {
let count = 0; // private (외부에서 직접 접근 불가)
function validate(n) { // private helper
if (typeof n !== "number") throw new TypeError("number only");
}
return {
increment() { count++; }, // public
add(n) { validate(n); count += n; }, // public
get() { return count; } // public
};
})();
// 사용
Counter.increment();
Counter.add(2);
console.log(Counter.get()); // 3
- 핵심: IIFE가 반환한 객체만 공개. 내부 변수(count, validate)는 클로저로 캡슐화됨.
- 단점: “인스턴스” 개념이 약함(위 코드는 싱글톤). 매번 팩토리로 감싸야 다중 인스턴스 가능.
다중 인스턴스가 필요하면:
function createCounter() {
let count = 0; // 각 인스턴스별 클로저로 독립
return {
increment() { count++; },
get() { return count; }
};
}
const c1 = createCounter();
const c2 = createCounter();
c1.increment();
console.log(c1.get(), c2.get()); // 1 0
2) ES6 class + #private 필드
// Counter.mjs (모듈)
export default class Counter {
#count = 0; // 진짜 private (언어 차원에서 숨김)
static from(n = 0) { // 정적 팩토리
const c = new Counter();
c.add(n);
return c;
}
increment() { this.#count++; }
add(n) {
if (typeof n !== "number") throw new TypeError("number only");
this.#count += n;
}
get() { return this.#count; }
}
// 사용 (다른 모듈)
import Counter from "./Counter.mjs";
const c = new Counter();
c.increment();
console.log(c.get()); // 1
const c2 = Counter.from(10); // 정적 팩토리
console.log(c2.get()); // 10
- 핵심: #count는 진짜로 외부에서 접근 불가(c.#count 하면 문법 에러).
- 장점: 명확한 인스턴스, 상속/다형성, 정적 메서드 등 OOP 기능을 언어가 지원.
3) ES 모듈로 네임스페이스/캡슐화
IIFE가 전역 오염을 막으려던 목적은 이제 ES 모듈이 담당:
// math.mjs
const PI = 3.14159; // 모듈 내부에만 존재
export function area(r) { return PI * r * r; } // 필요한 것만 export
// use.mjs
import { area } from "./math.mjs";
console.log(area(3));
- 모듈 스코프 덕분에 불필요한 심볼이 전역에 안 새어 나감.
- 번들러/브라우저(모듈 스크립트)에서 표준적으로 동작.
언제 무엇을 쓰나?
- 레거시/번들러 없이 즉시 동작 + 간단한 초기화/한 번만 쓰는 구성 → IIFE 간단.
- 인스턴스, 상속, 테스트, 트리셰이킹, 타입 추론(TS) → class + ES 모듈 권장.
- 진짜 프라이빗이 필요하면 → #private 필드가 클로저보다 명확하고 안전.
ES모듈의 장점을 더 자세히 설명해줘
ES 모듈(ESM)의 장점: 트리셰이킹과 테스트 관점에서
ES 모듈(ESM) 의 도입은 단순한 문법 변화가 아니라, 빌드 최적화와 테스트 환경에 큰 전환점을 만들었습니다.
1. 트리셰이킹(Tree-shaking)이란?
트리셰이킹은 빌드 과정에서 사용되지 않는(dead) 코드를 제거하는 최적화 기법입니다.
이 덕분에 번들 크기를 줄이고 성능을 높일 수 있습니다.
예를 들어, 다음 코드에서 mul 함수는 사용되지 않으므로 최종 번들에 포함되지 않습니다.
// util.mjs
export function add(a, b) { return a + b; }
export function mul(a, b) { return a * b; }
// app.mjs
import { add } from './util.mjs';
console.log(add(2, 3)); // mul은 사용되지 않음
2. 과거 방식의 한계
2.1 IIFE (즉시 실행 함수)
const Util = (function () {
function add(a, b) { return a + b; }
function mul(a, b) { return a * b; }
return { add, mul };
})();
- 내부 함수는 클로저로 감춰져 있어 정적 분석이 어려움
- 불필요한 코드도 번들에 포함됨
2.2 CommonJS (require)
// util.js
exports.add = (a, b) => a + b;
exports.mul = (a, b) => a * b;
// app.js
const util = require('./util');
console.log(util.add(2, 3));
- require는 런타임 로딩 → 어떤 함수가 쓰일지 빌드 타임에 확실히 알 수 없음
- 항상 모듈 전체가 로드되므로 dead code 제거 불완전
3. ES 모듈의 장점
3.1 정적 import/export
- import/export는 정적으로 선언되어 있어, 빌드 툴이 의존성 그래프를 완전히 파악 가능
- 필요한 것만 가져오기 가능 → 트리셰이킹에 최적화
3.2 사이드 이펙트 제거
- 모듈 스코프가 독립적이라, 사이드 이펙트 없는 코드는 안전하게 제거할 수 있음
- package.json의 "sideEffects": false 설정으로 더 과감한 최적화 가능
3.3 테스트 용이성
- 개별 함수만 import 가능 → 단위 테스트 격리성 강화
- 불필요한 코드가 로드되지 않아 테스트 실행 속도와 커버리지 정확도 향상
4. 테스트 관점에서 비교
IIFE
- 전역 객체에 붙여야만 테스트 가능
- private 로직 검증 불편
CommonJS
// util.test.js
const util = require('./util');
test('add works', () => {
expect(util.add(2, 3)).toBe(5);
});
- 항상 전체 모듈 로드
- mocking/stubbing 제약
ES 모듈
// util.mjs
export function add(a, b) { return a + b; }
export function mul(a, b) { return a * b; }
// util.test.js (Jest)
import { add } from './util.mjs';
test('add works', () => {
expect(add(2, 3)).toBe(5);
});
- 필요한 함수만 import 가능 → 격리된 테스트
- mul은 import하지 않으면 번들에 포함조차 되지 않음 (트리셰이킹 효과)
5. 결론
ES 모듈은 단순히 import/export 문법을 제공하는 것 이상의 가치를 가집니다.
- 트리셰이킹 최적화: 불필요한 코드 제거로 번들 크기 최소화
- 테스트 효율성: 단위별 테스트 가능, mock/stub 용이
- 현대 생태계 친화적: Rollup, Webpack, Vite 등 최신 번들러와 완벽 호환
'javascript' 카테고리의 다른 글
| 플라톤의 이데아 vs OOP(객체지향프로그래밍) or FP(함수형프로그래밍) (1) | 2025.09.18 |
|---|