从源码里了解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浮点数运算出现Bug的方法
Mar 12 Javascript
使用jQuery validate 验证注册表单实例演示
Mar 25 Javascript
jQuery实现的网页左侧在线客服效果代码
Oct 23 Javascript
基于jQuery实现点击列表加载更多效果
May 31 Javascript
Javascript实现图片加载从模糊到清晰显示的方法
Jun 21 Javascript
jQuery模拟淘宝购物车功能
Feb 27 Javascript
Angular2学习笔记之数据绑定的示例代码
Jan 03 Javascript
Vue中Axios从远程/后台读取数据
Jan 21 Javascript
js实现删除li标签一行内容
Apr 16 Javascript
vue以组件或者插件的形式实现throttle或者debounce
May 22 Javascript
基于layui table返回的值的多级嵌套的解决方法
Sep 19 Javascript
react实现移动端下拉菜单的示例代码
Jan 16 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相关资料
2006/10/09 PHP
使用php来实现网络服务
2009/09/15 PHP
PHP 字符串加密函数(在指定时间内加密还原字符串,超时无法还原)
2010/04/28 PHP
php强制下载类型的实现代码
2011/04/21 PHP
Trying to clone an uncloneable object of class Imagic的解决方法
2012/01/11 PHP
Laravel中获取路由参数Route Parameters的五种方法示例
2017/09/29 PHP
Javascript实例教程(19) 使用HoTMetal(7)
2006/12/23 Javascript
JS弹出窗口代码大全(详细整理)
2012/12/21 Javascript
关于jquery input textare 事件绑定及用法学习
2013/04/03 Javascript
node.js中的fs.fchmod方法使用说明
2014/12/16 Javascript
JS实用的动画弹出层效果实例
2015/05/05 Javascript
基于jquery步骤进度条源码分享
2015/11/12 Javascript
值得分享的轻量级Bootstrap Table表格插件
2016/05/30 Javascript
简单的jQuery拖拽排序效果的实现(增强动态)
2017/02/09 Javascript
javascript ES6 新增了let命令使用介绍
2017/07/07 Javascript
JavaScript实现无刷新上传预览图片功能
2017/08/02 Javascript
vue2配置scss的方法步骤
2019/06/06 Javascript
vue项目,代码提交至码云,iconfont的用法说明
2020/07/30 Javascript
python映射列表实例分析
2015/01/26 Python
对Python中列表和数组的赋值,浅拷贝和深拷贝的实例讲解
2018/06/28 Python
Python中函数的基本定义与调用及内置函数详解
2019/05/13 Python
python中entry用法讲解
2020/12/04 Python
python 数据类型强制转换的总结
2021/01/25 Python
Python3+SQLAlchemy+Sqlite3实现ORM教程
2021/02/16 Python
Monnier Freres中文官网:法国领先的奢侈品配饰在线零售商
2017/11/01 全球购物
巴西服装和鞋子购物网站:Marisa
2018/10/25 全球购物
JD Sports西班牙:英国领先的运动服装公司
2020/01/06 全球购物
异常和异常类的概念
2014/09/12 面试题
工地安全检查制度
2014/02/04 职场文书
爱之链教学反思
2014/04/30 职场文书
2015年三八妇女节活动总结
2015/02/06 职场文书
求职自我推荐信
2015/03/24 职场文书
建党伟业电影观后感
2015/06/01 职场文书
Python3 多线程(连接池)操作MySQL插入数据
2021/06/09 Python
MySQL快速插入一亿测试数据
2021/06/23 MySQL
Python读写yaml文件
2022/03/20 Python