第53期Vue.js异步更新DOM策略及nextTick

Posted 前端指南

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第53期Vue.js异步更新DOM策略及nextTick相关的知识,希望对你有一定的参考价值。

【第53期】Vue.js异步更新DOM策略及nextTick
作者|染陌同学
编辑|小龙
https://github.com/frontend9/fe9-library/issues/78

往期精彩回顾



导读

今天在github上瞎逛时看到的一篇好文,一定要 查看原文 哦,既是对原作者的支持也能发现更大的世界。




操作Dom


在使用vue.js的时候,有时候因为一些特定的业务场景,不得不去操作DOM,比如这样:

【第53期】Vue.js异步更新DOM策略及nextTick

打印的结果是begin,为什么我们明明已经将test设置成了“end”,获取真实DOM节点的innerText却没有得到我们预期中的“end”,而是得到之前的值“begin”呢?

Watcher队列

带着疑问,我们找到了Vue.js源码的Watch实现。当某个响应式数据发生变化的时候,它的setter函数会通知闭包中的Dep,Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。我们来看一下update的实现。

【第53期】Vue.js异步更新DOM策略及nextTick

我们发现Vue.js默认是使用异步执行DOM更新
当异步执行update的时候,会调用queueWatcher函数。

【第53期】Vue.js异步更新DOM策略及nextTick

查看queueWatcher的源码我们发现,Watch对象并不是立即更新视图,而是被push进了一个队列queue,此时状态处于waiting的状态,这时候会继续会有Watch对象被push进这个队列queue,等到下一个tick运行时,这些Watch对象才会被遍历取出,更新视图。同时,id重复的Watcher不会被多次加入到queue中去,因为在最终渲染时,我们只需要关心数据的最终结果。

那么,什么是下一个tick?

nextTick


vue.js提供了一个nextTick函数,其实也就是上面调用的nextTick。

nextTick的实现比较简单,执行的目的是在microtask或者task中推入一个funtion,在当前栈执行完毕(也行还会有一些排在前面的需要执行的任务)以后执行nextTick传入的funtion,看一下源码:

【第53期】Vue.js异步更新DOM策略及nextTick

它是一个立即执行函数,返回一个queueNextTick接口。

传入的cb会被push进callbacks中存放起来,然后执行timerFunc(pending是一个状态标记,保证timerFunc在下一个tick之前只执行一次)。

timerFunc是什么?

看了源码发现timerFunc会检测当前环境而不同实现,其实就是按照Promise,MutationObserver,setTimeout优先级,哪个存在使用哪个,最不济的环境下使用setTimeout。

这里解释一下,一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法。
优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法的回调函数都会在microtask中执行,它们会比setTimeout更早执行,所以优先使用。
如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。

为什么要优先使用microtask?我在顾轶灵在知乎的回答中学习到:

【第53期】Vue.js异步更新DOM策略及nextTick

首先是Promise,(Promise.resolve()).then()可以在microtask中加入它的回调,

MutationObserver新建一个textNode的DOM对象,用MutationObserver绑定该DOM并指定回调函数,在DOM变化的时候则会触发回调,该回调会进入microtask,即textNode.data = String(counter)时便会加入该回调。

setTimeout是最后的一种备选方案,它会将回调函数加入task中,等到执行。

综上,nextTick的目的就是产生一个回调函数加入task或者microtask中,当前栈执行完以后(可能中间还有别的排在前面的函数)调用该回调函数,起到了异步触发(即下一个tick时触发)的目的。

flushSchedulerQueue

【第53期】Vue.js异步更新DOM策略及nextTick

flushSchedulerQueue是下一个tick时的回调函数,主要目的是执行Watcher的run函数,用来更新视图

为什么要异步更新视图

来看一下下面这一段代码

【第53期】Vue.js异步更新DOM策略及nextTick


现在有这样的一种情况,mounted的时候test的值会被++循环执行1000次。
每次++时,都会根据响应式触发setter->Dep->Watcher->update->patch。
如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。
所以Vue.js实现了一个queue队列,在下一个tick的时候会统一执行queue中Watcher的run。同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行1000次Watcher的run。最终更新视图只会直接将test对应的DOM的0变成1000。
保证更新视图操作DOM的动作是在当前栈执行完以后下一个tick的时候调用,大大优化了性能。

访问真实DOM节点更新后的数据

所以我们需要在修改data中的数据后访问真实的DOM节点更新后的数据,只需要这样,我们把文章第一个例子进行修改。


使用Vue.js的global API的$nextTick方法,即可在回调中获取已经更新好的DOM实例了。


【关于投稿】


如果大家有原创好文投稿,请直接给公号发送留言。


① 留言格式:
【投稿】+《 文章标题》+ 文章链接

② 示例:
【投稿】《github摸鱼技巧》:https://github.com/moyu/Blog/issues/50

③ 最后请附上您的个人简介哈~


觉得本文对你有帮助?请分享给更多人

关注「前端指南」,一起来菜鸡互啄



查看更多前端好文
扫码关注前端指南


以上是关于第53期Vue.js异步更新DOM策略及nextTick的主要内容,如果未能解决你的问题,请参考以下文章

第735期关于Vue.js 2.0 的 Vuex 2.0,你需要更新的知识库

vue.js初级教程--01.简介

Vue.js入门

使用轴和 Vue.js 进行异步/等待调用 - `.then()` 回调未更新 `this.something`

第1496期新手向之Vue.js + Node.js(koa) 合体指南

前端周刊第49期:React 和 Vue.js SSR 框架 + 微信小程序 + Grid 布局