I'm reading the documentation here, and it seems to imply that even for private fields, it would be possible to access them via a Proxy. See the blurb that starts with "....To fix this....". But the example given, doesn't work. My code is shown below:
The class with the private field:
class Danger {
#areYouSure = "magic";
grave(){
console.log("Yes, grave danger");
return true;
}
}
module.exports = Danger;
Trying to use a proxy to access it:
const Danger = require('./testing-private');
const handler = {
get(target, property, receiver){
return target[property];
}
};
const dangerProxy = new Proxy(new Danger(), handler);
console.log("--------------------------------------", dangerProxy.areYouSure);
This prints out:
-------------------------------------- undefined
If I replace "dangerProxy.areYouSure" with "dangerProxy.#areYouSure". I get
/home/zaphod/code/math/js-experiments/src/danger-proxy.js:29
console.log("--------------------------------------", dangerProxy.#areYouSure);
^
SyntaxError: Private field '#areYouSure' must be declared in an enclosing class
at Object.compileFunction (node:vm:352:18)
at wrapSafe (node:internal/modules/cjs/loader:1032:15)
at Module._compile (node:internal/modules/cjs/loader:1067:27)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1155:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47
Is there a way to get this working or are my expectations incorrect?
The section of the documentation is really about this
within a getter property (though it's not very clear about that), not about private properties per se. It's just that the value of this
within the getter matters more when the getter is accessing a private field.
Suppose we do the same thing using a pseudo-private property (private only by convention):
class Secret {
_secret;
constructor(secret) {
this._secret = secret;
}
get secret() {
return this._secret.replace(/\d+/, "[REDACTED]");
}
}
const aSecret = new Secret("123456");
console.log(aSecret.secret); // [REDACTED]
// Looks like a no-op forwarding...
const proxy = new Proxy(aSecret, {});
console.log(proxy.secret); // [REDACTED]
As you can see, that works. But why does it work when a true private field didn't? Because of the value of this
during the getter call. Let's see what this
is:
class Secret {
_secret;
constructor(secret) {
this._secret = secret;
}
get secret() {
console.log(`this === aSecret? ${this === aSecret}`);
console.log(`this === proxy? ${this === proxy}`);
return this._secret.replace(/\d+/, "[REDACTED]");
}
}
const aSecret = new Secret("123456");
const proxy = new Proxy(aSecret, {}); // (Had to move this, but it doesn't change anything important)
console.log(aSecret.secret); // [REDACTED]
console.log(proxy.secret); // [REDACTED]
When we use aSecret
directly, this
is (perhaps obviously) aSecret
:
this === aSecret? true
this === proxy? false
[REDACTED]
...but when we go through the proxy, this
is the proxy:
this === aSecret? false
this === proxy? true
[REDACTED]
That's because the default implementation of the get
trap effectively looks like this:
get(target, propName, receiver) {
return Reflect.get(target, propName, receiver);
}
In proxy.secret
, target
will be aSecret
, propName
will be "secret"
, and receiver
will be proxy
. Reflect.get
will call the getter function using receiver
(the proxy) as this
, because that's what the third argument is for, the value of this
during the operation. this
being proxy
instead of aSecret
during the operation doesn't matter for our pseudo-private example, because when the getter method tries to access this._secret
, it accesses it on the proxy, which then gets it from the target. But that won't work with a truly private field, because the proxy object doesn't have the private field.
The solution they provide is to change the get
trap from the default above to:
get(target, propName, receiver) {
return target[propName];
// The above is like `return Reflect.get(target, propName, target);`
// Note `target` instead of `receiver` for third argument −^
}
That way, this
in the getter method is target
(aSecret
), not receiver
(proxy
), and so accessing the private field works.
Can a Proxy really provide access to private fields of a class?
A Proxy can't expose the private fields of an instance that doesn't already provide a non-private way of accessing them, no. But it can provide access to getters and methods that have access to those fields. But you have to provide the right this
.