JavaScript에서 private 필드

내 이 세상 도처에서 쉴 곳을 찾아보았으나, 마침내 찾아낸, 컴퓨터가 있는 구석방보다 나은 곳은 없더라.

JavaScript에서 private 필드

JavaScript를 주로 사용하게 되면서 JavaScript에 대한 부정적인 생각이 줄어들었지만, 여전히 마음에 들지 않는 부분이 있다. 그 중 하나가 private 필드를 만들기가 애매하다는 점이다.

function User() {
  this.passwordHash = ...;
  ...
}

이렇게 하면 passwordHash을 객체 밖에서 마음대로 참조하고 덮어쓸 수 있다. 이렇게 하는 것은 왠지 불안하다.

function User() {
  var passwordHash;
  ...
}

이렇게 하면 passwordHashUser 객체 밖에서 볼 수 없게 된다. 문제는 User 객체의 메서드에서도 볼 수 없다는 점이다. 메서드는 보통 prototype에 만들어주는 것이 좋다. 객체 인스턴스마다 중복된 함수 객체를 별도로 생성하지 않게 하기 때문이다.

User.prototype.setPasswordHash(passwd) {
  // 여기서 passwordHash을 참조하려면???
}

클로저를 이용하면 private 필드를 만드는 것이 가능하긴 하다. 다만 그렇게 하려면 메서드를 생성자 안으로 넣어야 한다.

function User() {
  var passwordHash;
  ...
  this.setPasswordHash = function (passwd) {
    // 여기서는 passwordHash을 참조할 수 있다.
    passwordHash = hash(passwd);
  }
}

이렇게 하면 두 가지 마음에 안 드는 점이 생긴다. 첫째는 함수 setPasswordHash의 인스턴스가 User 인스턴스마다 생긴다는 점이다. 만약 User가 매우 빈번하게 생성되며 많은 인스턴스를 유지해야 하는 경우라면 성능에 불리해진다. 불필요하게 메모리를 많이 소비할뿐 아니라 User 인스턴스를 생성할 때마다 함수 객체도 다시 생성해야 하기 때문에 속도에서도 손해를 볼 것이다. 둘째는 약간 미학적인 측면인데, 생성자 코드와 메서드 정의가 섞여버려 보기가 안 좋아진다는 점이다.

필드를 읽기전용 속성으로 만드는 방법도 있긴 하다.

function User() {
  var passwordHash;
  ...
  Object.defineProperty(this, 'passwordHash', {
    get: function () { return passwordHash; }
  });
}

이렇게 하면 prototype에 정의한 메서드에서도 passwordHash을 참조할 수 있다. 그러나 property를 넣으면 객체 생성 속도가 10배 이상 느려진다. 또한 필드를 완전히 밖으로 노출하지 않고 싶을 때는 이 방법도 사용할 수 없다.

아직 어떻게 하는 것이 최선인지 잘 모르겠다. 지금까지는 다음과 같이 타협하고 있다.

  • 보통 객체 인스턴스를 많이 만들지 않는 것이 확실한 경우에는 그냥 클로저를 사용하고 메서드를 생성자 안에 넣는 방법을 사용한다.
  • 필드를 읽기전용으로 만들면 밖으로 노출해도 상관 없는 경우에는 property를 사용한다.
  • 이도 저도 안 될때는 그냥 공개 필드로 선언한다. 프로그램 내부에서만 사용하는 객체라면 이렇게 해도 내가 주의해 사용하면 되므로 그럭저럭 견딜만하다.