一文看懂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. 前言
- 2. 背景与原理
-
-
- 2.1 Object.defineProperty
- 2.2 Proxy
-
- 3. 使用方式对比
- 4. 实战应用案例
-
-
- 4.1 Vue2 响应式原理 (defineProperty)
- 4.2 Vue3 响应式原理 (Proxy)
- 4.3 高级验证器实现 (Proxy)
-
- 5. 深度监听实现对比
-
-
- 5.1 defineProperty 深度监听
- 5.2 Proxy 深度监听
-
- 6. 场景与选型建议
- 7. 结语
1. 前言
在前端开发中,需要对对象属性进行拦截、监听或动态处理时,常会用到两种原生 API:Object.defineProperty
和 Proxy
。对象属性拦截 是实现响应式编程、数据验证和代理模式的核心技术。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 属性\'); return this._age || 18; }, set(value) { console.log(\'设置 age 属性\'); if (value < 0) throw new Error(\'年龄不能为负\'); this._age = value; }});console.log(obj.age); // 获取 age 属性 → 18obj.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}`); return this[`_${index}`]; }, set(value) { console.log(`设置 index ${index}`); this[`_${index}`] = value; } });});arr[0] = 10; // 设置 index 0console.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 = new Proxy(target, handler);console.log(proxy.name); // 读取属性: name → Bobproxy.age = 31; // 设置属性: age = 31
核心特点
对象级拦截:拦截整个对象的所有操作
13 种拦截类型:支持 get, set, has, deleteProperty 等
非侵入式:不修改原始对象,创建代理对象
动态代理:可在运行时创建和修改
完整的数组拦截
const arrayHandler = { get(target, prop) { if (prop === \'push\') { return function(...args) { console.log(\'数组 push 操作:\', ...args); return Array.prototype.push.apply(target, args); }; } return Reflect.get(target, prop); }};const arr = [1, 2, 3];const proxyArr = new Proxy(arr, arrayHandler);proxyArr.push(4); // 数组 push 操作: 4console.log(proxyArr); // [1, 2, 3, 4]
3. 使用方式对比
get
、set
get
、set
、has
、deleteProperty
、ownKeys
、apply
、construct
等4. 实战应用案例
4.1 Vue2 响应式原理 (defineProperty)
function defineReactive(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)
function reactive(target) { return new Proxy(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\') throw new TypeError(\'年龄必须是数字\'); if (value < 0) throw new RangeError(\'年龄不能为负数\'); } if (prop === \'email\') { const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; if (!emailRegex.test(value)) throw new Error(\'邮箱格式无效\'); } return Reflect.set(target, prop, value); }};const user = new Proxy({}, validator);user.age = 25; // 成功user.email = \'test@example.com\'; // 成功try { user.age = -5; // 抛出错误: 年龄不能为负数} catch (e) { console.error(e.message);}
5. 深度监听实现对比
5.1 defineProperty 深度监听
function observe(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 深度监听
function deepProxy(target) { if (typeof target === \'object\' && target !== null) { for (const key in target) { if (typeof target[key] === \'object\') { target[key] = deepProxy(target[key]); } } return new Proxy(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
还是有必要的,博主总结如下:
Object.defineProperty
Proxy
delete
、in
、ownKeys
等Proxy
Object.defineProperty
Proxy
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格式文件上传详解:原理、实现与最佳实践