从源码里了解vue中的nextTick的使用


Posted in Javascript onNovember 22, 2018

今天做了一个需求,场景是这样的:

在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,然后我在接口一返回数据就展示了这个浮层组件,展示的同时,上报一些数据给后台(这些数据就是父组件从接口拿的),这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候,这些数据还未更新到组件上去。

父组件:

<template>
  .....
  <pop ref="pop" :name="name"/>
</template>
<script>
export default {
  .....
  created() {
    ....
    // 请求数据,并从接口获取数据
    Data.get({
      url: xxxx,
      success: (data) => {
        // 问题出现在这里,我们赋值以后直接调用show方法,去展现,show方法调用的同时上报数据,而上报的数据这个时候还未更新到子组件
        this.name = data.name
        this.$refs.pop.show()
      }
    })
  }
}
</script>

子组件

<template>
  <div v-show="isShow">
    ......
  </div>
</template>
<script>
export default {
  .....
  props: ['name'],
  methods: {
    show() {
      this.isShow = true
      // 上报
      Report('xxx', {name: this.name})
    }
  }
}
</script>

问题分析:

原因vue官网上有解析( cn.vuejs.org/v2/guide/re… )

可能你还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

这句话就是说,当我们在父组件设置this.name=name的时候,vue并不会直接更新到子组件中(dom的更新也一样未立即执行),而是把这些更新操作全部放入到一个队列当中,同个组件的所有这些赋值操作,都作为一个watcher的更新操作放入这个队列当中,然后等到事件循环结束的时候,一次性从这个队列当中获取所有的wathcer执行更新操作。在我们这个例子当中,就是我们在调用show的时候,实际上,我们的this.name=name并未真正执行,而是被放入队列中。vue的这种做法是基于优化而做的,毋庸置疑,不然我们如果有n多个赋值vue就执行n多个dom更新,那效率将会非常的低效和不可取的。

从源码里了解vue中的nextTick的使用

下文中的更新操作指对data的值进行更新的操作,在vue中,都会被放入队列异步执行。

解决方案:

1、 使用nextTick来延迟执行show方法(笼统得说,执行所有需要在数据真正更新后的操作

通过上面的分析我们知道,我们的所有的对vue实例的更新操作,都会先被放入一个队列当中,延迟异步执行,这些异步操作,要么是microtask,要么是macrotask(是microtask还是macroktask取决于环境,nextTick的源码中有所体现),根据事件循环机制,先入队列的先执行,所以如果我们在nextTick当中执行操作就会变成这样。

从源码里了解vue中的nextTick的使用

2、 使用setTimeout来延迟执行show方法,原理同上

所以我们的解决方法可以是:

this.name = data.name
setTimeout(() => {
 this.$refs.pop.show()
})

或者

this.name = data.name
this.$nextTick(() => {
 this.$refs.pop.show()
})

nextTick的实现原理

其实nextTick的实现原理是挺简单的,简单点说,就是实现异步,通过不同的执行环境,用不同的方式来实现,保证nextTick里面的回调函数能够异步执行。为什么要这么做呢?因为vue对dom的更新也是异步的呀。

下面贴出源码:

/**
 * Defer a task to execute it asynchronously.
 */
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 < copies.length; i++) {
   copies[i]()
  }
 }

 // the nextTick behavior leverages the microtask queue, which can be accessed
 // via either native Promise.then or MutationObserver.
 // MutationObserver has wider support, however it is seriously bugged in
 // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
 // completely stops working after triggering a few times... so, if native
 // Promise is available, we will use it:
 /* istanbul ignore if */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError)
   // in problematic UIWebViews, Promise.then doesn't completely break, but
   // it can get stuck in a weird state where callbacks are pushed into the
   // microtask queue but the queue isn't being flushed, until the browser
   // needs to do some other work, e.g. handle a timer. Therefore we can
   // "force" the microtask queue to be flushed by adding an empty timer.
   if (isIOS) setTimeout(noop)
  }
 } else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
 )) {
  // use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  var counter = 1
  var observer = new MutationObserver(nextTickHandler)
  var textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  // fallback to setTimeout
  /* istanbul ignore next */
  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()
  }
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve = resolve
   })
  }
 }
})()

首先我们看到这个是利用了闭包的特性,返回queueNextTick,所以我们实际调用的nextTick其实就是调用queueNextTick,一调用这个方法,就会把nextTick的回调放入队列callbacks当中,等到合适的时机,会将callbacks中的所有回调取出来执行,以达到延迟执行的目的。为啥要用闭包呢,我觉得有两个原因:

1、共享变量,比如callbacks、pending和timerFunc。

2、避免反复判断,即是避免反复判断timerFunc是利用Promise还是利用MutationObserver或是setTimeout来实现异步,这是函数柯里化的一种运用。

这里有两个最主要的方法需要解释下:

1、 nextTickHandler

这个函数,就是把队列中的回调,全部取出来执行,类似于microtask的任务队列。我们通过调用Vue.$nextTick就会把回调全部放入这个队列当中,等到要执行的时候,调用nextTickHandler全部取出来执行。

2、 timerFunc

这个变量,它的作用就是通过Promise/Mutationobserver/Settimeout把nextTickHandler放入到真正的任务队列当中,等到事件循环结束,就从任务队列当中取出nextTickHandler来执行,nextTickHandler一执行,callbacks里面的所有回调就会被取出来执行来,这样就达到来延迟执行nextTick传的回调的效果。

通过这个简单的源码分析,我们可以得出两个结论

1、nextTick会根据不同的执行环境,异步任务可能为microtask或者macrotask,而不是固定不变的。所以,如果你想让nextTick里面的异步任务统统看成是microtask的话,你会遇到坑的。

2、nextTick的并不能保证一定能获取得到更新后的dom,这取决于你是先进行数据赋值还是先调用nextTick。比如:

new Vue({
   el: '#app',
   data() {
    return {
     id: 2
    }
   },
   created() {
    
   },
   mounted() {
    this.$nextTick(() => {
     console.log(document.getElementById('id').textContent) // 这里打印出来的是2,因为先调用了nextTick
    })
    this.id = 3
   }
 })

结论

如果想要获取更新后的DOM或者子组件(依赖父组件的传值),可以在更新操作之后立即使用Vue.nextTick(callback),注意这里的先后顺序,先进行更新操作,再调用nextTick获取更新后的DOM/子组件,源码里面我们知道nextTick是无法保证一定是能够获取得到更新后的DOM/子组件的

以上所述是小编给大家介绍的vue中的nextTick的使用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
测试你的JS的掌握程度的代码
Dec 09 Javascript
js清除input中type等于file的值域(示例代码)
Dec 24 Javascript
用jQuery实现的智能隐藏、滑动效果的返回顶部代码
Mar 18 Javascript
基于javascript实现全屏漂浮广告
Mar 31 Javascript
Bootstrap表单组件教程详解
Apr 26 Javascript
JS中cookie的使用及缺点讲解
May 13 Javascript
Webpack中publicPath路径问题详解
May 03 Javascript
vue 弹窗时 监听手机返回键关闭弹窗功能(页面不跳转)
May 10 Javascript
什么时候不能在 Node.js 中使用 Lock Files
Jun 24 Javascript
vue中使用v-model完成组件间的通信
Aug 22 Javascript
微信小程序 组件的外部样式externalClasses使用详解
Sep 06 Javascript
vue页面加载时的进度条功能(实例代码)
Jan 13 Javascript
Vue动态加载异步组件的方法
Nov 21 #Javascript
微信小程序局部刷新触发整页刷新效果的实现代码
Nov 21 #Javascript
跨域解决之JSONP和CORS的详细介绍
Nov 21 #Javascript
如何去除富文本中的html标签及vue、react、微信小程序中的过滤器
Nov 21 #Javascript
JSON生成Form表单的方法示例
Nov 21 #Javascript
apicloud拉起小程序并传递参数的方法示例
Nov 21 #Javascript
vue中组件的过渡动画及实现代码
Nov 21 #Javascript
You might like
php获取随机数组列表的方法
2014/11/13 PHP
php实现图片转换成ASCII码的方法
2015/04/03 PHP
PHP实践教程之过滤、验证、转义与密码详解
2017/07/24 PHP
JS滚轮事件onmousewheel使用介绍
2013/11/01 Javascript
javascript父、子页面交互技巧总结
2014/08/08 Javascript
浅谈javascript中createElement事件
2014/12/05 Javascript
JavaScript中的立即执行函数表达式介绍
2015/03/15 Javascript
jQuery固定元素插件scrolltofixed使用指南
2015/04/21 Javascript
JavaScript中通过提示框跳转页面的方法
2016/02/14 Javascript
AngularJS通过ng-route实现基本的路由功能实例详解
2016/12/13 Javascript
Vue响应式原理详解
2017/04/18 Javascript
快速掌握jquery分页插件jqPaginator的使用方法
2017/08/09 jQuery
Node.js服务器开启Gzip压缩教程
2017/08/11 Javascript
js截取字符串功能的实现方法
2017/09/27 Javascript
react实现菜单权限控制的方法
2017/12/11 Javascript
微信小程序block的使用教程
2018/04/01 Javascript
详解JavaScript中操作符和表达式
2018/09/12 Javascript
PWA介绍及快速上手搭建一个PWA应用的方法
2019/01/27 Javascript
产制造追溯系统之通过微信小程序实现移动端报表平台
2019/06/03 Javascript
jQuery实现input[type=file]多图预览上传删除等功能
2019/08/02 jQuery
JavaScript实现简单的弹窗效果
2020/05/19 Javascript
python处理PHP数组文本文件实例
2014/09/18 Python
Python 利用切片从列表中取出一部分使用的方法
2019/02/01 Python
python字符串查找函数的用法详解
2019/07/08 Python
Python生命游戏实现原理及过程解析(附源代码)
2019/08/01 Python
Django中间件拦截未登录url实例详解
2019/09/03 Python
基于Python解密仿射密码
2019/10/21 Python
friso美素佳儿官方海外旗舰店:荷兰原产原罐
2017/07/03 全球购物
Zipadee-Zip襁褓过渡毯:Sleeping Baby
2018/12/30 全球购物
德国滑雪和户外用品网上商店:XSPO
2019/10/30 全球购物
舞蹈毕业生的自我评价
2014/03/05 职场文书
九一八事变演讲稿范文
2014/09/14 职场文书
学生喝酒检讨书500字
2014/11/02 职场文书
幼儿园感谢信
2015/01/21 职场文书
WordPress多语言翻译插件 - WPML使用教程
2021/04/01 PHP
二维码条形码生成的JavaScript脚本库
2022/07/07 Javascript