Vue的响应式原理是如何工作的

Vue的响应式原理是如何工作的

💡 原文英文,约2000词,阅读约需8分钟。
📝

内容提要

Vue响应式原理中,Dep代表依赖(响应式数据),Watcher为订阅者(watch、computed、渲染函数)。Watcher运行前设置Dep.target,访问数据时getter通过dep.depend()建立订阅;数据更新时setter调用dep.notify()通知Watcher更新,实现视图自动刷新。Observer负责递归处理响应式对象。

🔎

延伸解读

Dep与Watcher的多对多关系

Dep和Watcher之间通过subs和deps属性维护多对多关系。Dep的subs数组存储所有订阅该依赖的Watcher,Watcher的deps数组存储其订阅的所有Dep。这种双向记录确保当一方被清除时,能及时更新另一方,避免内存泄漏。例如,当Watcher被销毁时,会从所有相关Dep的subs中移除自己;当Dep不再需要时,也会清理其subs中的Watcher引用。这种设计是Vue响应式系统高效运行的基础。

依赖收集的时机与Dep.target

Vue在Watcher的get方法中通过pushTarget设置Dep.target,然后执行回调函数(如渲染函数)。当回调中访问响应式数据时,会触发getter,此时Dep.target存在,getter调用dep.depend()将当前Watcher添加到Dep的subs中。这一机制确保只有被访问的数据才会建立依赖关系,避免不必要的更新。Dep.target在get方法结束后通过popTarget恢复,保证依赖收集的准确性。

更新通知与异步队列

当响应式数据变化时,setter调用dep.notify(),遍历subs中的Watcher并调用其update方法。update方法根据Watcher类型处理:如果是lazy(如computed),仅标记dirty;如果是sync,立即执行run;否则将Watcher推入异步更新队列(queueWatcher),在nextTick中批量执行。异步更新避免重复渲染,提升性能。run方法中会重新收集依赖并执行回调,完成视图更新。

Observer的递归处理与__ob__属性

Observer负责将对象或数组转换为响应式。对于对象,调用walk方法遍历每个属性,使用defineReactive定义getter/setter;对于数组,调用observeArray方法处理每个元素。被观察的对象会添加__ob__属性,指向Observer实例,作为已观察的标记。__ob__属性还用于在数组变更时通知依赖,以及避免重复观察。Observer是响应式系统的入口,确保嵌套数据也能被追踪。

❓

Q&A

Vue响应式原理中Dep和Watcher分别是什么?

Dep代表依赖,是经过响应式处理的数据;Watcher是订阅者,包括watch函数、computed函数和模板渲染函数。Dep有subs数组存放订阅者,Watcher有deps数组存放依赖,两者形成多对多关系。

Vue如何知道哪些Watcher依赖于某个数据?

Vue在Watcher运行回调函数前,通过Dep.target记录当前Watcher。当回调函数访问响应式数据时,会触发getter,在getter中通过dep.depend()将当前Watcher添加到Dep的订阅者列表中,从而建立依赖关系。

Vue数据更新时如何通知视图刷新?

当响应式数据更新时,会触发setter,setter中调用dep.notify(),遍历Dep的subs数组,通知所有订阅者(Watcher)更新。如果订阅者是渲染函数,则会触发组件重新渲染,从而更新视图。

Observer在Vue响应式系统中起什么作用?

Observer负责递归地将对象或数组的所有属性转换为响应式。它通过walk方法处理对象,通过observeArray方法处理数组,并为每个属性调用defineReactive来定义getter和setter。被观察的对象会带有__ob__属性作为标记。

defineReactive函数是如何实现响应式的?

defineReactive为对象的每个属性创建一个Dep实例,并使用Object.defineProperty重写属性的getter和setter。getter中通过dep.depend()收集依赖,setter中通过dep.notify()通知更新。同时会处理原有的getter/setter,并对子对象递归观察。

Vue的Watcher有哪些类型?它们分别在什么时候创建?

Watcher有三种类型:watch、computed和渲染函数。渲染Watcher在mountComponent中创建;computed Watcher在initComputed中创建;用户自定义watch在$watch中创建。它们都是订阅者,用于响应数据变化。

🏷️

标签

➡️

继续阅读