一、什么是响应式数据
响应式数据是指当数据发生变化时,相关的视图或组件会自动更新,保持与数据的同步。这样的设计使得开发者能够更方便地管理和更新数据,无需手动操作DOM或显式地更新视图。当数据发生变化时,所有使用该数据的地方都会自动更新。
二、观察者模式
-
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
-
在vue中,可以在模板、计算属性、侦听器等地方使用定义在data中的数据,可以理解为这三者都观察着data中对应数据的变化。模板是渲染watcher、计算属性是计算属性watcher、侦听器是用户watcher。
-
在某个属性发生变化时,需要去通知使用该属性的所有地方进行更新,所以该属性需要将所有依赖该属性的地方提前收集起来。这个收集所有依赖的容器称为dep,dep有两个方法:addSub(收集依赖)、notify(派发更新)。addSub用来保存watcher,notify用来通知watcher更新。
// 观察者 class Watcher { constructor(fn) { this.getter = fn; } // 更新函数,用于触发更新 update() { this.getter(); } } // 依赖收集器 class Dep { constructor() { // 用来保存 watcher this.subs = []; } // 添加 watcher addSub(watcher) { this.subs.push(watcher); } // 属性更新,通知所有 watcher 执行对应的更新方法 notify() { this.subs.forEach((watcher) => { watcher.update(); }); } } const watcher1 = new Watcher(() => { console.log("更新Dom"); }); const watcher2 = new Watcher(() => { console.log("更新计算属性"); }); const dep = new Dep(); dep.addSub(watcher1); dep.addSub(watcher2); // 当属性变化时,调用该属性的依赖收集器的notify方法,通知所有watcher更新 dep.notify();
三、如何实现响应式数据
数据劫持
:即在数据对象身上定义一些特殊的行为。在Vue2中,是通过Object.defineProperty
方法实现的。这个方法允许我们为对象的属性定义getter和setter。- 递归转换:如果数据对象是一个复杂的结构(例如,包含其他对象),我们需要递归地对其所有属性进行相同的转换,以确保无论数据如何嵌套,都能追踪其变化。
- 依赖收集:当属性被访问时(通过getter),需要记录下哪些视图或组件依赖于这个属性。通常是通过一个依赖收集器或观察者模式实现的。每个依赖都会被添加到一个“依赖列表”中。在下一次属性被更新时,就可以通过“依赖列表”知道,需要通知哪些视图或组件去更新。
- 通知更新:当属性被修改时(通过setter),我们需要通知所有依赖于这个属性的视图或组件进行更新。这可以通过遍历依赖列表并触发它们的更新方法来实现。
- 优化性能:为了优化性能,可能需要实现一些额外的功能,如异步更新队列和批处理更新。这可以确保多个属性的变化只触发一次视图更新,而不是每次属性变化都立即更新。
- 处理数组和特殊方法:对于数组,我们需要特别处理,因为使用
Object.defineProperty
来对数组中的每一个元素实现响应式,性能很差,而且无法监听到数组长度的变化。所以Vue通过重写数组的一些方法来实现对数组变化的追踪。 - 提供API:需要提供一套API,让开发者能够方便地声明响应式数据、观察数据变化以及触发视图更新。由于新增属性和删除属性无法监控变化。Vue提供了
$set
、$delete
来实现数据的响应式。
四、Vue2实现响应式数据
- Vue2通过
Object.defineProperty
进行数据劫持,需要遍历对象为属性添加getter和setter,性能很差 - 在组件第一次挂载时,会使用某些响应式数据,因此会触发数据对应的getter方法,就可以在 getter 中进行依赖收集。在后续数据变化时,会触发setter 方法,就可以在 setter 中通知依赖进行更新
- 新增属性和删除属性监测不到。需要使用
$set
、$delete
来实现数据的响应式 - 由于数组的元素可能有很多,循环遍历为每一个元素添加getter和setter性能太差,所以Vue2不对基本类型的数组元素进行响应式处理,而是重写数组相关的方法。
- 对于ES6中新产生的Map、Set等数据结构不支持