언더스코어(_) 방식의 한계
오랜 기간 동안 자바스크립트에는 private 필드라는 개념 자체가 없었습니다. 그래서 관례적으로 프로퍼티 이름 앞에 언더스코어(_)를 붙여두고 "이건 건드리지 마세요"라고 암묵적으로 약속하는 수밖에 없었죠:
_count는 얼핏 private처럼 보이지만 실제로는 그렇지 않습니다. 외부에서 누구든 읽고, 쓰고, 심지어 삭제까지 할 수 있죠. 밑줄(_)은 "건드리지 마세요"라고 붙여둔 안내문일 뿐, 문 자체는 활짝 열려 있는 셈입니다.
최신 자바스크립트에서는 이 문제를 해결하기 위해 #로 표시하는 진짜 private 필드를 도입했습니다.
#를 붙이면 진짜 private이 됩니다
자바스크립트 private 필드를 만들려면 선언부와 사용하는 모든 곳에서 필드 이름 앞에 #를 붙여주면 됩니다:
클래스 내부에서는 this.#count를 평소처럼 쓸 수 있지만, 외부에서는 아예 존재하지 않는 것처럼 동작합니다:
#은 말 그대로 필드 이름의 일부입니다. 다른 언어의 private 같은 수식어(modifier) 키워드가 아니라, 파서가 객체의 별도 보호된 슬롯을 찾을 때 사용하는 기호(sigil)죠. 그래서 코드가 실행되기도 전, 파싱 단계에서 에러가 나는 겁니다.
private 메서드와 getter
private으로 지정할 수 있는 건 필드만이 아닙니다. 메서드, getter, setter 모두 # 접두사를 붙일 수 있습니다:
#assertPositive는 내부에서만 쓰는 헬퍼입니다. 공개 API가 아니기 때문에 완전한 private으로 만들어두면 외부에서 실수로 호출할 일도 없고, 누군가 이 메서드에 의존할 수도 없습니다. 덕분에 나중에 이름을 바꾸거나 아예 삭제해도 부담이 없죠.
private static 멤버
static 멤버도 private으로 만들 수 있습니다. 방식은 똑같이 앞에 #를 붙이면 됩니다:
Private static 필드는 인스턴스가 아니라 클래스 자체에 붙습니다. 카운터나 캐시, 또는 클래스 밖으로 새어 나가면 안 되는 설정값 같은 걸 담아둘 때 유용하죠.
서브클래스에서는 접근 불가
Java나 C#을 쓰다 넘어온 분들이 여기서 자주 헷갈립니다. 자바스크립트의 private 필드는 _인스턴스 단위_가 아니라 _클래스 단위_로 private입니다. 그래서 서브클래스라 하더라도 부모 클래스의 private 필드에는 손을 댈 수 없어요:
자바스크립트에는 protected가 없습니다. 서브클래스가 부모의 데이터에 접근해야 한다면, 부모 쪽에서 메서드나 getter를 노출하거나(드물게는) private이 아닌 필드로 열어줘야 합니다. 이건 의도된 설계입니다. private은 말 그대로 private이고, 상속이라고 해서 이 벽에 구멍을 뚫을 수는 없습니다.
in 연산자로 private 필드 확인하기
어떤 객체가 내 클래스의 인스턴스가 맞는지 확인하고 싶을 때가 있습니다. 이른바 브랜드 체크(brand check) 죠. 클래스 내부에서는 in 연산자를 private 필드 이름과 함께 쓸 수 있습니다:
#balance 필드를 만들 수 있는 건 오직 Wallet 클래스뿐이므로, #balance in obj는 obj가 진짜 Wallet 인스턴스인지 확인하는 확실한 방법입니다. private 필드는 외부에서 위조할 수 없기 때문에 어떤 경우에는 instanceof보다 더 빠르고 안전한 판별법이 되죠.
주의할 점: 일반 객체에는 private 필드가 없습니다
private 필드는 클래스 생성자가 만든 인스턴스에만 존재합니다. new로 생성하지 않은 객체에 접근하려 하면 에러가 납니다.
this가 Point 인스턴스가 아닌 상태로 메서드를 호출하면 런타임 에러가 납니다. 앞서 본 brand check가 이런 방식으로 동작하죠. 즉, private 필드는 "그럴듯하게 생긴 객체"가 아니라 해당 필드를 선언한 클래스 자체에 묶여 있다는 뜻입니다.
언제 # private 필드를 써야 할까
클래스의 공개 API가 아닌 상태나 헬퍼라면, 기본값으로 자바스크립트 private 필드를 쓰세요. 이유는 이렇습니다.
- 리팩터링이 자유롭다. 바깥에서 아예 볼 수 없는 내부 구현에는 의존할 수가 없습니다.
- 진짜 캡슐화가 된다. 외부 코드가 실수로 읽거나, 쓰거나, 지울 일이 없습니다.
- 자동완성이 깔끔해진다. 에디터가 외부 호출자에게 private 멤버를 추천하지 않습니다.
진짜로 인터페이스의 일부인 값은 public 프로퍼티로 두세요. private 필드를 읽기 전용으로 노출하고 싶다면 getter(get name())를 쓰면 됩니다. 언더스코어(_) 컨벤션은 이제 잊어도 됩니다. 언어 차원에서 채워진 빈자리를 임시로 메우던 관습일 뿐이니까요.
#celsius는 외부에 노출되지 않는 저장 공간이고, celsius와 fahrenheit는 읽기 전용 뷰 역할을 합니다. 호출하는 쪽에서 내부 상태를 망가뜨릴 수 없고, 클래스는 나중에 값을 저장하는 방식을 얼마든지 바꿔도 괜찮습니다.
다음은 프로토타입
사실 클래스는 자바스크립트의 프로토타입 시스템 위에 얹힌 문법적 설탕(syntactic sugar)에 가깝습니다. 프로토타입이야말로 이 언어가 실제로 기반을 두고 있는, 더 오래되고 근본적인 모델이죠. 프로토타입을 이해하면 this가 왜 그런 식으로 동작하는지, 상속이 실제로 어떻게 이뤄지는지, 그리고 클래스의 extends가 내부적으로 무엇을 하는지 자연스럽게 풀립니다. 바로 다음 페이지에서 다뤄 보겠습니다.
자주 묻는 질문
자바스크립트에서 private 필드는 어떻게 선언하나요?
필드 이름 앞에 #을 붙이면 됩니다. 선언할 때도, 접근할 때도 항상 #을 함께 써야 해요. 예를 들어 class Counter { #count = 0; increment() { this.#count++; } }처럼 쓰면 진짜 private 필드가 됩니다. 여기서 #은 연산자가 아니라 이름의 일부라는 점을 기억하세요.
#field와 _field는 어떻게 다른가요?
#field와 _field는 어떻게 다른가요?_field는 "건드리지 말아주세요"라는 관례일 뿐입니다. 실제로는 완전히 public이라 누구나 읽고 쓸 수 있어요. 반면 #field는 언어 차원에서 강제됩니다. 클래스 바깥에서는 접근 자체가 불가능하고, 시도하면 파싱 단계에서 SyntaxError가 발생하죠. 진짜 비공개가 필요하면 #을 쓰세요.
서브클래스에서 부모 클래스의 private 필드에 접근할 수 있나요?
접근할 수 없습니다. private 필드는 선언한 클래스 안에서만 유효하기 때문에 자식 클래스도 손댈 수 없어요. 자식 쪽에서 접근이 필요하다면 부모 클래스가 메서드나 getter로 열어줘야 합니다. 다른 언어의 protected보다 더 엄격한 규칙인데, 의도된 설계입니다.
객체에 특정 private 필드가 있는지 확인할 수 있나요?
네, 클래스 내부에서 in 연산자를 쓰면 됩니다. #field in obj가 true를 반환하면 해당 private 필드를 가진 객체라는 뜻이에요. 메서드를 호출하기 전에 "이 객체가 정말 우리 클래스의 인스턴스인가?"를 검증하는 브랜드 체크(brand check) 용도로 유용합니다.