事件循环机制

  • 同步任务:按顺序逐行执行的代码,需原地等待结果后才继续向下执行
  • 异步任务:调用后耗时,不阻塞代码继续执行,在将来完成后触发一个回调函数

组成

  • 调用栈:存放正在执行的函数调用,顺序为先进后出。
  • 任务队列:存储异步函数,包括setTimeoutsetInterval、I/O事件、UI渲染
  • 微任务队列:存储微任务,如Promise的.then .catch .finally

流程

  1. 执行一次宏任务,将同步代码存入调用栈并执行;
  2. 处理微任务,直到队列清空;
  3. 渲染页面;
  4. 取一个宏任务执行,重复上述步骤

注意

  • 微任务优先级高于宏任务
  • 每个宏任务执行后会清空微任务队列
  • 同类型任务按入队顺序执行

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
console.log('1');

async function asyncFuncA() {
console.log('2');
// await 后面的表达式先执行,再将后续 “微任务” 注册到微任务队列
await Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
}

async function asyncFuncB() {
console.log('5');
// 这里没有显式 then,直接 await 会将后续作为微任务
await asyncFuncNested();
console.log('6');
}

async function asyncFuncNested() {
console.log('7');
return Promise.resolve('nested result').then(value => {
console.log('8');
});
}

// 注册多个宏任务与微任务
setTimeout(() => {
console.log('9');
Promise.resolve().then(() => {
console.log('10');
});
setTimeout(() => {
console.log('11');
}, 0);
}, 0);

Promise.resolve().then(() => {
console.log('12');
});

asyncFuncA();
asyncFuncB();

console.log('13');
// 错误1 2 5 13 3 4 7 8 6 12 9 10 11
// 正确1 2 5 7 13 3 4 8 6 12 9 10 11
  • 先执行同步任务,输出1,setTimeout为宏任务,进入任务队列,asyncFuncA进入队列输出二,执行await后的表达式并将后续代码注册进微任务队列,await后的表达式.then为微任务并进入微任务队列,asyncFuncA离开队列,asyncFuncB进入队列输出5,await后的asyncFuncNested函数进入队列,输出7并将.then注册进微任务队列,asyncFuncNested,asyncFunB离开队列,输出13,同步代码执行完毕,接下来处理微任务
  • 先是asyncFuncA里await后的.then,输出3,之后执行await后续代码输出4,然后是asyncFuncBasyncFuncNested里的微任务,输出8,接着执行asyncFuncB里await后续的代码,输出6,微任务执行完毕,接下来处理下一个宏任务
  • 执行setTimeout里的代码,输出9,.then进入微任务队列,setTimeout进入任务队列,同步代码执行完毕,执行微任务,输出10,取下一个宏任务,输出12
  • 注意:async函数中await后的表达式先执行,再将后续代码注册进微任务队列。

HTTP缓存机制

分类

  • 强制缓存:在有效期内,客户端直接使用本地缓存,不向服务器发送请求
  • 协商缓存:强制缓存过期后,客户端携带上次响应提供的校验标识(如ETagLast-Modified)向服务器询问资源是否变化

关键响应头

  • Expires强制缓存,示例:Expires: Wed, 21 Oct 2025 07:28:00 GMT,使用GMT格式的绝对时间,问题是客户端与服务端时间不一致会导致缓存不准确。
  • Cache-Control强制缓存,常见指令:max-age=3600(单位为秒)、no-cache(不使用强制缓存)、no-store(禁止缓存)、private``public(只有浏览器可缓存与浏览器、服务器和代理服务器均可缓存)。
  • Last-Modified协商缓存,资源的最后修改时间,之后客户端会发送值相同的If-Modified-Since,请求时与最后请求时间对比并确定是否继续使用缓存,问题是该响应头以秒级别记录,若资源在一秒内发送变化则无法感知到。
  • ETag协商缓存,首次请求后响应头返回ETag,值为服务器为文件生成的唯一标识,之后请求客户端会发送值为ETag的If-None-Match,将该值与服务器资源的ETag对比。

流程

  • 强制缓存:客户端拿到资源后记录本地缓存的max-ageExpires,下次请求时,若未过期则直接读取本地缓存,不发送网络请求。
  • 协商缓存:强制缓存过期后,客户端带上 If-None-MatchIf-Modified-Since 向服务器发送请求。若未修改,返回304,继续使用本地缓存,且会更新本次的缓存头;若已修改,返回200和新的资源以及新的缓存头。