一、定义[nextTick、事件循环]
nextTick的由来: 由于VUE的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。 nextTick的触发时机: 在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调。 应用场景: 需要在视图更新之后,基于新的视图进行操作。 以上出现了事件循环的概念,其涉及到JS的运行机制,包括主线程的执行栈、异步队列、异步API、事件循环的协作,此处不展开之后再总结。大致理解:主线程完成同步环境执行,查询任务队列,提取队首的任务,放入主线程中执行;执行完毕,再重复该操作,该过程称为事件循环。而主线程的每次读取任务队列操作,是一个事件循环的开始。异步callback不可能处在同一事件循环中。 简单总结事件循环: 同步代码执行 -> 查找异步队列,推入执行栈,执行callback1[事件循环1] ->查找异步队列,推入执行栈,执行callback2[事件循环2]... 即每个异步callback,最终都会形成自己独立的一个事件循环。 结合nextTick的由来,可以推出每个事件循环中,nextTick触发的时机: 同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发 tips:本文的任务队列、消息队列、异步队列指同一个东西,均指macrotask queue。 事件循环详解:www.cnblogs.com/hity-tt/p/6…二、实例理解nextTick的使用,并给出在页面渲染上的优化巧用
(tips:代码的正确阅读方式:看template组成、跳过script代码、看代码后面的用例设计、看之后的代码分析、同时结合回头结合script代码理解)<template>
<div>
<ul>
<li v-for="item in list1">{{item}}</li>
</ul>
<ul>
<li v-for="item in list2">{{item}}</li>
</ul>
<ol>
<li v-for="item in list3">{{item}}</li>
</ol>
<ol>
<li v-for="item in list4">{{item}}</li>
</ol>
<ol>
<li v-for="item in list5">{{item}}</li>
</ol>
</div>
</template>
<script type="text/javascript">
export default {
data() {
return {
list1: [],
list2: [],
list3: [],
list4: [],
list5: []
}
},
created() {
this.composeList12()
this.composeList34()
this.composeList5()
this.$nextTick(function() {
// DOM 更新了
console.log('finished test ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
},
methods: {
composeList12() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list1, i, 'I am a 测试信息~~啦啦啦' + i)
}
console.log('finished list1 ' + new Date().toString())
for (let i = 0; i < count; i++) {
Vue.set(me.list2, i, 'I am a 测试信息~~啦啦啦' + i)
}
console.log('finished list2 ' + new Date().toString())
this.$nextTick(function() {
// DOM 更新了
console.log('finished tick1&2 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
},
composeList34() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list3, i, 'I am a 测试信息~~啦啦啦' + i)
}
console.log('finished list3 ' + new Date().toString())
this.$nextTick(function() {
// DOM 更新了
console.log('finished tick3 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
setTimeout(me.setTimeout1, 0)
},
setTimeout1() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list4, i, 'I am a 测试信息~~啦啦啦' + i)
}
console.log('finished list4 ' + new Date().toString())
me.$nextTick(function() {
// DOM 更新了
console.log('finished tick4 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
},
composeList5() {
let me = this
let count = 10000
this.$nextTick(function() {
// DOM 更新了
console.log('finished tick5-1 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
setTimeout(me.setTimeout2, 0)
},
setTimeout2() {
let me = this
let count = 10000
for (let i = 0; i < count; i++) {
Vue.set(me.list5, i, 'I am a 测试信息~~啦啦啦' + i)
}
console.log('finished list5 ' + new Date().toString())
me.$nextTick(function() {
// DOM 更新了
console.log('finished tick5 ' + new Date().toString())
console.log(document.querySelectorAll('li').length)
})
}
}
}
</script>
2.1、用例设计 用例1:通过list1、2、3验证,处在同步代码中的DOM更新情况及nextTick的触发时机; 用例2:通过list3、list4验证,同步代码及异步代码中Dom更新及nextTick触发的区别; 用例3:通过list4、list5对比验证,多个异步代码中nextTick触发的区别; 用例4:通过在视图更新后获取DOM中<li>的数量,判断nextTick序列渲染的时间点。 2.2、代码分析 函数执行步骤: 事件循环1: step1: this.composeList12() -> update list1, update list2 -> 绑定tick’1&2’ step2: this.composeList34() -> update list3, 设置异步1setTimeout1 -> 绑定tick’3’ step3: this.composeList5() -> 绑定tick’5-1’ -> 设置异步2setTimeout2 step4: 绑定tick’test’ 事件循环2: 将setTimeout1的callback推入执行栈 -> update list4 -> 绑定tick’4’ 事件循环3: 将setTimeout2的callback推入执行栈 -> update list5 -> 绑定tick’5’ 2.3、推断输出消息 由于同一事件循环中的tick按执行顺序,因此消息输出为即: [同步环境]update list1 -> update list2 -> update list3 -> tick‘1&2’ -> tick‘3’ -> tick’5-1’ -> tick’test' [事件循环1]->update list4 -> tick’4’ [事件循环2] ->update list5 -> tick’5’ 2.4、实际运行结果如下图
