一文看懂Proxy与Object.defineProperty深度解析 - JavaScript的拦截艺术

一文看懂Proxy与Object.defineProperty深度解析 - JavaScript的拦截艺术
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

一文看懂Proxy与Object.defineProperty深度解析 - JavaScript的拦截艺术

1. 前言

在前端开发中,需要对对象属性进行拦截、监听或动态处理时,常会用到两种原生 API:Object.definePropertyProxy对象属性拦截是实现响应式编程、数据验证和代理模式的核心技术。ES5 引入了 Object.defineProperty,为对象属性提供了基础拦截能力。而 ES6 引入的 Proxy 则彻底改变了游戏规则,提供了更强大、更灵活的拦截机制。

本章节博主将从原理、使用方式、性能和兼容性等角度,详解两者的区别,并通过实际案例展示它们在现代前端开发中的应用。


2. 背景与原理

2.1 Object.defineProperty

推出时间:ES5

原理:在已有对象上为单个属性添加或修改访问器(getter/setter),只能拦截对该属性的读取与写入

基础语法与使用

const obj ={ name:'Alice'}; Object.defineProperty(obj,'age',{ enumerable:true,// 可枚举 configurable:true,// 可配置get(){ console.log('获取 age 属性');returnthis._age ||18;},set(value){ console.log('设置 age 属性');if(value <0)thrownewError('年龄不能为负');this._age = value;}}); console.log(obj.age);// 获取 age 属性 → 18 obj.age =25;// 设置 age 属性 console.log(obj.age);// 获取 age 属性 → 25

核心特点

属性级拦截:只能拦截特定属性的读写操作
需预先定义:必须在属性访问前定义拦截器
直接修改对象:会修改原始对象的结构
Vue2 的响应式基础:Vue2 使用它实现数据响应式

数组处理的局限性

const arr =[1,2,3]; arr.forEach((_, index)=>{ Object.defineProperty(arr, index,{get(){ console.log(`获取 index ${index}`);returnthis[`_${index}`];},set(value){ console.log(`设置 index ${index}`);this[`_${index}`]= value;}});}); arr[0]=10;// 设置 index 0 console.log(arr[1]);// 获取 index 1 → 2// 但无法检测以下操作: arr.push(4);// 无拦截 arr.length =0;// 无拦截

2.2 Proxy

推出时间:ES6

原理:创建一个“代理”对象,所有对原对象的操作都会先经过代理,再由 handler 中对应的 trap(陷阱)方法处理

基础语法与使用

const target ={ name:'Bob', age:30};const handler ={get(target, prop){ console.log(`读取属性: ${prop}`);return Reflect.get(target, prop);},set(target, prop, value){ console.log(`设置属性: ${prop} = ${value}`);return Reflect.set(target, prop, value);}};const proxy =newProxy(target, handler); console.log(proxy.name);// 读取属性: name → Bob proxy.age =31;// 设置属性: age = 31

核心特点

对象级拦截:拦截整个对象的所有操作
13 种拦截类型:支持 get, set, has, deleteProperty 等
非侵入式:不修改原始对象,创建代理对象
动态代理:可在运行时创建和修改

完整的数组拦截

const arrayHandler ={get(target, prop){if(prop ==='push'){returnfunction(...args){ console.log('数组 push 操作:',...args);returnArray.prototype.push.apply(target, args);};}return Reflect.get(target, prop);}};const arr =[1,2,3];const proxyArr =newProxy(arr, arrayHandler); proxyArr.push(4);// 数组 push 操作: 4 console.log(proxyArr);// [1, 2, 3, 4]

3. 使用方式对比

特性definePropertyProxy
拦截粒度单个属性整个对象
支持的拦截类型getsetgetsethasdeleteProperty
ownKeysapplyconstruct
后续新增属性是否拦截否,需要手动再定义是,代理对象创建后对新增属性自动生效
性能相对更轻量(只在目标属性上做拦截)额外一层代理,性能开销更大
兼容性IE9+现代浏览器,IE 不支持

4. 实战应用案例

4.1 Vue2 响应式原理 (defineProperty)

functiondefineReactive(obj, key, val){ Object.defineProperty(obj, key,{get(){ console.log(`获取 ${key}: ${val}`);return val;},set(newVal){ console.log(`设置 ${key}: ${newVal}`); val = newVal;}});}const vue2Data ={};defineReactive(vue2Data,'message','Hello Vue2'); vue2Data.message ='Updated';// 设置 message: Updated

4.2 Vue3 响应式原理 (Proxy)

functionreactive(target){returnnewProxy(target,{get(target, key){ console.log(`获取 ${String(key)}`);return Reflect.get(target, key);},set(target, key, value){ console.log(`设置 ${String(key)} = ${value}`);return Reflect.set(target, key, value);}});}const vue3Data =reactive({ message:'Hello Vue3'}); vue3Data.message ='Updated';// 设置 message = Updated

4.3 高级验证器实现 (Proxy)

const validator ={set(target, prop, value){if(prop ==='age'){if(typeof value !=='number')thrownewTypeError('年龄必须是数字');if(value <0)thrownewRangeError('年龄不能为负数');}if(prop ==='email'){const emailRegex =/^[^\s@]+@[^\s@]+\.[^\s@]+$/;if(!emailRegex.test(value))thrownewError('邮箱格式无效');}return Reflect.set(target, prop, value);}};const user =newProxy({}, validator); user.age =25;// 成功 user.email ='[email protected]';// 成功try{ user.age =-5;// 抛出错误: 年龄不能为负数}catch(e){ console.error(e.message);}

5. 深度监听实现对比

5.1 defineProperty 深度监听

functionobserve(obj){if(typeof obj !=='object'|| obj ===null)return; Object.keys(obj).forEach(key=>{let value = obj[key];observe(value);// 递归监听 Object.defineProperty(obj, key,{get(){ console.log(`获取 ${key}`);return value;},set(newVal){if(newVal === value)return;observe(newVal);// 监听新值 console.log(`设置 ${key} = ${newVal}`); value = newVal;}});});}const data ={ user:{ name:'Alice'}};observe(data); data.user.name ='Bob';// 获取 user → 设置 user.name = Bob

5.2 Proxy 深度监听

functiondeepProxy(target){if(typeof target ==='object'&& target !==null){for(const key in target){if(typeof target[key]==='object'){ target[key]=deepProxy(target[key]);}}returnnewProxy(target,{get(target, prop){ console.log(`读取 ${prop}`);return Reflect.get(target, prop);},set(target, prop, value){if(typeof value ==='object'){ value =deepProxy(value);} console.log(`设置 ${prop} = ${value}`);return Reflect.set(target, prop, value);}});}return target;}const data =deepProxy({ user:{ name:'Alice'}}); data.user.name ='Bob';// 读取 user → 设置 name = Bob

6. 场景与选型建议

虽然 Proxy 相较于 Object.defineProperty 具备更高的性能以及更多的支持,但是在某些场景下 Object.defineProperty 还是有必要的,博主总结如下:

场景推荐方案
只需对少数已知属性监听,且需兼容 IE9+Object.defineProperty
需要对大量或不确定属性统一拦截Proxy
需要拦截 deleteinownKeysProxy
性能敏感、拦截量少Object.defineProperty
现代应用,无需兼容 IEProxy

7. 结语

  • Object.defineProperty 简单、兼容性好,但只能逐个属性配置,难以一次性拦截整个对象。
  • Proxy 功能强大、拦截面广,适合做状态管理、数据双向绑定、权限控制等高级场景,但需要考虑兼容性与性能开销。

随着浏览器支持度的提高,Proxy 正成为越来越主流的解决方案。Vue3 的响应式系统全面转向 Proxy 也印证了这一趋势。然而,defineProperty 在特定场景下仍有其价值,特别是在需要支持旧版浏览器或进行精细属性控制时。

希望本文能帮小伙伴们理清两者差异,并在开发中快速落地合适的方案。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家一键三连给博主一点点鼓励!


前端技术专栏回顾:

01【前端技术】 ES6 介绍及常用语法说明
02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解
03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决
05 前端AJAX请求上传下载进度监控指南详解与完整代码示例
06 TypeScript 进阶指南 - 使用泛型与keyof约束参数
07 前端实现视频文件动画帧图片提取全攻略 - 附完整代码样例
08 前端函数防抖(Debounce)完整讲解 - 从原理、应用到完整实现
09 JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践
10 前端图片裁剪上传全流程详解:从预览到上传的完整流程
11 前端大文件分片上传详解 - Spring Boot 后端接口实现
12 前端实现图片防盗链技术详解 - 原理分析与SpringBoot解决方案
13 前端拖拽排序实现详解:从原理到实践 - 附完整代码
14 前端Base64格式文件上传详解:原理、实现与最佳实践

Read more

【C++】如何快速实现一棵支持key或key-value的二叉搜索树?关键技巧一文掌握!

【C++】如何快速实现一棵支持key或key-value的二叉搜索树?关键技巧一文掌握!

🎬 个人主页:MSTcheng · ZEEKLOG 🌱 代码仓库 :MSTcheng · Gitee 🔥 精选专栏: 《C语言》 《数据结构》 《C++由浅入深》 💬座右铭:路虽远行则将至,事虽难做则必成! 前言:在前面的文章中我们向大家介绍了一些序列式容器,比如:basic_string、vector、deque、list等。而本篇文章我们将要进入树形容器——二叉搜索树的学习。 文章目录 * 一、二叉搜索树的认识 * 1.1二叉搜索树的概念 * 1.2二叉搜索树的性能分析 * 二、二叉搜索树的实现 * 2.1二叉搜索树的整体框架 * 2.2二叉搜索树的插入 * 2.3二叉搜索树的查找 * 2.4二叉树的删除 * 三、二叉搜索树key和value的使用场景 * 四、总结 一、二叉搜索树的认识 1.1二叉搜索树的概念 二叉搜索树(

By Ne0inhk
【C++笔记】STL知识铺垫

【C++笔记】STL知识铺垫

前言:          在前面的学习中,我们已经掌握了C++的基础语法和编程概念,本文将深入探讨C++标准库的使用,并详细介绍迭代器、auto关键字以及范围for循环等相关知识。          一、STL简介          1.1 什么是STL          STL(Standard Template Library,标准模板库)是C++标准库的核心组成部分,它不仅提供了可复用的组件库,更是一个集成了高效数据结构与算法的软件框架。          1.2 STL的六大组件          由于历史原因,string 类型先于 STL 出现,STL 后来由惠普实验室开发并开源,因此人们通常不将 string 归入 STL 范畴。                   二、迭代器                  迭代器(Iterator)是 C++ STL 中最精妙的设计之一,如果把 STL 的容器比作各种不同类型的仓库(数组、链表、

By Ne0inhk
计算机基础知识总结(八股文总结----计算机网络、操作系统、数据库、c++、数据结构与算法)

计算机基础知识总结(八股文总结----计算机网络、操作系统、数据库、c++、数据结构与算法)

一、操作系统 0.内存管理 01.什么是虚拟内存?为什么需要虚拟内存? 虚拟内存为程序提供比实际物理内存更大的内存空间,同时提高内存管理的灵活性和系统的多任务处理能力。虚拟地址空间就是进程所能看到的内存空间,这段空间是连续的、独立的,实际地址空间则是内存上的空间,这段是所有进程共享的、有限的空间。虚拟内存就是把实际地址空间映射到虚拟地址空间的技术,这样就实现了内存隔离、内存扩展、物理内存管理、页面交换等技术。内存隔离就是每个进程都有自己的虚拟地址空间,因此一个进程无法访问另一个进程的内存。内存扩展就是虚拟内存让每个进程拥有比实际大的内存空间地址,可以处理更多的数据、更大的进程。物理内存管理,内存空间不足时把不常用的数据转移到硬盘上,释放内存,以助于更多进程使用。页面交换,进程可能会造成外部内存碎片,可能会导致内存空间不足,这时把不常用的数据交换到硬盘上,再交换回来,就能消除内存碎片,之前技术是内存分段,现在都是内存分页,一页或几页的内存交换就能解决内存不足的问题,而且效率高,内存分段的大数据在硬盘上读取速度慢。 02.什么是内存分段和分页?作用是什么? 内存分段是将一个程序

By Ne0inhk
C++:继承

C++:继承

Hello大家好! 很高兴与大家见面! 给生活添点快乐,开始今天的编程之路。 我的博客:<但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C++ 欢迎点赞,关注 目录   一 继承的概念及定义        1.1继承的概念        1.2继承的定义               1.2.1定义格式               1.2.2类继承基类方式改变对应成员访问⽅式的变化               1.2.3  继承类模板【类继承类似】      二 基类和派⽣类间的转换          2.1不同的转换方式                 2.1.1会产生临时变量                 2.1.2不会产生临时变量(基类和派⽣类间的转换)                         2.1.2.1不会产生临时变量(

By Ne0inhk