news 2026/9/3 1:31:18

你怎么理解 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/9/2 2:49:15

windows著名漏洞——智能屏幕绕过漏洞 (CVE-2023-36025)

今天,我将与大家深入探讨一个在2023年末引起广泛关注的网络安全事件——Windows智能屏幕绕过漏洞,编号CVE-2023-36025。这个漏洞不仅暴露了现代操作系统安全机制的脆弱性,更向我们提出了关于数字时代安全防御本质的深刻问题。在接下来的时间里…

作者头像 李华
网站建设 2026/9/2 21:50:41

windows著名漏洞——永恒之蓝

永恒之蓝漏洞指2017年被公开利用的一个针对Windows系统SMBv1协议的远程代码执行漏洞。其核心是NSA开发的攻击工具“EternalBlue”(永恒之蓝)所利用的漏洞。以下是其关键信息的总结:项目详细说明官方编号MS17-010(微软安全公告&…

作者头像 李华
网站建设 2026/9/1 22:51:12

4、技术文档编写指南

技术文档编写指南 1. 代码示例 代码示例是计算机程序的一部分,用于在文档中辅助解释主题。它可以包含用户输入与计算机响应之间的对话,也可以仅包含用户输入到计算机的代码。 由于编程代码具有精确性,必须原样复制代码,即使代码存在拼写、语法或标点方面的语言错误。若有…

作者头像 李华
网站建设 2026/9/2 8:55:00

MkDocs快速上手:构建专业文档的完整实践指南

MkDocs快速上手:构建专业文档的完整实践指南 【免费下载链接】mkdocs Project documentation with Markdown. 项目地址: https://gitcode.com/gh_mirrors/mk/mkdocs 还在为项目文档的编写和维护而烦恼吗?MkDocs让技术文档编写变得简单高效。作为一…

作者头像 李华
网站建设 2026/9/2 9:42:03

科研论文重复率不合格?五种智能改写方案助你达标

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

作者头像 李华
网站建设 2026/9/2 23:31:05

15、技术文档中的插图与图形用户界面写作指南

技术文档中的插图与图形用户界面写作指南 1. 常见插图类型 在技术文档中,插图是传达信息的重要工具。以下是一些常见的插图类型: - 图表(Diagrams) :涵盖范围广泛,从简单的流程图和演示辅助工具到复杂的架构图都属于此类。 - 在线图形(Online Graphics) :包括…

作者头像 李华