JavaScript 中 Proxy 的 apply 和 construct 捕获器(trap)的语法和具体用法
-
Proxy是 ES6 提供的对象拦截器,其中apply和construct是专门用于拦截函数的两个核心方法:apply:拦截函数的普通调用(func()、func.call()、func.apply())construct:拦截函数的构造调用(new Func())
只有函数对象才能被这两个方法拦截,普通对象无法使用。
一、apply 捕获器基础
1. 核心定义
apply 是 Proxy 的一个捕获器方法,当被代理的对象是函数,且该函数被调用(包括直接调用、call/apply 调用)时,会触发 apply 捕获器,你可以在其中拦截、修改函数调用的行为。2. 语法结构
const proxy = new Proxy(target, {
// apply 捕获器的语法
apply(target, thisArg, argumentsList) {
// 拦截逻辑
// 最终可返回修改后的结果,或调用原函数返回结果
return Reflect.apply(target, thisArg, argumentsList);
}
});
参数说明:
target:被代理的原函数(必须是函数类型,否则apply不会触发);thisArg:调用函数时绑定的this指向;argumentsList:调用函数时传入的参数数组(类数组对象);- 返回值:
apply捕获器的返回值会作为函数调用的最终结果。
3. 触发场景
只要是对代理后的函数进行 “调用操作”,都会触发
apply:- 直接调用:
proxy(); call调用:proxy.call(obj, 1, 2);apply调用:proxy.apply(obj, [1, 2]);- 注意:
bind不会直接触发apply(bind返回新函数),但调用bind后的新函数时会触发。
二、实战示例
示例 1:基础拦截(打印调用信息)
// 原函数:求和
function sum(a, b) {
return a + b;
}
// 创建代理,拦截函数调用
const sumProxy = new Proxy(sum, {
apply(target, thisArg, args) {
// 1. 拦截:打印调用信息
console.log(`函数 sum 被调用,参数:${args.join(', ')}`);
console.log(`绑定的 this 指向:`, thisArg);
console.log('args is:',args);
// 2. 调用原函数(推荐用 Reflect.apply,更规范)
const result = Reflect.apply(target, thisArg, args);
// 3. 拦截返回值(比如翻倍)
return result * 2;
}
});
// 测试调用
console.log(sumProxy(1, 2)); // 输出:函数 sum 被调用,参数:1, 2 → 绑定的 this 指向:undefined → 6
console.log(sumProxy.call(null, 3, 4)); // 输出:函数 sum 被调用,参数:3, 4 → 绑定的 this 指向:null → 14
输出结果如下:

从结果可见,参数args是个数组类型。
示例2:没有使用Reflect的情况
// 原函数
function sum(a, b) {
return a + b;
}
// 创建代理,拦截函数调用
const proxySum = new Proxy(sum, {
// 拦截所有普通函数调用
apply(target, thisArg, args) {
console.log("函数被调用了,参数:", args);
console.log("this 指向:", thisArg);
console.log("args is :",args);
// 执行原函数,返回结果
return target.apply(thisArg, args);
}
});
// 测试:普通调用
console.log(proxySum(1, 2));
// 输出:
// 函数被调用了,参数: [1, 2]
// this 指向: undefined
// 3
// 测试:call 调用
proxySum.call({ name: "test" }, 3, 4);
// 输出:this 指向: { name: 'test' }
输出结果如下所示:

本次使用的是target参数,没有使用Reflect。
示例 3:参数校验 / 修正
// 原函数:计算平方
function square(num) {
return num * num;
}
const squareProxy = new Proxy(square, {
apply(target, thisArg, args) {
// 拦截:校验参数类型,非数字则修正为 0
const [num] = args;
if (typeof num !== 'number') {
console.warn('参数必须是数字,已自动修正为 0');
args[0] = 0;
}
return Reflect.apply(target, thisArg, args);
}
});
console.log(squareProxy(5)); // 25
console.log(squareProxy('abc')); // 警告 → 0
三、关键注意点
-
- 仅对函数生效:如果
target不是函数(比如对象、数字),调用代理对象时不会触发apply,甚至可能报错; - Reflect.apply 的作用:
Reflect.apply(target, thisArg, args)是调用原函数的标准方式,等价于target.apply(thisArg, args),但更符合 Proxy/Reflect 的设计理念(语义更清晰); - 返回值可控:你可以完全不调用原函数,直接返回自定义结果(比如模拟函数返回值)。
- 仅对函数生效:如果
总结
-
apply捕获器专用于拦截函数对象的调用操作(直接调用、call/apply),参数包含原函数、this指向、调用参数;- 核心用途是监控函数调用、校验 / 修改参数、拦截返回值,不改变原函数逻辑的前提下扩展函数行为;
- 推荐使用
Reflect.apply调用原函数,保证代码的规范性和可维护性。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
四、construct 拦截器(拦截 new 构造调用)
1. 核心定义
construct(target, args, newTarget)target:被代理的原构造函数args:new调用时的参数数组newTarget:最初被调用的构造函数(指向代理对象)
必须返回一个对象(这是
construct 的强制规则,不返回会报错)。2. 基础用法
// 原构造函数
class Person {
constructor(name) {
this.name = name;
}
}
// 创建代理,拦截 new 调用
const ProxyPerson = new Proxy(Person, {
// 拦截 new 操作
construct(target, args, newTarget) {
console.log("new 被拦截,参数:", args);
console.log("newTarget:", newTarget === ProxyPerson); // true
// 必须返回一个对象!
return new target(...args);
}
});
// 测试:new 调用
const p = new ProxyPerson("张三");
// 输出:new 被拦截,参数: ["张三"]
console.log(p.name); // 张三
注意:
以上这段代码着重讲一下误区,就是"ProxyPerson已经是Proxy的实例了,为什么还可以像下面这样再次实例化成p呢?“
const p = new ProxyPerson("张三");
先纠正一个根深蒂固的误区
你默认套用了普通类的逻辑:
class Foo {}
const f = new Foo(); // f 是 Foo 的普通实例对象
你以为:只要调用
new X(),返回的一定是 X 的普通实例。但
Proxy 是 JS 引擎内置的特殊原生构造函数,不遵守普通 class 的实例化规则!底层核心 1:Proxy 不是普通构造函数,是「元编程包装器」
JS 引擎规范(ECMAScript)规定:
new Proxy(target, handler)不会创建 Proxy 类型的实例对象它的行为是:创建一个「代理外包装对象」,内部封装原 target,对外完全复刻 target 的身份、内部插槽、能力
直白说:
- 普通
new 类():造实例 new Proxy(目标):造一层透明包装壳,壳里面装着原目标
所以:
const ProxyPerson = new Proxy(Person, {});
ProxyPerson 不是 Proxy 实例,它本质:
Person,只是外面套了一层拦截壳。底层核心 2:JS 对象的关键内部插槽 [[Target]]
Proxy 实例(代理对象)底层结构:
代理对象 ProxyPerson
├─ 内部私有插槽 [[Target]] = 原构造函数 Person
├─ 内部私有插槽 [[Handler]] = 拦截器对象
└─ 对外所有行为 全部转发给 [[Target]]
引擎规则:
代理对象会完整继承原
原
[[Target]] 的所有内部方法
Person 拥有内部方法:[[Call]]可被调用Person()[[Construct]]可被 new 调用
👉 代理对象原样继承了
[[Construct]],所以依然可以 new ProxyPerson()如果原目标没有
[[Construct]](普通对象、数组):const p = new Proxy({}, {});
new p // 报错:不是构造函数
完全印证这个规则。
底层核心 3:new 运算符的底层工作机制
new X() 引擎内部做两件事:- 检查
X是否拥有[[Construct]]内部方法- 有 → 允许 new
- 无 → 直接报错
- 执行
X的[[Construct]]逻辑
当
引擎不看 Proxy 本身,看内部插槽
X 是代理对象时:
[[Target]]- 内部目标是 class / 普通构造函数 → 有
[[Construct]]→ 能 new - 内部目标是普通对象 → 无
[[Construct]]→ 不能 new
为什么你会觉得矛盾?
你混淆了两个东西:
- 语法上:
ProxyPerson = new Proxy(...)用了new - 语义上:Proxy 不生产自己的实例,只包装原对象
普通类:
Proxy 特殊内置类:
new Class → 生成属于 Class 的实例
new Proxy → 不生成 Proxy 专属实例,只生成包装壳,复用原目标的身份return new target(...args);
重点:return 的这个实例被 new ProxyPerson("张三") 这个表达式接收了
👉 p 就是 return new target(...args) 吐出来的那个实例对象
五、关键区别与总结
| 方法 | 拦截场景 | 必须返回值 |
|---|---|---|
| apply | 函数普通调用 func() |
无强制要求 |
| construct | 函数构造调用 new Func() |
必须返回对象 |
核心注意事项
- 只有函数能被这两个方法拦截,普通对象不行;
construct必须返回对象,否则抛出TypeError;- 两个方法都可以修改参数、拦截执行、添加额外逻辑(日志、校验、埋点等);
- 执行原函数 / 构造函数时,推荐用
target.apply(thisArg, args)/new target(...args)。
完整示例:同时拦截 apply + construct
function Test() {
// 普通调用
if (!(this instanceof Test)) {
return "普通调用";
}
// 构造调用
this.type = "构造调用";
}
const proxy = new Proxy(Test, {
apply(target, thisArg, args) {
console.log("拦截普通调用");
return target.apply(thisArg, args);
},
construct(target, args) {
console.log("拦截构造调用");
return new target(...args);
}
});
console.log(proxy()); // 拦截普通调用 → 普通调用
console.log(new proxy()); // 拦截构造调用 → Test { type: '构造调用' }
输出结果如下所示:

this instanceof Test是 JavaScript 里判断函数是【普通调用】还是【new 构造调用】的经典写法
一句话结论
this instanceof Test
new 调用的- 返回
true→ 是 new 构造调用 - 返回
false→ 是 普通函数调用
原理:this 的指向规则
JavaScript 函数里的
this 由调用方式决定:① 普通调用 Test()
this = 全局对象(浏览器是 window / Node 是 global)
this instanceof Test → false② new 调用 new Test()
JS 会自动创建一个空对象,把
并且这个新对象的原型指向
→
this 指向这个新对象
Test.prototype
this instanceof Test → true
浙公网安备 33010602011771号