剖析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相关知识哦(●'◡'●)。 如果不足,请多指教。

目录
相关文章
|
8天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
vue学习第四章
|
8天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
vue学习第九章(v-model)
|
7天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
vue学习第十章(组件开发)
|
13天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
14天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
14天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
14天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
13天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
14天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
13天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉