캡슐화(Encapsulation)는 데이터와 함수를 하나의 컴포넌트(예: 클래스)에 묶고, 해당 컴포넌트에 대한 접근을 제어하여 객체를 블랙박스처럼 만드는 개념이다. 정보를 은닉(Information Hiding)은 모듈이나 객체의 내부 구현 세부사항을 외부에 노출하지 않고, 인터페이스만 제공하여 변경 가능성을 최소화하는 원칙이다. 따라서 캡슐화는 데이터를 물리적으로 묶어 관리하는 방법이라면, 정보 은닉은 그 내부 동작을 숨기는 행위를 말한다. 객체지향 설계에서 캡슐화는 정보 은닉을 실현하기 위한 수단으로 사용된다.
자바스크립트는 프로토타입 기반 언어이므로 전통적인 클래스 기반 언어의 접근 제어와는 다르다. 자바스크립트에서 캡슐화/정보 은닉을 구현하는 주요 방법은 다음과 같다.
클로저는 함수가 선언된 어휘적 환경(렉시컬 환경)을 기억하므로, 내부 변수를 외부에서 직접 접근할 수 없도록 한다. 이를 통해 상태(state)와 함수를 번들링하여 객체와 유사한 구조를 만들 수 있다. 예를 들어 즉시 실행 함수(IIFE) 패턴을 사용해 카운터를 구현하면 다음과 같다:
function createCounter() {
let count = 0;
return {
increment() { count++; },
getCount() { return count; }
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
위 예제에서 count 변수는 createCounter 함수 내부에 존재하므로 외부에서 직접 접근할 수 없다. increment와 getCount 메서드만 공개되어 데이터의 변경과 조회를 제어한다. 이러한 패턴은 모듈 패턴 또는 Revealing Module Pattern으로 불리며, 자바스크립트에서 널리 사용된다.
Symbol은 고유한 식별자를 생성하여 객체의 프로퍼티 키로 사용할 수 있다. MDN에 따르면 심볼 키는 일반적인 방법으로 접근하기 어려운 형태로 숨길 수 있어 약한 형태의 캡슐화나 정보 은닉을 구현할 수 있다. 예를 들어:
const _secret = Symbol('secret');
class MyClass {
constructor(secretValue) {
this.name = 'Alice';
this[_secret] = secretValue;
}
getSecret() {
return this[_secret];
}
}
const obj = new MyClass('hidden');
console.log(Object.keys(obj)); // ["name"] (심볼 키는 표시되지 않음)
console.log(obj.getSecret()); // "hidden"
위 코드에서 _secret은 심볼이기 때문에 Object.keys()나 JSON 변환 등에 노출되지 않는다. 그러나 주의할 점은 심볼도 완전한 비공개를 제공하지 않는다는 것이다. Symbol로 선언한 프로퍼티는 런타임에 리플렉션 API(Object.getOwnPropertySymbols 등)로 접근할 수 있으며, 내부 값이 노출될 수 있다.
# Private 필드를 통한 은닉최신 자바스크립트(ES2022)에서는 클래스 내부에 # 접두사를 붙여 진정한(private) 필드/메서드를 선언할 수 있다. MDN에 따르면 #로 만든 private 속성은 클래스 외부에서 합법적으로 참조할 수 없으며, 자바스크립트 엔진이 접근을 차단한다. 또한 클래스 외부에서 # 필드를 접근하면 문법 오류가 발생한다. 예를 들어:
class SecretHolder {
#secret;
constructor(secret) {
this.#secret = secret;
}
reveal() {
return this.#secret;
}
}
const sh = new SecretHolder('top secret');
console.log(sh.reveal()); // 'top secret'
console.log(sh.#secret); // SyntaxError: private field '#secret' must be declared in an enclosing class
위 예제에서 #secret은 클래스 외부에서 접근할 수 없으며, 자바스크립트가 이를 강제한다. 이 문법 도입 이전에는 WeakMap이나 클로저로 비공개 필드를 흉내 냈지만, MDN에 따르면 이러한 방법은 # 문법만큼 편리하지 않다.
ES6 이전에는 _ 접두사(named convention)나 WeakMap을 사용해 비공개 프로퍼티를 구현하기도 했다. 예를 들어 WeakMap을 사용하면 인스턴스별 비공개 데이터를 안전하게 저장할 수 있다:
const privates = new WeakMap();
class Example {
constructor(secret) {
privates.set(this, { secret });
}
getSecret() {
return privates.get(this).secret;
}
}
또한 ES6 모듈(import/export)을 사용하면 모듈 범위 자체가 은닉을 제공한다. 모듈에서 export하지 않는 변수는 모듈 외부에서 접근할 수 없으므로 구현 세부사항이 자연히 숨겨진다.
class와 이후 클래스 필드(initializer), # private 필드/메서드 등이 추가되어 문법적으로 캡슐화 지원이 강화되었다.Symbol이 지원되며, 객체 프로퍼티 충돌 방지와 약한 은닉에 사용된다.WeakMap은 객체를 키로 사용해 비공개 데이터를 저장하기에 유용하며, 키 객체가 가비지 콜렉션 대상이 되면 자동 소멸되어 메모리 누수를 줄인다.export되지 않은 식별자는 외부에서 보이지 않는다. 이를 통해 자연스럽게 은닉을 달성할 수 있다.#privateMethod, #privateGetter/#privateSetter 등이 지원되어 클래스 내부에 완전한 은닉 메서드/접근자를 정의할 수 있다.class와 # private 필드를 사용하는 것이 명시적이고 유지보수가 쉽다._) 컨벤션: 실제로 강제 은닉을 제공하지는 않지만, 코드 스타일 가이드로서 변수나 메서드명 앞에 _를 붙여서 암묵적으로 비공개임을 표시하는 관습이 있다.private: TypeScript의 private 접근제어자는 컴파일 타임 검사를 위한 것일 뿐 런타임에는 일반 프로퍼티이므로 진정한 은닉을 보장하지 않는다. 진짜 은닉이 필요하면 # 문법이나 클로저를 사용해야 한다.