vue中nextTick源码

102 阅读2分钟

源码在src/core/util/next-tick.js 中,源码在100行左右,记录下。

核心机制: 利用微任务的机制,进行实现。Vue更新dom时,也用到了nextTick。vue异步更新,本质上是js事件机制的一种运用。

主要步骤

  • 1、延迟调用方式的判断,取得timerFunc。 Promise.then > MutationObserver > setImmediate > setTimeout 来做延迟 (setImmediate & setTimeout 是两种宏任务,是降级处理方案。)
  • 2、收集cb,放到一个数组中
  • 3、timerFunc调用,执行flushCallbacks所有收集到的cb。
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0 // 置空
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

简易版 nextTick兼容

  let timerFunc; 
  if(Promise){
  	timerFunc = () => {
    	Promise.resolve().then(flushCallbacks)
    }
  } else if(MutationObserver){
  	let observer = new MutationObserver(flushCallbacks)
    let textNode = document.createTextNode(1)
    observer.observe(textNode, {characterData: true})
    timerFunc = () => {
    	textNode.textContent = 2
    }
  } else if( setImmediate) {
  	timerFunc = () => {
    	setImmediate(flushCallbacks)
    }
  } else {
  	timerFunc = () => {
    	setTimeout(flushCallbacks)
    }
  }

vue的特点之一就是能实现响应式,但数据更新时,dom不会立即更新,而是放入一个异步队列中。如果代码里的逻辑要在DOM更新之后才能执行,则需要使用this.$nextTick() 来实现。

tips:

vue3已经完全抛弃setTimeoutMutationObserver相信当前环境支持promise

常见微任务、宏任务:

macrotasks(宏任务): setTimeout、setInterval、setImmediate、I/O、UI rendering 等。 microtasks(微任务): Promise、process.nextTick、MutationObserver 等。

next-tick源码

/* @flow */
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

const callbacks = []  // 收集
let pending = false

function flushCallbacks () {
      // 执行
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
let timerFunc // 延迟调用方式的判断 Promise.then > MutationObserver > setImmediate > setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  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)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

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()
  }
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

面试题

  • nextTick原理
  • for循环更新count数值,dom会被更新100次吗?
for(let i = 0;i< 100; i++){
	this.count++
}
// count搬定在dom上
  • nextTick是如何做到监听dom更新完毕的?

nextTick原理