TL;DR: Use private identifiers to define your private fields. This way, their accessibility will be enforced at runtime as well.
As it has already been pointed out, accessibility is only enforced statically (at compile–time) by the TypeScript transpiler.
Therefore, all properties, either public or private, are emitted as normal JavaScript properties. There's no magic here, and you cannot do anything to hide them except using a Proxy
to trap the ownKeys()
method or using Object.defineProperties
rather than declaring them the TypeScript way. For the latter idea, I've come up with an example:
class Foo {
constructor() {
Object.defineProperties(this, {
bar: {
enumerable: false,
value: "Hello world"
}
})
console.log((this as any).bar)
}
}
The above example can be tested in the TypeScript Playground.
However, I think it's an anti–pattern to do such a thing, since it breaks all the TypeScript safety, which is the only reason why opt for it rather than just writing out JavaScript code.
Therefore, the only solution we're left with is using private identifiers. This is a TypeScript feature such that any field whose name starts with #
is enforced as private not only at compile–time but even at runtime.
class Foo {
#bar = "Hello world"
constructor() {
console.log(this.#bar)
}
}
console.log(Object.keys(new Foo()))
The above example can be tested in the TypeScript Playground.
How does that work? Well, you may just take a look at the transpiled JavaScript code and you'll suddenly notice a WeakMap
. Indeed, a reference to a new WeakMap
is defined for every field in a class with a private identifier, in the same lexical scope where their declaring class is defined. Wherever the private field is accessed, it's done by calling a getter or setter function that uses a given map (passed when referencing that field) to get or set the value at a given key, which is the instance of the class to access the field on. A runtime check is also made in both the getter and setter functions such that a TypeError
is thrown when calling with a non–registered receiver to prevent private fields from being accessed with an instance of a different type or no instance at all.