javascript

JavaScript IIFE(즉시 실행 함수) vs OOP(객체지향프로그래밍)

콩콩(๓° ˘ °๓)♡ 2025. 9. 22. 16:04

📌 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 문법을 제공하는 것 이상의 가치를 가집니다.

  1. 트리셰이킹 최적화: 불필요한 코드 제거로 번들 크기 최소화
  2. 테스트 효율성: 단위별 테스트 가능, mock/stub 용이
  3. 현대 생태계 친화적: Rollup, Webpack, Vite 등 최신 번들러와 완벽 호환