JS: Proxy

By Xah Lee. Date: . Last updated: .
xtodo

2016-11-08 xtodo this page is work in progress

(new in ECMAScript 2015)

proxy is like a wrapper to js objects. It lets you change the behavior of things that act on a object.

Proxy object is used to

create new proxy like this:

var = new Proxy(target , handler)
// object
var tt = {x:3};

// we want to change the behavior of accessing properties of tt


// the “handler”
var hh = {
   get: function(tt, propertyName, receiver) {
       return 99;
   }
};

// create proxy object.
var pr = new Proxy ( tt, hh );
// this will target tt, with handler hh

// access a property
console.log (pr.x); // prints 99

console.log (pr.y); // prints 99

ECMAScript 2015 §Reflection#sec-proxy-constructor

xtodo

(AI generated)

JavaScript Proxy Tutorial: Complete Guide

JavaScript Proxy is one of the most powerful and underused features introduced in ES6 (2015). It lets you create a proxy object that intercepts and customizes fundamental operations on a target object (getting properties, setting them, calling functions, etc.).

1. What is a Proxy?

A Proxy wraps a target object and uses traps (handler functions) to control behavior when operations are performed on it.

const target = { name: "Alice" };
const handler = {
  get(target, prop) {
    console.log(`Getting ${prop}`);
    return target[prop];
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name);
// Output: "Getting name" followed by "Alice"

The proxy looks and behaves like the target, but you can intercept every interaction.

2. Basic Syntax

const proxy = new Proxy(target, handler);

3. Most Useful Traps

Here are the most commonly used traps with examples:

get(target, property, receiver)

Intercepts property access (obj.prop or obj['prop']).

const person = { age: 30 };

const loggedPerson = new Proxy(person, {
  get(target, prop) {
    if (prop in target) {
      console.log(`Accessed ${prop}`);
      return target[prop];
    }
    return `No such property: ${prop}`;
  }
});

console.log(loggedPerson.age);     // Accessed age → 30
console.log(loggedPerson.name);    // Accessed name → "No such property: name"

set(target, property, value, receiver)

Intercepts property assignment.

const user = {};

const validatedUser = new Proxy(user, {
  set(target, prop, value) {
    if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
      throw new Error('Age must be a positive number');
    }
    target[prop] = value;
    console.log(`Set ${prop} = ${value}`);
    return true; // must return true on success
  }
});

validatedUser.name = "Bob";   // Set name = Bob
validatedUser.age = 25;       // Set age = 25
// validatedUser.age = -5;    // throws Error

has(target, property)

Intercepts the in operator.

const secret = { _password: "12345" };

const safeSecret = new Proxy(secret, {
  has(target, prop) {
    if (prop.startsWith('_')) return false; // hide private props
    return prop in target;
  }
});

console.log('_password' in safeSecret); // false
console.log('toString' in safeSecret);  // true (inherited)

deleteProperty(target, property)

const data = { temp: "secret" };

const protectedData = new Proxy(data, {
  deleteProperty(target, prop) {
    if (prop === 'temp') {
      console.warn("Cannot delete protected property");
      return false;
    }
    delete target[prop];
    return true;
  }
});

apply(target, thisArg, argumentsList) – For functions

function add(a, b) { return a + b; }

const loggedAdd = new Proxy(add, {
  apply(target, thisArg, args) {
    console.log(`Called with ${args}`);
    return target.apply(thisArg, args);
  }
});

console.log(loggedAdd(3, 4)); // Called with 3,4 → 7

construct(target, argumentsList, newTarget) – For new

class User {
  constructor(name) { this.name = name; }
}

const LoggedUser = new Proxy(User, {
  construct(target, args) {
    console.log(`Creating new ${target.name} with`, args);
    return new target(...args);
  }
});

const u = new LoggedUser("Alice");

4. Other Notable Traps

5. Practical Examples

1. Negative Array Indexing (like Python)

function createNegativeArray(arr) {
  return new Proxy(arr, {
    get(target, prop) {
      const index = Number(prop);
      if (index < 0) {
        return target[target.length + index];
      }
      return target[prop];
    }
  });
}

const fruits = createNegativeArray(["apple", "banana", "cherry"]);
console.log(fruits[-1]); // "cherry"
console.log(fruits[-2]); // "banana"

2. Observable / Reactive (simple version)

function makeObservable(target) {
  const observers = new Set();

  return new Proxy(target, {
    set(obj, prop, value) {
      const result = Reflect.set(obj, prop, value);
      observers.forEach(observer => observer({ prop, value }));
      return result;
    },
    subscribe(callback) {
      observers.add(callback);
      return () => observers.delete(callback);
    }
  });
}

const state = makeObservable({ count: 0 });
state.subscribe(({ prop, value }) => console.log(`${prop} changed to ${value}`));

state.count = 42; // triggers callback

3. Default Values / Fallbacks

const config = new Proxy({}, {
  get(target, prop) {
    return prop in target ? target[prop] : `default_${prop}`;
  }
});

console.log(config.theme); // "default_theme"
config.theme = "dark";
console.log(config.theme); // "dark"

4. Memoization / Caching

function memoize(fn) {
  const cache = new Map();
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      if (!cache.has(key)) {
        cache.set(key, target.apply(thisArg, args));
      }
      return cache.get(key);
    }
  });
}

const expensive = memoize((x) => {
  console.log("Computing...");
  return x * x;
});

console.log(expensive(5)); // Computing... → 25
console.log(expensive(5)); // 25 (cached)

6. Best Practices & Gotchas

7. Real-World Use Cases

8. Browser/Node Support

Proxy is supported in all modern environments (Node.js since v6, all evergreen browsers).


Exercise for you:

Try implementing a readonly proxy that throws on any set or deleteProperty attempt.

Would you like me to expand any section (e.g., more advanced traps, TypeScript examples, comparison with Object.defineProperty, or a full reactive system example)?