监听javascript对象属性变化的核心方法是proxy和object.defineproperty;2. proxy是现代首选方案,能拦截属性的读取、设置、删除及数组方法等几乎所有操作;3. object.defineproperty仅能监听已存在的属性,无法监听新增属性或数组变异方法,适用于属性固定的简单场景;4. proxy通过get和set拦截实现深度监听时需递归代理嵌套对象,但存在性能开销、循环引用、对象身份变化、序列化等问题;5. 数组监听在proxy中天然支持push、pop等方法,因其内部操作会触发set拦截,而object.defineproperty需hack式重写数组方法;6. 实际开发中推荐新项目使用proxy实现响应式,老项目或兼容性要求下可沿用object.defineproperty,但应清楚其局限性;7. 构建复杂响应式系统时建议使用vue 3等成熟框架,避免重复造轮子。

在JavaScript中,监听对象属性的变化是实现数据响应式、调试或进行数据同步的关键技术。核心方法主要围绕着
Proxy
Object.defineProperty
Proxy

要监听JavaScript对象属性的变化,最强大且灵活的工具是
Proxy
get
set
deleteProperty
举个例子,如果你想在每次数据更新时都做点什么:

const data = {
name: '张三',
age: 30,
address: {
city: '北京',
street: '长安街'
},
hobbies: ['coding', 'reading']
};
const handler = {
set(target, key, value, receiver) {
console.log(`属性 '${String(key)}' 从 '${target[key]}' 变为 '${value}'`);
// 确保原始操作得以执行,否则属性不会真正更新
return Reflect.set(target, key, value, receiver);
},
get(target, key, receiver) {
console.log(`访问了属性 '${String(key)}'`);
return Reflect.get(target, key, receiver);
},
deleteProperty(target, key) {
console.log(`删除了属性 '${String(key)}'`);
return Reflect.deleteProperty(target, key);
}
};
const reactiveData = new Proxy(data, handler);
// 测试
reactiveData.age = 31; // 会触发set拦截
reactiveData.name = '李四'; // 会触发set拦截
console.log(reactiveData.age); // 会触发get拦截
// 注意:直接修改嵌套对象属性不会触发顶层Proxy的set,因为address本身没有被重新赋值
// reactiveData.address.city = '上海'; // 不会触发顶层Proxy的set,但可以被get捕捉到对address的访问
// 删除属性
delete reactiveData.age; // 会触发deleteProperty拦截
console.log(reactiveData);Proxy
push
pop
set
当然,在
Proxy
Object.defineProperty
getter
setter

function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`访问了属性 '${key}'`);
return val;
},
set(newVal) {
if (newVal === val) return;
console.log(`属性 '${key}' 从 '${val}' 变为 '${newVal}'`);
val = newVal;
}
});
}
const oldSchoolData = {
message: 'Hello World'
};
defineReactive(oldSchoolData, 'message', oldSchoolData.message);
oldSchoolData.message = 'Hello JS'; // 触发set
console.log(oldSchoolData.message); // 触发get
// 缺点:无法监听新增属性,也无法直接监听数组索引的变化
oldSchoolData.newProp = 'something'; // 不会触发任何监听Object.defineProperty
defineProperty
push
pop
Proxy
Object.defineProperty
这确实是个老生常谈的问题,但对于实际项目来说,选择哪种方式,其背后的考量远不止“新旧”那么简单。我个人觉得,如果你正在构建一个全新的、需要高度响应式的数据系统(比如一个类Vue 3的框架),或者你需要对对象操作进行全面、细粒度的拦截,那么毫无疑问,
Proxy
Proxy
Object.defineProperty
而
Object.defineProperty
getter
setter
Proxy
Proxy
Object.defineProperty
总而言之,新项目无脑选
Proxy
Object.defineProperty
Proxy
数组的监听在JavaScript中一直是个有点让人头疼的问题,尤其是在
Object.defineProperty
push
pop
splice
Object.defineProperty
而
Proxy
Proxy
push
Proxy
set
push
length
举个例子:
const myArray = [1, 2, 3];
const arrayHandler = {
set(target, key, value, receiver) {
console.log(`数组元素或属性 '${String(key)}' 发生变化:从 '${target[key]}' 变为 '${value}'`);
return Reflect.set(target, key, value, receiver);
},
deleteProperty(target, key) {
console.log(`删除了数组元素或属性 '${String(key)}'`);
return Reflect.deleteProperty(target, key);
}
};
const reactiveArray = new Proxy(myArray, arrayHandler);
reactiveArray.push(4); // 会触发set拦截器,因为push会改变length属性和新增元素
// 输出:
// 数组元素或属性 '3' 发生变化:从 'undefined' 变为 '4'
// 数组元素或属性 'length' 发生变化:从 '4' 变为 '4' (这里是因为length属性也被修改了)
reactiveArray[0] = 10; // 同样触发set拦截器
// 输出:
// 数组元素或属性 '0' 发生变化:从 '1' 变为 '10'
reactiveArray.pop(); // 也会触发set和deleteProperty拦截器
// 输出:
// 数组元素或属性 'length' 发生变化:从 '4' 变为 '3'
// 删除了数组元素或属性 '3'
console.log(reactiveArray);可以看到,无论是直接通过索引赋值,还是通过
push
pop
Proxy
Proxy
Object.defineProperty
深度监听复杂对象,也就是当对象内部嵌套了其他对象或数组时,我们希望无论哪个层级的属性发生变化都能被感知到。这在构建响应式系统时非常常见,但实践起来,确实有一些需要注意的“坑”。
Proxy
Proxy
const deepData = {
user: {
name: 'Alice',
info: {
age: 25
}
}
};
const deepHandler = {
set(target, key, value, receiver) {
console.log(`[Top Level] 属性 '${String(key)}' 变化`);
return Reflect.set(target, key, value, receiver);
}
};
const reactiveDeepData = new Proxy(deepData, deepHandler);
reactiveDeepData.user.name = 'Bob'; // 不会触发Top Level的set
// 因为你没有改变 reactiveDeepData.user 这个引用,只是改变了 user 对象内部的属性。
console.log(reactiveDeepData.user.name); // Bob要实现深度监听,你需要在每次获取到嵌套对象时,都将其也包裹成一个
Proxy
get
Proxy
function createDeepReactive(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 避免重复代理
if (obj.__isProxy__) {
return obj;
}
const handler = {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 如果获取到的是对象,且不是Proxy,就递归代理它
if (typeof res === 'object' && res !== null && !res.__isProxy__) {
return createDeepReactive(res);
}
return res;
},
set(target, key, value, receiver) {
console.log(`[Deep Reactive] 属性 '${String(key)}' 从 '${target[key]}' 变为 '${value}'`);
return Reflect.set(target, key, value, receiver);
}
// ... 其他拦截器如 deleteProperty 等
};
const proxy = new Proxy(obj, handler);
// 标记一下,防止重复代理
Object.defineProperty(proxy, '__isProxy__', {
value: true,
enumerable: false,
configurable: false
});
return proxy;
}
const deepReactiveData = createDeepReactive(deepData);
deepReactiveData.user.name = 'Charlie'; // 会触发set
deepReactiveData.user.info.age = 26; // 也会触发set
deepReactiveData.user.hobbies = ['swimming']; // 新增属性,也会触发set
console.log(deepReactiveData.user.info.age);实践中的坑:
Proxy
Proxy
get
Proxy
===
Proxy
JSON.stringify
Proxy
target
Proxy
toJSON
replacer
this
Proxy
get
this
Proxy
Reflect.get
this
Proxy.revocable
Proxy.revocable
在我看来,深度监听是响应式框架的基石,但它绝不是一个“开箱即用”的简单特性。它需要仔细权衡性能、内存和开发复杂度。很多时候,框架会采用一些优化策略,比如懒代理(只在需要时才创建嵌套对象的代理),或者通过特定的数据结构设计来避免过度递归。对于我们日常应用开发,如果不是在构建底层框架,直接使用Vue 3或React等框架提供的响应式API会是更明智的选择,它们已经帮你踩过了这些坑。
以上就是js如何监听对象属性变化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号