Vue的$nextTick的实现方式
Vue 的 $nextTick 是实现异步更新的核心 API,其实现方式经历了演变:
1. 实现原理
$nextTick 的本质是将回调函数延迟到下一个 DOM 更新周期之后执行。
2. 实现方式演变
2.1 优先使用微任务 (Microtask)
// Vue 2.5+ 的实现
const callbacks = []
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
// 优先级:Promise > MutationObserver > setImmediate > setTimeout
if (typeof Promise !== 'undefined') {
// 使用 Promise
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
} else if (typeof MutationObserver !== 'undefined') {
// 使用 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)
}
} else if (typeof setImmediate !== 'undefined') {
// 使用 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 降级到 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick(cb, ctx) {
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()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
2.2 Vue 实例方法
Vue.prototype.$nextTick = function(fn) {
return nextTick(fn, this)
}
3. 使用方式
3.1 回调函数方式
// 方式1:回调函数
this.message = 'Hello'
this.$nextTick(function() {
// DOM 更新完成
console.log(this.$el.textContent) // 输出: Hello
})
3.2 Promise 方式
// 方式2:Promise (Vue 2.1.0+)
this.message = 'Hello'
this.$nextTick().then(() => {
// DOM 更新完成
console.log('DOM updated')
})
// 方式3:async/await
async updateMessage() {
this.message = 'Hello'
await this.$nextTick()
// DOM 更新完成
console.log('DOM updated')
}
4. 执行时机
4.1 同步代码 vs 异步更新
export default {
data() {
return {
message: 'Hello',
count: 0
}
},
methods: {
updateData() {
// 同步代码
this.message = 'World'
this.count++
console.log('同步:', this.message) // 输出: World
console.log('同步:', this.count) // 输出: 1
// DOM 还未更新
console.log('DOM未更新:', this.$el.textContent) // 可能还是旧值
this.$nextTick(() => {
// DOM 已更新
console.log('DOM已更新:', this.$el.textContent) // 新值
console.log('nextTick:', this.count) // 1
})
}
}
}
5. 与事件循环的关系
// 执行顺序示例
console.log('1. 同步任务开始')
this.message = 'Hello'
// 微任务
Promise.resolve().then(() => {
console.log('3. Promise 微任务')
})
// nextTick 微任务
this.$nextTick(() => {
console.log('4. nextTick 微任务')
})
// 宏任务
setTimeout(() => {
console.log('6. setTimeout 宏任务')
}, 0)
console.log('2. 同步任务结束')
// 执行顺序:
// 1. 同步任务开始
// 2. 同步任务结束
// 3. Promise 微任务
// 4. nextTick 微任务
// 5. DOM 更新 (浏览器渲染)
// 6. setTimeout 宏任务
6. 实际应用场景
6.1 获取更新后的 DOM
methods: {
showInput() {
this.show = true
this.$nextTick(() => {
this.$refs.input.focus() // 确保 input 已渲染
})
}
}
6.2 基于更新后 DOM 的计算
methods: {
updateList() {
this.items.push('new item')
this.$nextTick(() => {
// 确保列表已更新
const lastItem = this.$el.querySelector('.item:last-child')
lastItem.scrollIntoView()
})
}
}
6.3 在 watcher 中使用
watch: {
data() {
// 数据变化时,DOM 还未更新
console.log('数据变化')
this.$nextTick(() => {
// DOM 已更新
console.log('DOM 更新完成')
})
}
}
总结
Vue 的 $nextTick 实现特点:
- 优先级:微任务 > 宏任务,确保在 DOM 更新后执行
- 兼容性:降级策略保证在各种环境下的可用性
- 灵活性:支持回调和 Promise 两种使用方式
- 批处理:同一事件循环中的多个
nextTick会被合并执行
这种实现确保了在数据变化后,能够可靠地在 DOM 更新完成后执行回调函数。
挣钱养家

浙公网安备 33010602011771号