解析 $nextTick 魔力,为啥大家都爱它?
作者:京东保险 卓雅倩
1.为什么需要使用 $nextTick?
首先我们来看看官方对于 $nextTick 的定义:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
由于 vue 的试图渲染是异步的,生命周期的 created()钩子函数进行的 DOM 操作一定要放在 Vue.nextTick()的回调函数中,原因是在 created()钩子函数执行的时候 DOM 其实并未进行渲染,而此时进行 DOM 操作是徒劳的,所以一定要将 DOM 操作的 js 代码放到 Vue.nextTick()的回调函数中。除了在 created()钩子函数中使用之外咱们还会遇到很多种需要使用到 Vue.nextTick()的场景,如下所示:
咱们日常生活中常常会遇上上述场景,会有很多问题,就比如当我们点击按钮更新数据时候,如下代码示例:
点击控制栏显示效果:控制栏报错,提示没有获取到 dom 元素;
所以现在 Vue.nextTick()派上了用场,Vue.nextTick() 方法的作用正是等待上一次事件循环执行完毕,并在下一次事件循环开始时再执行回调函数。这样可以保证回调函数中的 DOM 操作已经被 Vue.js 进行过更新,从而避免了一些潜在的问题,如下代码所示:
加上 this.$nextTick 后就能够使得输入框获取到焦点;
总而言之 Vue.nextTick()就是下次 DOM 更新渲染后执行延迟回调函数。在日常开发中,我们在修改数据之后使用这个方法,就可以获取更新后的 DOM 的同时进行在对 DOM 进行相对应操作的 js 代码;
2.$nextTick 如何实现的?
JS 是单线程执行的,所有的同步任务都是在主线程上执行的,形成了一个执行栈,从上到下依次执行,异步代码会放在任务队列里面。
•同步任务
在主线程里执行,当浏览器第一遍过滤 html 文件的时候可以执行完;(在当前作用域直接执行的所有内容,包括执行的方法、new 出来的对象)
•异步任务
耗费时间较长或者性能较差的,浏览器执行到这些的时候会将其丢到异步任务队列中,不会立即执行
同时异步任务分为宏任务(如 setTimeout、setInterval、postMessage、setImmediate 等)和微任务(Promise、process.nextTick 等),浏览器执行这两种任务的优先级不同;会优先执行微任务队列的代码,微任务队列清空之后再执行宏任务的队列,这样循环往复;
JS 自上向下进行代码的编译执行,遇到同步代码压入 JS 执行栈执行后出栈,遇到异步代码放入任务队列,当 JS 执行栈清空,去执行异步队列中的回调函数,先去执行微任务队列,当微任务队列清空后,去检测执行宏任务队列中的回调函数,直至所有栈和队列清空
整体流程如下图所示:
接下来让我们看看 nextTick 的源码~
vue 将 nextTick 的源码放在了 vue/core/util/next-tick.js 中。如下图所示:
我们把这个文件拆成三个部分来看:
1.nextTick 定义函数
我们将 nextTick 函数单独拿出来,callbacks 是一个回调队列,其实调用 nextTick 就是往这个数组里面传执行任务,callbacks 新增回调函数之后执行 timerFunc 函数,pending 是用来限制同一个事件循环内只能执行一次的 pending 锁;
2.timerFunc 函数 做了四个判断,先后尝试当前环境是否能够使用原生的 Promise.then、MutationObserver 和 setImmediate,不断的降级处理,如果以上三个都不支持,则最后就会直接使用 setTimeOut,主要操作就是将 flushCallbacks 中的函数放入微任务或者宏任务,等待下一个事件循环开始执行;宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务;
3. flushCallbacks****函数
flushCallbacks 函数只有几行,也很好理解,将 pending 锁置为 false,同时将 callbacks 数组复制一份之后再将 callbacks 置为空,接下来将复制出来的 callbacks 数组的每个函数依次进行执行,简单来说它的主要作用就是用来执行 callbacks 中的回调函数;
值得注意的是,$nextTick 并不是一个真正意义上的微任务 microtask,而是利用了事件循环机制来实现异步更新。因此,它的执行时机相对于微任务可能会有所延迟,但仍能保证在 DOM 更新后尽快执行回调函数。
总的来说,nextTick 就是:
1.将传入的回调函数放入 callbacks 数组等待执行,定义 pending 判断锁保证一个事件循环中只能调用一次 timerFunc 函数;
2.根据环境判断使用异步方式,调用 timerFunc 函数调用 flushCallbacks 函数依次执行 callbacks 中的回调函数;
3.个人小结
nextTick 可避免数据更新后导致 DOM 的数据不一致的问题,提供了更稳定的异步更新机制,解决了 created 钩子函数 DOM 未渲染会造成的异步数据渲染问题,但如果过多的使用 nextTick 会导致事件循环中任务数量和回调函数增多,有可能出现可怕的回调地狱,导致性能下降,同时过度依赖 nextTick 也会降低代码的可读性,所以大家还是"按需加载"的好~
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/cb0bf1253a7ebd2eeecfc4a60】。文章转载请联系作者。
评论