【VUE】nextTick的简易实现以及原理

Vue 中的 nextTick 到底干了什么?

在使用 Vue 的过程中,很多同学可能会遇到这样的场景:明明修改了数据,却无法立刻获取最新的 DOM。这时候你可能会听说,"用 nextTick 就好了"。那 nextTick 究竟是做什么的?它背后的实现原理又是什么?这篇文章我们来聊聊这个常被忽视却非常重要的 API。


1. nextTick 是用来干什么的?

Vue 是一个响应式框架,它会在数据变化时自动更新视图。但为了提升性能,Vue 并不会在每次数据变更时都立刻操作 DOM,而是采用"异步更新"的策略:将所有的数据变更缓存起来,等下一个"事件循环"中一起更新 DOM

这时候就会有个问题:我们在修改数据后立即访问 DOM,拿到的其实还是"旧的" DOM 结构 。这时候就需要 nextTick 出场了。

通俗点讲 :数据改了,但 DOM 还没改完。你要等 DOM 真改完了再做事,那就用 nextTick(fn)

2. nextTick 的原理是什么?

nextTick 的核心原理是利用 JavaScript 的事件循环机制 ------ 将回调函数插入到 微任务宏任务队列 中,从而实现"延迟执行"。

我们都知道事件循环的顺序是:

  • 当前同步任务执行完毕
  • 执行所有微任务(如 Promise.then
  • 执行一个宏任务(如 setTimeout

Vue 的 nextTick 就是将回调函数放入这些任务队列中,等 DOM 渲染完毕之后再执行回调

3. 模拟实现:深入理解调度机制

下面通过一个精简版的模拟实现,来看 Vue 如何利用 nextTick 和调度机制优化视图更新。

js 复制代码
let uid = 0
class Watcher {
    constructor() {
        this.id = ++uid
    }

    update() {
        console.log('watch ' + this.id + ' update')
        queueWatcher(this)
    }

    run() {
        console.log('watch ' + this.id + ' view is updating')
    }
}


let callbacks = []
let pending = false

function nextTick(cb) {
    callbacks.push(cb)

    if (!pending) {
        pending = true
        setTimeout(flushCallbacks, 0)
    }
}

function flushCallbacks() {
    pending = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; ++i) {
        copies[i]()
    }
}

let has = {}
let queue = []
let waiting = false

function flushScheduleQueue() {
    let watcher, id
    for (let index = 0; index < queue.length; index++) {
        watcher = queue[index]
        id = watcher.id
        has[id] = null
        watcher.run()
    }
    waiting = false
}

function queueWatcher(watcher) {
    const id = watcher.id
    // 防止重复
    if (has[id] == null) {
        has[id] = true
        queue.push(watcher)
        if (!waiting) {
            waiting = true
            nextTick(flushScheduleQueue)
        }
    }
}

(function () {
    let watch1 = new Watcher()
    let watch2 = new Watcher()

    watch1.update()
    watch1.update()
    watch2.update()
})()

这里的输出为

js 复制代码
watch 1 update
watch 1 update
watch 2 update
watch 1 view is updating
watch 2 view is updating

可以看到,这里的watch1的view更新只进行了一次。这是因为在queueWatcher函数中进行了一个去重处理,queue的队列之中只存放了两个watcher,分别是watch1和watch2。

分析代码

我们首先可以在立即执行函数中可以看到,对 watch1 还有 watch2 进行了声明,然后分别执行了 update 函数,然后在 update 函数之中,我们对 queueWatcher 函数进行了调用。

1. watch1.update()
2. queueWatcher(this)

queueWatcher(this) 函数内部,我们可以看到,进行了一个去重处理,使用 id 作为标记,然后将 watcher 放到 queue 中,最后再调用 nextTick 来进行更新。

3. queue.push(watcher)
4. nextTick(flushScheduleQueue)

虽然 watch1watch2 都调用了 update 方法,但整个过程中只会触发一次 nextTick,这是因为 queueWatcher 函数中使用了 waiting 标志位,确保在同一轮事件循环中只安排一次 flushScheduleQueue 调用,从而避免重复调度。
在函数 nextTick 中,会将当前的回调函数放入到 callbacks 之中,然后等待宏任务 setTimeout 的执行。在这个例子之中,我们可以看到,只有一个回调函数 flushScheduleQueue 放入到 callbacks 之中。最后我们在宏任务 setTimeout 之中执行 flushCallbacks ,这里就将 callbacks 中的函数进行遍历执行。

5. callbacks.push(cb)
6. 轮询callbacks中的回调函数

4. 分析逻辑:去重 + 调度 + 批量更新

上面的例子中,queueWatcher 是核心调度函数,负责:

  1. 去重处理 :通过 has[id] 记录 watcher 是否已加入队列,避免重复更新。
  2. 批量调度 :借助 waiting 标志位,确保同一轮事件循环中只调用一次 nextTick
  3. 延迟执行flushScheduleQueue 被推入宏任务,等 DOM 更新完成后统一执行。

nextTick 则通过回调队列 callbacks 缓存所有回调,并使用 setTimeout 模拟异步更新机制,确保这些回调在 DOM 更新之后再执行

总结

  • nextTick 是为了确保在 DOM 更新完成之后执行回调,用于访问最新的 DOM 状态。
  • 它的底层原理是利用事件循环机制,将回调函数推入微任务(或宏任务)队列中延迟执行。、
  • queueWatcher 通过去重与调度合并机制,避免重复更新,提升性能。
  • 本质上,Vue 利用 nextTick 实现了异步更新策略与高效的视图渲染调度。
相关推荐
H3091913 分钟前
vue3+dhtmlx-gantt实现甘特图展示
android·javascript·甘特图
CodeCraft Studio17 分钟前
数据透视表控件DHTMLX Pivot v2.1发布,新增HTML 模板、增强样式等多个功能
前端·javascript·ui·甘特图
llc的足迹28 分钟前
el-menu 折叠后小箭头不会消失
前端·javascript·vue.js
九月TTS1 小时前
TTS-Web-Vue系列:移动端侧边栏与响应式布局深度优化
前端·javascript·vue.js
曾经的你d1 小时前
【electron+vue】常见功能之——调用打开/关闭系统软键盘,解决打包后键盘无法关闭问题
vue.js·electron·计算机外设
积极向上的龙2 小时前
首屏优化,webpack插件用于给html中js自动添加异步加载属性
javascript·webpack·html
Bl_a_ck2 小时前
开发环境(Development Environment)
开发语言·前端·javascript·typescript·ecmascript
田本初3 小时前
使用vite重构vue-cli的vue3项目
前端·vue.js·重构
ai产品老杨3 小时前
AI赋能安全生产,推进数智化转型的智慧油站开源了。
前端·javascript·vue.js·人工智能·ecmascript
帮帮志3 小时前
vue实现与后台springboot传递数据【传值/取值 Axios 】
前端·vue.js·spring boot