JavaScript 是单线程的,这意味着它一次只能做一件事。为了不让耗时任务(如网络请求)阻塞主线程,它引入了非阻塞的事件循环机制。
js的任务分为同步任务和异步任务
同步任务在主线程上排队执行,前一个任务执行完毕再执行下一个任务,
######
JavaScript 本身是一段脚本,不能独立运行,必须依靠 JavaScript 引擎(如 Chrome 的 V8)。
事件循环的每一轮(Tick)都严格遵循以下顺序:
宏任务是由宿主环境(浏览器或 Node.js)发起的任务。它们通常代表一些离散的、独立的工作单元
微任务是由 JavaScript 引擎本身发起的任务。它们旨在当前任务执行完后,立即处理一些“需要尽快收尾”的工作
常见宏任务:
setTimeout / setInterval定时逻辑:用于非精确的延时操作,或轮询任务。
setImmediate (Node.js特有)插队逻辑:在当前 Poll 阶段结束后立即执行,效率通常高于 setTimeout(0)。
ajax,DOM操作
I/O 操作文件/网络:读取文件、数据库操作、网络请求回调。
UI 渲染 (浏览器)视觉更新:浏览器重新绘制页面,确保用户看到最新的界面。
常见微任务:
Promise.then/catch/finally异步流控制:处理异步操作的结果,确保逻辑的连续性。
process.nextTick (Node.js)绝对优先:在当前同步代码执行完后“立即”执行,甚至在 Promise 之前。
MutationObserverDOM 监听:当 DOM 发生变化时异步通知,避免频繁同步操作导致性能问题。
当一个页面完全静止、没有任何 JS 运行、没有任何用户操作时,这个“事件循环”还在跑吗?
它在“逻辑上”依然存在,但在“物理上”它进入了休眠状态。
如果事件循环是一个死守岗位的“士兵”,当没有任何任务时,他不会疯狂地原地打转(那样会白白消耗 100% 的 CPU),而是会进入一种“待命挂起”状态。
当渲染主线程的任务队列(Task Queue)为空时:
async function async1() {
console.log('1');
await async2();
console.log('2');
}
async function async2() {
console.log('3');
return new Promise((resolve) => {
setTimeout(() => {
console.log('4');
resolve();
}, 0);
});
}
console.log('5');
setTimeout(() => {
console.log('6');
Promise.resolve().then(() => {
console.log('7');
});
}, 0);
async1();
new Promise((resolve) => {
console.log('8');
resolve();
}).then(() => {
console.log('9');
});
console.log('10');结果:
5 1 3 10 8 9 6 7 4 2浏览器是一个多进程的架构设计,在浏览器中打开一个网页相当于开启一个新进程
## 主要包含以下几种进程:
主进程:(负责协调 主控,管理用户界面 管理子进程)
第三方插件进程:负责在后台运行浏览器插件 有多个
GPU进程:(用于3D绘制,视频解码等)
浏览器渲染进程:(负责页面渲染,脚本执行,事件处理等,通常有多个),当满足某些条件时会做优化,比如相同tab页面共用一个渲染进程
网络进程:负责处理所有的网络请求,包括http请求
## 多线程的浏览器渲染进程
GUI渲染线程:负责渲染浏览器界面(解析HTML,CSS,构建DOM树和CSSOM树和render树)
JS引擎线程:负责处理js脚本程序(GUI引擎和JS引擎是互斥的,这也是造成js阻塞的原因)
事件触发线程:负责管理和调度页面中的事件 归属于浏览器而不是js引擎
定时器触发线程:负责执行页面中的定时器 setInterval setTimeout
网络请求线程: 当发起http请求时,会将请求发送给网络进程,由网络进程去和服务器通信,当网络进程接收到服务器的响应后,会将响应结果返回给异步http请求线程
