如何使用Vue nextTick

这次给大家带来如何使用Vue nextTick,使用Vue nextTick的注意事项有哪些,下面就是实战案例,一起来看一下。

export default { data () {  return {   msg: 0  } }, mounted () {  this.msg = 1  this.msg = 2  this.msg = 3 }, watch: {  msg () {   console.log(this.msg)  } }}

登录后复制

这段脚本执行我们猜测1000m后会依次打印:1、2、3。但是实际效果中,只会输出一次:3。为什么会出现这样的情况?我们来一探究竟。

queueWatcher

我们定义 watch 监听 msg ,实际上会被Vue这样调用 vm.$watch(keyOrFn, handler, options) 。 $watch 是我们初始化的时候,为 vm 绑定的一个函数,用于创建 Watcher 对象。那么我们看看 Watcher 中是如何处理 handler 的:

this.deep = this.user = this.lazy = this.sync = false... update () {  if (this.lazy) {   this.dirty = true  } else if (this.sync) {   this.run()  } else {   queueWatcher(this)  } }...

登录后复制

初始设定 this.deep = this.user = this.lazy = this.sync = false ,也就是当触发 update 更新的时候,会去执行 queueWatcher 方法:

const queue: Array = []let has: { [key: number]: ?true } = {}let waiting = falselet flushing = false...export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) {  has[id] = true  if (!flushing) {   queue.push(watcher)  } else {   // if already flushing, splice the watcher based on its id   // if already past its id, it will be run next immediately.   let i = queue.length - 1   while (i > index && queue[i].id > watcher.id) {    i--   }   queue.splice(i + 1, 0, watcher)  }  // queue the flush  if (!waiting) {   waiting = true   nextTick(flushSchedulerQueue)  } }}

登录后复制

这里面的 nextTick(flushSchedulerQueue) 中的 flushSchedulerQueue 函数其实就是 watcher 的视图更新:

function flushSchedulerQueue () { flushing = true let watcher, id ... for (index = 0; index < queue.length; index++) {  watcher = queue[index]  id = watcher.id  has[id] = null  watcher.run()  ... }}

登录后复制

另外,关于 waiting 变量,这是很重要的一个标志位,它保证 flushSchedulerQueue 回调只允许被置入 callbacks 一次。 接下来我们来看看 nextTick 函数,在说 nexTick 之前,需要你对 Event Loop 、 microTask 、 macroTask 有一定的了解,Vue nextTick 也是主要用到了这些基础原理。如果你还不了解,可以参考我的这篇文章 Event Loop 简介 好了,下面我们来看一下他的实现:

export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () {  pending = false  const copies = callbacks.slice(0)  callbacks.length = 0  for (let i = 0; i  {   setImmediate(nextTickHandler)  } } else if (typeof MessageChannel !== 'undefined' && (  isNative(MessageChannel) ||  // PhantomJS  MessageChannel.toString() === '[object MessageChannelConstructor]' )) {  const channel = new MessageChannel()  const port = channel.port2  channel.port1.onmessage = nextTickHandler  timerFunc = () => {   port.postMessage(1)  } } else /* istanbul ignore next */ if (typeof Promise !== 'undefined' && isNative(Promise)) {  // use microtask in non-DOM environments, e.g. Weex  const p = Promise.resolve()  timerFunc = () => {   p.then(nextTickHandler)  } } else {  // fallback to setTimeout  timerFunc = () => {   setTimeout(nextTickHandler, 0)  } } return function queueNextTick (cb?: Function, ctx?: Object) {  let _resolve  callbacks.push(() => {   if (cb) {    try {     cb.call(ctx)    } catch (e) {     handleError(e, ctx, 'nextTick')    }   } else if (_resolve) {    _resolve(ctx)   }  })  if (!pending) {   pending = true   timerFunc()  }  // $flow-disable-line  if (!cb && typeof Promise !== 'undefined') {   return new Promise((resolve, reject) => {    _resolve = resolve   })  } }})()

登录后复制

首先Vue通过 callback 数组来模拟事件队列,事件队里的事件,通过 nextTickHandler 方法来执行调用,而何事进行执行,是由 timerFunc 来决定的。我们来看一下 timeFunc 的定义:

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {  timerFunc = () => {   setImmediate(nextTickHandler)  } } else if (typeof MessageChannel !== 'undefined' && (  isNative(MessageChannel) ||  // PhantomJS  MessageChannel.toString() === '[object MessageChannelConstructor]' )) {  const channel = new MessageChannel()  const port = channel.port2  channel.port1.onmessage = nextTickHandler  timerFunc = () => {   port.postMessage(1)  } } else /* istanbul ignore next */ if (typeof Promise !== 'undefined' && isNative(Promise)) {  // use microtask in non-DOM environments, e.g. Weex  const p = Promise.resolve()  timerFunc = () => {   p.then(nextTickHandler)  } } else {  // fallback to setTimeout  timerFunc = () => {   setTimeout(nextTickHandler, 0)  } }

登录后复制

可以看出 timerFunc 的定义优先顺序 macroTask –> microTask ,在没有 Dom 的环境中,使用 microTask ,比如weex

setImmediate、MessageChannel VS setTimeout

我们是优先定义 setImmediate 、 MessageChannel 为什么要优先用他们创建macroTask而不是setTimeout? HTML5中规定setTimeout的最小时间延迟是4ms,也就是说理想环境下异步回调最快也是4ms才能触发。Vue使用这么多函数来模拟异步任务,其目的只有一个,就是让回调异步且尽早调用。而MessageChannel 和 setImmediate 的延迟明显是小于setTimeout的。

解决问题

有了这些基础,我们再看一遍上面提到的问题。因为 Vue 的事件机制是通过事件队列来调度执行,会等主进程执行空闲后进行调度,所以先回去等待所有的进程执行完成之后再去一次更新。这样的性能优势很明显,比如:

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

有趣的问题

var vm = new Vue({  el: '#example',  data: {    msg: 'begin',  },  mounted () {   this.msg = 'end'   console.log('1')   setTimeout(() => { // macroTask     console.log('3')   }, 0)   Promise.resolve().then(function () { //microTask    console.log('promise!')   })   this.$nextTick(function () {    console.log('2')   }) }})

登录后复制

这个的执行顺序想必大家都知道先后打印:1、promise、2、3。

因为首先触发了 this.msg = ‘end’ ,导致触发了 watcher 的 update ,从而将更新操作callback push进入vue的事件队列。

this.$nextTick 也为事件队列push进入了新的一个callback函数,他们都是通过 setImmediate –> MessageChannel –> Promise –> setTimeout 来定义 timeFunc 。而 Promise.resolve().then 则是microTask,所以会先去打印promise。

在支持 MessageChannel 和 setImmediate 的情况下,他们的执行顺序是优先于 setTimeout 的(在IE11/Edge中,setImmediate延迟可以在1ms以内,而setTimeout有最低4ms的延迟,所以setImmediate比setTimeout(0)更早执行回调函数。其次因为事件队列里,优先收入callback数组)所以会打印2,接着打印3

但是在不支持 MessageChannel 和 setImmediate 的情况下,又会通过 Promise 定义 timeFunc ,也是老版本Vue 2.4 之前的版本会优先执行 promise 。这种情况会导致顺序成为了:1、2、promise、3。因为this.msg必定先会触发dom更新函数,dom更新函数会先被callback收纳进入异步时间队列,其次才定义 Promise.resolve().then(function () { console.log(‘promise!’)}) 这样的microTask,接着定义 $nextTick 又会被callback收纳。我们知道队列满足先进先出的原则,所以优先去执行callback收纳的对象。

相信看了本文案例你已经掌握了方法,更多精彩请关注【创想鸟】其它相关文章!

推荐阅读:

如何操作JS实现透明度渐变动画

怎样操作JS实现简单折叠展开动画

以上就是如何使用Vue nextTick的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2751728.html

(0)
上一篇 2025年3月8日 05:56:24
下一篇 2025年3月3日 00:25:47

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 怎样进行vue弹窗组件使用

    这次给大家带来怎样进行vue弹窗组件使用,vue弹窗组件使用的注意事项有哪些,下面就是实战案例,一起来看一下。 弹窗是一个项目必备的复用利器,所以封装起来,保证项目ui一致,是很有必要的。学了一段时间vue,想想还是用vue写一下吧。用的很…

    编程技术 2025年3月8日
    200
  • 怎样使用Vue组件

    这次给大家带来怎样使用Vue组件,使用Vue组件的注意事项有哪些,下面就是实战案例,一起来看一下。 Vue实例 项目启动过程 看一下现在我们的项目,想想整个项目的启动过程是什么(以直接打开index.html的方法访问为例来说明)? 你首先…

    编程技术 2025年3月8日
    200
  • 如何使用vue弹窗消息组件

    这次给大家带来如何使用vue弹窗消息组件,使用vue弹窗消息组件的注意事项有哪些,下面就是实战案例,一起来看一下。 本来打算写一个那种提示完了自动消失的弹窗的,但是没有想好淡入淡出的效果。所以暂时算是半成品。 练习代码如下: ys-aler…

    编程技术 2025年3月8日
    200
  • 怎样操作Vue使用Mint UI实现左滑删除效果CellSwipe

    这次给大家带来怎样操作Vue使用Mint UI实现左滑删除效果CellSwipe,操作Vue使用Mint UI实现左滑删除效果CellSwipe的注意事项有哪些,下面就是实战案例,一起来看一下。 Mint UI 是饿了么开源的,基于 Vue…

    2025年3月8日
    200
  • 如何使用Vue实现拖拽效果

    这次给大家带来如何使用Vue实现拖拽效果,使用Vue实现拖拽效果的注意事项有哪些,下面就是实战案例,一起来看一下。 效果图   分清clientY pageY screenY layerY offsetY的区别 在我们想要做出拖拽这个效果的…

    2025年3月8日
    200
  • 使用vue2.0+vue-dplayer这些技术如何实现hls播放的示例

    这篇文章主要介绍了vue2.0+vue-dplayer实现hls播放的示例,现在分享给大家,也给大家做个参考。 起因 之前写了一篇《 vue2.0+vue-video-player实现hls播放》,里边有提到在用vue-video-play…

    2025年3月8日
    200
  • 通过使用vue如何才能实现添加mp3音频文件

    本篇文章主要介绍了vue中添加mp3音频文件的方法,现在分享给大家,也给大家做个参考。 有的时候我们需要在vue中添加音频文件,但是直接将音频文件放置了assets目录下的时候,会发现并不能正常播放,下面是两种常用的配置方法: 方法一、将音…

    编程技术 2025年3月8日
    200
  • 怎样使用JS中console[”]输入方法

    这次给大家带来怎样使用JS中console[”]输入方法,使用JS中console[”]输入方法的注意事项有哪些,下面就是实战案例,一起来看一下。 1、console.log是最常用的输入方法,正常化输出语句,还具有…

    2025年3月8日 编程技术
    200
  • 怎样使用JS设计模式中链式调用

    这次给大家带来怎样使用JS设计模式中链式调用,使用JS设计模式中链式调用的注意事项有哪些,下面就是实战案例,一起来看一下。 写过jquery的可能都知道,jquery里面可以很方便的使用以下代码: // 不使用链式调用const eleme…

    编程技术 2025年3月8日
    200
  • 在vue项目中通过tween方法如何实现返回顶部

    这篇文章主要介绍了vue项目tween方法实现返回顶部,现在分享给大家,也给大家做个参考。 一、场景 tween.js是一款可生成平滑动画效果的js动画库 当你要实现一个返回顶部的功能时候你会怎么做,大部分人会使用document.body…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论