Mastering JavaScript Proxies: Rebuilding the on‑change Library Step‑by‑Step
This tutorial explains how JavaScript ES6 Proxies work, demonstrates creating a Proxy with get, set, and deleteProperty traps, and walks through rebuilding the on‑change library to monitor object and array mutations, including handling deep nesting and common pitfalls.
Introduction
JavaScript Proxy (ES6) allows interception of fundamental operations on objects – property access, assignment, deletion, etc. The article demonstrates how to recreate the functionality of the open‑source on-change library (GitHub: https://github.com/sindresorhus/on-change) using proxies.
Basic usage of on‑change
Original library watches an object/array and invokes a callback on any mutation.
const onChange = require('on-change');
const object = { foo: false, a: { b: [{ c: false }] } };
let i = 0;
const logger = () => console.log('Object changed:', ++i);
const watchedObject = onChange(object, logger);
watchedObject.foo = true; // → Object changed: 1
watchedObject.a.b[0].c = true; // → Object changed: 2Proxy fundamentals
A Proxy is created with new Proxy(target, handler). The target is the original object; the handler is an object whose methods (called traps ) intercept operations such as get, set, deleteProperty.
Simple proxy example
const originalObject = { firstName: 'Arfat', lastName: 'Salman' };
const handler = {
get(target, property, receiver) {
console.log(`GET ${property}`);
return target[property];
}
};
const proxiedObject = new Proxy(originalObject, handler);
console.log(proxiedObject.firstName); // logs "GET firstName" → "Arfat"Re‑implementing onChange with a proxy
The recreated onChange function receives a target object and a callback, builds a handler that calls the callback in each trap, and returns a proxy.
const onChange = (objToWatch, onChangeFn) => {
const handler = {
get(target, property, receiver) {
onChangeFn();
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
onChangeFn();
return Reflect.set(target, property, value, receiver);
},
deleteProperty(target, property) {
onChangeFn();
return Reflect.deleteProperty(target, property);
}
};
return new Proxy(objToWatch, handler);
};Example:
const logger = () => console.log('I was called');
const obj = { a: 'a' };
const proxy = onChange(obj, logger);
proxy.a; // logger called (get)
proxy.b = 'b'; // logger called (set)
delete proxy.a; // logger called (delete)Supporting deep nesting
To observe changes inside nested objects or arrays, the get trap returns a new proxy whenever the retrieved value is an object.
get(target, property, receiver) {
onChangeFn();
const value = Reflect.get(target, property, receiver);
if (value !== null && typeof value === 'object') {
return new Proxy(value, handler); // recursive proxy
}
return value;
}With this enhancement, a mutation such as watchedObject.a.b[0].c = true triggers the callback.
Additional traps for full on-change behavior
definePropertytrap can be used instead of set to match the original library’s implementation.
When sorting a proxied array, each internal element assignment invokes the set trap, causing the callback to fire many times (e.g., 12 times for a 7‑element array).
Known limitation – invariant violation
If a property is non‑configurable and non‑writable, a get trap that returns a different value violates Proxy invariants (see MDN “get invariants”). The original on-change library has an open issue about this behavior.
References
GitHub repository: https://github.com/sindresorhus/on-change
MDN Proxy documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
MDN Reflect API: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
Exploring JS Proxies: http://exploringjs.com/es6/ch_proxies.html
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
