【秋招备战】 Object.defineProperty vs Proxy

【秋招备战】 Object.defineProperty vs Proxy

Simon Lv2

如果你曾经好奇 Vue2 和 Vue3 的响应式原理有什么区别,或者在面试中被问到”如何实现数据劫持”时一脸懵逼,那么恭喜你,这篇文章就是你的救星!

📖 开篇:一个对象的自我修养

在 JavaScript 的世界里,对象就像是一个个小盒子,我们可以往里面放东西(属性),也可以取东西。但是有一天,产品经理突然说:”我想知道用户什么时候打开盒子,什么时候往里面放东西!”

这时候,Object.definePropertyProxy 就闪亮登场了——它们就是对象世界的”监控摄像头”。

🎭 第一幕:Object.defineProperty - 老牌特工

基本概念

Object.defineProperty() 是 ES5 引入的方法,它可以精确地定义或修改对象的属性,并且可以控制属性的行为。就像给对象的某个属性装上了一个”智能锁”。

基础语法

Object.defineProperty(obj, prop, descriptor)
  • obj:要定义属性的对象
  • prop:要定义或修改的属性名
  • descriptor:属性描述符对象

属性描述符的两种形态

属性描述符分为两种:数据描述符存取描述符(不能同时使用)。

1. 数据描述符

const person = {};

Object.defineProperty(person, 'name', {
value: '张三', // 属性值
writable: true, // 是否可写(默认 false)
enumerable: true, // 是否可枚举(默认 false)
configurable: true // 是否可配置(默认 false)
});

console.log(person.name); // 张三
person.name = '李四'; // 因为 writable: true,所以可以修改
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; // 🔍 有人在读取 magicProp!
obj.magicProp = '新值'; // ✏️ 有人在设置 magicProp 为: 新值

实战案例:简易版 Vue2 响应式

// 模拟 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; // 📊 收集依赖: count
data.count = 1; // 🔄 触发更新: count = 1

Object.defineProperty 的局限性

  1. 无法监听数组索引的变化
const arr = [1, 2, 3];
observe(arr);
arr[0] = 999; // 无法触发 setter
arr.push(4); // 无法监听
  1. 必须遍历对象的每个属性
// 如果有嵌套对象,需要递归处理
const complexObj = {
user: {
name: '张三',
address: {
city: '北京'
}
}
};
// 需要递归遍历所有层级
  1. 无法监听新增属性
const obj = { a: 1 };
observe(obj);
obj.b = 2; // 新增属性 b 不会被监听

🦸 第二幕: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) {}, // in 操作符
deleteProperty(target, property) {}, // delete 操作
ownKeys(target) {}, // Object.keys()
// ... 还有其他拦截方法
};

基础示例

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; // 🔍 正在访问属性: name
personProxy.age = 26; // ✏️ 正在设置属性: age = 26
personProxy.hobby = '编程'; // ✏️ 正在设置属性: 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; // 修改数组: arr[0] = 999
arrProxy.push(4); // 访问数组: arr[push]
// 访问数组: arr[length]
// 修改数组: arr[3] = 4
// 修改数组: arr[length] = 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'; // ✅ 设置 age = 25(自动转数字)
smartObj.phone = '138-0000-0000'; // ✅ 设置 phone = 13800000000(自动格式化)
// smartObj.age = '一百岁'; // 抛出错误!

⚔️ 终极对决:Object.defineProperty vs Proxy

特性 Object.defineProperty Proxy
兼容性 ES5,兼容 IE9+ ES6,不支持 IE
监听范围 单个属性 整个对象
监听数组索引 ❌ 不支持 ✅ 支持
监听新增属性 ❌ 需要额外处理 ✅ 自动支持
监听删除属性 ❌ 不支持 ✅ 支持
性能 需要递归遍历,初始化慢 懒处理,按需拦截
操作原对象 直接修改原对象 不修改原对象,返回代理

🎯 面试高频考点

1. Vue2 vs Vue3 响应式原理

// Vue2 风格(简化版)
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;
}

// Vue3 风格(简化版)
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; // count 变化了: 1

🚀 总结:选择的智慧

  • 使用 Object.defineProperty 的场景
    • 需要兼容旧浏览器(IE9+)
    • 只需要监听已知的、固定的属性
    • 项目已经大量使用且稳定运行
  • 使用 Proxy 的场景
    • 不需要考虑 IE 兼容性
    • 需要监听数组变化
    • 需要监听动态新增的属性
    • 需要实现更复杂的拦截逻辑
    • 新项目优先选择

💡 最后的小贴士

记住这个口诀:

  • defineProperty 是保安:只能看着指定的几个门(属性)
  • Proxy 是管家:整个房子(对象)的事都管

面试时如果被问到,记得提到:

  1. 两者的本质区别(属性级 vs 对象级)
  2. Vue2 到 Vue3 的升级原因
  3. 各自的优缺点和适用场景

现在,你已经掌握了 JavaScript 中两个最强大的”对象监控”技术。下次面试官问你”如何实现数据双向绑定”时,你可以自信地说:”这题我会,而且我还能告诉你两种方法的优缺点!”


Happy Coding! 愿你的秋招之路一帆风顺! 🎉

  • 标题: 【秋招备战】 Object.defineProperty vs Proxy
  • 作者: Simon
  • 创建于 : 2025-08-07 11:39:40
  • 更新于 : 2025-08-07 15:37:39
  • 链接: https://www.simonicle.cn/2025/08/07/【秋招备战】属性劫持/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论