阿里云主机折上折
  • 微信号
Current Site:Index > Function call interception

Function call interception

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

ECMAScript 6 introduced the Proxy and Reflect objects, providing native support for intercepting function calls. Using Proxy, you can capture the invocation behavior of functions, enabling dynamic interception, modification, or extension of functionality, which is highly useful in metaprogramming and AOP (Aspect-Oriented Programming).

Proxy Basics and Function Interception

Proxy is a new constructor in ES6 used to create proxies for objects. By defining traps in the handler object, you can intercept operations on the target object. For function calls, the key trap is apply:

const target = function() { console.log('Original function executed') };
const handler = {
  apply: function(target, thisArg, argumentsList) {
    console.log('Function call intercepted');
    return target.apply(thisArg, argumentsList);
  }
};
const proxy = new Proxy(target, handler);
proxy(); // Output: "Function call intercepted" → "Original function executed"

Interception Scenarios and Applications

Parameter Validation

Validate parameter types before function invocation:

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

const validator = {
  apply(target, thisArg, args) {
    if (args.some(arg => typeof arg !== 'number')) {
      throw new TypeError('Arguments must be numbers');
    }
    return target(...args);
  }
};

const safeSum = new Proxy(sum, validator);
safeSum(2, '3'); // Throws TypeError

Performance Monitoring

Measure function execution time:

const perfProxy = (fn) => new Proxy(fn, {
  apply(target, _, args) {
    const start = performance.now();
    const result = target(...args);
    console.log(`Execution time: ${performance.now() - start}ms`);
    return result;
  }
});

const heavyCalc = perfProxy(() => {
  let sum = 0;
  for(let i=0; i<1e6; i++) sum += i;
  return sum;
});

heavyCalc(); // Logs execution time

Cache Optimization

Implement memoization:

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

const factorial = memoize(n => n <= 1 ? 1 : n * factorial(n - 1));
factorial(5); // First computation
factorial(5); // Returns cached result

Advanced Interception Patterns

Chained Method Interception

Handle chained method calls:

class Api {
  getUsers() { return this }
  getPosts() { return this }
}

const logger = {
  apply(target, _, args) {
    console.log(`Method called: ${target.name}`);
    return target.apply(this, args);
  }
};

const proxiedApi = new Proxy(new Api(), {
  get(target, prop) {
    return prop in target ? 
      new Proxy(target[prop], logger) :
      target[prop];
  }
});

proxiedApi.getUsers().getPosts();
// Output: "Method called: getUsers" → "Method called: getPosts"

Lazy Function Initialization

Defer function initialization until first invocation:

const lazyInit = (factory) => {
  let initialized;
  return new Proxy(function() {}, {
    apply(_, thisArg, args) {
      if (!initialized) initialized = factory();
      return initialized.apply(thisArg, args);
    }
  });
};

const heavyObj = lazyInit(() => {
  console.log('Initialization executed');
  return {
    method() { return 'data' }
  };
});

heavyObj().method(); // Outputs "Initialization executed" on first call

Collaboration with Reflect

The Reflect object provides methods corresponding to Proxy traps, often used together:

const proxy = new Proxy(function(a, b) { return a + b }, {
  apply(target, thisArg, args) {
    console.log(`Call arguments: ${args.join(', ')}`);
    return Reflect.apply(target, thisArg, args);
  }
});

proxy(2, 3); // Output: "Call arguments: 2, 3" → returns 5

Edge Case Handling

Constructor Interception

Handle new operator calls:

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

const proxy = new Proxy(User, {
  construct(target, args) {
    console.log(`Instantiation arguments: ${args}`);
    return Reflect.construct(target, args);
  }
});

new proxy('John'); // Output: "Instantiation arguments: John"

Arrow Function Limitations

Arrow functions lack their own this binding—note this when intercepting:

const obj = {
  fn: () => console.log(this)
};

new Proxy(obj.fn, {
  apply(target, thisArg) {
    // Arrow function's `this` won't change
    return target.apply(thisArg); 
  }
}).call({}); // Still logs global object

Practical Use Cases

API Request Interception

Uniformly handle API errors:

const apiHandler = {
  async apply(target, _, args) {
    try {
      const res = await target(...args);
      return res.data;
    } catch (err) {
      console.error(`API request failed: ${err.message}`);
      throw new Error('Network request error');
    }
  }
};

const fetchData = new Proxy(async (url) => {
  const res = await fetch(url);
  return res.json();
}, apiHandler);

fetchData('invalid_url').catch(console.error);

Access Control

Intercept method calls based on permissions:

const user = { role: 'guest' };

const protectedActions = {
  deletePost: () => console.log('Post deleted')
};

const guard = {
  apply(target, _, args) {
    if (user.role !== 'admin') {
      throw new Error('Insufficient permissions');
    }
    return target(...args);
  }
};

const safeDelete = new Proxy(protectedActions.deletePost, guard);
safeDelete(); // Throws error when role is 'guest'

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

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.