JavaScript - Event Loop
单线程
- JavaScript 的主要宿主环境为
浏览器
,主要用途是与用户互动
和操作 DOM
- 如果采用
多线程
模型,会带来复杂的同步
问题
- 如果采用
- 从
诞生
伊始,JavaScript 就是单线程,是其核心特性
- 为了利用
多核 CPU
的计算能力,HTML 5
提出了Web Worker
标准- 允许 JavaScript 创建多个子线程,但
子线程完全受主线程控制
,且不允许操作 DOM
- 允许 JavaScript 创建多个子线程,但
任务队列
- JavaScript 在
单线程
模式下,所有任务都需要排队
- 任务分类
同步
任务(synchronous
)- 在
主线程
上排队执行
的任务,只有前一个任务执行完毕
,才能执行后一个任务
- 在
异步
任务(asynchronous
)- 不进入主线程,而是进入
任务队列
的任务 - 只有任务队列
通知
主线程,某个异步任务可以执行了,该异步任务才会进入主线程执行
- 不进入主线程,而是进入
- 运行机制
- 所有
同步任务
都在主线程
上执行,形成一个执行栈
- 主线程之外,还存在一个
任务队列
- 只要
异步任务
有了运行结果
,就会在任务队列
中放置一个事件
- 只要
- 一旦
执行栈
中所有的同步任务执行完毕
,系统就会读取任务队列
中的事件
事件
对应的异步任务
会结束等待
状态,进入执行栈
开始执行
- 主线程不断重复上一步骤(只要
主线程空闲
,就会去读取任务队列
)
- 所有
Event + Callback
任务队列
是一个事件的队列
异步任务完成后
会向任务队列
添加一个事件
:表示相关的异步任务可以进入执行栈
了- 异步任务本身不需要 CPU 参与,如
IO
(DMA) - 但异步任务对应事件的
回调函数
需要占用CPU
时间
- 异步任务本身不需要 CPU 参与,如
主线程
读取任务队列
,实际上就是读取事件
任务队列
中的事件
:IO 事件、鼠标点击、页面滚动- 只要为
Event
指定过Callback
,当这些Event
发生时就会进入任务队列
,等待主线程
读取
- 只要为
Callback
:被主线程挂起的代码
– 需要占用 CPU 时间
异步任务
必须指定Callback
- 当
主线程
开始执行异步任务
,实际执行的就是对应的Callback
任务队列
是一个FIFO
的数据结构:排在前面
的事件,优先
被主线程读取- 主线程的读取过程
基本上
是自动
的- 主要
执行栈
一清空,任务队列
上第1位
的事件会自动进入主线程 - 针对
定时器
,主线程会首先检查下执行时间
,某些事件只有到了规定时间,才能返回主线程
- 主要
- 主线程的读取过程
Event Loop
event loop
:主线程循环不断地从任务队列
中读取事件
- 主线程运行时,会产生
Heap
和Stack
Stack
中的代码调用各种外部 API
,在任务队列
中加入各种事件
- 只要
Stack
中的代码执行完毕
- 主线程就会去读取
任务队列
(Callback Queue
),依次执行那些事件对应的回调函数
- 主线程就会去读取
- 执行栈中的代码(
同步任务
),总是在读取任务队列(异步任务
)之前执行
1 | let request = new XMLHttpRequest(); |
定时器
任务队列
除了放置异步任务
的事件,还可以放置定时事件
(指定某些代码在多少时间后执行)定时器
功能主要由setTimeout
(一次性) 和setInterval
(周期性) 来完成,内部运行机制完全一样
1 | // 1 |
setTimeout(fn, 0)
:尽可能早地执行
往任务队列尾部
添加一个事件,等到同步任务
和任务队列现有的事件
都处理完后,才会得到执行
setTimeout
: 只是将事件插入到任务队列
必须等到执行栈
执行完,主线程才会去执行它指定
的Callback
如果
执行栈耗时很长
,此时无法保证
回调函数一定会在setTimeout
指定的时间执行
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.