vue异步更新流程梳理

前言

vue实例创建后,当我们重新赋值data中的数据时,视图就会更新,那么具体干了啥呢?本文用demo + 调试断点,一步步来研究一下具体流程。

demo代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">{{value}}</div>
  <script src="../dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          value: 1
        }
      },
      mounted () {
        this.value = 2
      }
    })
  </script>
</body>
</html>

众所周知,vue在实例化的时候会对data的数据进行响应式设置,当我们赋值的时候就会触发对应的setter,所以把断点打到setter看一下,如下图:


image.png

我们传入的newVal 是 2,老的val 是1,后面直接val = newVal ,那么此时val就更新为2了。更新之后,最后还有一句代码

dep.notify();

这里就是对观察者们进行通知了。当我们的值变化时,dep就负责去通知watcher们,每一个watcher实例就会调用自己内部的update方法。咱门进入notify方法看看它是不是这样子:


image.png

看果然是这样子,subs这个数组就是专门存放watcher实例的,循环遍历watcher,调用每个watcher自己的update方法。
这里只存放了一个watcher实例,看看这个watcher是啥。这里先解释一下watcher都有哪些分类:

  1. 渲染watcher, 负责更新视图变化的,即一个vue实例对应一个渲染watcher
  2. 用户自定义watcher,用户通过watch:{value(val, oldVal){}}选项定义的,或者this.$watch()方法生成的。
  3. computed选项里面的计算属性也是watcher, 和第2点中的watcher的区别是它的watcher实例有dirty属性控制着watcher.value值的变化

打开右边的scope栏,找到Local下的subs[0]


image.png

再去查看watcher.vm._wather是否有值,有就代表是渲染watcher。同时找到源码Watcher的声明:


image.png

由此可见我们现在正在执行update的这个watcher就是一个渲染watcher。
继续找看update方法干了啥:

image.png

update就是执行queueWatcher(this),再看这里干了啥
image.png

其实猜也能猜到就维护一个queue:[watcher, ...]的队列,里面的watcher不重复;最后执行nextTick(flushSchedulerQueue); 看看flushSchedulerQueue干了啥,其实就是执行watcher.run方法。
image.png

来到这里我们大概就清晰了这个流程:
赋值操作this.value = 2就是把渲染watcher放到一个queue的队列中,并且通过nextTick在将来某个时刻把queue队列的watcher拿出来一个个去执行watcher.run()方法。

这里的nextTick就是利用事件循环,在未来某个时刻会执行flushSchedulerQueue方法 , flushSchedulerQueue 又是循环执行 watcher.run();继续回到watcher看run方法干了啥:


image.png

image.png

注意观察上面两张图, 在run方法中执行this.get(),视图就改变了值,从1变成2,一切的谜团就在这一行代码中,我们继续研究get()方法!

image.png

get里面调用了this.getter.call(vm, vm),找到this.getter,发现这是一个updateComponent的方法:


image.png

那么这个方法是怎么来的呢?


image.png

如上图,是watcher实例化的第二个参数: expOrFn,那么就打断点在watcher实例化过程中,看看这个updateComponent怎么来的。刷新页面:


image.png

根据上图中1、2步骤,在call stack执行栈中往下点,一个个方法进去看,很幸运,在下面的mountComponent方法的执行栈就看到了updateComponent的声明,在下图2处,就是updateComponent这个方法做的事情,从方法中我们可以知道,就是这行代码起了作用
 vm._update(vm._render(), hydrating);
image.png

总结

那么,至此我们再总结一下流程:
this.value = 2 触发 setter,
同步执行过程:
setter => dep.notify() => watcher.update() =>queueWatcher(this) =>nextTick(flushSchedulerQueue);

由于把flushSchedulerQueue放到了nextTick里面,那么接下来未来的某个时刻会执行flushSchedulerQueue,然后从queque队列中提取watcher出来循环执行watcher.run

watcher.run() => watcher.get() => watcher.getter() => updateComponent() => vm._update(vm._render())

到此为止我们就知道大概就是这么一个流程。
最后再看看_update()方法与_render()方法,
_render()方法很纯粹,就是返回一个虚拟dom: vnode对象。而_update()方法就是把虚拟dom: vnode去进行patch的过程,得到一个新的真实dom。

一些问题:

  • 为什么watcher放在queue队列中不直接去执行watcher.run呢,而要放到nextTick里面,等待未来某个时刻统一执行呢?
    答:其实还是为了性能,高效,如果你这么写代码:
this.value = 2
this.value = 3

没有nextTick就会走两次 vm._update(vm._render())了,而这里面的patch过程的diff就是一个比较复杂消耗性能的过程。

  • 为什么只有一个渲染watcher?
    答: 因为vue1.x就是因为采用了一个绑定值一个watcher的方式,虽然变化可以精确到绑定值的位置,但是这样子在大一点的项目就很多watcher,会消耗大量内存造成性能瓶颈,vue2采用了虚拟dom更新的方案,以组件为单位进行更新,一个组件实例对应一个渲染watcher。组件内的一个或者多个响应式属性更新 --> 触发渲染watcher.unpdate() --> 同一个渲染watcher只被送入一次queueWatcher队列 -->nextTick之后的回调里面触发watcher.run()。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容