来点鸡汤
男人一事无成时的温柔是最廉价的
前言
在理解nextTick之前,我们先要了解js的运行机制
js运行机制
js执行是单线程的,基于事件循环,事件循环大致分为以下几个步骤:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件,可以看出,这个任务队列主要存放异步任务的
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
nextTick
它的源码很简单,
源码:src/core/util/next-tick.js
export let isUsingMicroTask = false
/* 存放异步执行的回调 */
const callbacks = []
/* 标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送 */
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]()
}
}
/* 一个函数指针,指向函数将被推送到任务队列中,等到主线程任务执行完时,任务队列中的timeFunc将被调用 */
let timerFunc
/* 兼容Promise的浏览器 */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
/* 执行微任务标志位 */
isUsingMicroTask = true
} 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)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
/* 不支持Promise和MutationObserver则使用setImmediate */
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
/* 否则都使用setTimeout */
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
/* 延迟一个任务使其异步执行,在下一个tick时执行,这个函数的作用是在task或者microtask中推入一个timerFunc,在当前调用栈执行完以后以此执行直到执行到timerFunc,目的是延迟到当前调用栈执行完以后执行 */
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)
}
})
/* 已经有timerFunc被推送到任务队列中去则不需要重复推送 */
if (!pending) {
pending = true
timerFunc()
}
/* nextTick没有传回调函数时,返回一个Promise,我们可以nextTick().then(() => {}) */
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
解析:可以看出,nextTick在内部对环境做了兼容,首先会检测是否支持微任务:Promise,支持的话则把我们传入的回调押入队列中在下一个tick中等待执行(flushCallbacks依次执行回调),若不支持则使用MutationObserver,如以上两者都不支持则使用宏任务:setImmediate和setTimeout。
最后如果我们没有给nextTick传入回调,则可以nextTick().then(() => {})跳到then逻辑中。
总结
结合上一节的 setter 分析,我们了解到数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。这就是我们平时在开发的过程中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行。比如下面的伪代码:
getData(res).then(()=>{
this.xxx = res.data
this.$nextTick(() => {
// 这里我们可以获取变化后的 DOM
})
})