一、前置知识:进程与线程
1. 核心概念:
-
进程: CPU 在运行指令和保存上下文所需要的时间 (比如: 手机打开微信,系统在执行打开指令到加载微信的上下文环境,直到彻底关闭微信之前的这段时间,都是一个进程)
-
线程:是进程中的一个更小的单位,指的是执行一段指令所需的时间 (比如: 打开微信聊天界面,就需要一个渲染线程,同时获取到最新的消息,需要一个网络线程)
2. 浏览器新公司开张啦! 🎉
当浏览器新开 tab 页时,就像成立了分公司,核心团队阵容:
graph LR
A[新tab进程] --> B(HTTP线程-外卖小哥)
A --> C(JS引擎线程-魔法师)
A --> D(渲染线程-装修队)
3. 致命问题:魔法师和装修队打架了! 🤼♂️
看这段代码引发的血案:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<h2>hello world</h2>
</body>
</html>
灾难现场直播 📺:
1. HTTP线程(外卖小哥)送来Vue套餐 🍱
2. JS引擎线程(魔法师)开始念咒:🔮
"天灵灵地灵灵——DOM给我变!"
3. 渲染线程(装修队)正刷墙:🖌️
"让我把这行字描漂亮...卧槽墙怎么塌了?!"
根本矛盾 ⚔️:
魔法师( JS 线程)和装修队(渲染线程)永远不能同时工作!
因为魔法师随手改 DOM 结构,会让装修队的梯子突然悬空🪜
线程之间通常都可以同时工作,但是只有 JS 引擎线程和渲染线程是互斥的,但是其它线程直接是可以同时工作的。 V8 引擎默认只开启一个主线程来执行 JS 代码,即常说的“单线程”特性。
二、时间管理大师:异步与事件循环 ⏱️✨
1. 单线程的魔法师遭遇中年危机 🧙♂️💥
当JS引擎线程(魔法师)独自扛起所有任务:
- 同步代码 = 紧急文件 ✍️
(立刻处理:`console.log('老板催命啦!')`)
- 异步代码 = 琐碎杂事 📦
(定时器/网络请求:`setTimeout(买咖啡,2000)`)
致命问题:
如果魔法师亲自等外卖(网络请求)、盯烤箱(定时器)——
页面直接卡成PPT!💥
2. 浏览器祭出终极大招:任务队列 📭
-
V8 在执行 JS 代码时,默认之开启一个线程工作 ( JS 是单线程的),所以考虑到执行效率, V8 会先执行同步代码,遇到异步代码,会将异步代码存放到 任务队列 ,等待 JS 引擎线程空闲时,再从任务队列中取出异步代码执行
-
任务队列 就像是魔法师的小助理,而小助理在处理文件时(处理异步代码)也有不同的处理方法
小助理的工作清单 📋:
| 任务类型 | 代表任务 | 紧急程度 |
|---|---|---|
| 微任务 | Promise.then , process.nextTick , MutationObserver | ⚡火箭级 |
| 宏任务 | setTimeout, setInterval, ajax, setImmediate, I/O,UI rendering/网络请求 | 🐢龟速级 |
小助理的工作流程 📭:
- 先执行同步代码(这属于宏任务),这个过程中遇到异步,就存入任务队列
- 同步执行完毕后,先执行微任务队列中的代码
- 微任务全部执行完毕后,有需要的情况下渲染页面
- 渲染完毕后,执行宏任务队列中的代码(开启了下一次的事件循环)
小助理的练习 ①
console.log(1);
new Promise((resolve) => {
console.log(2);
resolve();
})
.then(() => {
console.log(3);
setTimeout(() => {
console.log(4);
}, 0);
});
setTimeout(() => {
console.log(5);
setTimeout(() => {
console.log(6);
}, 0);
}, 0);
console.log(7);
分析
第一轮事件循环开始: 执行到 console.log(1); 时,为同步代码直接输出。接着执行new Promise((resolve) => { console.log(2); resolve(); })为同步代码直接输出。执行到 .then(() => { console.log(3); setTimeout(() => { console.log(4); }, 0); });时,因为Promise.then 为微任务,所以将其放入微任务队列中。继续执行setTimeout(() => { console.log(5); setTimeout(() => { console.log(6); }, 0); }, 0);因为setTimeout为宏任务,所以将其加入宏任务队列,接着执行 console.log(7);因为其为同步代码,所以直接输出。同步执行完毕后,先执行微任务队列中的代码,所以开始执行.then(() => { console.log(3); setTimeout(() => { console.log(4); }, 0); }); 执行到console.log(3); 因为其为同步代码,所以直接输出,执行到setTimeout(() => { console.log(4); }, 0);时,因为setTimeout 为宏任务,将其加入到宏任务列表。此时微任务已经执行完毕且没有需要渲染页面,此时执行宏任务列表。此时第一轮事件循环结束,开始执行第二轮事件循环。
第二轮事件循环开始: 开始执行setTimeout(() => { console.log(5); setTimeout(() => { console.log(6); }, 0); }, 0);执行到console.log(5); 因为其为同步代码,直接输出,执行到setTimeout(() => { console.log(6); }, 0);时,因为其为宏任务,将其放入宏任务列表。此时第一轮事件循环结束,开始执行第三轮事件循环。
第三轮事件循环开始: 执行setTimeout(() => { console.log(4); }, 0); 因为console.log(4);为同步代码,所以直接输出。此时代码已经执行完毕所以第二轮事件循环结束,开始第四轮事件循环。
第四轮事件循环开始: 执行 setTimeout(() => { console.log(6); }, 0);执行到console.log(6); 时,因为为同步代码,所以直接输出,此时所有代码执行完毕,事件循环结束。
输出结果为:
1
2
7
3
5
4
6
三、async/await:拯救程序员的时空魔法 ⏳✨
1. 为什么需要 async?回调地狱的血泪史
// 2015年之前的黑暗时代
买材料(function() {
煮水(function() {
下面条(function() {
加调料(function() {
// 回调地狱已形成 🌀
});
});
});
});
async 诞生的使命:
用同步写法解决异步问题,让代码像读小说一样流畅
2.async/await的核心特性
核心特性一:微任务快递柜 📦
当遇到
await:
async function 煮泡面() {
await 等水开(); // 同步执行
// ▼ 这行代码被快递员带走 ▼
下面条(); // 塞入微任务队列
}
就像把泡面调料包暂存快递柜,等水烧开(Promise完成)才取出
核心特性二:时间折叠术 🪄
浏览器对
await的魔改优化:
console.log('点火'); // 同步
await 烧水(); // 同步执行!浏览器偷偷优化的结果
console.log('下面'); // 微任务
看似连续的两步,实际被折叠成不同时空执行!
总结:
- 会将后续的代码挤入微任务队列
- 浏览器将 await 的执行时间提前了(await 后面的代码要当成同步代码来看待)
四、小助理的难题
通过了解 事件循环 的流程以及 async/await 的核心特性,相信你以及有足够的能力解决下面的难题:
console.log('script start');
async function async1() {
await async2() //
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1()
setTimeout(() => {
console.log('setTimeout');
}, 0)
new Promise((resolve, reject) => {
console.log('promise');
resolve()
})
.then(() => {
console.log('then1');
})
.then(() => {
console.log('then2');
});
console.log('script end');
请分析输出结果,打在评论区吧!
终极总结:浏览器宇宙的生存法则 🌌
在浏览器公司里,JS魔法师🧙♂️和渲染装修队🛠️因DOM修改权大打出手,逼得V8祭出事件循环小助理📭来调度任务!小助理手握两把神器:
1️⃣ 微任务火箭筒🚀(Promise.then等)—— 随叫随到秒处理
2️⃣ 宏任务乌龟车🐢(setTimeout等)—— 慢慢排队等叫号
而async/await这个时空魔术师🎩更狡猾:
🔥 把await后的代码塞进微任务快递柜📦
⏰ 却让await前的代码伪装成同步代码搞插队!
记住这三条宇宙真理:
✨ 同步代码 > 微任务 > 渲染 > 宏任务
💡 await前半场是影帝(装同步),后半场变快递(进微任务)
🚦 魔法师和装修队永远不能同台——除非你想看DOM坍塌的灾难片!
现在快去评论区破解小助理的终极谜题吧,赌一包辣条有人会掉进await的陷阱!🌶️💥