The remaining traps back the reflective Object methods. ownKeys feeds Object.keys() and JSON.stringify(), getOwnPropertyDescriptor and defineProperty handle the descriptors of Descriptors and Freezing, and getPrototypeOf answers instanceof.
const hidePrivate = (obj) => new Proxy(obj, {
ownKeys: (t) => Reflect.ownKeys(t).filter((k) => !String(k).startsWith("_")),
getOwnPropertyDescriptor: (t, k) =>
String(k).startsWith("_") ? undefined : Reflect.getOwnPropertyDescriptor(t, k),
defineProperty(t, k, desc) {
console.log(`define ${k}`);
return Reflect.defineProperty(t, k, desc);
},
getPrototypeOf: () => Array.prototype,
});
const rec = hidePrivate({ id: 7, _secret: "s3" });
rec.total = 10; // [[Set]] ends in [[DefineOwnProperty]]
console.log(Object.keys(rec), JSON.stringify(rec), rec instanceof Array);
console.log(Object.getOwnPropertyDescriptor(rec, "_secret"));define total
[ 'id', 'total' ] {"id":7,"total":10} true
undefinedThe engine enforces invariants: for a non-extensible target, ownKeys must list exactly its keys and getPrototypeOf its real prototype, or TypeError is thrown. Proxies also break internal slots and #private fields: new Proxy(new Map(), {}).size throws because the getter receives the proxy, not the map.