nextTick

nextTick

场景

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

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

          // slice() 方法以新的数组对象,返回数组中被选中的元素。

          // slice() 方法选择从给定的 start 参数开始的元素,并在给定的 end 参数处结束

        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 
  // nextTick 行为 利用 微任务队列,他通过
  // either native Promise.then or MutationObserver.
  // 或者原生Promise.then 或 MutationObserver
  // MutationObserver has wider support, however it is seriously bugged in
  // MutationObserver 有广泛的支持,尽管它确实是存在严重缺陷,
  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers.
  // UIWebView 在 IOS大于9.3.3的版本下,当触摸触发事件时,

  // It
completely stops working after triggering a few times... so, if native
  // 它会彻底的停止工作几秒,所以,如果原生
  // Promise is available, we will use it:
  // Promise是可用的,我们将使用它
  /* 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
// 在有问题的UIWebViews,Promise.then不会完全中断,但
      // 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,
// 使用 MutationObserver 当 Promise 不可用
    // e.g. PhantomJS, iOS7, Android 4.4
 // 例如, 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     })    }  } })()

 

 

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

 

nextTickHandler 这个函数用来执行callback里存储的所有回调函数,接下来是触发方式赋值给timerFunc

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

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

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

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

 

 

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

  • Promise
  • MutationObserver
  • setTimeout

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

MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等等。
调用过程很简单,但是有点不太寻常:你需要先给他绑回调:

var mo = new MutationObserver(callback)

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

 
这个时候你只是给MutationObserver实例绑定好了回调,他具体监听哪个DOM、监听节点删除还是监听属性修改,还没有设置。而调用他的observer方法就可以完成这一步:
var domTarget = 你想要监听的dom节点
mo.observe(domTarget, {
      characterData: true //说明监听文本内容的修改。
})

 

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

 

其实使用 MutationObserver的原因就是 nextTick想要一个异步API,用来在当前的同步代码执行完毕后,执行我想执行的异步回调,包括PromisesetTimeout都是基于这个原因。

posted on 2021-11-04 17:40  zhishiyv  阅读(181)  评论(0编辑  收藏  举报

导航