JavaScript 中 Proxy 的 apply 和 construct 捕获器(trap)的语法和具体用法

  • Proxy 是 ES6 提供的对象拦截器,其中 applyconstruct 是专门用于拦截函数的两个核心方法:
     
    • 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 不会直接触发 applybind 返回新函数),但调用 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

输出结果如下:

image

从结果可见,参数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' }

输出结果如下所示:

image

本次使用的是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

三、关键注意点

 
    1. 仅对函数生效:如果 target 不是函数(比如对象、数字),调用代理对象时不会触发 apply,甚至可能报错;
    2. Reflect.apply 的作用Reflect.apply(target, thisArg, args) 是调用原函数的标准方式,等价于 target.apply(thisArg, args),但更符合 Proxy/Reflect 的设计理念(语义更清晰);
    3. 返回值可控:你可以完全不调用原函数,直接返回自定义结果(比如模拟函数返回值)。
 

总结

 
    1. apply 捕获器专用于拦截函数对象的调用操作(直接调用、call/apply),参数包含原函数、this 指向、调用参数;
    2. 核心用途是监控函数调用、校验 / 修改参数、拦截返回值,不改变原函数逻辑的前提下扩展函数行为;
    3. 推荐使用 Reflect.apply 调用原函数,保证代码的规范性和可维护性。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

四、construct 拦截器(拦截 new 构造调用)

1. 核心定义

 
construct(target, args, newTarget)
 
  • target:被代理的原构造函数
  • argsnew 调用时的参数数组
  • 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() 引擎内部做两件事:
  1. 检查 X 是否拥有 [[Construct]] 内部方法
    • 有 → 允许 new
    • 无 → 直接报错
     
  2. 执行 X[[Construct]] 逻辑
X代理对象时:
 
引擎不看 Proxy 本身,看内部插槽 [[Target]]
  • 内部目标是 class / 普通构造函数 → 有 [[Construct]] → 能 new
  • 内部目标是普通对象 → 无 [[Construct]] → 不能 new

为什么你会觉得矛盾?

你混淆了两个东西:
  1. 语法上ProxyPerson = new Proxy(...) 用了 new
  2. 语义上:Proxy 不生产自己的实例,只包装原对象
普通类:new Class → 生成属于 Class 的实例
 
Proxy 特殊内置类:new Proxy不生成 Proxy 专属实例,只生成包装壳,复用原目标的身份
 
return new target(...args);

 重点:return 的这个实例被 new ProxyPerson("张三") 这个表达式接收了

👉 p 就是 return new target(...args) 吐出来的那个实例对象

 

 

五、关键区别与总结

方法 拦截场景 必须返回值
apply 函数普通调用 func() 无强制要求
construct 函数构造调用 new Func() 必须返回对象
 

核心注意事项

  1. 只有函数能被这两个方法拦截,普通对象不行;
  2. construct 必须返回对象,否则抛出 TypeError
  3. 两个方法都可以修改参数拦截执行添加额外逻辑(日志、校验、埋点等);
  4. 执行原函数 / 构造函数时,推荐用 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: '构造调用' }

输出结果如下所示:

image

this instanceof Test是 JavaScript 里判断函数是【普通调用】还是【new 构造调用】的经典写法

一句话结论

this instanceof Test
 
= 判断当前函数是不是通过 new 调用的
  • 返回 true → 是 new 构造调用
  • 返回 false → 是 普通函数调用

原理:this 的指向规则

JavaScript 函数里的 this调用方式决定

① 普通调用 Test()

this = 全局对象(浏览器是 window / Node 是 global
 
this instanceof Testfalse

② new 调用 new Test()

JS 会自动创建一个空对象,把 this 指向这个新对象
 
并且这个新对象的原型指向 Test.prototype
 
this instanceof Testtrue
posted @ 2026-01-29 18:09  chenlight  阅读(22)  评论(0)    收藏  举报