拦截函数调用或改变函数行为的方法有哪些?
What are ways of intercepting function calls or changing a function's behavior?
我想在每次调用对象中的某些函数并执行完毕时执行一些代码。
对象:
{
doA() {
// Does A
},
doB() {
// Does B
}
}
是否可以扩展它,改变那些功能,让它们做它们做的事,然后再做其他事情?好像这是一个监听那些函数完成的事件?
{
doA() {
// Does A
// Do something else at end
},
doB() {
// Does B
// Do something else at end
}
}
也许这可以使用代理 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
尝试使用代理:
const ob = {
doA() {
console.log('a');
},
doB() {
console.log('b');
}
};
const ob2 = new Proxy(ob, {
apply: function (target, key, value) {
console.log('c');
},
});
ob2.doA();
下面是一个将函数包装在对象中的直接方法示例。
这对调试很有用,但您不应该在生产环境中这样做,因为您(或稍后查看您的代码的任何人)将很难弄清楚为什么您的方法正在做一些不在原代码。
var myObj = {
helloWorld(){
console.log('Hello, world!');
}
}
// get a refernce to the original function
var f = myObj.helloWorld;
// overwrite the original function
myObj.helloWorld = function(...args){
// call the original function first
f.call(this, ...args);
// Then do other stuff afterwards
console.log('Goodbye, cruel world..');
};
myObj.helloWorld();
如果它是关于具有相同的事件侦听器机制,您可以创建一个包含存储函数并在需要时执行它们的对象
const emitter = {
events: {},
addListener(event, listener) {
this.events[event] = this.events[event] || [];
this.events[event].push(listener);
},
emit(event, data) {
if(this.events[event]) {
this.events[event].forEach(listener => listener(data));
}
}
}
//instead of the config object you could just type the string for the event
const config = {
doA: 'doA',
doB: 'doB'
}
//store first function for doA
emitter.addListener(config.doA, (data) => {
console.log('hardler for Function ' + data + ' executed!');
});
//store second function for doA
emitter.addListener(config.doA, () => {
console.log('Another hardler for Function A executed!');
});
//store first function for doB
emitter.addListener(config.doB, (data) => {
console.log('hardler for Function ' + data + ' executed!');
});
let obj = {
doA() {
let char = 'A';
console.log('doA executed!');
//You can pass data to the listener
emitter.emit(config.doA, char);
},
doB() {
let char = 'B';
console.log('doB executed!');
emitter.emit(config.doB, char);
}
}
obj.doA();
obj.doB();
//Output:
//doA executed!
//hardler for Function A executed!
//Another hardler for Function A executed!
//doB executed!
//hardler for Function B executed!
使用 Proxy 我们可以定位所有包含函数的 get
,然后我们可以检查正在获取的是否是一个函数,如果是我们创建和 return 我们自己的函数来包装目标函数并调用它。
从我们的包装函数中调用对象函数后,我们可以执行任何我们想要的,然后 return 对象函数的 return 值。
const ob = {
doA(arg1, arg2) {
console.log(arg1, arg2);
return 1;
},
doB() {
console.log('b');
}
};
const ob2 = new Proxy(ob, {
get: function(oTarget, sKey) {
if (typeof oTarget[sKey] !== 'function') return oTarget[sKey];
return function(...args) {
const ret = oTarget[sKey].apply(oTarget, args);
console.log("c");
return ret;
}
}
});
console.log(ob2.doA('aaa', 'bbb'));
如果有改进或其他选项,请添加评论!
对于 JavaScript 应用程序,有时需要 拦截 and/or 修改 控制流程 由于其他原因不拥有或拥有的功能, 不允许触摸。
对于这种情况,除了通过包装其原始实现来保留和更改此类逻辑之外,别无他法。这种能力并不是 JavaScript 独有的。通过 Reflection[= 启用 元编程 的编程语言历史悠久56=] 和 自修改.
原因之一 could/should 为人们可以想到的所有可能的修饰符用例提供防弹但方便的抽象。
由于 JavaScript 已经实现了 Function.prototype.bind
,它已经带有某种微小的修改功能,我个人不介意,如果有一天 JavaScript 正式推出定制和标准化的便捷 方法修饰符 工具集...... Function.prototype[before|around|after|afterThrowing|afterFinally]
.
// begin :: closed code
const obj = {
valueOf() {
return { foo: this.foo, bar: this.bar };
},
toString(link = '-') {
return [this.foo, this.bar].join(link);
},
foo: 'Foo',
bar: 'Bar',
baz: 'BAAAZZ'
};
// end :: closed code
console.log(
'obj.valueOf() ...',
obj.valueOf()
);
console.log(
'obj.toString() ...',
obj.toString()
);
enableMethodModifierPrototypes();
function concatBazAdditionally(proceed, handler, [ link ]) {
const result = proceed.call(this, link);
return `${ result }${ link }${ this.baz }`;
}
obj.toString = obj.toString.around(concatBazAdditionally, obj);
// obj.toString = aroundModifier(obj.toString, concatBazAdditionally, obj)
console.log(
'`around` modified ... obj.toString("--") ...',
obj.toString("--")
);
function logWithResult(result, args) {
console.log({ modifyerLog: { result, args, target: this.valueOf() } });
}
obj.toString = obj.toString.after(logWithResult, obj);
// obj.toString = afterModifier(obj.toString, logWithResult, obj)
console.log(
'`around` and `after` modified ... obj.toString("##") ...',
obj.toString("##")
);
function logAheadOfInvocation(args) {
console.log({ stats: { args, target: this } });
}
obj.valueOf = obj.valueOf.before(logAheadOfInvocation, obj);
// obj.valueOf = beforeModifier(obj.valueOf, logAheadOfInvocation, obj)
console.log(
'`before` modified ... obj.valueOf() ...',
obj.valueOf()
);
restoreDefaultFunctionPrototype();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
function isFunction(value) {
return (
typeof value === 'function' &&
typeof value.call === 'function' &&
typeof value.apply === 'function'
);
}
function getSanitizedTarget(value) {
return value ?? null;
}
function around(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function aroundType(...args) {
const context = getSanitizedTarget(this) ?? target;
return handler.call(context, proceed, handler, args);
}
) || proceed;
}
around.toString = () => 'around() { [native code] }';
function before(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function beforeType(...args) {
const context = getSanitizedTarget(this) ?? target;
handler.call(context, [...args]);
return proceed.apply(context, args);
}
) || proceed;
}
before.toString = () => 'before() { [native code] }';
function after(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function afterReturningType(...args) {
const context = getSanitizedTarget(this) ?? target;
const result = proceed.apply(context, args);
handler.call(context, result, args);
return result;
}
) || proceed;
}
after.toString = () => 'after() { [native code] }';
function aroundModifier(proceed, handler, target) {
return around.call(proceed, handler, target);
}
function beforeModifier(proceed, handler, target) {
return before.call(proceed, handler, target);
}
function afterModifier(proceed, handler, target) {
return after.call(proceed, handler, target);
}
const { prototype: fctPrototype } = Function;
const methodIndex = {
around,
before,
after/*Returning*/,
// afterThrowing,
// afterFinally,
};
const methodNameList = Reflect.ownKeys(methodIndex);
function restoreDefaultFunctionPrototype() {
methodNameList.forEach(methodName =>
Reflect.deleteProperty(fctPrototype, methodName),
);
}
function enableMethodModifierPrototypes() {
methodNameList.forEach(methodName =>
Reflect.defineProperty(fctPrototype, methodName, {
configurable: true,
writable: true,
value: methodIndex[methodName],
}),
);
}
</script>
<!--
<script src="https://closure-compiler.appspot.com/code/jscd16735554a0120b563ae21e9375a849d/default.js"></script>
<script>
const {
disablePrototypes: restoreDefaultFunctionPrototype,
enablePrototypes: enableMethodModifierPrototypes,
beforeModifier,
aroundModifier,
afterModifier,
} = modifiers;
</script>
//-->
下一个提供的示例代码使用上述测试对象及其测试用例,但 implements/provides 是基于代理的解决方案。从测试用例需要如何调整,可以看出直接方法修改,基于 method-modifiers 的干净实现,允许更灵活地处理不同的用例,而基于代理的方法仅限于每个拦截的方法调用一个处理函数...
// begin :: closed code
const obj = {
valueOf() {
return { foo: this.foo, bar: this.bar };
},
toString(link = '-') {
return [this.foo, this.bar].join(link);
},
sayHi() {
console.log('Hi');
},
foo: 'Foo',
bar: 'Bar',
baz: 'BAAAZZ'
};
// end :: closed code
console.log(
'non proxy call ... obj.valueOf() ...',
obj.valueOf()
);
console.log(
'non proxy call ... obj.toString() ...',
obj.toString()
);
function toStringInterceptor(...args) {
const { proceed, target } = this;
const [ link ] = args;
// retrieve the original return value.
let result = proceed.call(target, link);
// modify the return value while
// intercepting the original method call.
result = `${ result }${ link }${ target.baz }`;
// log before ...
console.log({ toStringInterceptorLog: { result, args, target: target.valueOf() } });
// ... returning the
// modified value.
return result;
}
function valueOfInterceptor(...args) {
const { proceed, target } = this;
// log before returning ...
console.log({ valueOfInterceptorLog: { proceed, args, target } });
// ... and save/keep the
// original return value.
return proceed.call(target);
}
function handleTrappedGet(target, key) {
const interceptors = {
toString: toStringInterceptor,
valueOf: valueOfInterceptor,
}
const value = target[key];
return (typeof value === 'function') && (
interceptors[key]
? interceptors[key].bind({ proceed: value, target })
: value.bind(target)
) || value;
}
const objProxy = new Proxy(obj, { get: handleTrappedGet });
console.log('\n+++ proxy `get` handling +++\n\n');
const { foo, bar, baz } = objProxy;
console.log(
'non method `get` handling ...',
{ foo, bar, baz }
);
console.log('\nproxy call ... objProxy.sayHi() ... but not intercepted ...');
objProxy.sayHi();
console.log('\nintercepted proxy calls ...');
console.log(
'objProxy.toString("--") ...',
objProxy.toString("--")
);
console.log(
'objProxy.valueOf() ...',
objProxy.valueOf()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
您当然可以使用代理来做到这一点。但是您也可以编写自己的通用函数装饰器来执行此操作。
基本的装饰器可能是这样工作的:
const wrap = (wrapper) => (fn) => (...args) => {
(wrapper .before || (() => {})) (...args)
const res = fn (...args)
const newRes = (wrapper .after || (() => {})) (res, ...args)
return newRes === undefined ? res : newRes
}
const plus = (a, b) => a + b
const plusPlus = wrap ({
before: (...args) => console .log (`Arguments: ${JSON.stringify(args)}`),
after: (res, ...args) => console .log (`Results: ${JSON.stringify(res)}`)
}) (plus)
console .log (plusPlus (5, 7))
我们在主体之前(使用相同的参数)和之后(使用结果以及初始参数)向 运行 提供可选函数,并将我们想要的函数传递给结果函数装饰。生成的函数将调用 before
、主函数,然后调用 after
,如果未提供则跳过它们。
要使用此包装对象的元素,我们可以编写一个处理所有功能的薄包装器:
const wrap = (wrapper) => (fn) => (...args) => {
(wrapper .before || (() => {})) (...args)
const res = fn (...args)
const newRes = (wrapper .after || (() => {})) (res, ...args)
return newRes === undefined ? res : newRes
}
const wrapAll = (wrapper) => (o) => Object .fromEntries (
Object .entries (o) .map (([k, v]) => [k, typeof v == 'function' ? wrap (wrapper) (v) : v])
)
const o = {
doA () {
console .log ('Does A')
},
doB () {
console .log ('Does B')
}
}
const newO = wrapAll ({
after: () => console .log ('Does something else at end')
}) (o)
newO .doA ()
newO .doB ()
当然这可以通过多种方式扩展。我们可能想要选择特定的函数属性来包装。我们可能想要流畅地处理 this
。我们可能希望 before
能够 改变 传递给主函数的参数。我们可能想给生成的函数起一个有用的名字。等等。但很难为通用包装器设计签名,而 所有 这些事情都可以轻松完成。
我想在每次调用对象中的某些函数并执行完毕时执行一些代码。
对象:
{
doA() {
// Does A
},
doB() {
// Does B
}
}
是否可以扩展它,改变那些功能,让它们做它们做的事,然后再做其他事情?好像这是一个监听那些函数完成的事件?
{
doA() {
// Does A
// Do something else at end
},
doB() {
// Does B
// Do something else at end
}
}
也许这可以使用代理 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
尝试使用代理:
const ob = {
doA() {
console.log('a');
},
doB() {
console.log('b');
}
};
const ob2 = new Proxy(ob, {
apply: function (target, key, value) {
console.log('c');
},
});
ob2.doA();
下面是一个将函数包装在对象中的直接方法示例。
这对调试很有用,但您不应该在生产环境中这样做,因为您(或稍后查看您的代码的任何人)将很难弄清楚为什么您的方法正在做一些不在原代码。
var myObj = {
helloWorld(){
console.log('Hello, world!');
}
}
// get a refernce to the original function
var f = myObj.helloWorld;
// overwrite the original function
myObj.helloWorld = function(...args){
// call the original function first
f.call(this, ...args);
// Then do other stuff afterwards
console.log('Goodbye, cruel world..');
};
myObj.helloWorld();
如果它是关于具有相同的事件侦听器机制,您可以创建一个包含存储函数并在需要时执行它们的对象
const emitter = {
events: {},
addListener(event, listener) {
this.events[event] = this.events[event] || [];
this.events[event].push(listener);
},
emit(event, data) {
if(this.events[event]) {
this.events[event].forEach(listener => listener(data));
}
}
}
//instead of the config object you could just type the string for the event
const config = {
doA: 'doA',
doB: 'doB'
}
//store first function for doA
emitter.addListener(config.doA, (data) => {
console.log('hardler for Function ' + data + ' executed!');
});
//store second function for doA
emitter.addListener(config.doA, () => {
console.log('Another hardler for Function A executed!');
});
//store first function for doB
emitter.addListener(config.doB, (data) => {
console.log('hardler for Function ' + data + ' executed!');
});
let obj = {
doA() {
let char = 'A';
console.log('doA executed!');
//You can pass data to the listener
emitter.emit(config.doA, char);
},
doB() {
let char = 'B';
console.log('doB executed!');
emitter.emit(config.doB, char);
}
}
obj.doA();
obj.doB();
//Output:
//doA executed!
//hardler for Function A executed!
//Another hardler for Function A executed!
//doB executed!
//hardler for Function B executed!
使用 Proxy 我们可以定位所有包含函数的 get
,然后我们可以检查正在获取的是否是一个函数,如果是我们创建和 return 我们自己的函数来包装目标函数并调用它。
从我们的包装函数中调用对象函数后,我们可以执行任何我们想要的,然后 return 对象函数的 return 值。
const ob = {
doA(arg1, arg2) {
console.log(arg1, arg2);
return 1;
},
doB() {
console.log('b');
}
};
const ob2 = new Proxy(ob, {
get: function(oTarget, sKey) {
if (typeof oTarget[sKey] !== 'function') return oTarget[sKey];
return function(...args) {
const ret = oTarget[sKey].apply(oTarget, args);
console.log("c");
return ret;
}
}
});
console.log(ob2.doA('aaa', 'bbb'));
如果有改进或其他选项,请添加评论!
对于 JavaScript 应用程序,有时需要 拦截 and/or 修改 控制流程 由于其他原因不拥有或拥有的功能, 不允许触摸。
对于这种情况,除了通过包装其原始实现来保留和更改此类逻辑之外,别无他法。这种能力并不是 JavaScript 独有的。通过 Reflection[= 启用 元编程 的编程语言历史悠久56=] 和 自修改.
原因之一 could/should 为人们可以想到的所有可能的修饰符用例提供防弹但方便的抽象。
由于 JavaScript 已经实现了 Function.prototype.bind
,它已经带有某种微小的修改功能,我个人不介意,如果有一天 JavaScript 正式推出定制和标准化的便捷 方法修饰符 工具集...... Function.prototype[before|around|after|afterThrowing|afterFinally]
.
// begin :: closed code
const obj = {
valueOf() {
return { foo: this.foo, bar: this.bar };
},
toString(link = '-') {
return [this.foo, this.bar].join(link);
},
foo: 'Foo',
bar: 'Bar',
baz: 'BAAAZZ'
};
// end :: closed code
console.log(
'obj.valueOf() ...',
obj.valueOf()
);
console.log(
'obj.toString() ...',
obj.toString()
);
enableMethodModifierPrototypes();
function concatBazAdditionally(proceed, handler, [ link ]) {
const result = proceed.call(this, link);
return `${ result }${ link }${ this.baz }`;
}
obj.toString = obj.toString.around(concatBazAdditionally, obj);
// obj.toString = aroundModifier(obj.toString, concatBazAdditionally, obj)
console.log(
'`around` modified ... obj.toString("--") ...',
obj.toString("--")
);
function logWithResult(result, args) {
console.log({ modifyerLog: { result, args, target: this.valueOf() } });
}
obj.toString = obj.toString.after(logWithResult, obj);
// obj.toString = afterModifier(obj.toString, logWithResult, obj)
console.log(
'`around` and `after` modified ... obj.toString("##") ...',
obj.toString("##")
);
function logAheadOfInvocation(args) {
console.log({ stats: { args, target: this } });
}
obj.valueOf = obj.valueOf.before(logAheadOfInvocation, obj);
// obj.valueOf = beforeModifier(obj.valueOf, logAheadOfInvocation, obj)
console.log(
'`before` modified ... obj.valueOf() ...',
obj.valueOf()
);
restoreDefaultFunctionPrototype();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
function isFunction(value) {
return (
typeof value === 'function' &&
typeof value.call === 'function' &&
typeof value.apply === 'function'
);
}
function getSanitizedTarget(value) {
return value ?? null;
}
function around(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function aroundType(...args) {
const context = getSanitizedTarget(this) ?? target;
return handler.call(context, proceed, handler, args);
}
) || proceed;
}
around.toString = () => 'around() { [native code] }';
function before(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function beforeType(...args) {
const context = getSanitizedTarget(this) ?? target;
handler.call(context, [...args]);
return proceed.apply(context, args);
}
) || proceed;
}
before.toString = () => 'before() { [native code] }';
function after(handler, target) {
target = getSanitizedTarget(target);
const proceed = this;
return (
isFunction(handler) &&
isFunction(proceed) &&
function afterReturningType(...args) {
const context = getSanitizedTarget(this) ?? target;
const result = proceed.apply(context, args);
handler.call(context, result, args);
return result;
}
) || proceed;
}
after.toString = () => 'after() { [native code] }';
function aroundModifier(proceed, handler, target) {
return around.call(proceed, handler, target);
}
function beforeModifier(proceed, handler, target) {
return before.call(proceed, handler, target);
}
function afterModifier(proceed, handler, target) {
return after.call(proceed, handler, target);
}
const { prototype: fctPrototype } = Function;
const methodIndex = {
around,
before,
after/*Returning*/,
// afterThrowing,
// afterFinally,
};
const methodNameList = Reflect.ownKeys(methodIndex);
function restoreDefaultFunctionPrototype() {
methodNameList.forEach(methodName =>
Reflect.deleteProperty(fctPrototype, methodName),
);
}
function enableMethodModifierPrototypes() {
methodNameList.forEach(methodName =>
Reflect.defineProperty(fctPrototype, methodName, {
configurable: true,
writable: true,
value: methodIndex[methodName],
}),
);
}
</script>
<!--
<script src="https://closure-compiler.appspot.com/code/jscd16735554a0120b563ae21e9375a849d/default.js"></script>
<script>
const {
disablePrototypes: restoreDefaultFunctionPrototype,
enablePrototypes: enableMethodModifierPrototypes,
beforeModifier,
aroundModifier,
afterModifier,
} = modifiers;
</script>
//-->
下一个提供的示例代码使用上述测试对象及其测试用例,但 implements/provides 是基于代理的解决方案。从测试用例需要如何调整,可以看出直接方法修改,基于 method-modifiers 的干净实现,允许更灵活地处理不同的用例,而基于代理的方法仅限于每个拦截的方法调用一个处理函数...
// begin :: closed code
const obj = {
valueOf() {
return { foo: this.foo, bar: this.bar };
},
toString(link = '-') {
return [this.foo, this.bar].join(link);
},
sayHi() {
console.log('Hi');
},
foo: 'Foo',
bar: 'Bar',
baz: 'BAAAZZ'
};
// end :: closed code
console.log(
'non proxy call ... obj.valueOf() ...',
obj.valueOf()
);
console.log(
'non proxy call ... obj.toString() ...',
obj.toString()
);
function toStringInterceptor(...args) {
const { proceed, target } = this;
const [ link ] = args;
// retrieve the original return value.
let result = proceed.call(target, link);
// modify the return value while
// intercepting the original method call.
result = `${ result }${ link }${ target.baz }`;
// log before ...
console.log({ toStringInterceptorLog: { result, args, target: target.valueOf() } });
// ... returning the
// modified value.
return result;
}
function valueOfInterceptor(...args) {
const { proceed, target } = this;
// log before returning ...
console.log({ valueOfInterceptorLog: { proceed, args, target } });
// ... and save/keep the
// original return value.
return proceed.call(target);
}
function handleTrappedGet(target, key) {
const interceptors = {
toString: toStringInterceptor,
valueOf: valueOfInterceptor,
}
const value = target[key];
return (typeof value === 'function') && (
interceptors[key]
? interceptors[key].bind({ proceed: value, target })
: value.bind(target)
) || value;
}
const objProxy = new Proxy(obj, { get: handleTrappedGet });
console.log('\n+++ proxy `get` handling +++\n\n');
const { foo, bar, baz } = objProxy;
console.log(
'non method `get` handling ...',
{ foo, bar, baz }
);
console.log('\nproxy call ... objProxy.sayHi() ... but not intercepted ...');
objProxy.sayHi();
console.log('\nintercepted proxy calls ...');
console.log(
'objProxy.toString("--") ...',
objProxy.toString("--")
);
console.log(
'objProxy.valueOf() ...',
objProxy.valueOf()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
您当然可以使用代理来做到这一点。但是您也可以编写自己的通用函数装饰器来执行此操作。
基本的装饰器可能是这样工作的:
const wrap = (wrapper) => (fn) => (...args) => {
(wrapper .before || (() => {})) (...args)
const res = fn (...args)
const newRes = (wrapper .after || (() => {})) (res, ...args)
return newRes === undefined ? res : newRes
}
const plus = (a, b) => a + b
const plusPlus = wrap ({
before: (...args) => console .log (`Arguments: ${JSON.stringify(args)}`),
after: (res, ...args) => console .log (`Results: ${JSON.stringify(res)}`)
}) (plus)
console .log (plusPlus (5, 7))
我们在主体之前(使用相同的参数)和之后(使用结果以及初始参数)向 运行 提供可选函数,并将我们想要的函数传递给结果函数装饰。生成的函数将调用 before
、主函数,然后调用 after
,如果未提供则跳过它们。
要使用此包装对象的元素,我们可以编写一个处理所有功能的薄包装器:
const wrap = (wrapper) => (fn) => (...args) => {
(wrapper .before || (() => {})) (...args)
const res = fn (...args)
const newRes = (wrapper .after || (() => {})) (res, ...args)
return newRes === undefined ? res : newRes
}
const wrapAll = (wrapper) => (o) => Object .fromEntries (
Object .entries (o) .map (([k, v]) => [k, typeof v == 'function' ? wrap (wrapper) (v) : v])
)
const o = {
doA () {
console .log ('Does A')
},
doB () {
console .log ('Does B')
}
}
const newO = wrapAll ({
after: () => console .log ('Does something else at end')
}) (o)
newO .doA ()
newO .doB ()
当然这可以通过多种方式扩展。我们可能想要选择特定的函数属性来包装。我们可能想要流畅地处理 this
。我们可能希望 before
能够 改变 传递给主函数的参数。我们可能想给生成的函数起一个有用的名字。等等。但很难为通用包装器设计签名,而 所有 这些事情都可以轻松完成。