当前位置: 移动技术网 > IT编程>脚本编程>vue.js > 简单理解Vue中的nextTick方法

简单理解Vue中的nextTick方法

2018年02月08日  | 移动技术网IT编程  | 我要评论

宝丽贝缇,二次元流浪者,jinniumovie.be

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
  }
 }
})

点击前

点击后

从图中可以得知: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里存入回调函数。

上图是整个执行流程,关键在于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 //说明监听文本内容的修改。
})

在nexttick中 mutationobserver的作用就如上图所示。在监听到dom更新后,调用回调函数。

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网