$nextTick详解

1,085 阅读2分钟

1617799999(1).jpg

1.什么情况下使用$nextTick?nextTick的作用?

当更新了数据后,我们需要对新DOM做一些操作,但是这时我们其实获取不到更新后的DOM。因为DOM还没有重新渲染,那么我们就需要使用$nextTick. nextTick可以让我们将回调延迟到下次DOM更新周期之后执行。 比如我们用v-if控制一个元素的显示或者隐藏,但是我们又需要获取这个元素时。我们就需要使用nextTick

html:<div v-if="isShow" id="tag"></div>
js: this.isShow = true
    document.getElementById('tag').text // 此时就获取不到id为tag的元素,需要使用nextTick

2.什么是事件循环?

js是一门单线程非阻塞的脚本语言,意味着js只有一个主线程处理业务,非阻塞指的是当主线程遇到异步任务时会先将其挂起,当处理完同步任务时,再以一定规则执行相应异步任务。 异步任务主要分两组:微任务和宏任务,不同类型的任务会被分配到不同类型的任务队列中。 微任务主要有:
promise,Object.observe,process.nextTick
宏任务主要有:
setTimeout,setInterval,I/O,UI交互事件,MessageChannel
执行顺序为先微后宏

3.$nextick源码解析

const callbacks = []
let pending = false
export function nextTick (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()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

可以看出nextTick这段函数做的最主要的就是将回调函数进行封装然后注入callbacks,然后根据pending控制执行一次timeFunc. 那么timeFunc是什么呢?

let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // 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]'
)) {
  // 当promise不能使用时就使用原生MutationObserver
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 属于宏任务,但并setTimeout依旧要好
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

代码可以看出,timeFunc其实就是变着法的想让flushCallbacks先执行。从promise=>MutationObserver=>setImmediate=>setTimeout。我们再来看看flushCallbacks是什么?

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

好家伙,原来就是把callbacks又拿出来循环执行了一遍。那我们其实完全可以理解为$nextTick其实就是在对setTimeOut进行一个降级处理。