这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
最近部门分享,有同学提到了 Vue 响应式原理,大家在讨论时,发现一些同学对这一知识理解还不够深入,不能形成一个闭环,为了帮助大家理解这个问题,我重新过了一下 Vue 源码,并整理了多张流程图,便于大家理解。
- Vue 初始化
- 模板渲染
- 组件渲染
本文 Vue 源码版本:2.6.11,为了便于理解,均有所删减。
本文将从以下两个方面进行探索:
-
从 Vue 初始化,到首次渲染生成 DOM 的流程。
-
从 Vue 数据修改,到页面更新 DOM 的流程。
Vue 初始化
先从最简单的一段 Vue 代码开始:
<template> <div> {{ message }} </div> </template> <script> new Vue({ data() { return { message: "hello world", }; }, }); </script>
这段代码很简单,最终会在页面上打印一个 hello world,它是如何实现的呢?
我们从源头:new Vue 的地方开始分析。
// 执行 new Vue 时会依次执行以下方法 // 1. Vue.prototype._init(option) // 2. initState(vm) // 3. observe(vm._data) // 4. new Observer(data) // 5. 调用 walk 方法,遍历 data 中的每一个属性,监听数据的变化。 function walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } // 6. 执行 defineProperty 监听数据读取和设置。 function defineReactive(obj, key, val) { // 为每个属性创建 Dep(依赖搜集的容器,后文会讲) const dep = new Dep(); // 绑定 get、set Object.defineProperty(obj, key, { get() { const value = val; // 如果有 target 标识,则进行依赖搜集 if (Dep.target) { dep.depend(); } return value; }, set(newVal) { val = newVal; // 修改数据时,通知页面重新渲染 dep.notify(); }, }); }数据描述符绑定完成后,我们就能得到以下的流程图:
![](/i/l/?n=22&i=blog/2149129/202210/2149129-20221017164219505-2014943359.png)
图中我们可以看到,Vue 初始化时,进行了数据的 get、set 绑定,并创建了一个 Dep 对象。
对于数据的 get、set 绑定我们并不陌生,但是 Dep 对象什么呢?
Dep 对象用于依赖收集,它实现了一个发布订阅模式,完成了数据 Data 和渲染视图 Watcher 的订阅,我们一起来剖析一下。
class Dep { // 根据 ts 类型提示,我们可以得出 Dep.target 是一个 Watcher 类型。 static target: ?Watcher; // subs 存放搜集到的 Watcher 对象集合 subs: Array<Watcher>; constructor() { this.subs = []; } addSub(sub: Watcher) { // 搜集所有使用到这个 data 的 Watcher 对象。 this.subs.push(sub); } depend() { if (Dep.target) { // 搜集依赖,最终会调用上面的 addSub 方法 Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { // 调用对应的 Watcher,更新视图 subs[i].update(); } } }
根据对 Dep 的源码分析,我们得到了下面这张逻辑图:
了解 Data 和 Dep 之后,我们来继续揭开 Watcher 的面纱。
class Watcher { constructor(vm: Component, expOrFn: string | Function) { // 将 vm._render 方法赋值给 getter。 // 这里的 expOrFn 其实就是 vm._render,后文会讲到。 this.getter = expOrFn; this.value = this.get(); } get() { // 给 Dep.target 赋值为当前 Watcher 对象 Dep.target = this; // this.getter 其实就是 vm._render // vm._render 用来生成虚拟 dom、执行 dom-diff、更新真实 dom。 const value = this.getter.call(this.vm, this.vm); return value; } addDep(dep: Dep) { // 将当前的 Watcher 添加到 Dep 收集池中 dep.addSub(this); } update() { // 开启异步队列,批量更新 Watcher queueWatcher(this); } run() { // 和初始化一样,会调用 get 方法,更新视图 const value = this.get(); } }源码中我们看到,Watcher 实现了渲染方法
_render
和 Dep 的关联, 初始化 Watcher 的时候,打上 Dep.target 标识,然后调用 get 方法进行页面渲染。加上上文的 Data,目前 Data、Dep、Watcher 三者的关系如下:
![](/i/l/?n=22&i=blog/2149129/202210/2149129-20221017164356522-1774857195.png)
defineProperty
完成了 Data 中所有数据的代理,当数据触发 get 查询时,会将当前的 Watcher 对象加入到依赖收集池 Dep 中,当数据 Data 变化时,会触发 set 通知所有使用到这个 Data 的 Watcher 对象去 update 视图。
目前的整体流程如下:
![](/i/l/?n=22&i=blog/2149129/202210/2149129-20221017164439185-1192589420.png)
上图的流程中 Data 和 Dep 都是 Vue 初始化时创建的,但现在我们并不知道 Wacher 是从哪里创建的,带着这个问题,我们接着往下探索。
模板渲染
上文中,我们分析了初始化 Vue 过程中处理数据的部分,接下来,我们分析一下数据渲染的部分。
其实 new Vue 执行到最后,会调用 mount 方法,将 Vue 实例渲染成 dom 。
// new Vue 执行流程。 // 1. Vue.prototype._init(option) // 2. vm.$mount(vm.$options.el) // 3. render = compileToFunctions(template) ,编译 Vue 中的 template 模板,生成 render 方法。 // 4. Vue.prototype.$mount 调用上面的 render 方法挂载 dom。 // 5. mountComponent // 6. 创建 Watcher 实例 const updateComponent = () => { vm._update(vm._render()); }; // 结合上文,我们就能得出,updateComponent 就是传入 Watcher 内部的 getter 方法。 new Watcher(vm, updateComponent); // 7. new Watcher 会执行 Watcher.get 方法 // 8. Watcher.get 会执行 this.getter.call(vm, vm) ,也就是执行 updateComponent 方法 // 9. updateComponent 会执行 vm._update(vm._render()) // 10. 调用 vm._render 生成虚拟 dom Vue.prototype._render = function (): VNode { const vm: Component = this; const { render } = vm.$options; let vnode = render.call(vm._renderProxy, vm.$createElement); return vnode; }; // 11. 调用 vm._update(vnode) 渲染虚拟 dom Vue.prototype._update = function (vnode: VNode) { const vm: Component = this; if (!prevVnode) { // 初次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { // 更新 vm.$el = vm.__patch__(prevVnode, vnode); } }; // 12. vm.__patch__ 方法就是做的 dom diff 比较,然后更新 dom,这里就不展开了。
看完 Vue 模板渲染的过程,我们可以得到如下的流程图:
到这里,我们就知道了 Watcher 其实是在 Vue 初始化的阶段创建的,属于生命周期中 beforeMount 的位置创建的,创建 Watcher 时会执行 render 方法,最终将 Vue 代码渲染成真实的 DOM。
我们再将之前的流程整合一下,就能得到以下的流程:
上图分析了 Vue 初始化到渲染 DOM 的整个过程,最后我们再分析一下,当数据变化时,Vue 又是怎么进行更新的?
其实,在上图也能看出,在 Data 变化时,会调用 Dep.notify 方法,随即调用 Watcher 内部的 update 方法,此方法会将所有使用到这个 Data 的 Watcher 加入一个队列,并开启一个异步队列进行更新,最终执行 _render
方法完成页面更新。
整体的流程如下:
好了,探索到这里,Vue 的响应式原理,已经被我们分析透彻了,如果你还没有明白,不妨再细品一下上图。
组件渲染
本来探索到上面的流程图就结束了,但好奇的我又想到了一个问题
标签:Vue,const,--,vm,Watcher,Dep,._,图解 From: https://www.cnblogs.com/smileZAZ/p/16799780.html