nextTick
1.概念
这是一个生命周期钩子,主要作用是在数据更新后获取最新的dom
nextTick是一个函数,挂载在vm实例上,可以通过this.$nextTick()调用,并且接受一个回调函数作为参数
js
nextTick(callback,context)- 第一个参数为:回调函数(可以获取最近的
DOM结构) - 第二个参数为:执行函数上下文
2.使用
有如下例子,一个div.box中应用了data中的一个数据
定义了一个改变数据的方法change(),改变数据后获取box中的innerHtml
vue
<template>
<div>
<div class="box" ref="box">{{ value }}</div>
<button @click="change">click</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
value: 11,
}
},
methods: {
change() {
this.value = 22
console.log(this.$refs.box.innerHTML)
},
},
}
</script>发现第一次点击,虽然视图更新了,但打印的仍然是旧数据
第二次点击,由于第一次点击视图上的数据已经是22了,才会打印22

如此类型的操作,就没有获取到最新的dom
而使用nextTick,则解决此问题
vue
<template>
<div>
<div class="box" ref="box">{{ value }}</div>
<button @click="change">click</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
value: 11,
}
},
methods: {
change() {
this.value = 22
this.$nextTick(() => {
console.log(this.$refs.box.innerHTML)
})
},
},
}
</script>可以看到点击一次,就获取到了最新dom中的数据

3.原理
在Vue中,dom更新是异步的,当改变了一个数据导致dom发生改变,而同时我们在同步代码中获取这个dom,获取的是旧的dom,数据也会是旧的
而nextTick是将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
源码大致如下
callbacks也就是异步操作队列
callbacks新增回调函数后又执行了timerFunc函数,pending是用来标识同一个时间只能执行一次
js
export function nextTick(cb?: Function, ctx?: Object) {
let _resolve;
// cb 回调函数会经统一处理压入 callbacks 数组
callbacks.push(() => {
if (cb) {
// 给 cb 回调函数执行加上了 try-catch 错误处理
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
// 执行异步延迟函数 timerFunc
if (!pending) {
pending = true;
timerFunc();
}
// 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve;
});
}
}timerFunc函数定义,这里是根据当前环境支持什么方法则确定调用哪个,分别有:Promise.then、MutationObserver、setImmediate、setTimeout
通过上面任意一种方法,进行降级操作
js
export let isUsingMicroTask = false
if (typeof Promise !== 'undefined' && isNative(Promise)) {
//判断1:是否原生支持Promise
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//判断2:是否原生支持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)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
//判断3:是否原生支持setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
//判断4:上面都不行,直接用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}