简单理解Vue中的nextTick方法


Posted in Javascript onJanuary 30, 2018

Vue中的nextTick涉及到Vue中DOM的异步更新,感觉很有意思,特意了解了一下。其中关于nextTick的源码涉及到不少知识,很多不太理解,暂且根据自己的一些感悟介绍下nextTick。

一、示例

先来一个示例了解下关于Vue中的DOM更新以及nextTick的作用。

模板

<div class="app">
 <div ref="msgDiv">{{msg}}</div>
 <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
 <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
 <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
 <button @click="changeMsg">
  Change the Message
 </button>
</div>

Vue实例

new Vue({
 el: '.app',
 data: {
  msg: 'Hello Vue.',
  msg1: '',
  msg2: '',
  msg3: ''
 },
 methods: {
  changeMsg() {
   this.msg = "Hello world."
   this.msg1 = this.$refs.msgDiv.innerHTML
   this.$nextTick(() => {
    this.msg2 = this.$refs.msgDiv.innerHTML
   })
   this.msg3 = this.$refs.msgDiv.innerHTML
  }
 }
})

点击前

简单理解Vue中的nextTick方法

点击后

简单理解Vue中的nextTick方法

从图中可以得知:msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的(详细解释在后面)。

二、应用场景

下面了解下nextTick的主要应用的场景及原因。

在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。

具体原因在Vue的官方文档中详细解释:

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

例如,当你设置vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

三、nextTick源码浅析

作用

Vue.nextTick用于延迟执行一段代码,它接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise对象。

源码

/**
 * 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
   })
  }
 }
})()

首先,先了解nextTick中定义的三个重要变量。

  1. callbacks:用来存储所有需要执行的回调函数
  2. pending:用来标志是否正在执行回调函数
  3. timerFunc:用来触发执行回调函数

接下来,了解nextTickHandler()函数。

function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
   copies[i]()
  }
 }

这个函数用来执行callbacks里存储的所有回调函数。

接下来是将触发方式赋值给timerFunc。

先判断是否原生支持promise,如果支持,则利用promise来触发执行回调函数;

否则,如果支持MutationObserver,则实例化一个观察者对象,观察文本节点发生变化时,触发执行所有回调函数。

如果都不支持,则利用setTimeout设置延时为0。

最后是queueNextTick函数。因为nextTick是一个即时函数,所以queueNextTick函数是返回的函数,接受用户传入的参数,用来往callbacks里存入回调函数。

简单理解Vue中的nextTick方法

上图是整个执行流程,关键在于timeFunc(),该函数起到延迟执行的作用。

从上面的介绍,可以得知timeFunc()一共有三种实现方式。

  1. Promise
  2. MutationObserver
  3. setTimeout

其中Promise和setTimeout很好理解,是一个异步任务,会在同步任务以及更新DOM的异步任务之后回调具体函数。

下面着重介绍一下MutationObserver。

MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等等。

调用过程很简单,但是有点不太寻常:你需要先给他绑回调:

var mo = new MutationObserver(callback)

通过给MutationObserver的构造函数传入一个回调,能得到一个MutationObserver实例,这个回调就会在MutationObserver实例监听到变动时触发。

这个时候你只是给MutationObserver实例绑定好了回调,他具体监听哪个DOM、监听节点删除还是监听属性修改,还没有设置。而调用他的observer方法就可以完成这一步:

var domTarget = 你想要监听的dom节点
mo.observe(domTarget, {
   characterData: true //说明监听文本内容的修改。
})

简单理解Vue中的nextTick方法

在nextTick中 MutationObserver的作用就如上图所示。在监听到DOM更新后,调用回调函数。

其实使用 MutationObserver的原因就是 nextTick想要一个异步API,用来在当前的同步代码执行完毕后,执行我想执行的异步回调,包括Promise和 setTimeout都是基于这个原因。其中深入还涉及到microtask等内容,暂时不理解,就不深入介绍了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
使用jQuery.Validate进行客户端验证(初级篇) 不使用微软验证控件的理由
Jun 28 Javascript
使用CSS和jQuery模拟select并附提交后取得数据的代码
Oct 18 Javascript
JS小游戏之极速快跑源码详解
Sep 25 Javascript
js+html5实现可在手机上玩的拼图游戏
Jul 17 Javascript
向JavaScript的数组中添加元素的方法小结
Oct 24 Javascript
举例说明如何为JavaScript的方法参数设置默认值
Nov 17 Javascript
jQuery日程管理插件fullcalendar使用详解
Jan 07 Javascript
jQuery完成表单验证的实例代码(纯代码)
Sep 30 jQuery
JavaScript中立即执行函数实例详解
Nov 04 Javascript
webpack分离css单独打包的方法
Jun 12 Javascript
jQuery实现文本显示一段时间后隐藏的方法分析
Jun 20 jQuery
详解vue身份认证管理和租户管理
May 25 Vue.js
Vue组件中slot的用法
Jan 30 #Javascript
vue初尝试--项目结构(推荐)
Jan 30 #Javascript
JS实现的邮箱提示补全效果示例
Jan 30 #Javascript
JS实现的抛物线运动效果示例
Jan 30 #Javascript
express如何使用session与cookie的方法
Jan 30 #Javascript
JavaScript实现的贝塞尔曲线算法简单示例
Jan 30 #Javascript
详解刷新页面vuex数据不消失和不跳转页面的解决
Jan 30 #Javascript
You might like
php 执行系统命令的方法
2009/07/07 PHP
为PHP安装imagick时出现Cannot locate header file MagickWand.h错误的解决方法
2014/11/03 PHP
php usort 使用用户自定义的比较函数对二维数组中的值进行排序
2017/05/02 PHP
PHP用户注册邮件激活账户的实现代码
2017/05/31 PHP
javascript下4个跨浏览器必备的函数
2010/03/07 Javascript
JS获取后台Cookies值的小例子
2013/03/04 Javascript
使用JQuery在线制作ppt并在线演示源码特效
2015/09/08 Javascript
js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?
2015/11/18 Javascript
详解nodejs与javascript中的aes加密
2016/05/22 NodeJs
bootstrap实现每隔5秒自动轮播效果
2016/12/20 Javascript
拖动时防止选中
2017/02/03 Javascript
微信小程序 跳转方式总结
2017/04/20 Javascript
ES6新特性六:promise对象实例详解
2017/04/21 Javascript
基于express中路由规则及获取请求参数的方法
2018/03/12 Javascript
微信上传视频文件提示(推荐)
2018/11/22 Javascript
微信小程序实现watch监听
2020/06/04 Javascript
微信小程序实现倒计时功能
2020/11/19 Javascript
Vue中ref和$refs的介绍以及使用方法示例
2021/01/11 Vue.js
[01:05:40]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS DT第三场
2014/05/24 DOTA
[05:09]2016国际邀请赛中国区预选赛淘汰赛首日精彩回顾
2016/06/29 DOTA
Python中的pack和unpack的使用
2018/03/12 Python
Python 统计字数的思路详解
2018/05/08 Python
Python数据持久化shelve模块用法分析
2018/06/29 Python
python实现两张图片拼接为一张图片并保存
2019/07/16 Python
numpy.random.shuffle打乱顺序函数的实现
2019/09/10 Python
python wxpython 实现界面跳转功能
2019/12/17 Python
Python如何通过Flask-Mail发送电子邮件
2020/01/29 Python
浅谈Python中的生成器和迭代器
2020/06/19 Python
python 代码运行时间获取方式详解
2020/09/18 Python
html5教程画矩形代码分享
2013/12/04 HTML / CSS
HTML5 canvas基本绘图之绘制五角星
2016/06/27 HTML / CSS
网吧消防安全责任书
2014/07/29 职场文书
2014年团员学习十八大思想汇报
2014/09/13 职场文书
群众路线表态发言材料
2014/10/17 职场文书
检讨书范文1000字
2015/01/28 职场文书
十二生肖观后感
2015/06/12 职场文书