剖析vue中的nextTick

简介: 本文从nextTick API 的概念到使用,再到源码,层层剖析。系统地回顾nextTick的相关用法,以及内部调用逻辑。

网络异常,图片无法展示
|


本文从nextTick API 的概念到使用,再到源码,层层剖析。系统地回顾nextTick的相关用法,以及内部调用逻辑。


概念


官方解释:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。


大白话:nextTick是vue中的批量异步更新策略。监听到组件的变化时,不会立即更新,会开启一个队列,同一watcher只入队一次。在下一次事件循环时,刷新队列执行更新。


原理


使用nextTick接收传入的回调函数,将回调函数暂时存放到一个队列中,开启异步更新(调用timerfuc函数)。在timerfuc中会优先使用Promise微任务去执行队列中的所有回调,在下一次事件循环时,更新页面。


在timerfuc中还存在一个降级的过程,是考虑浏览器的兼容设置的。 降级顺序: Promise => MutationObserver => setImmediate => setTimeout

网络异常,图片无法展示
|


可以看到nextTick其实是利用了浏览器的事件循环机制。会将nextTick中的任务存放到微任务队列中去(如果浏览器兼容微任务)。


当函数执行栈为空时,会去判断微任务队列中是否存在微任务,如果存在就会先去执行微任务队列中的任务(可以执行nextTick中的任务),然后再去执行宏任务。

网络异常,图片无法展示
|

关于JS运行机制的细节可以点击 JS运行机制 这篇文章。


如何工作


使用


nextTick会接收两个传入对象,一个是用户的回调,还有一个是上下文对象。在组件内,用户使用的时候,this 已经自动绑定到当前的 Vue 实例上,所有就不需要全局的Vue去调用 Vue.nextTick(cb)


<div id="app">
    <h1>异步更新</h1>
    <p id="p1">{{count}}</p>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        count: '原始值'
      },
      mounted() {
        this.count = Math.random()
        console.log('第一次改动值:', this.count)
        this.$nextTick(() => {
          console.log('innerHTML', p1.innerHTML);
        })
      },
    })
  </script>

网络异常,图片无法展示
|

我们可以看到在页面加载之后,count成功获取到了第一次改动的值,并在页面上更新。


源码剖析


那么,页面它是怎么渲染的呢?


老样子,我们通过控制台打断点来看看源码内部到底发生了什么吧 ^_^ !

网络异常,图片无法展示
|


首先从this.count的赋值,因为变量是通过this获取的,并不是使用this内部的$data,所以会走到变量的代理proxy中,然后进入了defineRective中的set拦截。

网络异常,图片无法展示
|


在set拦截中,回去通知dep做更新操作。


网络异常,图片无法展示
|


notify中执行watcher的更新函数update。在此之前,我们可以看出,存在一个watcher的排序,源码中将先创建的更高级的watcher放到前面,先执行。


网络异常,图片无法展示
|


通过遍历执行update,尝试将传入的watcher实例入队,启动异步任务。


网络异常,图片无法展示
|


在nextTick中添加一个flushSchedulerQueue回调。并将其放到callbacks数组中。

网络异常,图片无法展示
|


启动异步任务timerfuc


网络异常,图片无法展示
|


在异步任务中,执行flushCallbacks


网络异常,图片无法展示
|
在其内部就


是遍历执行callbacks数组就相当于在执行之前的flushSchedulerQueue回调。

而在flushSchedulerQueue回调中,是遍历所有的watchers,执行他们的run函数。

网络异常,图片无法展示
|


在watcher的run函数中,会执行自身的get。真正会走到组件的新updateComponent。在updateComponent内部,会先执行render()得到虚拟dom,执行_update()接收vnode,再执行patch(oldvnode,vnode),真实dom变更。



网络异常,图片无法展示
|


大家可以通过看这张nextTick流程图,再配合控制台流程进行解读。


网络异常,图片无法展示
|


应用


1、获取dom更新后最新数值。 根据本文案例,我们将count的修改n遍后,想获取dom更新后最新的count值时:


mounted() {
  this.count = Math.random()
  console.log('第一次改动值:', this.count)
  this.count = Math.random()
  console.log('第二次改动值:', this.count)
  this.count = Math.random()
  console.log('第三次改动值:', this.count)
  this.$nextTick(() => {
    console.log('innerHTML', p1.innerHTML);
  })
},

网络异常,图片无法展示
|


注意点


  • 当我将nextTick放到最前面时,将会获取不到最新值,值会是原始值。


网络异常,图片无法展示
|
这是因为


nextTick时,想要的变量还未进入到异步更新的队列中。在异步更新队列中nextTick先进入,然后才是相关变量进入队列当中,所以nextTick输出的会是原始值。


  • 放到第一次或第二次修改值后面就不会发生变化。

网络异常,图片无法展示
|


这是因为相关watcher只入队一次。在第一次修改值的时候,count变量就会进入异步更新队列中,然后nextTick才会进入。根据队列特性,会先执行count变量的修改,再执行nextTick。所以仍然可以获取到count的最新值。


  • 如果前面有变量修改,nextTick会先于Promise执行。
mounted() {
  this.count = Math.random()
  console.log('第一次改动值:', this.count)
  Promise.resolve().then((res) => {
    console.log('Promise', p1.innerHTML);
  })
  this.$nextTick(() => {
    console.log('innerHTML', p1.innerHTML);
  })
  this.count = Math.random()
  console.log('第二次改动值:', this.count)
  this.count = Math.random()
  console.log('第三次改动值:', this.count)
},


在修改相关值时,已经触发异步更新队列,会将相关变量先存放到队列当中,然后再去执行Promise,将其放入到异步队列中。

网络异常,图片无法展示
|

2、点击获取元素的宽高、滚动位置等


感兴趣的朋友可以关注 Vue源码初识专栏或者关注我哦,会持续输出vue相关知识哦(●'◡'●)。 如果不足,请多指教。

目录
相关文章
|
3天前
|
JavaScript
vue中watch的用法
vue中watch的用法
|
3天前
|
JavaScript 前端开发
vue动态添加style样式
vue动态添加style样式
|
3天前
|
JavaScript 前端开发
Vue项目使用px2rem
Vue项目使用px2rem
|
2天前
|
JavaScript API
vue学习(13)监视属性
vue学习(13)监视属性
10 2
|
2天前
|
JavaScript
vue 函数化组件
vue 函数化组件
|
2天前
|
JavaScript 前端开发
vue学习(15)watch和computed
vue学习(15)watch和computed
9 1
|
7天前
|
JavaScript 前端开发
Vue项目使用px2rem
Vue项目使用px2rem
|
2天前
|
JavaScript
vue学习(14)深度监视
vue学习(14)深度监视
10 0
|
7天前
|
JavaScript
Vue组件传值异步问题--子组件拿到数据较慢
Vue组件传值异步问题--子组件拿到数据较慢
11 0
|
7天前
|
缓存 JavaScript
Vue中的keep-alive是什么意思?以及如何使用
Vue中的keep-alive是什么意思?以及如何使用