Class의 Private 필드는 어떻게 트랜스파일될까?

hoonsbory·2023년 6월 8일
post-thumbnail

자바스크립트 Class에서 Private 필드는 ECMAScript2020(es11) 에서 도입되었습니다.

비교적 최근에 도입되었기 때문에 지원되지 않는 브라우저가 꽤 있습니다.

그렇다면 역시 Babel을 통해 트랜스파일해야합니다.

그럼 어떤식으로 Private를 트랜스파일할까요?

실제로 트랜스파일된 결과를 보면서 알아보겠습니다.


📜 트랜스 파일 전 코드

class User{
   #name = 'john'
   getName(){
   	return this.#name;
   }
}
const user = new User();
console.log(user.getName());

📜 트랜스 파일 후 코드

function _classPrivateFieldInitSpec(obj, privateMap, value) {
  _checkPrivateRedeclaration(obj, privateMap);
  privateMap.set(obj, value);
}
function _checkPrivateRedeclaration(obj, privateCollection) {
  if (privateCollection.has(obj)) {
    throw new TypeError(
      'Cannot initialize the same private elements twice on an object',
    );
  }
}
function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = _classExtractFieldDescriptor(receiver, privateMap, 'get');
  return _classApplyDescriptorGet(receiver, descriptor);
}
function _classExtractFieldDescriptor(receiver, privateMap, action) {
  if (!privateMap.has(receiver)) {
    throw new TypeError(
      'attempted to ' + action + ' private field on non-instance',
    );
  }
  return privateMap.get(receiver);
}
function _classApplyDescriptorGet(receiver, descriptor) {
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}
var _name = /*#__PURE__*/ new WeakMap();
class User {
  constructor() {
    _classPrivateFieldInitSpec(this, _name, {
      writable: true,
      value: 'john',
    });
  }
  getName() {
    return _classPrivateFieldGet(this, _name);
  }
}
const user = new User();
console.log(user.getName());

갑자기 너무 길어져서 당황스럽네요.

천천히 한 번 살펴보겠습니다.

먼저 Private 필드로 선언했던 name을 보면 WeakMap으로 선언이 된 걸 볼 수 있습니다.

WeakMap은 인스턴스 생성시에 _classPrivateFieldInitSpec라는 함수로 Init 하는 걸 확인할 수 있습니다.

인스턴스 (this)key로 데이터를 set 합니다. 그 전에 중복된 키가 있는지 확인하는 함수도 있네요.

꺼내올때는 인스턴스 (this)를 key값으로 가지는 value를 꺼내옵니다.

복잡해보이지만 사실 단순한 코드입니다.

🤷‍♂️ 그럼 왜 WeakMap을 사용하는 걸까요?

첫번째로 인스턴스를 통한 접근이 불가해야합니다.

그러기 위해서, 인스턴스 밖에 WeakMap을 생성한 후, 인스턴스를 key 값으로 value를 생성합니다.

이렇게 되면 인스턴스를 통해서 name에 접근할 수가 없습니다.

두번째는 메모리 해제입니다.

일반 Map으로 구현했다면, 키값인 인스턴스를 영원히 메모리에서 해제할 수 없습니다.

let _name = new Map();
class User {
  constructor() {
    _name.set(this, 'john');
  }
  getName() {
    return _name.get(this);
  }
}

let user = new User();
console.log(user.getName()); // john
user = null;
console.log(user); //null
console.log(_name); //Map(1) { User { } => 'john' }

인스턴스를 담고있는 usernull을 할당하여 메모리 해제를 시도해도, map에서 참조 중이기 때문에 해제되지 않습니다.

하지만 WeakMap을 사용한다면 key값으로 사용된 객체여도 가비지 콜렉터의 대상이 되어 메모리가 해제됩니다.


WeakMap을 사용해도, _name.get(user) 와 같은 방법으로 접근이 가능하기 때문에, 완벽한 은닉화는 아니지만, 개발 단계에서 은닉화를 달성할 수 있고, 메모리 누수를 방지할 수 있기 때문에 충분히 괜찮은 대안입니다.

1개의 댓글

comment-user-thumbnail
2023년 8월 24일

private 이 내부적으로 바벨로 트랜스파일링 될 때 WeakMap으로 구현된다니 놀라운 사실이네요! 좋은 글 감사합니다.

답글 달기