When it comes to defining visibility in TypeScript, the public
, protected
, and private
keywords are commonly used. However, it's worth noting that with ES6 JavaScript, you can use the "#" prefix for a class member or method to achieve similar results.
To gain a better understanding of how things work under the hood, I decided to create a toy class in TypeScript and observe how it compiles into JavaScript:
class aClass
{
#jsPrivate: number;
get jsPrivate()
{ return this.#jsPrivate};
private tsPrivate: number;
protected tsProtected: number;
public tsPublic: number;
constructor( a: number, b: number, c: number, d: number)
{
this.#jsPrivate = a;
this.tsPrivate = b;
this.tsProtected = c;
this.tsPublic = d;
}
}
console.log(new aClass(1,2,3,4));
After compiling using tsc --target es6
with TypeScript version 4.3.5, the code transforms into:
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 __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _aClass_jsPrivate;
class aClass {
constructor(a, b, c, d) {
_aClass_jsPrivate.set(this, void 0);
__classPrivateFieldSet(this, _aClass_jsPrivate, a, "f");
this.tsPrivate = b;
this.tsProtected = c;
this.tsPublic = d;
}
get jsPrivate() { return __classPrivateFieldGet(this, _aClass_jsPrivate, "f"); }
;
}
_aClass_jsPrivate = new WeakMap();
console.log(new aClass(1, 2, 3, 4));
While reviewing the compiled code, I noticed that the JavaScript-style private member is now within the global scope. Additionally, all members declared with TypeScript modifiers are now treated as public. Despite TypeScript catching attempts to access private members during compilation, it raises concerns about code security.
Do you have any recommendations on the preferred approach for modifying member visibility? Furthermore, could you elaborate on the reasons behind these discrepancies?