Skip to Content
Course content

244: Advanced Proxy Traps

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By this point, you're likely comfortable with the basic get and set traps. They feel like a superpower—intercepting access to an object and changing the result on the fly. But there's a trap in using traps: if you only implement the basics, your Proxy is a leaky abstraction. I've seen a lot of engineers build "protected" objects or "virtual" properties only to find that their logic completely evaporates the moment someone calls Object.keys() or JSON.stringify().

The illusion of the simple getter

Let's say we want to create a "private" property convention where any key starting with an underscore is strictly inaccessible from the outside. The naive approach is to just block it in the get trap. It looks clean, and for 90% of your code, it works perfectly:

const target = { _secret: '12345', publicData: 'Hello' };
const handler = {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('Access Denied');
    }
    return target[prop];
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.publicData); // 'Hello'
console.log(proxy._secret);    // Throws Error: Access Denied

On the surface, we've succeeded. We've guarded the gate. But as a software engineer, you know that "surface" is where bugs love to hide. If you're handing this proxy to a library or another teammate, they aren't just going to access properties directly. They're going to iterate.

When the engine looks behind the curtain

Here is where the naive implementation falls apart. Try calling Object.keys(proxy) or using a for...in loop. You'll notice that _secret is still right there, staring back at you. This is because Object.keys doesn't trigger the get trap; it triggers ownKeys. And if you try to use JSON.stringify(proxy), the internal logic of the JS engine performs a series of checks that bypass your simple getter entirely.

The real danger, though, is the Proxy Invariant. JavaScript has strict rules about consistency. If you override ownKeys to hide a property, but you don't override getOwnPropertyDescriptor, the engine might see that a key exists (via ownKeys) but then find that it "doesn't exist" when it asks for the descriptor. In many environments, this doesn't just return undefined—it throws a TypeError because the Proxy is lying inconsistently to the engine.

Syncing the traps for a believable lie

To make a Proxy truly robust, you have to treat it as a cohesive system. If you hide a property in get, you must also hide it in ownKeys and getOwnPropertyDescriptor. I think of it as "closing the loop."

Here is how we handle this properly. We don't just block the value; we remove the property from the object's perceived existence:

const handler = {
  get(target, prop) {
    if (prop.startsWith('_')) throw new Error('Access Denied');
    return target[prop];
  },
  ownKeys(target) {
    // We filter the keys so that 'ownKeys' doesn't even mention the secret
    return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
  },
  getOwnPropertyDescriptor(target, prop) {
    // If we lied in ownKeys, we must lie here too.
    if (prop.startsWith('_')) return undefined;
    return Reflect.getOwnPropertyDescriptor(target, prop);
  }
};

Notice the use of Reflect. When you're doing advanced proxy work, Reflect is your best friend. It ensures that the default behavior of the object is preserved for all the properties you aren't messing with. By syncing these three traps, Object.keys(proxy) now returns only ['publicData'], and JSON.stringify will ignore the secret properties entirely. You've moved from a fragile "guard" to a legitimate architectural boundary.




📋 Practical Task

Exercise: Implementing a Stealth-Property Shield

Your task is to build a Proxy that implements a "Stealth Shield." The goal is to create an object where properties marked as "stealth" are completely invisible to the outside world, regardless of how the object is inspected.

Requirements:

  • Create a target object with at least three properties: one normal property (e.g., name: 'Project X') and two stealth properties (starting with the prefix __stealth_).
  • Implement a Proxy handler that:
    • Throws a TypeError if someone tries to get or set a __stealth_ property.
    • Ensures Object.keys(), Object.getOwnPropertyNames(), and for...in loops cannot see the __stealth_ properties.
    • Ensures that Object.getOwnPropertyDescriptor() returns undefined for stealth properties to avoid Proxy invariant TypeErrors.
  • Test your implementation by calling JSON.stringify(proxy) and verifying that only the non-stealth properties are serialized.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.