深⼊响应式原理
前⾯ 2 章介绍的都是 Vue 怎么实现数据渲染和组件化的,主要讲的是初始化的过程,把原始的数据最 终映射到 DOM 中,但并没有涉及到数据变化到 DOM 变化的部分。⽽ Vue 的数据驱动除了数据渲染 DOM 之外,还有⼀个很重要的体现就是数据的变更会触发 DOM 的变化。 其实前端开发最重要的 2 个⼯作,⼀个是把数据渲染到⻚⾯,另⼀个是处理⽤户交互。Vue 把数据渲 染到⻚⾯的能⼒我们已经通过源码分析出其中的原理了,但是由于⼀些⽤户交互或者是其它⽅⾯导致 数据发⽣变化重新对⻚⾯渲染的原理我们还未分析。
总结
这⼀节我们介绍了响应式对象,核⼼就是利⽤ Object.defineProperty 给数据添加了 getter 和 setter,⽬的就是为了在我们访问数据以及写数据的时候能⾃动执⾏⼀些逻辑:getter 做的事情是依赖收 集,setter 做的事情是派发更新,那么在接下来的章节我们会重点对这两个过程分析。
依赖收集
考虑到 Vue 是数据驱动的,所以每次数据变化都会重新 render,那么 vm._render() ⽅法⼜会再次执 ⾏,并再次触发数据的 getters,所以 Wathcer 在构造函数中会初始化 2 个 Dep 实例数 组, newDeps 表⽰新添加的 Dep 实例数组,⽽ deps 表⽰上⼀次添加的 Dep 实例数组。 在执⾏ cleanupDeps 函数的时候,会⾸先遍历 deps ,移除对 dep 的订阅,然后把 newDepIds 和 depIds 交换, newDeps 和 deps 交换,并把 newDepIds 和 newDeps 清空。 那么为什么需要做 deps 订阅的移除呢,在添加 deps 的订阅过程,已经能通过 id 去重避免重复 订阅了。
考虑到⼀种场景,我们的模板会根据 v-if 去渲染不同⼦模板 a 和 b,当我们满⾜某种条件的时候渲 染 a 的时候,会访问到 a 中的数据,这时候我们对 a 使⽤的数据添加了 getter,做了依赖收集,那么当 我们去修改 a 的数据的时候,理应通知到这些订阅者。那么如果我们⼀旦改变了条件渲染了 b 模板, ⼜会对 b 使⽤的数据添加了 getter,如果我们没有依赖移除的过程,那么这时候我去修改 a 模板的数 据,会通知 a 数据的订阅的回调,这显然是有浪费的。 因此 Vue 设计了在每次添加完新的订阅,会移除掉旧的订阅,这样就保证了在我们刚才的场景中,如 果渲染 b 模板的时候去修改 a 模板的数据,a 数据订阅回调已经被移除了,所以不会有任何浪费,真的 是⾮常赞叹 Vue 对⼀些细节上的处理。
总结
通过这⼀节的分析,我们对 Vue 数据的依赖收集过程已经有了认识,并且对这其中的⼀些细节做了分 析。收集依赖的⽬的是为了当这些响应式数据发送变化,触发它们的 setter 的时候,能知道应该通知哪 些订阅者去做相应的逻辑处理,我们把这个过程叫派发更新,其实 Watcher 和 Dep 就是⼀个⾮常 经典的观察者设计模式的实现,下⼀节我们来详细分析⼀下派发更新的过程。
派发更新
所以这就是当我们去修改组件相关的响应式数据的时候,会触发组件重新渲染的原因,接着就会重新 执⾏ patch 的过程,但它和⾸次渲染有所不同,之后我们会花⼀⼩节去详细介绍。
总结
通过这⼀节的分析,我们对 Vue 数据修改派发更新的过程也有了认识,实际上就是当数据发⽣变化的 时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher ,都触发它们的 update 过程,这个过程⼜利⽤了队列做了进⼀步优化,在 nextTick 后执⾏所有 watcher 的 run ,最后执⾏它们的回调函数。 nextTick 是 Vue ⼀个⽐较核⼼的实现了,下⼀节我们来重点分 析它的实现。
nextTick
检测变化的注意事项
总结
通过这⼀节的分析,我们对响应式对象⼜有了更全⾯的认识,如果在实际⼯作中遇到了这些特殊情 况,我们就可以知道如何把它们也变成响应式的对象。其实对于对象属性的删除也会⽤同样的问题, Vue 同样提供了 Vue.del 的全局 API,它的实现和 Vue.set ⼤相径庭,甚⾄还要更简单⼀些,这 ⾥我就不去分析了,感兴趣的同学可以⾃⾏去了解。
计算属性 VS 侦听属性
组件更新
新旧节点不同
总结
组件更新的过程核⼼就是新旧 vnode diff,对新旧节点相同以及不同的情况分别做不同的处理。新旧节 点不同的更新流程是创建新节点->更新⽗占位符节点->删除旧节点;⽽新旧节点相同的更新流程是去获 取它们的 children,根据不同情况做不同的更新逻辑。最复杂的情况是新旧节点相同且它们都存在⼦节
组件更新
点,那么会执⾏ updateChildren 逻辑,这块⼉可以借助画图的⽅式配合理解。