【vue篇】Vue 响应式更新揭秘:数据变了,DOM 为何不立即刷新?

在 Vue 开发中,你是否写过这样的代码:

js 复制代码
this.message = 'New Value';
console.log(this.$el.textContent); // ❓ 为什么还是旧值?

"我明明改了数据,DOM 怎么没变?"

这背后是 Vue 异步更新队列的精妙设计。

本文将从 事件循环源码级实现,彻底解析 Vue 为何不立即渲染。


一、核心结论

Vue 的 DOM 更新是异步的。

js 复制代码
// 数据变化
this.message = 'Hello';

// ❌ 此时 DOM 尚未更新
console.log(this.$el.textContent); // 可能仍是旧值

// ✅ 在下一个 "tick" 后,DOM 才更新
this.$nextTick(() => {
  console.log(this.$el.textContent); // ✅ 'Hello'
});

二、为什么异步更新?

✅ 1. 性能优化:避免重复渲染

js 复制代码
// 同步更新:❌ 3 次 render
this.a = 1; // render()
this.b = 2; // render()
this.c = 3; // render()

// 异步更新:✅ 1 次 render
this.a = 1;
this.b = 2;
this.c = 3; // 批量更新

✅ 2. 避免无效计算

js 复制代码
this.items.push(1);
this.items.push(2);
this.items.pop(); // 最终 items 未变
  • 同步更新:执行 3 次 patch
  • 异步更新:去重后发现无需更新,跳过渲染。

✅ 3. 符合浏览器渲染机制

浏览器在 一次事件循环结束 后才进行重排(reflow)和重绘(repaint)。

Vue 的异步更新与浏览器节奏同步,避免强制同步布局(Forced Synchronous Layout)。


三、更新机制:微任务队列

🔄 事件循环(Event Loop)回顾

text 复制代码
| 宏任务 (MacroTask) |
      ↓
| 微任务 (MicroTask) | → 清空所有微任务
      ↓
| 渲染 (Render) | ← DOM 更新在此发生
      ↓
| 下一个宏任务 |

✅ Vue 的更新策略

  1. 数据变化 → 触发 setter
  2. Watcher 被推入 异步队列
  3. 使用 Promise.then(微任务)延迟执行;
  4. 在当前宏任务结束前,清空队列,批量更新 DOM。

四、源码级解析:queueWatcher

📌 核心逻辑

js 复制代码
const queue = [];
let waiting = false;

function queueWatcher(watcher) {
  const id = watcher.id;
  
  // ❌ 去重:同一 watcher 只入队一次
  if (queue.indexOf(watcher) === -1) {
    queue.push(watcher);
  }
  
  // ✅ 延迟刷新
  if (!waiting) {
    waiting = true;
    // 使用微任务(如 Promise)延迟执行
    nextTick(flushSchedulerQueue);
  }
}

function flushSchedulerQueue() {
  // 排序:父组件先于子组件
  queue.sort((a, b) => a.id - b.id);
  
  // 批量更新
  for (let i = 0; i < queue.length; i++) {
    const watcher = queue[i];
    watcher.run(); // 执行组件更新
  }
  
  // 重置
  queue.length = 0;
  waiting = false;
}

五、实战演示

📌 场景 1:连续修改数据

js 复制代码
methods: {
  updateData() {
    this.message = 'Step 1';
    this.count = 1;
    this.message = 'Final'; // 只触发一次更新
    
    // ❌ DOM 未更新
    console.log(this.$el.textContent); // 'Old Value'
    
    // ✅ 在 nextTick 中,DOM 已更新
    this.$nextTick(() => {
      console.log(this.$el.textContent); // 'Final'
    });
  }
}

📌 场景 2:避免强制同步布局

js 复制代码
// ❌ 反模式:强制同步布局
this.items.push(newItem);
console.log(this.listEl.scrollHeight); // 浏览器被迫同步计算布局

// ✅ 正确做法
this.items.push(newItem);
this.$nextTick(() => {
  console.log(this.listEl.scrollHeight); // 在 DOM 更新后读取
});

六、Vue 2 vs Vue 3 的差异

版本 队列刷新时机
Vue 2 Promise.then(微任务)
Vue 3 queueMicrotaskPromise(微任务)

💥 两者均为异步更新,行为一致。


七、如何强制"同步"更新?

❌ 不推荐:setTimeout

js 复制代码
this.message = 'new';
setTimeout(() => {
  console.log(this.$el.textContent); // 依赖宏任务,时机不可控
}, 0);

✅ 推荐:$nextTick

js 复制代码
this.message = 'new';
this.$nextTick(() => {
  // ✅ DOM 确保已更新
  console.log(this.$el.textContent);
});

💡 结语

"Vue 的异步更新,是性能与正确性的完美平衡。"

问题 答案
数据变,DOM 立即更新? ❌ 异步
为何异步? 避免重复渲染、提升性能
更新时机? 当前事件循环的微任务阶段
如何获取更新后 DOM? this.$nextTick()

记住:

"不要假设数据变化后 DOM 立即更新。"

掌握异步更新机制,你就能:

✅ 避免 DOM 读取时机错误;

✅ 写出更高效的 Vue 代码;

✅ 理解 nextTick 的真正意义。

相关推荐
wangjialelele1 天前
Qt中的常用组件:QWidget篇
开发语言·前端·c++·qt
乔冠宇1 天前
vue需要学习的点
前端·vue.js·学习
用户47949283569151 天前
同样是 #,锚点和路由有什么区别
前端·javascript
Hero_11271 天前
在pycharm中install不上需要的包
服务器·前端·pycharm
爱上妖精的尾巴1 天前
5-26 WPS JS宏数组元素添加删除应用
开发语言·前端·javascript·wps·js宏
是谁眉眼1 天前
wpsapi
前端·javascript·html
谅望者1 天前
Flexbox vs Grid:先学哪一个?CSS 布局完全指南(附可视化示例)
前端·css·html·css3·css布局·css flexbox·css grid
老华带你飞1 天前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·商城推荐系统
JS.Huang1 天前
【JavaScript】Pointer Events 与移动端交互
前端·javascript
一 乐1 天前
物业管理系统|小区物业管理|基于SprinBoot+vue的小区物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端