手写 vue3 nextTick

对于nextTick大家都不陌生,由于vue采用异步更新dom策略,当我们修改响应式数据时,vue并不会立即更新dom,而是将所有更新任务缓存在一个队列中,等待一段时间后统一执行,所以在修改响应式数据后,并不能直接拿到修改后的dom,这时就需要使用nextTicket获取更新之后的dom。

核心设计思路

Vue3 的异步更新机制依赖三个核心概念:

  • 任务队列(queue) :缓存所有待执行的 DOM 更新任务,避免频繁 DOM 操作。
  • 微任务调度:通过 Promise 将任务队列的执行推迟到同步代码完成后,实现异步批量更新。
  • 回调订阅(nextTick) :让用户回调在任务队列执行完毕后触发,确保获取最新 DOM 状态。

核心原理可以用下面简单的代码表示,就是通过promise,控制更新dom方法和nextTick中回调的执行顺序,保证nextTick中的回调在dom更新之后执行。

js 复制代码
Promise.resolve()
  .then(() => {
    // 执行任务队列中的所有方法,更新dom
  })
  .then(() => {
    // 执行nextTick中的回调
  });

代码实现

在理解了上面的核心原理后,下面参考源码来进行具体实现

首先定义三个全局变量

js 复制代码
  const queue = []; 
  let currentFlushPromise = null; // 刷新时的Pr
  const resolvedPromise = Promise.resolve(); 

queue:更新dom的任务队列, currentFlushPromise:通过订阅这个promise,在执行完所有更新dom的任务队列之后执行nextTick回调 resolvedPromise 一个resolved的

queueJob方法:

用于将dom更新任务插入到队列中,判断是否已经插入,避免重复插入。 同时在插入的时候判断currentFlushPromise是否为空,如果不为空,直接插入,如果为空,证明这次插入是在此次更新周期中的首次插入,此时将flushJobs(执行任务队列中的所有方法)插入到微任务队列中,在同步代码执行完成后,完成此次周期的全部更新任务入队,执行flushJobs方法,同时将resolvedPromise.then(flushJobs)赋值给currentFlushPromise,这样在所有更新任务完成后,会执行currentFlushPromise.then中的回调,执行nextTick中的回调函数,这样就保证了nextTick中的函数在所有dom更新之后执行。

js 复制代码
 function queueJob(fn) {
    console.log('插入队列');
    if (!queue.includes(fn)) {
      queue.push(fn);
    }
    if (!currentFlushPromise) {
      currentFlushPromise = resolvedPromise.then(flushJobs);
    }
  }

flushJobs方法

执行任务队列中的所有更新任务,并在执行完成后清空队列和currentFlushPromise,进入下一个更新周期。

js 复制代码
 function flushJobs() {
    console.log('开始执行队列');
    try {
      for (let i = 0; i < queue.length; i++) {
        const job = queue[i];
        job();
      }
    } finally {
      console.log('执行完毕,清空队列');
      queue.length = 0;
      currentFlushPromise = null;
    }
  }

nextTick方法

保证在任务队列执行完成后执行回调,同时返回一个promise

js 复制代码
  function nextTick(fn) {
    console.log('订阅nextTick');
    const p = currentFlushPromise || resolvedPromise;
    return fn ? p.then(fn) : p;
  }

通过下面代码测试,最终执行结果如下:

js 复制代码
 queueJob(() => {
    console.log('更新dom1');
    queueJob(() => {
      console.log('更新dom3');
    });
  });
  nextTick(() => {
    console.log('nextTick1');
    queueJob(() => {
      console.log('更新dom4');
    });
    nextTick(() => {
      console.log('nextTick2');
    });
  });
  queueJob(() => {
    console.log('更新dom2');
  });
复制代码
插入队列(fn1 入队)
订阅nextTick(cb1 注册)
插入队列(fn2 入队)
开始执行队列(第一次 flushJobs)
更新dom1
插入队列(fn3 入队)
更新dom2
更新dom3
执行完毕,清空队列
nextTick1(cb1 执行)
插入队列(fn4 入队)
订阅nextTick(cb2 注册)
开始执行队列(第二次 flushJobs)
更新dom4
执行完毕,清空队列
nextTick2(cb2 执行)

注:vue2中的nextTick和vue3中的实现有些不同,由于vue2需要适配低版本浏览器,会对不支持promise的浏览器有降级处理,最终会降级到setTimeout,所以需要将nextTick中的回调放在一个队列中单独维护,确保无论使用哪种异步方式,回调都能按顺序执行。

相关推荐
浅水壁虎7 小时前
vue基础(第二章 )
前端·javascript·vue.js
腻害兔8 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
java·前端·javascript·vue.js·产品经理·ai编程
m0_5474866613 小时前
《Vue.js + uni-app全栈开发从入门到实践 》全套PPT课件2026版
vue.js·uni-app
达子66615 小时前
第10章_HarmonyOs开发图解 用JS开发UI
vue.js
奶糖 肥晨15 小时前
想return数据 返回的确是函数问题处理
vue.js
无巧不成书021816 小时前
Vue+Vue-CLI全平台零基础搭建教程
前端·javascript·vue.js·vue·前端开发·vue-cli·前端环境搭建
腻害兔17 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro IoT 物联网模块:从协议网关到物模型,一文讲透开源 IoT 平台架构
前端·vue.js·物联网·产品经理·ai编程
张人玉17 小时前
基于 Vue 3 + ECharts + Express + SQLite 的可视化大屏与业务管理系统——YOLOv8 高精度车辆行人检测与计数系统
数据库·vue.js·yolo·sqlite·echarts·express
腻害兔1 天前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
sugar__salt1 天前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js