• 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都是基于这个原因。

    未完,待续......
  • 相关阅读:
    怎样才是全能的程序员?
    [HDU 2553]N皇后问题
    [HDU 1870]愚人节的礼物
    [HDU 1016]Prime Ring Problem
    [HDU 1241]Oil Deposits
    [POJ 3250]Bad Hair Day
    [HDU 1276]士兵队列训练问题
    [POJ 2796]Feel Good
    [HDU 1237] 简单计算器
    总算开通博客园啦~
  • 原文地址:https://www.cnblogs.com/zhishiyv/p/11776681.html
Copyright © 2020-2023  润新知