内存模型
进程是程序运行时所需的内存空间,进程内的线程用于实现代码执行。浏览器是多进程的:
- 浏览器进程
- 网络进程
- 渲染进程
- ...
INFO
可以打开浏览器内置任务管理器查看浏览器进程。
默认情况下,浏览器为每个标签页开启一个渲染进程。 渲染进程是处于沙盒环境的,避免网络病毒对计算机硬件造成破坏。
渲染主线程
渲染进程内的主线程就是渲染主线程,它需要处理:
- 解析 HTML
- 执行脚本
- 计算样式
- 布局
- 分层
- 绘制
TIP
为什么渲染进程不开多个线程去执行这些任务?
事件循环
事件循环是为了解决任务调度问题,详见w3c 规范。
- 主线程启动时进入死循环,每次循环检查消息队列中是否还有任务
- 如果有,则取出第一个任务执行;如果没有,则进入休眠状态,此时调用
requestIdleCallback
- 其他线程可以随时向消息队列的末尾添加任务,并唤醒主线程
异步
当主线程遇到一些需要等待的任务时,就把任务交给浏览器的其他线程去处理,然后去处理其他任务。 其他线程处理完后,把事先传递的回调包装成任务,并加入相应的消息队列中,等待主线程处理。 这保证了主线程永不阻塞。
js
/**
* 主线程执行这段代码时,将计时器任务发送给计时线程,然后就去执行别的代码了
* 计时线程收到任务后开始计时,1s 后将回调包装成任务,加入延时队列中
*/
setTimeout(() => console.log(0),1e3);
js
// 主线程执行这段代码后,会添加一个渲染任务到消息队列
document.querySelector('h1').textContent = 'new text';
// 主线程运行 3s,在此期间不会取出消息队列里的任务
const currentTimestamp = Date.now();
while (Date.now() - currentTimestamp < 3e3) {}
消息队列
一个事件循环可以有多个消息队列。
W3C 标准
- 相同类型的任务必须放在相同消息队列
- 不同类型的任务可以放在不同消息队列
- 不同队列具有不同优先级,浏览器自行决定
- 必须有微队列,并具有最高优先级
Chrome 实现
类型 | 对应接口 | 优先级 |
---|---|---|
微队列 | Promise 、MutationObserver 、queueMicrotask | 最高 |
交互队列 | addEventListener | 高 |
延时队列 | setTimeout 、setInterval | 中 |
... |