深度监听实现
- 递归遍历:在使用
Object.defineProperty()
对对象进行监听时,对对象的每个属性值进行判断,如果属性值还是一个对象,则递归调用 Object.defineProperty()
对其内部属性进行监听。例如:
function observe(obj) {
if (!obj || typeof obj!== 'object') {
return;
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
});
}
function defineReactive(obj, key, val) {
observe(val); // 深度监听
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal!== val) {
observe(newVal); // 新值如果是对象也要深度监听
val = newVal;
}
}
});
}
- Vue.set 或 this.$set:Vue 提供了
Vue.set
或 this.$set
方法,当给对象添加新属性时,可以使用该方法触发视图更新。在深度监听中,用于处理新增的深层属性。例如:
import Vue from 'vue';
let data = {
obj: {
a: 1
}
};
// 给深层对象添加属性
Vue.set(data.obj, 'b', 2);
性能问题
- 大量计算:在深度监听中,递归遍历对象属性会带来大量的计算。尤其是对于嵌套层次非常深且属性众多的对象,每一次属性的变化都可能导致整个递归流程的重新执行,计算量呈指数级增长。
- 内存占用:大量的
Object.defineProperty()
调用会创建很多的 getter 和 setter 函数,这些函数会占用额外的内存空间。随着监听数据量的增加,内存占用会不断上升,可能导致内存泄漏或程序性能下降。
优化方法
- 防抖与节流:对于频繁触发的属性变化,可以使用防抖或节流技术。防抖是指在一定时间内,多次触发同一事件,只执行最后一次;节流则是指在一定时间间隔内,只允许触发一次事件。例如,使用 Lodash 的
debounce
或 throttle
函数。
import _ from 'lodash';
let updateFn = _.debounce(() => {
// 处理数据更新逻辑
}, 300);
// 在属性 setter 中调用 updateFn
- 取消不必要的监听:在数据不再使用时,及时取消监听。例如,在组件销毁时,通过维护一个监听列表,将对应的
Object.defineProperty()
监听移除,释放内存。
- 虚拟 DOM 与批量更新:Vue 内部通过虚拟 DOM 实现了高效的 DOM 更新。在数据变化时,Vue 会批量收集变化,然后一次性更新 DOM,而不是每次数据变化都立即更新 DOM,从而减少了 DOM 操作带来的性能开销。