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.

AutoHome Frontend
AutoHome Frontend
AutoHome Frontend
Mastering JavaScript Proxies: Rebuilding the on‑change Library Step‑by‑Step

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: 2

Proxy 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

defineProperty

trap 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

Proxy diagram
Proxy diagram
Proxy interaction
Proxy interaction
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ProxyReflectiontutorialon-change
AutoHome Frontend
Written by

AutoHome Frontend

AutoHome Frontend Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.