包装函数:"apply"
我们也可以将代理(proxy)包装在函数周围。
apply(target, thisArg, args) 捕捉器能使代理以函数的方式被调用:
target 是目标对象(在 JavaScript 中,函数就是一个对象),
thisArg 是 this 的值。
args 是参数列表。
例如,让我们回忆一下我们在 装饰者模式和转发,call/apply 一章中所讲的 delay(f, ms) 装饰器。
在该章中,我们没有用 proxy 来实现它。调用 delay(f, ms) 会返回一个函数,该函数会在 ms 毫秒后把所有调用转发给 f。
这是以前的基于函数的实现:
function delay(f, ms) {
// 返回一个包装器(wrapper),该包装器将在时间到了的时候将调用转发给函数 f
return function() { // (*)
setTimeout(() => f.apply(this, arguments), ms);
};
}
function sayHi(user) {
alert(`Hello, ${user}!`);
}
// 在进行这个包装后,sayHi 函数会被延迟 3 秒后被调用
sayHi = delay(sayHi, 3000);
sayHi("John"); // Hello, John! (after 3 seconds)
正如我们所看到的那样,大多数情况下它都是可行的。包装函数 (*) 在到达延迟的时间后后执行调用。
但是包装函数不会转发属性读取/写入操作或者任何其他操作。进行包装后,就失去了对原始函数属性的访问,例如 name,length 和其他属性:
function delay(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms);
};
}
function sayHi(user) {
alert(`Hello, ${user}!`);
}
alert(sayHi.length); // 1(函数的 length 是函数声明中的参数个数)
sayHi = delay(sayHi, 3000);
alert(sayHi.length); // 0(在包装器声明中,参数个数为 0)
Proxy 的功能要强大得多,因为它可以将所有东西转发到目标对象。
让我们使用 Proxy 来替换掉包装函数:
function delay(f, ms) {
return new Proxy(f, {
apply(target, thisArg, args) {
setTimeout(() => target.apply(thisArg, args), ms);
}
});
}
function sayHi(user) {
alert(`Hello, ${user}!`);
}
sayHi = delay(sayHi, 3000);
alert(sayHi.length); // 1 (*) proxy 将“获取 length”的操作转发给目标对象
sayHi("John"); // Hello, John!(3 秒后)
结果是相同的,但现在不仅仅调用,而且代理上的所有操作都能被转发到原始函数。所以在 (*) 行包装后的 sayHi.length 会返回正确的结果。
我们得到了一个“更丰富”的包装器。
还存在其他捕捉器:完整列表在本文的开头。它们的使用模式与上述类似。