美文网首页Vue.js专区
关于vue的nextTick

关于vue的nextTick

作者: 勤奋的大鱼 | 来源:发表于2018-05-30 12:52 被阅读68次

      在vue的代码中,有时候会用到this.$nextTick,这个方法的回调函数里可以获取到数据更新之后的DOM,使用的方法这里就不说了。在vue中可以简单理解为nextTick是下一次dom更新。
      另外,在我之前写的源码中,关于Watcher中的update方法只写了简单的同步更新,在vue的源码中,update方法,除非watcher中指定了sync为true,否则都为异步更新。异步更新会通过queueWatcher把当前观察者加入一个全局的watcher队列中,该队列中的watcher都会在nextTick后统一调用。

    事件循环(关于macroTask与microTask)

      简单来说,事件循环是这样的,先执行一个macroTask,然后执行完microTask队列中所有的microTask,然后再去执行一个macroTask。
      macroTask有这几种:setTimeout,setInterval,setImmediate,postMessage,MessageChannel,I/O,UI渲染等。
      microTask有这几种:原生Promise,MutationObserver,process.nextTick(Node环境)
    看了下源码,在vue2.5.0之前,vue对nextTick的处理都是这样的:
    1.是否有原生的Promise对象,如果是,就使用Promise.then异步触发nextTick
    2.如果不支持Promise,就使用MutationObserver来异步触发nextTick
    3.如果都不支持,则使用setTimeout。
      总而言之,从大部分的浏览器来说,所有的nextTick都是一个microTask。
      但是,如果去看最新的vue源码,就会发现独立了一个next-tick.js文件出来,里面的逻辑有了一些改变。
    至于改动的原因,源码中也有说明,我试着翻译了一下:

    // Here we have async deferring wrappers using both microtasks and (macro) tasks.
    // In < 2.4 we used microtasks everywhere, but there are some scenarios where
    // microtasks have too high a priority and fire in between supposedly
    // sequential events (e.g. #4521, #6690) or even between bubbling of the same
    // event (#6566). However, using (macro) tasks everywhere also has subtle problems
    // when state is changed right before repaint (e.g. #6813, out-in transitions).
    // Here we use microtask by default, but expose a way to force (macro) task when
    // needed (e.g. in event handlers attached by v-on).
    // 
    // 这里我们的异步函数一起使用了microtasks和macrotasks,
    // 在2.4版本之前,我们在任何地方都是用了microtasks,但是这里是一些microtasks有错误的场景
    // 由于microtasks有着太高的优先级,它在两次本应该顺序执行的事件之间触发了(比如 #4521, #6690),
    // 甚至在同一个事件冒泡的过程之间触发了(比如#6566)。
    // 然而,所有的地方都使用macroTask也会有一些小问题,比如当状态刚好在浏览器重绘之前改变(#6813)
    // 因此,我们默认使用了microtask,但是提供了一个方法强制使用macroTask如果需要的话。(比如在事件的绑定中)
    

    关于事件冒泡的情况,我写了如下一个小demo:

      <div id="a">
        aaaaa
        <div id="b">bbbbb</div>
      </div>
      <script>
        var $a = document.querySelector('#a')
        var $b = document.querySelector('#b')
        $b.addEventListener('click', function () {
          Promise.resolve()
            .then(function () {
              console.log('microTask')
            })
          console.log('b')
        })
        $a.addEventListener('click', function () {
          console.log('a')
        })
      </script>
    

      按理说nextTick应该在点击完b然后更新完DOM之后执行,但是如果使用了microTask,就像上面的代码,输出的结果为:b,microTask,a,也就是说冒泡到a上面的事件里如果有状态的改变,那么在nextTick中便无法获得,其余的场景原理应该也是差不多。如果把这里的microTask换成一个macroTask,那么在nextTick中就能获得这两个事件之后的DOM。另外,据我推测,涉及到事件的,不管是冒泡还是捕获的,应该都是一个macroTask。
      说回源码,为了解决这个问题,vue2.5之后,next-tick.js中有这样一段代码:

    export function withMacroTask (fn: Function): Function {
      return fn._withTask || (fn._withTask = function () {
        useMacroTask = true
        const res = fn.apply(null, arguments)
        useMacroTask = false
        return res
      })
    }
    

     这个方法的作用是,回调函数(fn)中的状态改变引起的nextTick操作,异步任务使用macroTask方法,具体的实现可以参照源码:源码。全局去搜索withMacroTask,可以发现目前只有events模块使用了这个方法。
     另外,最新的源码中,使用的macroTask的优先级为:setImmediate(只有IE支持),MessageChannel,setTimeout

    相关文章

      网友评论

      本文标题:关于vue的nextTick

      本文链接:https://www.haomeiwen.com/subject/myczjftx.html