JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
244: Advanced Proxy Traps
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
TypeErrorif someone tries togetorseta__stealth_property. - Ensures
Object.keys(),Object.getOwnPropertyNames(), andfor...inloops cannot see the__stealth_properties. - Ensures that
Object.getOwnPropertyDescriptor()returnsundefinedfor stealth properties to avoid Proxy invariant TypeErrors.
- Throws a
- Test your implementation by calling
JSON.stringify(proxy)and verifying that only the non-stealth properties are serialized.
There are no comments for now.