-
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)
199: Practice Exercise: Building an Observable Object with Proxies
When people first start playing with Proxy, they often fall into the trap of thinking that a Proxy is like a "magic shield" that covers everything inside an object. I see this all the time: a developer wraps a configuration object in a Proxy to detect changes, and then they're baffled when their nested properties—like settings.theme.color—change without triggering a single trap.
The Myth of Automatic Deep Tracking
The misconception is that the set trap is recursive. It isn't. A Proxy only intercepts operations on the immediate target it is wrapping. If you access a nested object, the Proxy simply returns that nested object as a regular, non-proxied object. Once you're "inside" that nested object, you've stepped outside the Proxy's jurisdiction.
const state = {
user: { name: 'Alice', preferences: { theme: 'dark' } }
};
const observedState = new Proxy(state, {
set(target, prop, value) {
console.log(`Property ${prop} changed to ${value}`);
target[prop] = value;
return true;
}
});
observedState.user = { name: 'Bob' }; // Logs: Property user changed...
observedState.user.name = 'Charlie'; // SILENCE. No log.
In the example above, the second assignment doesn't trigger the trap because observedState.user returned the original nested object. You aren't calling set on the Proxy anymore; you're calling set on a plain JavaScript object.
Recursive Proxies: Making the Observation Sticky
To build a truly observable object, we have to be intentional. The trick is to intercept the get trap. When a user requests a property that happens to be another object, we don't just hand it over—we wrap that nested object in a Proxy too, using the same handler. I like to think of this as "infecting" the object tree with observability as you traverse it.
const handler = {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
// If the value is an object, wrap it in a proxy before returning it
if (typeof value === 'object' && value !== null) {
return new Proxy(value, handler);
}
return value;
},
set(target, prop, value) {
console.log(`Change detected: ${prop} is now ${value}`);
return Reflect.set(target, prop, value);
}
};
const appState = new Proxy({
settings: { theme: 'light' }
}, handler);
appState.settings.theme = 'dark'; // Logs: Change detected: theme is now dark
By doing this, we've created a dynamic chain. It doesn't matter how deep your data structure goes; the moment you touch a nested object, it gets proxied on the fly. One small tip: I used Reflect here instead of direct assignment. While target[prop] = value works most of the time, Reflect is the professional standard when working with Proxies because it handles edge cases (like inherited properties) much more predictably.
📋 Practical Task
Exercise: Building a Reactive Store with Change Callbacks
Instead of just logging to the console, let's build a primitive "Store" that allows us to register a callback function that runs whenever any property—at any depth—is modified. This is essentially how the core of frameworks like Vue.js handles reactivity.
Requirements:
- Create a function
createObservableStore(initialState, onChangeCallback). - The function should return a Proxy that implements the recursive "sticky" observation pattern we discussed.
- Whenever a property is changed (at the top level or within a nested object), the
onChangeCallbackmust be executed. - The callback should receive the name of the property that changed and the new value.
Test your implementation with this snippet:
const myStore = createObservableStore(
{ profile: { name: 'Dev', stats: { level: 1 } } },
(prop, val) => console.log(`Store Update -> ${prop}: ${val}`)
);
myStore.profile.name = 'Senior Dev'; // Should trigger callback
myStore.profile.stats.level = 2; // Should trigger callback
There are no comments for now.