阿里云主机折上折
  • 微信号
Current Site:Index > The trap of a Proxy

The trap of a Proxy

Author:Chuan Chen 阅读数:11835人阅读 分类: JavaScript

ECMAScript 6 introduced the Proxy object, which provides metaprogramming capabilities to JavaScript, allowing developers to intercept and customize fundamental operations on objects. The core of Proxy lies in its traps, which define how the proxy object intercepts underlying operations.

Basic Concepts of Proxy

The Proxy object is used to create a proxy for an object, enabling the interception and customization of fundamental operations. The Proxy constructor takes two parameters: the target object and the handler object. The handler object contains a set of trap methods that define the proxy's behavior.

const target = {};
const handler = {
  get(target, prop, receiver) {
    return Reflect.get(...arguments);
  }
};
const proxy = new Proxy(target, handler);

Common Traps

get Trap

The get trap intercepts property read operations. It is called when accessing a property of the proxy object.

const handler = {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    }
    return `Property ${prop} does not exist`;
  }
};

const proxy = new Proxy({}, handler);
console.log(proxy.name); // "Property name does not exist"

set Trap

The set trap intercepts property assignment operations. It is called when assigning a value to a property of the proxy object.

const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('Age must be an integer');
      }
      if (value < 0) {
        throw new RangeError('Age must be positive');
      }
    }
    target[prop] = value;
    return true;
  }
};

const person = new Proxy({}, validator);
person.age = 30; // Success
person.age = 'young'; // Throws TypeError

apply Trap

The apply trap intercepts function calls. It is triggered when the proxy object is invoked as a function.

const handler = {
  apply(target, thisArg, argumentsList) {
    console.log(`Calling function with args: ${argumentsList}`);
    return target(...argumentsList) * 2;
  }
};

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

const proxy = new Proxy(sum, handler);
console.log(proxy(1, 2)); // Output: Calling function with args: 1,2 followed by 6

construct Trap

The construct trap intercepts the new operator. It is triggered when the proxy object is used as a constructor.

const handler = {
  construct(target, args, newTarget) {
    console.log(`Constructing with args: ${args}`);
    return new target(...args);
  }
};

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

const PersonProxy = new Proxy(Person, handler);
const p = new PersonProxy('Alice'); // Output: Constructing with args: Alice

Other Traps

has Trap

The has trap intercepts the in operator. It is called when checking for property existence.

const handler = {
  has(target, prop) {
    if (prop.startsWith('_')) {
      return false;
    }
    return prop in target;
  }
};

const obj = { _secret: 'foo', public: 'bar' };
const proxy = new Proxy(obj, handler);
console.log('_secret' in proxy); // false
console.log('public' in proxy); // true

deleteProperty Trap

The deleteProperty trap intercepts the delete operator. It is called when deleting a property.

const handler = {
  deleteProperty(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error(`Cannot delete private "${prop}" property`);
    }
    delete target[prop];
    return true;
  }
};

const obj = { _id: 123, name: 'Test' };
const proxy = new Proxy(obj, handler);
delete proxy.name; // Success
delete proxy._id; // Throws error

ownKeys Trap

The ownKeys trap intercepts operations like Object.keys() and Object.getOwnPropertyNames().

const handler = {
  ownKeys(target) {
    return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
  }
};

const obj = { _id: 1, name: 'John', _password: 'secret' };
const proxy = new Proxy(obj, handler);
console.log(Object.keys(proxy)); // ["name"]

Advanced Usage

Revocable Proxy

The Proxy.revocable() method creates a revocable Proxy object. Once the revoke function is called, the proxy becomes unusable.

const { proxy, revoke } = Proxy.revocable({}, {
  get(target, prop) {
    return `Intercepted: ${prop}`;
  }
});

console.log(proxy.foo); // "Intercepted: foo"
revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked

Chained Proxies

Multiple layers of proxies can be created, each handling different interception logic.

const firstHandler = {
  get(target, prop) {
    console.log(`First handler: ${prop}`);
    return Reflect.get(target, prop);
  }
};

const secondHandler = {
  get(target, prop) {
    console.log(`Second handler: ${prop}`);
    return Reflect.get(target, prop);
  }
};

const target = { message: 'Hello' };
const firstProxy = new Proxy(target, firstHandler);
const secondProxy = new Proxy(firstProxy, secondHandler);

console.log(secondProxy.message);
// Output:
// Second handler: message
// First handler: message
// Hello

Practical Applications

Data Validation

Proxy can be used to implement complex data validation logic.

const validator = {
  set(target, prop, value) {
    if (prop === 'email') {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (!emailRegex.test(value)) {
        throw new Error('Invalid email format');
      }
    }
    target[prop] = value;
    return true;
  }
};

const user = new Proxy({}, validator);
user.email = 'test@example.com'; // Success
user.email = 'invalid-email'; // Throws error

Performance Monitoring

Proxy can be used to monitor property access and modifications.

function createMonitoredObject(target) {
  const accesses = new Set();
  const modifications = new Set();

  const handler = {
    get(target, prop) {
      accesses.add(prop);
      console.log(`Property accessed: ${prop}`);
      return Reflect.get(target, prop);
    },
    set(target, prop, value) {
      modifications.add(prop);
      console.log(`Property modified: ${prop}`);
      return Reflect.set(target, prop, value);
    }
  };

  const proxy = new Proxy(target, handler);
  
  return {
    proxy,
    getAccesses: () => [...accesses],
    getModifications: () => [...modifications]
  };
}

const { proxy, getAccesses } = createMonitoredObject({ x: 1, y: 2 });
proxy.x; // Property accessed: x
proxy.y = 3; // Property modified: y
console.log(getAccesses()); // ["x"]

Implementing Negative Array Indices

Proxy can extend JavaScript syntax to implement negative array indices, similar to Python.

function createNegativeArray(array) {
  return new Proxy(array, {
    get(target, prop, receiver) {
      if (typeof prop === 'string' && /^-?\d+$/.test(prop)) {
        const index = parseInt(prop, 10);
        if (index < 0) {
          prop = target.length + index;
        }
      }
      return Reflect.get(target, prop, receiver);
    }
  });
}

const array = createNegativeArray(['a', 'b', 'c', 'd']);
console.log(array[-1]); // "d"
console.log(array[-2]); // "c"

Considerations

Performance

Proxy operations are slower than direct object access. They should be used cautiously in performance-critical paths. For simple use cases, regular getters/setters may be more appropriate.

Non-proxyable Operations

Certain operations cannot be intercepted by Proxy, such as the instanceof operator, typeof operator, etc.

Target Object Immutability

Proxy does not modify the target object itself. All intercepted operations occur on the proxy object. Direct operations on the target object will not trigger any traps.

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.