【秋招备战】 Object.defineProperty vs Proxy
如果你曾经好奇 Vue2 和 Vue3 的响应式原理有什么区别,或者在面试中被问到”如何实现数据劫持”时一脸懵逼,那么恭喜你,这篇文章就是你的救星!
📖 开篇:一个对象的自我修养
在 JavaScript 的世界里,对象就像是一个个小盒子,我们可以往里面放东西(属性),也可以取东西。但是有一天,产品经理突然说:”我想知道用户什么时候打开盒子,什么时候往里面放东西!”
这时候,Object.defineProperty 和 Proxy 就闪亮登场了——它们就是对象世界的”监控摄像头”。
🎭 第一幕:Object.defineProperty - 老牌特工
基本概念
Object.defineProperty() 是 ES5 引入的方法,它可以精确地定义或修改对象的属性,并且可以控制属性的行为。就像给对象的某个属性装上了一个”智能锁”。
基础语法
Object.defineProperty(obj, prop, descriptor)
|
obj:要定义属性的对象
prop:要定义或修改的属性名
descriptor:属性描述符对象
属性描述符的两种形态
属性描述符分为两种:数据描述符和存取描述符(不能同时使用)。
1. 数据描述符
const person = {};
Object.defineProperty(person, 'name', { value: '张三', writable: true, enumerable: true, configurable: true });
console.log(person.name); person.name = '李四'; console.log(person.name);
|
2. 存取描述符(重点!)
这就是实现”数据劫持”的核心:
let internalValue = '我是内部值'; const obj = {};
Object.defineProperty(obj, 'magicProp', { enumerable: true, configurable: true, get() { console.log('🔍 有人在读取 magicProp!'); return internalValue; }, set(newValue) { console.log(`✏️ 有人在设置 magicProp 为: ${newValue}`); internalValue = newValue; } });
obj.magicProp; obj.magicProp = '新值';
|
实战案例:简易版 Vue2 响应式
function observe(obj) { Object.keys(obj).forEach(key => { let internalValue = obj[key]; Object.defineProperty(obj, key, { get() { console.log(`📊 收集依赖: ${key}`); return internalValue; }, set(newValue) { console.log(`🔄 触发更新: ${key} = ${newValue}`); internalValue = newValue; } }); }); }
const data = { count: 0, message: 'Hello' }; observe(data);
data.count; data.count = 1;
|
Object.defineProperty 的局限性
- 无法监听数组索引的变化
const arr = [1, 2, 3]; observe(arr); arr[0] = 999; arr.push(4);
|
- 必须遍历对象的每个属性
const complexObj = { user: { name: '张三', address: { city: '北京' } } };
|
- 无法监听新增属性
const obj = { a: 1 }; observe(obj); obj.b = 2;
|
🦸 第二幕:Proxy - 新时代的全能管家
基本概念
Proxy 是 ES6 引入的新特性,它可以创建一个对象的代理,从而实现对这个对象的基本操作的拦截和自定义。如果说 Object.defineProperty 是给属性装监控,那 Proxy 就是给整个对象配了一个全能管家。
基础语法
const proxy = new Proxy(target, handler)
|
target:要代理的目标对象
handler:一个对象,定义了各种拦截行为
13 种拦截方法
Proxy 支持拦截 13 种操作,常用的有:
const handler = { get(target, property, receiver) {}, set(target, property, value, receiver) {}, has(target, property) {}, deleteProperty(target, property) {}, ownKeys(target) {}, };
|
基础示例
const person = { name: '张三', age: 25 };
const personProxy = new Proxy(person, { get(target, property) { console.log(`🔍 正在访问属性: ${property}`); return target[property]; }, set(target, property, value) { console.log(`✏️ 正在设置属性: ${property} = ${value}`); target[property] = value; return true; } });
personProxy.name; personProxy.age = 26; personProxy.hobby = '编程';
|
高级应用:数组的完美监听
const arr = [1, 2, 3]; const arrProxy = new Proxy(arr, { get(target, property) { console.log(`访问数组: arr[${property}]`); return target[property]; }, set(target, property, value) { console.log(`修改数组: arr[${property}] = ${value}`); target[property] = value; return true; } });
arrProxy[0] = 999; arrProxy.push(4);
|
实战案例:实现一个智能对象
const smartObj = new Proxy({}, { set(target, property, value) { if (property === 'age') { value = Number(value); if (isNaN(value) || value < 0 || value > 150) { throw new Error('年龄必须是 0-150 之间的数字'); } } if (property === 'phone') { value = value.replace(/[^0-9]/g, ''); if (value.length !== 11) { throw new Error('手机号必须是 11 位'); } } console.log(`✅ 设置 ${property} = ${value}`); target[property] = value; return true; } });
smartObj.age = '25'; smartObj.phone = '138-0000-0000';
|
⚔️ 终极对决:Object.defineProperty vs Proxy
| 特性 |
Object.defineProperty |
Proxy |
| 兼容性 |
ES5,兼容 IE9+ |
ES6,不支持 IE |
| 监听范围 |
单个属性 |
整个对象 |
| 监听数组索引 |
❌ 不支持 |
✅ 支持 |
| 监听新增属性 |
❌ 需要额外处理 |
✅ 自动支持 |
| 监听删除属性 |
❌ 不支持 |
✅ 支持 |
| 性能 |
需要递归遍历,初始化慢 |
懒处理,按需拦截 |
| 操作原对象 |
直接修改原对象 |
不修改原对象,返回代理 |
🎯 面试高频考点
1. Vue2 vs Vue3 响应式原理
function reactive2(obj) { Object.keys(obj).forEach(key => { let value = obj[key]; Object.defineProperty(obj, key, { get() { return value; }, set(newValue) { value = newValue; } }); }); return obj; }
function reactive3(obj) { return new Proxy(obj, { get(target, key) { return target[key]; }, set(target, key, value) { target[key] = value; return true; } }); }
|
2. 如何实现一个简单的观察者模式?
class Observer { constructor(data) { this.data = data; this.callbacks = {}; this.observe(); } observe() { const that = this; this.proxy = new Proxy(this.data, { set(target, property, value) { target[property] = value; if (that.callbacks[property]) { that.callbacks[property].forEach(cb => cb(value)); } return true; } }); return this.proxy; } subscribe(property, callback) { if (!this.callbacks[property]) { this.callbacks[property] = []; } this.callbacks[property].push(callback); } }
const obs = new Observer({ count: 0 }); const data = obs.observe();
obs.subscribe('count', (value) => { console.log(`count 变化了: ${value}`); });
data.count = 1;
|
🚀 总结:选择的智慧
- 使用 Object.defineProperty 的场景:
- 需要兼容旧浏览器(IE9+)
- 只需要监听已知的、固定的属性
- 项目已经大量使用且稳定运行
- 使用 Proxy 的场景:
- 不需要考虑 IE 兼容性
- 需要监听数组变化
- 需要监听动态新增的属性
- 需要实现更复杂的拦截逻辑
- 新项目优先选择
💡 最后的小贴士
记住这个口诀:
- defineProperty 是保安:只能看着指定的几个门(属性)
- Proxy 是管家:整个房子(对象)的事都管
面试时如果被问到,记得提到:
- 两者的本质区别(属性级 vs 对象级)
- Vue2 到 Vue3 的升级原因
- 各自的优缺点和适用场景
现在,你已经掌握了 JavaScript 中两个最强大的”对象监控”技术。下次面试官问你”如何实现数据双向绑定”时,你可以自信地说:”这题我会,而且我还能告诉你两种方法的优缺点!”
Happy Coding! 愿你的秋招之路一帆风顺! 🎉