1. 前文回顾

在上一篇文章《Vue源码之计算属性watcher》中,我们学习了计算属性watcher是如何与计算属性的computedGetter协作,在计算属性所依赖的数据发生变化时,通知渲染watcher去设置一个dirty标识位,然后在页面更新过程中,通过render方法计算最新的虚拟DOM时,会读取页面引用的该计算属性,从而触发computedGetter,发现该计算属性变dirty了,继而会调用对应的计算属性watcher的求值方法evaluate,然后调用get方法,即我们自己写的计算属性函数,来计算出最新的计算属性值,最终交给patch来渲染最新的真实DOM呈现给用户。

这里提到了渲染watcher,我们此时还只是知其然而不知其所以然,今天我们就来扒一扒。不过开始之前,我们还是先列下vue使用watcher 的几个场景

  • 计算属性watcher: vue会为我们在computed选项写的每个我们自定义的计算属性创建一个计算属性watcher,该watcher会在依赖的响应式数据变化时将计算属性标志位设置成dirty,使得页面在下次更新时调用计算属性函数进行求值。
  • 渲染watcher: vue会为每个组件创建一个渲染watcher来在依赖的响应式数据状态发生变化时重新渲染页面
  • 用户watcher: vue会为我们在watch选项写的每个要监控的属性创建一个watcher来在其变化时执行提供的回调函数

2. vue实例和每个vue组件实例拥有各自的渲染watcher

其实vue不仅会为每个组件创建一个渲染watcher,还会为我们在main.js中创建的vue实例对象创建一个渲染watcher。但是因为Vue类和VueComponent类都是指向相同的prototype的,所以Vue实例对象也可以看成是组件实例对象,只是有些轻微的区别,比如组件实例对象的options配置项不能提供$el选项之类的。

Vue和VueComponent的关系今后会另外开一篇文章来分析,在本文中,我们只需要知道他们的prototype是一样的,即Vue类和VueComponent类的Vue.prototype._init是同一个方法。

而从下面的分析中我们将会看到,凡是经过_init发起的一系列调用,无论是vue实例还是组件实例,都会创建一个与之对应的渲染watcher。

现在我们先从new Vue开始分析,看下这个渲染watcher是怎么建立起来的。

3. 渲染watcher创建流程分析

3.1. vue实例初始化和vue组件实例初始化用的是同一个_init流程

首先,假设我们有代码如下

<!DOCTYPE html>
<html lang="en">...<body><div id="app"><div>{{msg}}</div></div><script>const vm = new Vue({el: "#app",data: {msg: "Hello world!",},methods: {},});</script></body>
</html>

文件的目的就是通过vue实现一个页面显示data里面的Hello world!这个msg,期间会通过new Vue调用Vue构造函数来实例化Vue实例对象。

下面我们看下Vue的构造函数

function Vue(options) {...this._init(options);
}

直接调用_init, 但是要注意这里的_init是在Vue.prototype下面的,即Vue类和VueComponent类用的是同一个。

这里我们顺便看下创建组件的代码,其实里面也是调用了_init方法的

Vue.extend = function (extendOptions: Object): Function {extendOptions = extendOptions || {};...const Sub = function VueComponent(options) {this._init(options);};...return Sub;
};

这里的extend方法其实是vue提供的创建一个组件的的方法,参数就是我们平时写的data,methods这些options。 该方法返回的是组件的构造函数VuecComponent。

我们平时写代码都是import一个组件,然后components上声明使用下该组件,然后就直接在模板中进行使用,并没有调用过 extend方法,那是因为vue框架在背后为我们做了这个事情。

这个’背后‘,指的就是在patch(关于patch更多的分析,请参考之前的文章《Vue源码虚拟DOM将去往何处?》)根据虚拟DOM更新真实DOM的过程中,会调用组件构造函数来为该组件节点创建相应的组件实例。

好,我们继续分析_init方法

3.2. 初始化过程会进行组件的挂载并创建对应渲染watcher

Vue.prototype._init = function (options?: Object) {const vm: Component = this;// a uidvm._uid = uid++;...if (vm.$options.el) {// debugger;vm.$mount(vm.$options.el);}
};

_init中会调用一系列的初始化函数来处理我们选项中提供的data,methods,props等选项,但是因为和这里我们的分析关系不是很大,为了更好的理解,这里统统省略掉。

因为我们在上面的示例代码中提供了$el选项,这里会进入到vm.$mount的代用逻辑。如果没有提供$el选项的话,那么示例代码中new 完Vue之后必须手动调用下$mount方法。

我们继续往下看$mount的调用。

这里$mount有两个版本,如果最终所用的vue是带有compiler的版本,那么会先进入到platforms/web/entry-runtime-with-compiler页面中的$mount进行模板编译等处理,然后再调用platforms/web/runtime中的$mount,否则直接调用后者。

这里我们并不关心模板编译之类的,所以我们直接看后面的$mount

Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefined;return mountComponent(this, el, hydrating);
};

这里$mount的函数体我连省略号都没有写,因为它真的就只有mountComponent这一行。注意这里的this,因为$mount函数是Vue的原型函数,且这里一系列的调用都是在new Vue创建Vue实例对象或者new VueComponent的语境下发生的,所以这里的this指向的就是Vue实例对象或者Vue组件实例对象。

export function mountComponent(vm: Component,el: ?Element,hydrating?: boolean
): Component {vm.$el = el;...callHook(vm, "beforeMount");let updateComponent;...updateComponent = () => {const vnode = vm._render();vm._update(vnode, hydrating);};new Watcher(vm,updateComponent,noop,{before() {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, "beforeUpdate");}},},true /* isRenderWatcher */);...return vm;
}

这里创建的watcher就是所谓的渲染watcher,因为同一个组件和vue实例都只会创建一次,即_init下来的这一系列方法只会调用一次,所以我们通常说一个组件对应个一个渲染watcher。

这里updateComponent方法我们在前面的虚拟DOM相关的文章也都分析过,里面的render和update方法是vue组件的灵魂,render负责重新生成虚拟DOM,而update负责新老虚拟DOM做diff然后上树更新页面。

3.3. 渲染watcher内部构建细节

这根据渲染watcher创建时提供的参数,我们可以整理下watcher的构造函数:

export default class Watcher {constructor(vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vm;if (isRenderWatcher) {vm._watcher = this;}vm._watchers.push(this);// optionsif (options) {...this.lazy = !!options.lazy;} ...if (typeof expOrFn === "function") {this.getter = expOrFn;} else {...}this.value = this.lazy ? undefined : this.get();}

首先,第二个参数updateComponent作为expOrFn形参对应的实参,是个function,所以会赋值给getter, 而getter会在watcher的get方法中触发,这我们往下会看到。

然后第四个参数作为options形参对应的实参,没有提供lazy设置,所以这了this.lazy为false。也就是最后那一句代码会判断为去执行this.get()方法,即watcher的get方法。

最后一个参数实参为true,那么将该渲染watcher放到组件实例对象的_vm中保存起来,以便今后使用。比如我们也许会用过Vue的forceUpdate方法,里面的实现就是直接调用_vm的update方法来强制更新真实DOM的。

Vue.prototype.$forceUpdate = function () {const vm: Component = this;if (vm._watcher) {vm._watcher.update();}};

3.4. 渲染watcher如何触发渲染

我们这里继续看下watcher的get方法

 get() {pushTarget(this);let value;const vm = this.vm;try {value = this.getter.call(vm, vm);} catch (e) {...} finally {...popTarget();...}return value;}

根据上面的分析,这里的getter就是updateComponent方法。所以这里的get方法就是去调用updateComponent方法来去收集依赖,因为updateComponent调用render方法去生成虚拟DOM的时候会去读页面模板引用到的data等上面的数据,这时就会触发这些属性在defineReactive时创建的对应的getter,从而将渲染watcher收集为该数据的依赖,也就是将渲染watcher放到对应数据的dep.subs数组下。

既然渲染watcher被作为data下的数据收集为依赖,一旦该数据发生变化,必然就会被notify通知到对应的渲染watcher来更新,从而触发watcher的get方法,继而调用updateComponent来重新生成虚拟DOM,并渲染最新的更新到页面。

4. 一些前置知识

如果你对上面两段话比较迷糊的话,那很有可能你还没有学习watcher的相关实现知识,这时我会建议你先看下我之前的几篇文章:

  • Vue源码分析基础之响应式原理
  • Vue源码实现之watcher拾遗

以上两篇文章的知识点必须要有。下面两篇说虚拟DOM的最好也能看下

  • Vue源码之虚拟DOM来自何方?
  • Vue源码虚拟DOM将去往何处?

我是@天地会珠海分舵,「青葱日历」和「三日清单」作者。能力一般,水平有限,觉得我说的还有那么点道理的不妨点个赞关注下!

Vue源码之渲染watcher相关推荐

  1. Vue源码之用户watcher

    通过上一篇文章<Vue源码之渲染watcher>我们学习到了每个组件实例初始化时都会创建一个渲染watcher来监控页面引用到的响应式数据的变动,一旦数据发生变化,就会通知渲染watche ...

  2. Vue源码实现之watcher拾遗

    目录 1. Watcher构造函数参数options和渲染watcher标志位 2. watcher收集的新老依赖deps和newDeps的作用 3. watcher中getter的目的就是去touc ...

  3. Vue源码之计算属性watcher

    在之前的文章<Vue源码分析基础之响应式原理>和<Vue源码实现之watcher拾遗>中,我们学习了watcher的实现原理.紧跟着这几天准备花点时间学习下watcher在vu ...

  4. 深入剖析Vue源码 - 响应式系统构建(上)

    从这一小节开始,正式进入Vue源码的核心,也是难点之一,响应式系统的构建.这一节将作为分析响应式构建过程源码的入门,主要分为两大块,第一块是针对响应式数据props,methods,data,comp ...

  5. 0215前端日报:vue源码剖析思维导图

    给 「前端开发博客」 加星标,每天打卡学习 长按二维码即可识别"进入网页"查看哟~ 1.vue源码剖析思维导图(一) 趁这个"难得"的假期,学习了一下vue源码 ...

  6. Vue源码学习之Computed与Watcher原理

    前言  computed与watch是我们在vue中常用的操作,computed是一个惰性求值观察者,具有缓存性,只有当依赖发生变化,第一次访问computed属性,才会计算新的值.而watch则是当 ...

  7. vue源码分析系列二:$mount()和new Watcher()的执行过程

    续vue源码分析系列一:new Vue的初始化过程 在initMixin()里面调用了$mount() if (vm.$options.el) {vm.$mount(vm.$options.el);/ ...

  8. vue源码:Watcher系列(一)

    少年驰骋,仗剑天涯 愿你眼眸有星辰,心中有大海 从此,以梦为马,不负韶华 在分析之前我们先来看看,vue中都有哪些Watcher种类呢?以及分别在什么时候创建呢?从vue源码里面看,Watcher是一 ...

  9. vue实例没有挂载到html上,vue 源码学习 - 实例挂载

    前言 在学习vue源码之前需要先了解源码目录设计(了解各个模块的功能)丶Flow语法. src ├── compiler # 把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能. ├── ...

最新文章

  1. 【JavaWeb】Access restriction The type is not accessible due to restriction on required library
  2. 连接LilyPad之Windows平台的驱动
  3. CodeForces - 1350B Orac and Models(dp)
  4. CG-CTF-Web-文件包含
  5. 华为云大数据存储的冗余方式是三副本_华为OceanStor分布式存储,引领智能时代大数据创新...
  6. Tomcat部署Web应用
  7. ajax请求模拟登录
  8. semihost/ITM机制浅析以及使用JLINK通过ITM调试stm32单片机
  9. 金蝶二次开发好跳槽吗_金蝶财务软件不会操作怎么办?
  10. 用python画长方形_Python+opencv:绘制矩形,编写文本,PythonOpenCV,画,矩形框
  11. CF1109D Sasha and Interesting Fact from Graph Theory
  12. 2054无法登陆mysql_张虹亮'blog » ubuntu20.04安装mysql8之后,php5程序和phpmyadmin出现#2054 无法登录MySQL服务器的解决方案...
  13. 15个实用的管理mysql的MySQLadmin命令
  14. 鼠标右键添加新建类型
  15. 三维点云学习(4)5-DBSCNA python 复现-2-kd-_tree加速
  16. 将webService(CXF)与spring集成
  17. setTimeout 方法用于在指定的毫秒数后调用函数或计算表达式
  18. CoFlash 基本操作说明和Flash编程算法
  19. 目前常见软件保护技术概述
  20. As Manufacturers Buckle, Winners Emerge From Havoc

热门文章

  1. cd.ssh bash: cd.ssh: 未找到命令.../没有这个文件或目录
  2. linux下Local Adress(本地ip:端口)和Foreign Address(外部ip:端口)
  3. java锁的种类及研究
  4. 关于学计算机趣味段子,搞笑段子:路上一个女孩突然朝我走来问:你是不是学计算机的?...
  5. 强大的代码编档工具—Doxygen
  6. 《黑客秘笈——渗透测试实用指南》—第1章1.1节搭建渗透测试主机
  7. excel行列互换_小白学Excel怎么做?|苦苦整理四小时!
  8. php artisan 命令详解,Artisan命令详解
  9. Beyond Compare4试用过期解决方法
  10. 一個有趣的白加黑样本分析