最近有人问我一个问题:
<template>
<div id="app">
<div class="info" v-if="start">123456</div>
<button @click="test">show</button>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
start:false,
}
},
methods:{
test(){
this.$nextTick(()=>{
let el=document.querySelector('.info');
console.log(el);
})
this.start=!this.start;
}
},
watch:{
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
首先我们先来看下nextTick的源码:
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)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
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)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
可见timerFunc是想模拟微任务,首先判断浏览器是否支持promise,如果不支持改用MutationObserver如果MutationObserver也不支持就改用setImmediate或者setTimeout来模拟。
timerFunc = () => {
p.then(flushCallbacks)
...
}
这是关键代码 timerFunc是一个函数 会以微任务的方式去执行flushCallbacks函数,flushCallbacks函数是一个清空回调队列的函数,其实这个队列就是刚刚nextTick函数里的callbacks数组,每次nextTick都会往里push你传入的函数,而且这个队列其实就是vue官方文档里所谓的 异步更新队列 。
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
好了,好像似乎没有什么问题,可是。。。他们是在哪被调用的呢? 现在就要debugger了!
this.$nextTick(()=>{
let el=document.querySelector('.info');
console.log(el);
})
首先这段代码 打断点,然后 没有任何问题 进入了 nextTick push了我们写的函数(我们的函数是写在App.vue页面第23行的),并且调用了timerFunc函数 注册了微任务flushCallbacks
this.start=!this.start;
这是一个响应式属性,被绑定在了v-if上,这次赋值的话一定会触发watcher去通知vue进行重新渲染 即调用update函数 通过diff算法 比对新旧node进行更新(这部分内容很多,要讲的话篇幅会比较长,而且我也没有深入研究过,,所以要是不知道的话可以去网上查看他人的博客了解大致过程是怎样的) 所以我们在watcher的update方法里打断点