源码在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
已经完全抛弃setTimeout
和MutationObserver
相信当前环境支持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更新完毕的?