proxy.construct仅拦截显式new调用,无法捕获第三方库内部new;需用reflect.construct透传并手动挂载prototype和静态属性,否则instanceof及方法调用失效。

直接 new Proxy(ThirdPartyClass, { construct }) 是可行的,但仅对显式调用 new 生效
Proxy.construct 陷阱只在你明确用 new proxy(...) 创建实例时触发,不会自动拦截第三方库内部的 new SomeInternalClass() 调用。比如你代理了 axios.Axios,但 axios 内部自己 new 的 CancelToken 或 InterceptorManager 完全不受影响。真正能监控到的,是你代码里写的那一行 new Axios() —— 这就是“无侵入”的实际边界。
常见错误现象:代理后发现类方法调用报 TypeError: Class constructor X cannot be invoked without 'new',这是因为没正确透传 this 或返回了非对象值。
- 必须在
construct中返回一个对象(通常用Reflect.construct(target, args, newTarget)) - 不能直接
return new target(...args),否则会丢失原型链和instanceof判断 - 若目标类是箭头函数或严格模式下的普通函数,
newTarget参数必须传,否则Reflect.construct会抛错
对已导出类(ESM/CJS)做 construct 劫持要先拿到原始构造器引用
如果你用的是 import { Modal } from 'antd',得立刻用 new Proxy(Modal, handler) 包裹,再重导出或挂到全局;如果等组件渲染完再代理,就晚了——实例早已创建完毕。CDN 引入的 window.Modal 反而更方便,因为它是运行时可访问的全局变量。
模块加载方式直接影响你能否稳定拿到构造器:
- ESM:静态导入,可在
import后立即代理,但要注意 tree-shaking 可能移除未引用的类 - CJS:
require('xxx').SomeClass可以直接代理,但需确保模块已加载完成 - UMD/CDN:依赖
window或globalThis,最稳妥,但也最容易被后续脚本覆盖
混淆代码中动态构造类名(如 ['M','o','d','a','l'].join(''))无法被 Proxy 静态捕获,这种只能靠 AST 分析或运行时字符串匹配补救。
劫持 construct 时必须同步处理 prototype 和静态属性透传
只代理构造函数本身,不等于代理了它的整个行为契约。第三方类的静态方法(如 Lodash.cloneDeep)、原型方法(如 Modal.prototype.open)和 instanceof 检查都会失效,除非你手动桥接。
安全更新和维护 CLI Proxy API(CPA)部署与配置。用于 CPA 镜像升级、配置变更、认证目录兼容修复、上线验证与回滚。适用于用户提到“CPA 更新/升级/配置改了/容器重建/回滚”等场景。
正确做法是用 Reflect.construct 创建实例后,再把原类的 prototype 和静态属性挂载到代理上:
const originalClass = ThirdPartyClass;
const handler = {
construct(target, args, newTarget) {
console.log('[construct]', { className: target.name, args });
const instance = Reflect.construct(target, args, newTarget);
// 确保 instanceof 有效
Object.setPrototypeOf(instance, originalClass.prototype);
return instance;
}
};
// 静态属性需单独代理,例如:
const proxiedClass = new Proxy(originalClass, handler);
Object.getOwnPropertyNames(originalClass).forEach(key => {
if (key !== 'prototype' && key !== 'name') {
Object.defineProperty(proxiedClass, key, {
value: originalClass[key],
writable: true,
configurable: true,
enumerable: false
});
}
});
漏掉 Object.setPrototypeOf 会导致所有基于原型的方法调用失败;漏掉静态属性代理会让 proxiedClass.version 返回 undefined。
真正需要监控的不是“类”,而是“实例生命周期关键点”
多数场景下,你并不关心“谁被 new 了”,而是关心“这个实例什么时候被初始化、配置、销毁”。与其死磕 construct,不如在实例创建后立即 patch 其关键方法:
- 监听
instance.mount、instance.init、instance.destroy等生命周期钩子 - 代理实例上的事件发射器(如
instance.on、instance.emit)来捕获状态流转 - 用
WeakMap关联原始实例与监控元数据,避免内存泄漏
比如监控 Map 实例:你代理 new Map() 只能知道它被创建,但真正有价值的是它第一次 .set()、最后一次 .clear() 或 size 超过阈值的时刻——这些必须靠后续对实例方法的二次代理才能拿到。
construct 劫持只是入口,真正的监控深度取决于你是否愿意继续代理实例身上的方法和属性访问。没有银弹,只有分层拦截。










