从源码里了解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 相关文章推荐
常用参考资料(手册)下载或者链接
Jul 22 Javascript
一个tab标签切换效果代码
Mar 27 Javascript
基于jquery的滑动样例代码
Nov 20 Javascript
javascript字符串替换及字符串分割示例代码
Dec 12 Javascript
JS控制图片等比例缩放的示例代码
Dec 24 Javascript
jquery 绑定回车动作扑捉回车键触发的事件
Mar 26 Javascript
node.js中的emitter.emit方法使用说明
Dec 10 Javascript
JavaScript的内存释放问题详解
Jan 21 Javascript
JavaScript前端开发之实现二进制读写操作
Nov 04 Javascript
JS实现的简单分页功能示例
Aug 23 Javascript
策略模式实现 Vue 动态表单验证的方法
Sep 16 Javascript
文章或博客自动生成章节目录索引(支持三级)的实现代码
May 10 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获取字符串的编码格式的方法(函数)
2013/06/21 PHP
Zend Framework教程之视图组件Zend_View用法详解
2016/03/05 PHP
js单词形式的运算符
2014/05/06 Javascript
深入分析JSONP跨域的原理
2014/12/10 Javascript
html的DOM中document对象forms集合用法实例
2015/01/21 Javascript
jQuery使用fadeout实现元素渐隐效果的方法
2015/03/27 Javascript
javascript实现对表格元素进行排序操作
2015/11/18 Javascript
jquery层级选择器(匹配父元素下的子元素实现代码)
2016/09/05 Javascript
nodejs操作mongodb的增删改查功能实例
2017/11/09 NodeJs
浅谈vue中改elementUI默认样式引发的static与assets的区别
2018/02/03 Javascript
微信小程序实现换肤功能
2018/03/14 Javascript
对angularJs中自定义指令replace的属性详解
2018/10/09 Javascript
Vuex的actions属性的具体使用
2019/04/14 Javascript
vue实现codemirror代码编辑器中的SQL代码格式化功能
2019/08/27 Javascript
解决layui页面按钮点击无反应,也不报错的问题
2019/09/29 Javascript
python中使用smtplib和email模块发送邮件实例
2014/04/22 Python
python字符串对其居中显示的方法
2015/07/11 Python
Request的中断和ErrorHandler实例解析
2018/02/12 Python
pycharm debug功能实现跳到循环末尾的方法
2018/11/29 Python
通过pykafka接收Kafka消息队列的方法
2018/12/27 Python
Python算法的时间复杂度和空间复杂度(实例解析)
2019/11/19 Python
如何使用python实现模拟鼠标点击
2020/01/06 Python
Python实现发票自动校核微信机器人的方法
2020/05/22 Python
HTML5之SVG 2D入门12—SVG DOM及DOM操作介绍
2013/01/30 HTML / CSS
KLOOK客路:发现更好玩的世界,预订独一无二的旅行体验
2016/12/16 全球购物
Wedgwood英国官方网站:英式精致骨瓷餐具、礼品与生活精品,源于1759年
2019/09/02 全球购物
个人自我鉴定写法
2013/11/30 职场文书
心得体会范文
2014/01/04 职场文书
《富饶的西沙群岛》教学反思
2014/04/09 职场文书
领导班子党的群众路线教育实践活动对照检查材料
2014/09/25 职场文书
四风对照检查材料范文
2014/09/27 职场文书
教师党员整改措施
2014/10/24 职场文书
个人典型事迹材料
2014/12/30 职场文书
CSS3点击按钮圆形进度打钩效果的实现代码
2021/03/30 HTML / CSS
Redis 操作多个数据库的配置的方法实现
2022/03/23 Redis
vue3使用vuedraggable实现拖拽功能
2022/04/06 Vue.js