Function call interception
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