概述
Vue是数据驱动视图模式,数据变更后,会自动更新视图。开发很便利,不要手动管理视图更新。哪 Vue 是如何实现自动更新,也就是响应式的?
以原生 JS 为例,数据变化了需要手动获取 Dom 节点,然后使用相关 Api 去修改视图


- 如何监听数据的变化?
- 如何触发引用有变化数据的视图镜像更新?
代码实现, 一共 373 行JS代码,包含Watcher/Observer的简单实现
DEMO展示
入口代码:
new Vue({
el: '#app',
data() {
return {
message: 'hello'
}
},
render(h) {
return h('div', null, this.message)
},
})
渲染效果:

响应式更新:

监听数据变化
一般而言,对于基本类型的值是否变化,可以对比前后值是否相同。对于引用类型是否有变化,则可能需要递归判断属性是否都一致。
在 Vue 中,组件数据 data 是 Object 类型的,这样做的好处,我理解一是方便扩展属性、二是为了能方便对数据进行拦截、代理等。
针对 Object 类型的的数据,其相关key/value可以通过 数据属性/访问器属性 获取。两者可以是可以互换的:

在Vue中定义的数据结构为

data 函数的值会存储到 _data 私有属性中:

function proxy(target, sourceKey, key) {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return target[sourceKey][key]
},
set(val) {
target[sourceKey][key] = val
}
})
}
借助 getter/setter 即可感知到数据变化,而自动触发视图更新做准备
数据关联视图(依赖收集)
Vue视图渲染的过程为: 模板 + 数据 -> 虚拟Dom -> Diff ... Dom操作 -> 真实Dom(渲染到页面中),这个过程可以很自然的使用数据。但数据不仅包含视图展示数据、还会有一些视图不需要数据,如果每次检测到数据变化都重新执行一遍视图的渲染,这样可能造成极大的性能损耗。那如何才能识别哪些是视图所依赖的数据呢?
数据是在函数中引用的,故可以在访问数据时,记录当前执行的函数即可,整个思路如下:

- 设置一个全局变量记录当前执行的 render 函数
- render 函数会引用data,属性的 getter 访问器,在通过闭包保存当前的执行的 render 函数(此时记录在 全局变量中)
- 将全局变量置空
- 数据变化时,会触发属性的 setter
访问器,执行闭包的保存的render函数,触发视图重新渲染 在Vue中实现,为了更好的性能(异步渲染,可能多处修改属性值)、更好的扩展性(属性可能多处被引用,需要自动更新、以及支持watch、compute属性等),引入了 watcher/observer/dep 来实现依赖收集以及自动更新。

- initData 属性时,对属性进行 observe 处理 <= observer概念
- mount 时,使用watcher管理更新
- 渲染时,设置一个全局变量,被引用属性的 getter 属性会通过该全部变量 收集对应的依赖
- 数据更新时,触发watcher 的 update 方法进行重新渲染操作
总结
通过 defineProperty 对数据属性进行拦截,再拦截的基础上 基于 JS 单线程原理进行依赖收集,数据变更时触发视图的重新渲染。