🙄 그런데 Private, Protected 가 뭘 하는지 아시나요?
protected는 조금 생소한 개념이어서 저는 조금 감이 안잡혔던 경험이 있습니다.
간단하게 정리하고 가면
public
어디에서나 접근 가능하며 접근제어자를 따로 정의하지 않으면 설정되는 default 값입니다.
private
private 키워드로 선언된 프로퍼티들은 서브 클래스에서도 접근할 수 없습니다.
타입스크립트는 인스턴스 간 private 액세스를 허용합니다.
class A {
private x = 10;
public sameAs(other: A) {
// No error
return other.x === this.x;
}
}
이렇듯 classA는 sameAs 를 통해 간접적으로 private 키워드의 정보에 접근할 수 있습니다.
protected
protected는 private과 달리 서브 클래스에서도 접근할 수 있습니다.
클래스의 인스턴스는 접근할 수 없습니다.
protected의 이해가 조금 어려울 수 있습니다.
class Hello {
constructor(public name: string) {}
greet() {
console.log(`hi! ${this.name}, log: ${this.test()}`);
}
protected test() {
return 'test';
}
}
const hello = new Hello('kimminsoo');
hello.greet(); // greet은 Hello 클래스이기 때문에 호출할 수 있습니다.
hello.test(); // hello는 인스턴스이기 때문에 접근할 수 없습니다.
hello['test'](); // 그러나 protected 되어도 key 로 접근할수도 있고 호출할 수도 있습니다
class Hi extends Hello {
constructor(name: string) {
super(name);
}
hey() {
console.log(this.test()); // 서브클래스에서는 test()에 접근할 수 있습니다.
}
}
protected 키워드의 핵심은 서브클래스 또한 protected 된 프로퍼티들에 접근할 수 있다는 것입니다.
이제 접근제어자들이 어떤 것들인지 알았다면 접근제한자 #을 알아보겠습니다.
😆접근 제한자 #을 알아보기전에
JavaScript는 TypeScript처럼 명시적으로 private 속성을 지원하지 않습니다.
만약 클래스의 프로퍼티를 비공개로 수정하고 싶다면 # 접근제한자를 이용하여 수행합니다.
반면 TypeScript는 JavaScript의 superset으로 자바스크립트에서 지원하지 않는
여러가지 키워드, 기능들을 실험적으로 , 혹은 공식적으로 지원하고 있습니다.
그래서 TypeScript에서는 public, protected, private 접근 제어자를 지원하는 것도 확인할 수 있습니다.
class Example {
protected protectHi = 'protect hi';
private privateHi = 'secret hi';
public publicHi = 'public hi';
}
이렇게 접근 제어자를 지원하며
TypeScript 에서는 이 접근제어자들이 잘 동작하는 것 처럼 보이기 때문에
개발자가 쉽게 착각할 수 있는 부분이 있습니다.
🙄TypeScript는 대부분의 상황에서 Runtime에 영향을 주지 않습니다.
TypeScript로 작성된 코드들은 실행을 위해 JavaScript로 컴파일되는 과정을 거치며
JavaScript로 컴파일이 완료된 경우 TypeScript 코드의 대부분은 없어진 상태가 됩니다.
그렇기 때문에 타입스크립트에서만 존재하는 private, public과 같은 접근제어자 키워드 또한
컴파일 이후의 자바스크립트 코드에는 찾아볼 수 없게 됩니다.
타입스크립트의 컴파일 결과를 실시간으로 확인해볼 수 있는
https://www.typescriptlang.org/play
playground를 타입스크립트 공식문서에서 지원해주고 있습니다.
이를 통해서 실제로 그렇게 동작하는지 확인해보겠습니다.
protected , private, public 키워드는 컴파일이후 사라졌으며
constructor 없이 바로 할당문을 통해 작성한것이
construct의 형태로 변환된 것을 확인할 수 있습니다.
그리고 컴파일된 자바스크립트 코드가 동작하게되는 "런타임"에는
접근제어자들이 존재하지 않으며 접근제어를 강제할 방법 역시 없기때문에
private, protected 키워드는 개발자가 기대하는 동작을 수행할 수 없습니다.
😏 접근 제한자 "#"은 어떨까요?
export class MBTIService {
#mbti = 'runtime private'
}
접근 제한자 #을 이용하여 런타임에도 private으로 동작하는 것이
보장된 형태로 만들어보겠습니다.
이렇게 #을 이용하여 접근제한자로 만드는 것은 타입스크립트 뿐만 아니라
자바스크립트에서도 지원되는 기능입니다.
var _MBTIService_mbti;
export class MBTIService {
constructor() {
_MBTIService_mbti.set(this, 'runtime private');
}
}
_MBTIService_mbti = new WeakMap();
접근 제한자 '#'은 위와 같이 컴파일 과정에서 WeakMap을 이용하여
데이터가 런타임에도 비공개 처리 될 수 있도록 보장합니다.
즉 WeakMap의 키값이 인스턴스로 지정되기 때문에
해당 인스턴스만 접근할 수 있는 private한 식별자를 만들수 있다는 것이 원리로 보입니다.
export class MBTIService {
#mbti: string;
constructor(mbti: string) {
this.#mbti = mbti;
}
}
이번엔 constructor를 이용하여 #mbti를 입력값을 받아 생성하도록 만들어보겠습니다.
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _MBTIService_mbti;
export class MBTIService {
constructor(mbti) {
_MBTIService_mbti.set(this, void 0);
__classPrivateFieldSet(this, _MBTIService_mbti, mbti, "f");
}
}
_MBTIService_mbti = new WeakMap();
코드의 기능만 조금 달라졌을 뿐인데 __classPrivateFieldSet이라는 변수가 생성되었고
컴파일 이후 코드의 길이도 꽤나 길어진것을 확인할 수 있습니다.
이렇듯 # 접근제한자는 런타임에도 private을 지켜주어야하기 때문에
코드의 절대적인 양을 늘리게 됩니다.
이는 프로젝트에 따라 런타임 오버헤드를 불러올 수 있습니다.
😏 접근 제한자 "#"의 주의사항
접근제한자 #은 tsconfig.ts의 target 속성 값에 제한이 존재합니다.
이는 위 사진과 같이 TS18028 Error로 나타나는데요
프라이빗 식별자는 ECMAScript 2015 이상이 대상일 때에만 지정할 수 있습니다.
tsconfig.ts
{
"compilerOptions": {
"target": "ES2015",
}
}
많은 분들이 target 옵션으로 지정하는 es5의 경우에는
접근제한자를 사용시 에러가 발생할 수 있다는 점 유의하시기 바랍니다.
이렇게 # 접근제한자를 사용하는데에 컴파일 옵션 제한이 있는 이유는
# 접근제한자를 이용한 프로퍼티를 컴파일하고 난 결과물에 앞서 알아본 것과 같이
ES6(ES 2015)에 추가된 WeakMap 자료구조를 사용하기 때문으로 보여집니다.
🤩마치며
예전에는 #으로 작성하나 private으로 작성하나 똑같이 private한거 아니야?
그냥 문법만 조금 다른거면 왜 private으로 만들 수 있는 방법을 두개나 주는거야??
라는 의문이 존재했었습니다.
그러나 이 두 키워드는 눈에 보이는 문법 이상의 큰 차이를 가진 키워드이며
따라서 상황에 적절하게 키워드를 골라 사용하는 것이 중요합니다.
더욱 엄격한 규칙과 런타임 안전성의 보장이 코드크기보다 중요한 상황에는
접근 제한자 #을 사용하는 것을 고려해볼 수 있을 것이며
런타임 오버헤드를 최소화하면서도 개발과정에서 실수를 줄이고자 하는 목적이라면
private 키워드를 사용하는 것이 적절할 수 있겠습니다.
번들러, 컴파일러,트랜스파일러들은 마치 마법처럼 프레임워크위에 작성한 내 코드들을
브라우저에서 쉽게 사용할 수 있게 바꿔주는 "마법 지팡이"처럼 느껴지기 쉽습니다.
그렇기 때문에 내가 작성한 코드들이 컴파일, 트랜스파일 되고 난 이후에는
어떤 코드가 되는지를 더더욱 신경써야할 것 같습니다.
😮레퍼런스
https://www.howdy-mj.me/typescript/access-modifiers
https://www.typescriptlang.org/docs/handbook/2/classes.html
https://github.com/XionWCFM/effective-typescript-study/blob/main/giljong/item56_don'tuseprivate.md
effective TypeScript item 56장
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
https://ui.toast.com/posts/ko_20210901
'best' 카테고리의 다른 글
husky와 commitlint로 jira 이슈번호 자동화 시키기 (2) | 2023.11.27 |
---|---|
toss/slash의 use-funnel 훅 내부 구현 탐구하고 직접 구현하기(2) (0) | 2023.11.03 |
toss/slash의 use-funnel 훅 내부 구현 탐구하고 직접 구현하기(1) (1) | 2023.11.03 |
자바스크립트의 호이스팅에 Deep Dive 해보자 (3) | 2023.04.11 |
클로저가 뭐냐?라는 일상적인 질문에 잘 아는 것처럼 행동하는 방법 (6) | 2023.02.23 |