前言
最近在学习 ustbhuangyi 老师的 Vue源码全方位深入解析课程,以下内容算是课堂笔记了吧。
JS 的运行机制
为方便理解 nextTick,需要先理解 JS 的运行机制。
参考链接:
JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
我的理解
简单测试
<script>
console.log(1)
console.log(2)
console.log(3)
console.log(4)
test()
console.log(5)
console.log(6)
console.log(7)
console.log(8)
function test(){
console.log("a")
setTimeout(function(){console.log("b")},1000)
}
</script>
打印顺序
源码实现
nextTick源码实现的本质,是把要实现的函数收集起来,收集到 callbacks 数组中,然后在下一个 tick 中遍历数组,执行回调。
src/core/util/next-tick.js
const callbacks = []
......
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
})
}
}
测试
<template>
<div class="hello">
<h1 ref="msg">{{ msg }}</h1>
<div>
<button @click="changeText1">change1</button>
<button @click="changeText2">change2</button>
</div>
</div>
</template>
<script>
export default {
name: 'NextTick',
data () {
return {
msg: 'hello world'
}
},
methods: {
changeText1 () {
this.msg = 'hello vue'
// 同步输出dom结果
console.log('sync: msg = ' + this.$refs.msg.innerText)// 输出hello world
// 异步输出dom结果
// 以下两种方式写都可以
// this.$nextTick(()=>{
// console.log("nextTick: msg = "+this.$refs.msg.innerText)
// })
this.$nextTick().then(() => {
console.log('nextTick: msg = ' + this.$refs.msg.innerText)// 输出hello vue
})
},
changeText2 () {
// 执行顺序和书写顺序有关
this.$nextTick(() => {
console.log('nextTick: msg = ' + this.$refs.msg.innerText)// 输出hello world
})
this.msg = 'hello vue'
// 同步输出dom结果
console.log('sync: msg = ' + this.$refs.msg.innerText)// 输出hello world
// 异步输出dom结果
// 结论:数据的变化到dom的重新渲染是一个异步的过程
this.$nextTick().then(() => {
console.log('nextTick: msg = ' + this.$refs.msg.innerText)// 输出hello vue
})
}
}
}
</script>
总结
数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。
这就是我们平时在开发的过程中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行。
比如下面的伪代码:
getData(res).then(()=>{
this.xxx = res.data
this.$nextTick(() => {
// 这里我们可以获取变化后的 DOM
})
})
数据改变后触发渲染 watcher 的 update ,但是watchers 的flush 是在nextTick 后,所以重新渲染是异步的。