当前位置: 首页 > news >正文

vue事件循环机制

一、基础:JavaScript 事件循环(复习)

Vue 的异步更新机制建立在 JS 事件循环之上,必须先理解这个基础。

核心概念:

  • 调用栈(Call Stack): 执行同步代码的地方
  • 任务队列(Task Queue): 存放宏任务(Macro Tasks),如 setTimeout, setInterval, I/O
  • 微任务队列(Microtask Queue): 存放微任务(Micro Tasks),如 Promise.then, MutationObserver
  • 事件循环流程:
    1. 执行同步代码(调用栈)
    2. 清空微任务队列(所有微任务)
    3. 渲染页面(如有需要)
    4. 取一个宏任务执行
    5. 重复步骤 1-4
console.log('1'); // 同步setTimeout(() => console.log('2'), 0); // 宏任务Promise.resolve().then(() => console.log('3')); // 微任务console.log('4'); // 同步// 输出顺序:1 → 4 → 3 → 2

二、Vue 的异步更新队列(核心机制)

这是 Vue 事件循环机制最独特和重要的部分。

1. 为什么要异步更新?

问题: 如果数据变化立即更新 DOM,在同一个事件循环中多次修改数据会导致不必要的重复渲染。

// 如果同步更新,会渲染3次,性能差
this.name = 'Alice';
this.age = 25;
this.city = 'Beijing';

解决方案: Vue 将 DOM 更新推迟到下一个事件循环的微任务中执行,批量更新。

2. 异步更新流程
// 示例代码
export default {data() {return { count: 0 }},methods: {updateCount() {this.count = 1; // 修改数据this.count = 2; // 再次修改this.count = 3; // 再次修改console.log('同步代码结束');}}
}

执行流程:

  1. 数据变化:count 被修改时,Vue 会通知所有依赖(Watcher)
  2. 加入队列: Vue 不会立即更新 DOM,而是将需要更新的 Watcher 加入异步队列
  3. 去重优化: 同一个 Watcher 在同一个事件循环中被多次触发,只会被推入队列一次
  4. 异步执行: 在下一个事件循环的微任务中,Vue 清空队列,执行所有 Watcher 的更新
  5. DOM 更新: 最终 DOM 只更新一次
// 伪代码表示Vue内部机制
class Vue {constructor() {this._watchers = [];this._pending = false;}// 数据变化时调用notify() {// 将watcher加入队列const watchers = this._watchers.slice();if (!this._pending) {this._pending = true;// 使用微任务异步执行Promise.resolve().then(() => {this._pending = false;// 清空队列,执行所有更新for (let watcher of watchers) {watcher.update();}});}}
}

三、$nextTick 的原理和应用

1. 什么是 $nextTick?

$nextTick 是 Vue 提供的 API,用于在 DOM 更新完成后执行回调函数。

this.count = 100;
this.$nextTick(() => {// 这里可以获取到更新后的 DOMconsole.log('DOM updated:', this.$el.textContent);
});
2. $nextTick 的实现原理

$nextTick 会尝试使用以下微任务API(按优先级降序):

  1. Promise.then()(现代浏览器)
  2. MutationObserver(备选方案)
  3. setImmediate(IE)
  4. setTimeout(fn, 0)(降级方案)

核心思想: 将回调函数推迟到下一个事件循环的微任务中执行。

// 简化的nextTick实现
const callbacks = [];
let pending = false;function nextTick(cb) {callbacks.push(cb);if (!pending) {pending = true;// 优先使用Promiseif (typeof Promise !== 'undefined') {Promise.resolve().then(flushCallbacks);} // 降级方案else {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]();}
}

四、实战代码示例

示例1:理解更新时机
export default {data() {return { message: 'Hello' }},methods: {updateMessage() {this.message = 'Updated';// 此时DOM还未更新console.log('同步代码:', this.$el.textContent); // 可能还是 'Hello'this.$nextTick(() => {// DOM已经更新console.log('nextTick中:', this.$el.textContent); // 'Updated'});}}
}
示例2:批量更新的好处
export default {data() {return { list: [] }},methods: {addItems() {// 三次数据修改,但只触发一次DOM更新this.list.push('Item 1');this.list.push('Item 2'); this.list.push('Item 3');// 此时DOM还未更新,list长度是3console.log('List length:', this.list.length); // 3this.$nextTick(() => {// DOM已更新,可以操作更新后的DOMconsole.log('DOM更新完成');});}}
}
示例3:事件循环顺序
export default {methods: {testEventLoop() {console.log('1. 同步代码开始');// 数据变化 - 加入Vue异步更新队列(微任务)this.message = 'Updated';// 宏任务setTimeout(() => console.log('4. setTimeout'), 0);// 微任务Promise.resolve().then(() => console.log('3. Promise'));// Vue的nextTick(微任务)this.$nextTick(() => console.log('2. nextTick'));console.log('1. 同步代码结束');}}
}// 输出顺序:
// 1. 同步代码开始
// 1. 同步代码结束
// 2. nextTick(Vue异步更新在此执行)
// 3. Promise
// 4. setTimeout

五、面试常见问题

Q1:为什么Vue使用异步更新队列?

A: 为了性能优化。批量处理数据变化,避免不必要的重复渲染,确保在同一个事件循环中的多次数据变化只触发一次DOM更新。

Q2:$nextTick和setTimeout(fn, 0)有什么区别?

A:

  • $nextTick 优先使用微任务(Promise/MutationObserver),执行时机更早
  • setTimeout 是宏任务,要等到下一个事件循环才执行
  • 在Vue中,$nextTick 能确保在DOM更新后立即执行,而setTimeout可能要等到浏览器渲染之后
Q3:什么时候需要使用$nextTick?

A:

  1. 操作更新后的DOM:数据变化后需要立即操作DOM
  2. 在created钩子中操作DOM:此时DOM还未渲染,需要等到下一个tick
  3. 等待子组件渲染完成
export default {created() {this.$nextTick(() => {// 此时DOM已渲染完成this.doSomethingWithDOM();});}
}

总结

Vue事件循环机制的核心要点:

  1. 异步更新:数据变化 → 通知Watcher → 加入队列 → 下一个tick批量更新DOM
  2. 性能优化:同一个事件循环中的多次数据变化只会触发一次渲染
  3. $nextTick原理:利用微任务队列,确保回调在DOM更新后执行
  4. 执行顺序:同步代码 → Vue异步更新(微任务)→ 其他微任务 → 宏任务

  • 宏任务(MacroTask/Task): 代表一个个独立的、离散的工作单元。JavaScript 引擎在每次事件循环中会执行一个宏任务,然后检查并清空微任务队列。
  • 微任务(MicroTask): 代表需要在当前宏任务结束后、渲染之前立即执行的任务。每个宏任务执行完后,会清空整个微任务队列

宏任务(MacroTask)列表

宏任务由浏览器或 Node.js 环境本身调度。

类型描述示例
setTimeout / setInterval定时器回调setTimeout(cb, 0)
I/O 操作文件读写、网络请求等(在 Node.js 中尤为常见)fs.readFile('file.txt', cb)
UI 渲染浏览器自行决定的渲染时机(注意: 渲染本身也是一个宏任务)-
事件回调用户交互事件(点击、滚动等)button.addEventListener('click', cb)
setImmediate(仅Node.js) 在当前事件循环结束时执行setImmediate(cb)
requestAnimationFrame(仅浏览器) 在下一次重绘之前执行,通常用于动画requestAnimationFrame(cb)
MessageChannel用于跨文档通信或 Web Worker 通信channel.port1.onmessage = cb

微任务(MicroTask)列表

微任务是由 JavaScript 引擎本身调度的,优先级更高。

类型描述示例
Promise.then() / catch() / finally()最常用、最主要的微任务Promise.resolve().then(cb)
queueMicrotask()现代浏览器提供的专门用于创建微任务的 APIqueueMicrotask(cb)
MutationObserver监听 DOM 变化的接口,其回调是微任务new MutationObserver(cb)
process.nextTick(仅Node.js) 优先级甚至高于其他微任务process.nextTick(cb)

经典面试题与执行顺序分析

理解执行顺序的最佳方式是通过代码。

示例1:基础顺序
console.log('1. 同步脚本开始'); // 同步代码setTimeout(() => {console.log('6. setTimeout - 宏任务');
}, 0);Promise.resolve().then(() => {console.log('4. Promise - 微任务');
});console.log('2. 同步脚本结束'); // 同步代码// 输出顺序:
// 1. 同步脚本开始
// 2. 同步脚本结束
// 4. Promise - 微任务
// 6. setTimeout - 宏任务

流程分析:

  1. 执行同步代码(第一个宏任务)。
  2. 遇到 setTimeout,将其回调函数放入宏任务队列
  3. 遇到 Promise.resolve().then(),将其回调函数放入微任务队列
  4. 同步代码执行完毕(第一个宏任务结束)。
  5. 清空微任务队列,执行 Promise 回调。
  6. 微任务队列清空后,进行可能的页面渲染。
  7. 宏任务队列中取出下一个任务(setTimeout 的回调)并执行。
示例2:微任务中产生新的微任务
console.log('脚本开始');setTimeout(() => console.log('setTimeout'), 0);Promise.resolve().then(() => {console.log('Promise 1');// 在微任务中又产生了一个新的微任务return Promise.resolve('内部Promise');}).then((res) => {console.log('Promise 2', res);});console.log('脚本结束');

输出顺序:

脚本开始
脚本结束
Promise 1
Promise 2 内部Promise
setTimeout

分析: 引擎会持续清空微任务队列,直到队列为空,才会执行下一个宏任务。因此,即使微任务中产生了新的微任务,也会在同一个事件循环周期内被执行完毕。

示例3:混合场景(非常重要)
console.log('1. Start');setTimeout(() => {console.log('2. setTimeout - MacroTask');Promise.resolve().then(() => {console.log('3. Promise inside setTimeout - MicroTask');});
}, 0);Promise.resolve().then(() => {console.log('4. Promise - MicroTask');setTimeout(() => {console.log('5. setTimeout inside Promise - MacroTask');}, 0);
});console.log('6. End');

输出顺序:

1. Start
6. End
4. Promise - MicroTask
2. setTimeout - MacroTask
3. Promise inside setTimeout - MicroTask
5. setTimeout inside Promise - MacroTask

流程分析:

  1. 第一个宏任务(主线程脚本):
    • 输出 1. Start
    • setTimeout 回调加入宏任务队列。
    • Promise.then 回调加入微任务队列。
    • 输出 6. End
    • 宏任务结束,开始清空微任务队列。
  2. 执行微任务(第一个Promise):
    • 输出 4. Promise - MicroTask
    • 将内部的 setTimeout 回调加入宏任务队列。
  3. 微任务队列清空,执行下一个宏任务(第一个setTimeout):
    • 输出 2. setTimeout - MacroTask
    • 将内部的 Promise.then 回调加入微任务队列。
    • 当前宏任务结束,开始清空微任务队列。
  4. 执行微任务(setTimeout内部的Promise):
    • 输出 3. Promise inside setTimeout - MicroTask
  5. 微任务队列清空,执行下一个宏任务(Promise内部的setTimeout):
    • 输出 5. setTimeout inside Promise - MacroTask

总结与记忆技巧

特征宏任务(MacroTask)微任务(MicroTask)
触发时机每次事件循环执行一个在当前宏任务执行完后立即清空整个队列
调度者浏览器/Node.js(宿主环境)JavaScript 引擎(JS本身)
典型例子setTimeout, setInterval, I/O, UI事件Promise.then, MutationObserver, queueMicrotask
优先级

记忆口诀:

同(同步代码) > 微(微任务) > 渲(渲染) > 宏(宏任务)

面试要点:

  • 能清晰解释事件循环的流程。
  • 能准确判断给定代码的输出顺序。
  • 理解 Promiseasync/await(本质是Promise的语法糖)是微任务。
  • 了解 Vue.$nextTick 的原理就是利用微任务队列来实现异步更新DOM。
http://www.dtcms.com/a/398663.html

相关文章:

  • 分布式专题——19 Zookeeper分布式一致性协议ZAB源码剖析
  • 前端核心框架vue之(组件篇2/5)
  • 【分布式】分布式事务方案:两阶段、TCC、SEATA
  • Kafka介绍
  • Netty 解码器 DelimiterBasedFrameDecoder
  • 位运算 常见方法总结 算法练习 C++
  • 电子商务平台网站源码国外炫网站
  • PTZ相机的知识体系
  • Nginx反向代理配置全流程实战:从环境搭建到HTTPS部署
  • HTTPS 能抓包吗?实战答案与逐步可行方案(HTTPS 抓包原理、证书Pinning双向认证应对、工具对比)
  • 对网站建设的讲话wordpress 自定义面板
  • 【23】C++实战篇——C++报错:LNK2001:无法解析的外部符号 ,LNK2019: 无法解析的外部符号,原因分析及解决方法
  • 东莞建设银行官方网站礼品网站制作
  • TiDB Cloud 可观测性最佳实践
  • python+springboot毕业季旅游一站式定制服务系统
  • docker 启用容器端口被占用报错500
  • 无人机台风天通信技术要点
  • ParaZero-无人机降落伞领先开发商:SafeAir降落伞系统、DropAir精确空投系统、DefendAir反无人机系统
  • 手机怎样创建网站网站内容保护
  • 电路基础与PCB设计(一)电路
  • YOLO入门教程(四):搭建YOLOv1网络
  • k8s中的Gateway API 和istio
  • K8S (使用步骤)
  • k8s 跟 nacos 关于服务注册以及服务发现
  • 专业的家居网站建设深圳高端网站建设公司
  • Ubuntu vscode软件的安装和使用
  • [Maven 基础课程]10_Maven 私服
  • Python11-集成学习
  • 代做网站灰色关键词青州网站搭建
  • Spring-MVC响应