熟悉 Vue 的同窗们都知道,Vue 有个 nextTick 方法,用来异步更新数据。javascript
<body>
<div id="main">
<ul class="list">
<li class="item" v-for="item in list">{{ item }}</li>
</ul>
</div>
<script> new Vue({ el: '#main', data: { list: [ 'AAAAAAAAAA', 'BBBBBBBBBB', 'CCCCCCCCCC' ] }, mounted: function () { this.list.push('DDDDD') } }) </script>
</body>
复制代码
随便给了点样式以后,页面是这样的:html
mounted: function () {
this.list.push('DDDDD')
console.log(this.$el.querySelectorAll('.item').length) // 3
}
复制代码
mounted: function () {
this.list.push('DDDDD')
Vue.nextTick(function() {
console.log(this.$el.querySelectorAll('.item').length) // 4
// ... 计算
})
复制代码
当你设置 vm.someData = 'new value' ,该组件不会当即从新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数状况咱们不须要关心这个过程,可是若是你想在 DOM 状态更新后作点什么,这就可能会有些棘手。虽然 Vue.js 一般鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,可是有时咱们确实要这么作。为了在数据变化以后等待 Vue 完成更新 DOM ,能够在数据变化以后当即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
简单说,由于 DOM 至少会在当前线程里面的代码所有执行完毕再更新。因此不可能作到在修改数据后而且 DOM 更新后再执行,要保证在 DOM 更新之后再执行某一块代码,就必须把这块代码放到下一次事件循环里面,好比 setTimeout(fn, 0),这样 DOM 更新后,就会当即执行这块代码。vue
划重点: 队列、事件循环java
咱们都知道,js 执行的全部任务都须要排队,一个任务必需要等它前面的一个任务执行完以后才能执行。若是前一个任务须要花费大量的时间来计算,那么后一个任务就必须一直等它执行完才会轮到它执行,这就是单线程的特性。 而 js 的任务分为两种,同步任务和异步任务:node
用一个丑但易懂的图来表示:git
被称做事件循环的缘由在于,同步的任务可能会生成新的任务,所以它一直在不停的查找新的事件并执行。一次循环的执行称之为 tick,在这个循环里执行的代码被称做 task,而整个过程是不断重复的。github
console.log(1);
setTimeout(()=>{
console.log(2);
},1000);
while (true){}
复制代码
上面代码在输出 1 以后(谨慎使用!个人浏览器就被卡死了~),定时器被塞到任务队列里,而后主线程继续往下执行,碰到一个死循环,致使任务队列里的任务永远不会被执行,所以不会输出 2数组
除了咱们的主线程以外,任务队列分为 microtask 和 macrotask,一般咱们会称之为微任务和宏任务。 microtask 这一名词在js中是个比较新的概念,咱们一般是在学习 ES6 的 Promise 时才初次接触到。promise
console.log(1)
setTimeout(function(){
console.log(2)
})
Promise.resolve().then(function(){
console.log('promise1')
}).then(function(){
console.log('promise2')
})
console.log(4)
复制代码
根据执行顺序,上面代码的输出结果很容易就能得出了:浏览器
让咱们回到上面的主题,Vue 的 nextTick方法, 从 源码 不难发现,Vue 在内部尝试对异步队列使用原生的setImmediate
Promise.then
和MessageChannel
,若是当前执行环境不支持,就采用setTimeout(fn, 0)
代替。
node原生就支持 process.nextTick(fn)
和setImmediate(fn)
方法,而且process.nextTick(fn)
会被当作microtask
顺序执行。