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进行一个降级处理。