news 2026/5/7 17:51:20

你怎么理解 Proxy 的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
你怎么理解 Proxy 的

基础问答

问:Proxy 是什么?怎么使用的?

答:Proxy 是用于创建 “对象代理” 的构造函数,它能封装目标对象(target),并通过 “拦截器对象(handler)” 自定义目标对象的基础操作(如属性读取、赋值),实现对对象行为的 “劫持”,手写使用方式。

// 语法:new Proxy(target, handler)

// 参数:target-目标对象,handler-拦截器对象

// 返回值:代理实例proxy

const target = { name: '前端面试', age: 2 };

// 定义拦截器对象

const handler = {

// 拦截“读取属性”操作:参数为target(目标对象)、prop(属性名)、receiver(代理实例)

get(target, prop, receiver) {

console.log(`触发get拦截:读取属性${prop}`);

// 执行原始读取操作(通过Reflect确保this指向正确)

return Reflect.get(target, prop, receiver);

},

// 拦截“赋值属性”操作:参数为target、prop、value(新值)、receiver

set(target, prop, value, receiver) {

console.log(`触发set拦截:给属性${prop}赋值${value}`);

// 自定义逻辑:校验age属性必须为数字

if (prop === 'age' && typeof value !== 'number') {

throw new Error('age属性必须是数字');

}

// 执行原始赋值操作

return Reflect.set(target, prop, value, receiver);

}

};

// 创建代理实例

const proxy = new Proxy(target, handler);

// 操作代理实例,触发拦截

console.log(proxy.name); // 输出:触发get拦截:读取属性name → 前端面试

proxy.age = 3; // 输出:触发set拦截:给属性age赋值3 → 成功

proxy.age = '3'; // 输出:触发set拦截:给属性age赋值3 → 抛出错误:age属性必须是数字

// 直接操作目标对象,不会触发拦截

console.log(target.name); // 输出:前端面试(无拦截日志)

target.age = '4'; // 无拦截日志,且不会触发age的类型校验

扩展延伸

Proxy API 的核心三要素是:“目标对象(target)”“拦截器对象(handler)”“代理实例(proxy)”。

目标对象(target):被代理的原始对象(可以是对象、数组、函数,甚至另一个 Proxy 实例);

拦截器(handler):包含 “拦截方法” 的对象,每个拦截方法对应一种目标对象的基础操作(如get拦截属性读取,set拦截属性赋值);

代理实例(proxy):通过new Proxy(target, handler) 创建的代理对象,所有对目标对象的操作需通过代理实例完成,才能触发拦截器。也就是说,直接操作目标对象,就不会走代理。

Proxy 是一种非侵入性的 API,他不会修改目标对象本身的结构或方法,所有拦截逻辑都封装在拦截器中,实现 “代理行为” 与 “目标对象” 的解耦,相对 Object.defineProperty 更加灵活。

拦截器方法除了基础的 get/set 方法,需要注意一些特殊的拦截方法(has/deleteProperty/apply/construct):

拦截方法 作用 关键参数 适用场景

get 拦截属性读取(含 obj.prop、obj [prop]) target, prop, receiver 数据劫持(如响应式)、默认值设置

set 拦截属性赋值 target, prop, value, receiver 数据校验、值格式化

has 拦截 in 运算符(如prop in proxy) target, prop 权限控制(隐藏某些属性不被检测)

deleteProperty 拦截 delete 操作(如delete proxy.prop) target, prop 禁止删除关键属性

apply 拦截函数调用(仅当 target 是函数时) target, thisArg, args 函数参数校验、调用日志记录

construct 拦截 new 操作(仅当 target 是构造函数时) target, args, newTarget 构造函数参数校验、实例计数

如果你使用 Vue3 框架,需要知道的时 Vue3 的响应式设计就是基于 Proxy 的,这是一个简化版的响应式 API 设计:

// 存储当前活跃的副作用函数(如组件渲染函数)

let activeEffect = null;

// 依赖映射表:target → { prop → [effect1, effect2,...] }

const targetMap = new WeakMap();

// 1. 依赖收集函数:将副作用函数与target、prop关联

function track(target, prop) {

if (!activeEffect) return; // 无活跃副作用,不收集

// 确保target在targetMap中存在映射

let depsMap = targetMap.get(target);

if (!depsMap) {

depsMap = new Map();

targetMap.set(target, depsMap);

}

// 确保prop在depsMap中存在副作用数组

let deps = depsMap.get(prop);

if (!deps) {

deps = new Set(); // 用Set避免重复副作用

depsMap.set(prop, deps);

}

// 添加当前副作用函数

deps.add(activeEffect);

}

// 2. 依赖触发函数:执行target、prop对应的所有副作用

function trigger(target, prop) {

const depsMap = targetMap.get(target);

if (!depsMap) return;

const deps = depsMap.get(prop);

if (deps) {

deps.forEach(effect => effect()); // 执行所有副作用

}

}

// 3. 响应式函数:创建Proxy代理,实现依赖收集与触发

function reactive(target) {

return new Proxy(target, {

get(target, prop, receiver) {

const value = Reflect.get(target, prop, receiver);

track(target, prop); // 读取时收集依赖

// 若value是对象,递归创建响应式(深度响应)

if (typeof value === 'object' && value !== null) {

return reactive(value);

}

return value;

},

set(target, prop, value, receiver) {

const oldValue = Reflect.get(target, prop, receiver);

const success = Reflect.set(target, prop, value, receiver);

if (success && oldValue !== value) {

trigger(target, prop); // 赋值时触发依赖

}

return success;

}

});

}

// 4. 副作用函数注册:执行fn并收集其依赖

function effect(fn) {

activeEffect = fn;

fn(); // 执行fn,触发get拦截,收集依赖

activeEffect = null; // 重置,避免后续误收集

}

// 测试响应式

const data = reactive({ count: 0 });

// 注册副作用函数(模拟组件渲染)

effect(() => {

console.log(`视图更新:count = ${data.count}`);

});

// 修改数据,触发副作用(视图更新)

data.count = 1; // 输出:视图更新:count = 1

data.count = 2; // 输出:视图更新:count = 2

面试追问

Proxy 和 Object.defineProperty 都能实现数据劫持,为什么 Vue3 放弃 Object.defineProperty 改用 Proxy?两者的核心差异是什么?

对比维度 Object.defineProperty Proxy

劫持范围 仅能劫持 “对象的单个属性”(需遍历属性逐个定义) 直接劫持 “整个对象”(无需遍历属性)

数组支持 无法劫持数组的原生方法(如 push、splice),需重写数组原型 能拦截数组的所有操作(包括索引赋值、原生方法调用)

嵌套对象处理 需递归遍历所有嵌套对象,手动为每个属性定义劫持 可在 get 拦截中递归创建代理(按需劫持,性能更优)

性能 初始化时需遍历所有属性,嵌套层级深时性能差 懒加载式劫持(访问嵌套对象时才创建代理),初始化性能更优

用 Proxy 拦截对象属性赋值时,若目标对象是冻结对象(Object.freeze),set 拦截器还能生效吗?为什么?

set 拦截器会触发,但最终赋值会失败,Object.freeze (target) 会让目标对象的属性变为 “不可写、不可配置”,但不会阻止 Proxy 拦截器的触发(拦截器是对操作的劫持,而非直接修改属性)。

若用 Proxy 代理一个频繁修改的大型对象(如包含 1000 个属性的列表),会有性能问题吗?如何优化?

会有性能问题,需要从两方面考虑:1. 若拦截器逻辑复杂(如每次 get/set 都执行大量校验、日志记录),频繁操作时会累积性能损耗;2. 对嵌套层级极深的大型对象,若初始化时递归创建代理(而非按需劫持),会导致初始化耗时过长。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 17:04:17

MinIO替代方案生态集成指南:RustFS如何无缝融入现代技术栈

存储系统的价值从不是“单打独斗”,而是能否与现有技术生态无缝衔接——这也是MinIO迁移时最容易被忽略的关键:选对方案但集成不畅,照样会导致业务中断、运维成本飙升。 本文聚焦主推方案RustFS,从技术团队最关心的5大核心集成场景…

作者头像 李华
网站建设 2026/5/3 3:23:27

腾讯混元3D部件分割技术:从JavaScript到Python的完整迁移指南

腾讯混元3D部件分割技术:从JavaScript到Python的完整迁移指南 【免费下载链接】Hunyuan3D-Part 腾讯混元3D-Part 项目地址: https://ai.gitcode.com/tencent_hunyuan/Hunyuan3D-Part 还在为跨语言语法迁移而烦恼吗?今天我要分享一个超级实用的解决…

作者头像 李华
网站建设 2026/4/25 3:44:53

windows著名漏洞——内核提权漏洞

引言:隐匿在系统深处的“万能钥匙” 我们要探讨的是一个既专业又紧迫的议题——内核提权漏洞。在数字世界的底层,存在着一种特殊的“万能钥匙”,它能打开计算机系统最核心的保险库,让攻击者获得至高无上的控制权。这种钥匙并非实体…

作者头像 李华
网站建设 2026/5/5 12:02:53

Sossoldi财富管理应用:7步完成全平台部署的终极指南

Sossoldi财富管理应用:7步完成全平台部署的终极指南 【免费下载链接】sossoldi "Sossoldi" is a wealth management / personal finance / Net Worth tracking app, made with Flutter. 项目地址: https://gitcode.com/GitHub_Trending/so/sossoldi …

作者头像 李华
网站建设 2026/5/2 4:24:16

学术成果相似度偏高?五个创新方法确保顺利通过审核

嘿,大家好!我是AI菌。今天咱们来聊聊一个让无数学生头疼的问题:论文重复率飙到30%以上怎么办?别慌,我这就分享5个实用降重技巧,帮你一次搞定,轻松压到合格线以下。这些方法都是我亲身试验过的&a…

作者头像 李华
网站建设 2026/5/6 20:05:01

19、文档管理全流程指南

文档管理全流程指南 在任何出版部门的工作中,制定准确且实际可行的时间表,并在项目进行过程中对其进行调整,是一项颇具挑战性的任务。下面将为大家详细介绍文档管理中的调度安排、文档流程等关键内容。 调度安排 在文档项目中,准确预估各项任务所需时间至关重要。以下是…

作者头像 李华